Edycja wyglądu tabeli procesów

This commit is contained in:
Flatlogic Bot 2026-01-10 11:21:09 +00:00
parent 47c0b35493
commit ee89357279
2 changed files with 198 additions and 197 deletions

41
_get_future_meetings.php Normal file
View 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
View File

@ -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 => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
}[match]));
}
}); });
</script> </script>