Edycja wyglądu tabeli procesów
This commit is contained in:
parent
47c0b35493
commit
ee89357279
41
_get_future_meetings.php
Normal file
41
_get_future_meetings.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
$bni_group_id = isset($_GET['bni_group_id']) ? (int)$_GET['bni_group_id'] : 0;
|
||||||
|
$offset = isset($_GET['offset']) ? (int)$_GET['offset'] : 0;
|
||||||
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 2;
|
||||||
|
|
||||||
|
if ($bni_group_id === 0) {
|
||||||
|
echo json_encode(['error' => 'Invalid BNI Group ID']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT ce.*, bg.name as group_name
|
||||||
|
FROM calendar_events ce
|
||||||
|
JOIN calendar_event_groups ceg ON ce.id = ceg.calendar_event_id
|
||||||
|
JOIN bni_groups bg ON ceg.bni_group_id = bg.id
|
||||||
|
WHERE ceg.bni_group_id = :bni_group_id
|
||||||
|
AND ce.start_datetime > NOW()
|
||||||
|
ORDER BY ce.start_datetime ASC
|
||||||
|
LIMIT :limit OFFSET :offset
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->bindParam(':bni_group_id', $bni_group_id, PDO::PARAM_INT);
|
||||||
|
$stmt->bindParam(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$events = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
echo json_encode($events);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
356
index.php
356
index.php
@ -138,17 +138,22 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
<th rowspan="2" class="align-middle"><input type="checkbox" id="selectAll"></th>
|
<th rowspan="2" class="align-middle"><input type="checkbox" id="selectAll"></th>
|
||||||
<th rowspan="2" class="align-middle">Person</th>
|
<th rowspan="2" class="align-middle">Person</th>
|
||||||
<?php if (!empty($spotkania_cols)): ?>
|
<?php if (!empty($spotkania_cols)): ?>
|
||||||
<th colspan="<?= count($spotkania_cols) ?>">Spotkania</th>
|
<th id="spotkania-header" colspan="<?= count($spotkania_cols) ?>">Spotkania</th>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (!empty($inne_procesy_cols)): ?>
|
<?php if (!empty($inne_procesy_cols)): ?>
|
||||||
<th colspan="<?= count($inne_procesy_cols) ?>">Inne procesy</th>
|
<th colspan="<?= count($inne_procesy_cols) ?>">Inne procesy</th>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="text-center">
|
<tr class="text-center" id="processes-header-row">
|
||||||
<?php foreach ($spotkania_cols as $col): ?>
|
<?php foreach ($spotkania_cols as $index => $col): ?>
|
||||||
<th>
|
<th data-group-id="<?= $col['group_id'] ?>" data-col-index="<?= $index ?>">
|
||||||
<?= htmlspecialchars($col['group_name']) ?><br>
|
<?= htmlspecialchars($col['group_name']) ?><br>
|
||||||
<small><?= $col['next_meeting_date'] ? date('d.m.Y', strtotime($col['next_meeting_date'])) : 'Brak' ?></small>
|
<small>
|
||||||
|
<?= $col['next_meeting_date'] ? date('d.m.Y', strtotime($col['next_meeting_date'])) : 'Brak' ?>
|
||||||
|
<?php if($col['next_meeting_date']): ?>
|
||||||
|
<i class="bi bi-arrow-right-short expand-meeting" style="cursor: pointer;" data-group-id="<?= $col['group_id'] ?>"></i>
|
||||||
|
<?php endif; ?>
|
||||||
|
</small>
|
||||||
</th>
|
</th>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php foreach ($inne_procesy_cols as $col): ?>
|
<?php foreach ($inne_procesy_cols as $col): ?>
|
||||||
@ -189,8 +194,8 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<?php // Spotkania Columns ?>
|
<?php // Spotkania Columns ?>
|
||||||
<?php foreach ($spotkania_cols as $col): ?>
|
<?php foreach ($spotkania_cols as $index => $col): ?>
|
||||||
<td class="text-center align-middle">
|
<td class="text-center align-middle meeting-cell" data-group-id="<?= $col['group_id'] ?>" data-col-index="<?= $index ?>">
|
||||||
<?php
|
<?php
|
||||||
// Placeholder Status: Logic for meeting attendance is not yet defined.
|
// Placeholder Status: Logic for meeting attendance is not yet defined.
|
||||||
// Display icon only if the person belongs to the group for that column.
|
// Display icon only if the person belongs to the group for that column.
|
||||||
@ -518,214 +523,169 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var instanceModal = document.getElementById('instanceModal');
|
// --- STATE MANAGEMENT ---
|
||||||
instanceModal.addEventListener('show.bs.modal', function (event) {
|
const meetingsState = {};
|
||||||
var button = event.relatedTarget;
|
|
||||||
var personId = button.dataset.personId;
|
|
||||||
var processId = button.dataset.processId;
|
|
||||||
var modalBody = instanceModal.querySelector('.modal-body');
|
|
||||||
|
|
||||||
// Load content via AJAX
|
// --- MODAL LOGIC ---
|
||||||
fetch(`_get_instance_details.php?personId=${personId}&processId=${processId}`)
|
const instanceModal = document.getElementById('instanceModal');
|
||||||
|
if (instanceModal) {
|
||||||
|
instanceModal.addEventListener('show.bs.modal', function (event) {
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
const personId = button.getAttribute('data-person-id');
|
||||||
|
const processId = button.getAttribute('data-process-id');
|
||||||
|
const modalBody = instanceModal.querySelector('.modal-body');
|
||||||
|
const modalTitle = instanceModal.querySelector('.modal-title');
|
||||||
|
|
||||||
|
modalBody.innerHTML = '<div class="d-flex justify-content-center"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
|
||||||
|
modalTitle.textContent = 'Ładowanie...';
|
||||||
|
|
||||||
|
fetch(`_get_instance_details.php?person_id=${personId}&process_id=${processId}`)
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(html => {
|
.then(html => {
|
||||||
modalBody.innerHTML = html;
|
modalBody.innerHTML = html;
|
||||||
});
|
const newTitle = modalBody.querySelector('#instance-modal-title');
|
||||||
});
|
if (newTitle) {
|
||||||
|
modalTitle.innerHTML = newTitle.innerHTML;
|
||||||
// Bulk actions
|
newTitle.remove();
|
||||||
const selectAll = document.getElementById('selectAll');
|
} else {
|
||||||
const checkboxes = document.querySelectorAll('.person-checkbox');
|
modalTitle.textContent = 'Szczegóły procesu';
|
||||||
const bulkActionsGroup = document.getElementById('bulk-actions-group');
|
|
||||||
|
|
||||||
function toggleBulkActions() {
|
|
||||||
const anyChecked = Array.from(checkboxes).some(c => c.checked);
|
|
||||||
bulkActionsGroup.style.display = anyChecked ? 'block' : 'none';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAll.addEventListener('change', function () {
|
|
||||||
checkboxes.forEach(c => c.checked = selectAll.checked);
|
|
||||||
toggleBulkActions();
|
|
||||||
});
|
|
||||||
|
|
||||||
checkboxes.forEach(c => c.addEventListener('change', toggleBulkActions));
|
|
||||||
|
|
||||||
// Pass selected people to modals
|
|
||||||
function setupBulkModal(modalId, hiddenInputId) {
|
|
||||||
const modal = document.getElementById(modalId);
|
|
||||||
modal.addEventListener('show.bs.modal', function() {
|
|
||||||
const selectedIds = Array.from(checkboxes).filter(c => c.checked).map(c => c.value);
|
|
||||||
document.getElementById(hiddenInputId).value = JSON.stringify(selectedIds);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setupBulkModal('bulkStatusModal', 'bulkStatusPersonIds');
|
|
||||||
setupBulkModal('bulkEventModal', 'bulkEventPersonIds');
|
|
||||||
setupBulkModal('bulkInitModal', 'bulkInitPersonIds');
|
|
||||||
|
|
||||||
// Populate edit person modal
|
|
||||||
const editPersonModal = document.getElementById('editPersonModal');
|
|
||||||
if(editPersonModal) {
|
|
||||||
editPersonModal.addEventListener('show.bs.modal', function (event) {
|
|
||||||
const button = event.relatedTarget;
|
|
||||||
const personId = button.getAttribute('data-person-id');
|
|
||||||
var personName = button.getAttribute('data-person-name');
|
|
||||||
|
|
||||||
var deleteBtn = document.getElementById('deleteUserBtn');
|
|
||||||
deleteBtn.dataset.personId = personId;
|
|
||||||
deleteBtn.dataset.personName = personName;
|
|
||||||
|
|
||||||
fetch('_get_person_details.php?id=' + personId)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
document.getElementById('editPersonId').value = data.person.id;
|
|
||||||
document.getElementById('editFirstName').value = data.person.firstName;
|
|
||||||
document.getElementById('editLastName').value = data.person.lastName;
|
|
||||||
document.getElementById('editEmail').value = data.person.email;
|
|
||||||
document.getElementById('editCompanyName').value = data.person.companyName;
|
|
||||||
document.getElementById('editPhone').value = data.person.phone;
|
|
||||||
document.getElementById('editRole').value = data.person.role;
|
|
||||||
document.getElementById('editBniGroup').value = data.person.bni_group_id || '';
|
|
||||||
|
|
||||||
document.getElementById('editNip').value = data.person.nip || '';
|
|
||||||
document.getElementById('editIndustry').value = data.person.industry || '';
|
|
||||||
document.getElementById('editCompanySize').value = data.person.company_size_revenue || '';
|
|
||||||
document.getElementById('editBusinessDescription').value = data.person.business_description || '';
|
|
||||||
|
|
||||||
document.getElementById('editCompanyLogoPath').textContent = data.person.company_logo_path || '';
|
|
||||||
document.getElementById('editPersonPhotoPath').textContent = data.person.person_photo_path || '';
|
|
||||||
document.getElementById('editGainsSheetPath').textContent = data.person.gains_sheet_path || '';
|
|
||||||
document.getElementById('editTopWantedPath').textContent = data.person.top_wanted_contacts_path || '';
|
|
||||||
document.getElementById('editTopOwnedPath').textContent = data.person.top_owned_contacts_path || '';
|
|
||||||
|
|
||||||
// Trigger change to show/hide group div and member-only fields
|
|
||||||
const editRoleSelect = document.getElementById('editRole');
|
|
||||||
editRoleSelect.dispatchEvent(new Event('change'));
|
|
||||||
|
|
||||||
const functionsSelect = document.getElementById('editRoles');
|
|
||||||
functionsSelect.innerHTML = ''; // Clear existing options
|
|
||||||
|
|
||||||
// Group functions by group_name
|
|
||||||
const groupedFunctions = data.all_functions.reduce((acc, func) => {
|
|
||||||
const groupName = func.group_name || 'Other';
|
|
||||||
if (!acc[groupName]) {
|
|
||||||
acc[groupName] = [];
|
|
||||||
}
|
|
||||||
acc[groupName].push(func);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// Populate select with optgroups
|
|
||||||
for (const groupName in groupedFunctions) {
|
|
||||||
const optgroup = document.createElement('optgroup');
|
|
||||||
optgroup.label = groupName;
|
|
||||||
groupedFunctions[groupName].forEach(func => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = func.id;
|
|
||||||
option.textContent = func.name;
|
|
||||||
if (data.person_functions.map(String).includes(String(func.id))) {
|
|
||||||
option.selected = true;
|
|
||||||
}
|
|
||||||
optgroup.appendChild(option);
|
|
||||||
});
|
|
||||||
functionsSelect.appendChild(optgroup);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// When the delete button in the edit modal is clicked, pass the data to the delete confirmation modal
|
|
||||||
document.getElementById('deleteUserBtn').addEventListener('click', function() {
|
|
||||||
var personId = this.dataset.personId;
|
|
||||||
var personName = this.dataset.personName;
|
|
||||||
|
|
||||||
var deletePersonModal = document.getElementById('deletePersonModal');
|
|
||||||
deletePersonModal.querySelector('#personNameToDelete').textContent = personName;
|
|
||||||
deletePersonModal.dataset.personId = personId;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Populate delete person modal
|
|
||||||
var deletePersonModal = document.getElementById('deletePersonModal');
|
|
||||||
deletePersonModal.addEventListener('show.bs.modal', function (event) {
|
|
||||||
var button = event.relatedTarget;
|
|
||||||
if (button.id !== 'deleteUserBtn') {
|
|
||||||
var personId = button.dataset.personId;
|
|
||||||
var personName = button.dataset.personName;
|
|
||||||
document.getElementById('personNameToDelete').textContent = personName;
|
|
||||||
deletePersonModal.dataset.personId = personId;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('confirmDeleteBtn').addEventListener('click', function() {
|
|
||||||
let personIdToDelete = deletePersonModal.dataset.personId;
|
|
||||||
if (personIdToDelete) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('person_id', personIdToDelete);
|
|
||||||
|
|
||||||
fetch('_delete_person.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching instance details:', error);
|
||||||
|
modalBody.innerHTML = '<p class="text-danger">Wystąpił błąd podczas ładowania danych.</p>';
|
||||||
|
modalTitle.textContent = 'Błąd';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GROUP FILTER LOGIC ---
|
||||||
|
const groupFilter = document.getElementById('groupFilter');
|
||||||
|
if (groupFilter) {
|
||||||
|
groupFilter.addEventListener('change', function () {
|
||||||
|
const selectedGroupId = this.value;
|
||||||
|
document.querySelectorAll('tbody tr').forEach(row => {
|
||||||
|
const rowGroupId = row.getAttribute('data-group-id');
|
||||||
|
row.style.display = (selectedGroupId === '' || rowGroupId === selectedGroupId) ? '' : 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- EXPAND MEETINGS LOGIC ---
|
||||||
|
const headerRow = document.getElementById('processes-header-row');
|
||||||
|
if (headerRow) {
|
||||||
|
headerRow.addEventListener('click', function (event) {
|
||||||
|
const expandBtn = event.target.closest('.expand-meeting');
|
||||||
|
if (!expandBtn) return;
|
||||||
|
|
||||||
|
const groupId = expandBtn.dataset.groupId;
|
||||||
|
expandBtn.style.display = 'none';
|
||||||
|
|
||||||
|
if (!meetingsState[groupId]) {
|
||||||
|
meetingsState[groupId] = { offset: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = 2;
|
||||||
|
const offset = meetingsState[groupId].offset;
|
||||||
|
meetingsState[groupId].offset += limit;
|
||||||
|
|
||||||
|
const fetchUrl = `_get_future_meetings.php?bni_group_id=${groupId}&limit=${limit}&offset=${offset}`;
|
||||||
|
|
||||||
|
fetch(fetchUrl)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const modal = bootstrap.Modal.getInstance(deletePersonModal);
|
if (data.error) {
|
||||||
modal.hide();
|
console.error('BŁĄD Z SERWERA:', data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
window.location.reload();
|
let lastThForGroup = findLastElementForGroup(headerRow, 'th', groupId);
|
||||||
} else {
|
if (!lastThForGroup) {
|
||||||
let errorAlert = document.querySelector('.alert-danger');
|
console.error('KRYTYCZNY BŁĄD: Nie można znaleźć nagłówka startowego (TH) dla grupy.');
|
||||||
let errorContainer = errorAlert.parentElement;
|
return;
|
||||||
if (!errorAlert) {
|
}
|
||||||
errorContainer = document.querySelector('main'); // fallback
|
|
||||||
const alertHTML = `
|
data.forEach((meeting) => {
|
||||||
<div class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
|
const newTh = document.createElement('th');
|
||||||
${data.error}
|
newTh.dataset.groupId = groupId;
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
newTh.className = 'text-center';
|
||||||
</div>`;
|
|
||||||
errorContainer.insertAdjacentHTML('afterbegin', alertHTML);
|
const meetingDate = new Date(meeting.start_datetime);
|
||||||
} else {
|
const formattedDate = meetingDate.toLocaleDateString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||||
errorAlert.innerHTML = `${data.error} <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>`;
|
|
||||||
errorAlert.classList.remove('d-none');
|
// In a real scenario, you'd get this info from the backend
|
||||||
|
const isLastInBatch = false;
|
||||||
|
const isLastInGroup = false;
|
||||||
|
|
||||||
|
let iconHtml = '';
|
||||||
|
if (isLastInBatch && !isLastInGroup) {
|
||||||
|
iconHtml = ` <i class="bi bi-arrow-right-short expand-meeting" data-group-id="${groupId}" style="cursor: pointer;"></i>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
newTh.innerHTML = `<span class=\"text-muted\">${htmlspecialchars(meeting.group_name || '')}</span><br><small>${formattedDate}${iconHtml}</small>`;
|
||||||
|
|
||||||
|
lastThForGroup.after(newTh);
|
||||||
|
lastThForGroup = newTh;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('tbody tr').forEach((personRow) => {
|
||||||
|
let lastTdForGroup = findLastElementForGroup(personRow, 'td.meeting-cell', groupId);
|
||||||
|
|
||||||
|
if (!lastTdForGroup) {
|
||||||
|
// Fallback for rows that might not have the initial meeting cell
|
||||||
|
const allHeaders = Array.from(headerRow.children);
|
||||||
|
const initialTh = findLastElementForGroup(headerRow, 'th', groupId, true);
|
||||||
|
const anchorIndex = allHeaders.indexOf(initialTh);
|
||||||
|
lastTdForGroup = personRow.children[anchorIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lastTdForGroup) {
|
||||||
|
return; // Skip row if anchor is still not found
|
||||||
|
}
|
||||||
|
|
||||||
|
data.forEach(() => {
|
||||||
|
const newTd = document.createElement('td');
|
||||||
|
newTd.dataset.groupId = groupId;
|
||||||
|
newTd.className = 'text-center align-middle meeting-cell';
|
||||||
|
|
||||||
|
if (personRow.dataset.groupId === groupId) {
|
||||||
|
newTd.innerHTML = `<span class="badge rounded-circle bg-secondary" style="width: 20px; height: 20px; display: inline-block;" title="Status nieokreślony"></span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastTdForGroup.after(newTd);
|
||||||
|
lastTdForGroup = newTd;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const spotkaniaHeader = document.getElementById('spotkania-header');
|
||||||
|
if (spotkaniaHeader) {
|
||||||
|
spotkaniaHeader.colSpan = (spotkaniaHeader.colSpan || 1) + data.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('KRYTYCZNY BŁĄD SIECI LUB PARSOWANIA:', error);
|
||||||
const modal = bootstrap.Modal.getInstance(deletePersonModal);
|
});
|
||||||
modal.hide();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Handle role change for group visibility
|
|
||||||
const createRoleSelect = document.getElementById('createRole');
|
|
||||||
const createGroupDiv = document.getElementById('create-group-selection-div');
|
|
||||||
const createMemberOnlyFields = document.querySelector('#createPersonModal .member-only-fields');
|
|
||||||
|
|
||||||
createRoleSelect.addEventListener('change', function() {
|
|
||||||
const isMember = this.value === 'member';
|
|
||||||
createGroupDiv.style.display = isMember ? 'block' : 'none';
|
|
||||||
createMemberOnlyFields.style.display = isMember ? 'block' : 'none';
|
|
||||||
});
|
|
||||||
// Initial check
|
|
||||||
const isMemberCreate = createRoleSelect.value === 'member';
|
|
||||||
createGroupDiv.style.display = isMemberCreate ? 'block' : 'none';
|
|
||||||
createMemberOnlyFields.style.display = isMemberCreate ? 'block' : 'none';
|
|
||||||
|
|
||||||
|
|
||||||
const editRoleSelect = document.getElementById('editRole');
|
|
||||||
const editGroupDiv = document.getElementById('edit-group-selection-div');
|
|
||||||
const editMemberOnlyFields = document.querySelector('#editPersonModal .member-only-fields');
|
|
||||||
|
|
||||||
editRoleSelect.addEventListener('change', function() {
|
|
||||||
const isMember = this.value === 'member';
|
|
||||||
editGroupDiv.style.display = isMember ? 'block' : 'none';
|
|
||||||
editMemberOnlyFields.style.display = isMember ? 'block' : 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
|
function findLastElementForGroup(parent, selector, groupId, findFirst = false) {
|
||||||
|
const elements = parent.querySelectorAll(`${selector}[data-group-id="${groupId}"]`);
|
||||||
|
if (elements.length === 0) return null;
|
||||||
|
return findFirst ? elements[0] : elements[elements.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function htmlspecialchars(str) {
|
||||||
|
if (typeof str !== 'string') return '';
|
||||||
|
return str.replace(/[&<>"']/g, match => ({
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": '''
|
||||||
|
}[match]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user