From 0550ffe923ed81656d8d44b4b85e8008b0da92b2 Mon Sep 17 00:00:00 2001 From: "r.mubarakzyanov" Date: Sat, 18 Apr 2026 15:26:04 +0300 Subject: [PATCH] Subject CRUD --- .../scadenzario/ajax/complete_deadline.php | 4 +- .../scadenzario/ajax/get_calendar_events.php | 8 +- .../scadenzario/ajax/save_deadline.php | 10 +- .../scadenzario/cron/send_notifications.php | 5 +- public/userarea/scadenzario/detail.php | 15 +- public/userarea/scadenzario/index.php | 144 +++++++-- public/userarea/scadenzario/print.php | 7 +- .../scadenzario/sql/migrate_subjects.sql | 25 ++ .../subjects/ajax/delete_subject.php | 34 +++ .../subjects/ajax/save_subject.php | 59 ++++ .../userarea/scadenzario/subjects/index.php | 282 ++++++++++++++++++ 11 files changed, 557 insertions(+), 36 deletions(-) create mode 100644 public/userarea/scadenzario/sql/migrate_subjects.sql create mode 100644 public/userarea/scadenzario/subjects/ajax/delete_subject.php create mode 100644 public/userarea/scadenzario/subjects/ajax/save_subject.php create mode 100644 public/userarea/scadenzario/subjects/index.php diff --git a/public/userarea/scadenzario/ajax/complete_deadline.php b/public/userarea/scadenzario/ajax/complete_deadline.php index 31dd90e..6592c08 100644 --- a/public/userarea/scadenzario/ajax/complete_deadline.php +++ b/public/userarea/scadenzario/ajax/complete_deadline.php @@ -57,12 +57,12 @@ try { $ins = $pdo->prepare(" INSERT INTO scad_deadlines - (category, topic, law_regulation, recurrence_type, due_date, check_date, + (subject_id, topic, law_regulation, recurrence_type, due_date, check_date, document_date, notification_days, storage_location, notes, created_by, departments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "); $ins->execute([ - $deadline['category'], $deadline['topic'], $deadline['law_regulation'], + $deadline['subject_id'], $deadline['topic'], $deadline['law_regulation'], $deadline['recurrence_type'], $dueDate->format('Y-m-d'), $checkDate ? $checkDate->format('Y-m-d') : null, $deadline['document_date'], diff --git a/public/userarea/scadenzario/ajax/get_calendar_events.php b/public/userarea/scadenzario/ajax/get_calendar_events.php index faaceaa..4185593 100644 --- a/public/userarea/scadenzario/ajax/get_calendar_events.php +++ b/public/userarea/scadenzario/ajax/get_calendar_events.php @@ -13,8 +13,10 @@ try { $filterDept = $_GET['department'] ?? ''; $filterEmployee = $_GET['employee'] ?? ''; - $sql = "SELECT DISTINCT d.id, d.topic, d.category, d.due_date, d.status, d.notification_days, d.departments + $sql = "SELECT DISTINCT d.id, d.topic, d.due_date, d.status, d.notification_days, d.departments, + s.name AS subject_name, s.color AS subject_color FROM scad_deadlines d + LEFT JOIN scad_subjects s ON s.id = d.subject_id LEFT JOIN scad_deadline_employee de ON de.deadline_id = d.id LEFT JOIN employees e ON e.id = de.employee_id"; @@ -73,14 +75,14 @@ try { else { $color = '#5a8fd8'; } $title = $d['topic']; - if ($d['category']) $title = $d['category'] . ': ' . $title; + if (!empty($d['subject_name'])) $title = $d['subject_name'] . ': ' . $title; $events[] = [ 'id' => $d['id'], 'title' => $title, 'start' => $d['due_date'], 'backgroundColor' => $color, - 'borderColor' => $color, + 'borderColor' => !empty($d['subject_color']) ? $d['subject_color'] : $color, 'url' => 'scadenzario/detail.php?id=' . $d['id'], ]; } diff --git a/public/userarea/scadenzario/ajax/save_deadline.php b/public/userarea/scadenzario/ajax/save_deadline.php index ce47ac3..58a999a 100644 --- a/public/userarea/scadenzario/ajax/save_deadline.php +++ b/public/userarea/scadenzario/ajax/save_deadline.php @@ -8,7 +8,7 @@ try { $pdo = $db->getConnection(); $id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : null; - $category = trim($_POST['category'] ?? '') ?: null; + $subject_id = isset($_POST['subject_id']) && is_numeric($_POST['subject_id']) && (int)$_POST['subject_id'] > 0 ? (int)$_POST['subject_id'] : null; $topic = trim($_POST['topic'] ?? ''); $law_regulation = trim($_POST['law_regulation'] ?? '') ?: null; $recurrence_type = $_POST['recurrence_type'] ?? 'once'; @@ -52,13 +52,13 @@ try { if ($id) { $stmt = $pdo->prepare(" UPDATE scad_deadlines SET - category = ?, topic = ?, law_regulation = ?, recurrence_type = ?, + subject_id = ?, topic = ?, law_regulation = ?, recurrence_type = ?, due_date = ?, check_date = ?, document_date = ?, notification_days = ?, storage_location = ?, notes = ?, departments = ? WHERE id = ? "); $stmt->execute([ - $category, $topic, $law_regulation, $recurrence_type, + $subject_id, $topic, $law_regulation, $recurrence_type, $due_date, $check_date, $document_date, $notification_days, $storage_location, $notes, $departmentsStr, $id ]); @@ -75,12 +75,12 @@ try { // INSERT $stmt = $pdo->prepare(" INSERT INTO scad_deadlines - (category, topic, law_regulation, recurrence_type, due_date, check_date, + (subject_id, topic, law_regulation, recurrence_type, due_date, check_date, document_date, notification_days, storage_location, notes, created_by, departments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ - $category, $topic, $law_regulation, $recurrence_type, + $subject_id, $topic, $law_regulation, $recurrence_type, $due_date, $check_date, $document_date, $notification_days, $storage_location, $notes, $currentUserId, $departmentsStr ]); diff --git a/public/userarea/scadenzario/cron/send_notifications.php b/public/userarea/scadenzario/cron/send_notifications.php index dca2536..a8930b7 100644 --- a/public/userarea/scadenzario/cron/send_notifications.php +++ b/public/userarea/scadenzario/cron/send_notifications.php @@ -31,8 +31,9 @@ $errors = 0; // Get active deadlines that are approaching or overdue $stmt = $pdo->query(" - SELECT d.id, d.topic, d.category, d.due_date, d.notification_days + SELECT d.id, d.topic, s.name AS subject_name, d.due_date, d.notification_days FROM scad_deadlines d + LEFT JOIN scad_subjects s ON s.id = d.subject_id WHERE d.status = 'active' AND d.due_date <= DATE_ADD(CURDATE(), INTERVAL d.notification_days DAY) "); @@ -143,7 +144,7 @@ foreach ($deadlines as $dl) { $mail->addAddress($emp['email'], trim($emp['first_name'] . ' ' . $emp['last_name'])); $detailUrl = $appUrl . '/userarea/scadenzario/detail.php?id=' . $dl['id']; - $topicText = ($dl['category'] ? $dl['category'] . ' — ' : '') . $dl['topic']; + $topicText = (!empty($dl['subject_name']) ? $dl['subject_name'] . ' — ' : '') . $dl['topic']; if ($isOverdue) { $mail->Subject = '⚠️ Scadenza superata: ' . $dl['topic']; diff --git a/public/userarea/scadenzario/detail.php b/public/userarea/scadenzario/detail.php index 01c5fb2..9d9a95b 100644 --- a/public/userarea/scadenzario/detail.php +++ b/public/userarea/scadenzario/detail.php @@ -9,7 +9,12 @@ if (!isset($_GET['id']) || !is_numeric($_GET['id'])) { $error = 'ID non valido.'; } else { $id = (int)$_GET['id']; - $stmt = $pdo->prepare("SELECT * FROM scad_deadlines WHERE id = ?"); + $stmt = $pdo->prepare(" + SELECT d.*, s.name AS subject_name, s.color AS subject_color + FROM scad_deadlines d + LEFT JOIN scad_subjects s ON s.id = d.subject_id + WHERE d.id = ? + "); $stmt->execute([$id]); $deadline = $stmt->fetch(PDO::FETCH_ASSOC); @@ -263,9 +268,13 @@ if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
- +
Argomento
-
+
+ + + +
Dettaglio
diff --git a/public/userarea/scadenzario/index.php b/public/userarea/scadenzario/index.php index 2d468ce..aa2a259 100644 --- a/public/userarea/scadenzario/index.php +++ b/public/userarea/scadenzario/index.php @@ -3,24 +3,46 @@ $db = DBHandlerSelect::getInstance(); $pdo = $db->getConnection(); -$stmt = $pdo->query(" +// Optional filter: show only deadlines of a given subject (used by "Storico" link from subjects CRUD) +$filterSubjectId = isset($_GET['subject_id']) && is_numeric($_GET['subject_id']) ? (int)$_GET['subject_id'] : null; +$filterSubjectName = null; +if ($filterSubjectId) { + $s = $pdo->prepare("SELECT name FROM scad_subjects WHERE id = ?"); + $s->execute([$filterSubjectId]); + $filterSubjectName = $s->fetchColumn() ?: null; + if (!$filterSubjectName) { $filterSubjectId = null; } +} + +$sql = " SELECT d.*, + s.name AS subject_name, + s.color AS subject_color, GROUP_CONCAT(DISTINCT CONCAT(e.first_name, ' ', e.last_name) ORDER BY e.first_name SEPARATOR ', ') as responsabili, GROUP_CONCAT(DISTINCT e.department ORDER BY e.department SEPARATOR ', ') as reparti_persone, d.departments as reparti_assegnati, (SELECT COUNT(*) FROM scad_deadline_attachments att WHERE att.deadline_id = d.id) as attachment_count FROM scad_deadlines d + LEFT JOIN scad_subjects s ON s.id = d.subject_id LEFT JOIN scad_deadline_employee de ON de.deadline_id = d.id LEFT JOIN employees e ON e.id = de.employee_id - GROUP BY d.id - ORDER BY (d.status = 'completed') ASC, d.due_date ASC -"); +"; +$params = []; +if ($filterSubjectId) { + $sql .= " WHERE d.subject_id = ?"; + $params[] = $filterSubjectId; +} +$sql .= " GROUP BY d.id ORDER BY (d.status = 'completed') ASC, d.due_date ASC"; + +$stmt = $pdo->prepare($sql); +$stmt->execute($params); $deadlines = $stmt->fetchAll(PDO::FETCH_ASSOC); $employees = $pdo->query("SELECT id, first_name, last_name, department FROM employees WHERE status = 'active' ORDER BY first_name")->fetchAll(PDO::FETCH_ASSOC); $departments = $pdo->query("SELECT DISTINCT department FROM employees WHERE department IS NOT NULL AND department != '' ORDER BY department")->fetchAll(PDO::FETCH_COLUMN); +$subjects = $pdo->query("SELECT id, name, color FROM scad_subjects ORDER BY name")->fetchAll(PDO::FETCH_ASSOC); + $today = date('Y-m-d'); ?> @@ -137,6 +159,32 @@ $today = date('Y-m-d'); #deadlinesTable tbody tr.row-completed { opacity: 0.6; } #deadlinesTable tbody tr:hover { filter: brightness(0.97); } + /* Subject color stripe */ + #deadlinesTable tbody tr[data-subject-color] td:first-child { + border-left: 4px solid var(--subject-color, transparent); + } + .subject-chip { + display: inline-block; + padding: 0.15rem 0.55rem; + border-radius: 1rem; + font-size: 0.72rem; + font-weight: 600; + color: #fff; + letter-spacing: 0.02em; + white-space: nowrap; + } + .deadline-card[data-subject-color] { + border-left: 4px solid var(--subject-color, var(--scad-card-border)); + } + + /* Filter banner (subject history mode) */ + .subject-filter-banner { + display: flex; align-items: center; justify-content: space-between; + gap: 0.75rem; padding: 0.75rem 1rem; + background: var(--scad-card-bg); border: 1px solid var(--scad-card-border); + border-radius: 0.5rem; margin-bottom: 1rem; font-size: 0.9rem; + } + /* Filter bar */ .filter-bar .form-select { border-radius: 0.5rem; @@ -244,19 +292,46 @@ $today = date('Y-m-d');
+ +
+
+ + Storico per argomento: + + (tutte le scadenze — aperte e chiuse) +
+ + Rimuovi filtro + +
+ +
+
+ +
- - query("SELECT DISTINCT category FROM scad_deadlines WHERE category IS NOT NULL AND category != '' ORDER BY category")->fetchAll(PDO::FETCH_COLUMN); - foreach ($cats as $c): ?> - + +
+ + + + +
@@ -617,6 +708,15 @@ $today = date('Y-m-d'); var fpCheck = flatpickr('#filterCheckRange', fpOpts); // --- Select2 --- + $('#dlSubject').select2({ + theme: 'bootstrap-5', + placeholder: 'Seleziona argomento...', + allowClear: true, + dropdownParent: $('#deadlineModal .modal-body'), + language: 'it', + width: '100%' + }); + $('#dlDepartments').select2({ theme: 'bootstrap-5', placeholder: 'Seleziona reparti...', @@ -645,6 +745,8 @@ $today = date('Y-m-d'); if (statusFilter === 'non-completata') { if (row.getAttribute('data-status') === 'completata') return false; } else if (statusFilter && row.getAttribute('data-status') !== statusFilter) return false; if (deptFilter && (row.getAttribute('data-department') || '').indexOf(deptFilter) === -1) return false; + var subjFilter = $('#filterSubject').val(); + if (subjFilter && String(row.getAttribute('data-subject-id')) !== String(subjFilter)) return false; var empFilter = $('#filterEmployee').val(); if (empFilter && (row.getAttribute('data-employees') || '').indexOf(empFilter) === -1) return false; var dueDates = fpDue.selectedDates; @@ -677,11 +779,13 @@ $today = date('Y-m-d'); function filterCards() { var statusVal = $('#filterStatus').val(); var deptVal = $('#filterDepartment').val(); + var subjVal = $('#filterSubject').val(); $('#mobileCards .deadline-card').each(function() { var show = true; if (statusVal === 'non-completata') { if ($(this).data('status') === 'completata') show = false; } else if (statusVal && $(this).data('status') !== statusVal) show = false; if (deptVal && ($(this).data('department') || '').indexOf(deptVal) === -1) show = false; + if (subjVal && String($(this).data('subject-id')) !== String(subjVal)) show = false; var empVal = $('#filterEmployee').val(); if (empVal && ($(this).data('employees') || '').indexOf(empVal) === -1) show = false; var dueDates = fpDue.selectedDates; @@ -696,7 +800,7 @@ $today = date('Y-m-d'); }); } - $('#filterStatus, #filterDepartment, #filterEmployee').on('change', function() { + $('#filterStatus, #filterDepartment, #filterEmployee, #filterSubject').on('change', function() { if (table) table.draw(); filterCards(); }); @@ -704,6 +808,7 @@ $today = date('Y-m-d'); $('#filterStatus').val('non-completata'); $('#filterDepartment').val(''); $('#filterEmployee').val(''); + $('#filterSubject').val(''); fpDue.clear(); fpCheck.clear(); if (table) table.draw(); @@ -725,6 +830,7 @@ $today = date('Y-m-d'); document.getElementById('dlNotifDays').value = '7'; document.getElementById('modalTitle').textContent = 'Nuova Scadenza'; document.getElementById('dlFiles').value = ''; + $('#dlSubject').val('').trigger('change'); $('#dlDepartments').val(null).trigger('change'); $('#dlEmployees').val(null).trigger('change'); renderAttachments([]); @@ -839,7 +945,7 @@ $today = date('Y-m-d'); if (!data.success) { Swal.fire('Errore', data.message, 'error'); return; } var d = data.data; document.getElementById('dlId').value = d.id; - document.getElementById('dlCategory').value = d.category || ''; + $('#dlSubject').val(d.subject_id || '').trigger('change'); document.getElementById('dlTopic').value = d.topic || ''; document.getElementById('dlLaw').value = d.law_regulation || ''; document.getElementById('dlRecurrence').value = d.recurrence_type || 'once'; @@ -927,7 +1033,7 @@ $today = date('Y-m-d'); if (!data.success) return; var d = data.data; document.getElementById('dlId').value = d.id; - document.getElementById('dlCategory').value = d.category || ''; + $('#dlSubject').val(d.subject_id || '').trigger('change'); document.getElementById('dlTopic').value = d.topic || ''; document.getElementById('dlLaw').value = d.law_regulation || ''; document.getElementById('dlRecurrence').value = d.recurrence_type || 'once'; diff --git a/public/userarea/scadenzario/print.php b/public/userarea/scadenzario/print.php index 93d1a8c..803c1c2 100644 --- a/public/userarea/scadenzario/print.php +++ b/public/userarea/scadenzario/print.php @@ -5,10 +5,13 @@ $pdo = $db->getConnection(); $sql = " SELECT d.*, + s.name AS subject_name, + s.color AS subject_color, GROUP_CONCAT(DISTINCT CONCAT(e.first_name, ' ', e.last_name) ORDER BY e.first_name SEPARATOR ', ') as responsabili, GROUP_CONCAT(DISTINCT e.department ORDER BY e.department SEPARATOR ', ') as reparti_persone, d.departments as reparti_assegnati FROM scad_deadlines d + LEFT JOIN scad_subjects s ON s.id = d.subject_id LEFT JOIN scad_deadline_employee de ON de.deadline_id = d.id LEFT JOIN employees e ON e.id = de.employee_id "; @@ -171,8 +174,8 @@ if ($checkFrom || $checkTo) { if (!empty($row['reparti_persone'])) $allDepts = array_merge($allDepts, array_map('trim', explode(',', $row['reparti_persone']))); $reparti = implode(', ', array_unique(array_filter($allDepts))); ?> - - + > + diff --git a/public/userarea/scadenzario/sql/migrate_subjects.sql b/public/userarea/scadenzario/sql/migrate_subjects.sql new file mode 100644 index 0000000..dcb941b --- /dev/null +++ b/public/userarea/scadenzario/sql/migrate_subjects.sql @@ -0,0 +1,25 @@ +-- One-time migration: move scad_deadlines.category into dedicated scad_subjects table + +CREATE TABLE IF NOT EXISTS scad_subjects ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + color VARCHAR(7) NOT NULL DEFAULT '#6c757d', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uniq_name (name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +INSERT IGNORE INTO scad_subjects (name) +SELECT DISTINCT TRIM(category) FROM scad_deadlines +WHERE category IS NOT NULL AND TRIM(category) <> ''; + +ALTER TABLE scad_deadlines + ADD COLUMN subject_id INT UNSIGNED NULL AFTER id, + ADD INDEX idx_subject (subject_id), + ADD CONSTRAINT fk_deadlines_subject FOREIGN KEY (subject_id) REFERENCES scad_subjects(id) ON DELETE SET NULL; + +UPDATE scad_deadlines d +JOIN scad_subjects s ON s.name = TRIM(d.category) +SET d.subject_id = s.id; + +ALTER TABLE scad_deadlines DROP COLUMN category; diff --git a/public/userarea/scadenzario/subjects/ajax/delete_subject.php b/public/userarea/scadenzario/subjects/ajax/delete_subject.php new file mode 100644 index 0000000..f666274 --- /dev/null +++ b/public/userarea/scadenzario/subjects/ajax/delete_subject.php @@ -0,0 +1,34 @@ +getConnection(); + + $id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : 0; + if ($id <= 0) { + echo json_encode(['success' => false, 'message' => 'ID non valido.']); + exit; + } + + $stmt = $pdo->prepare("SELECT COUNT(*) FROM scad_deadlines WHERE subject_id = ?"); + $stmt->execute([$id]); + $inUse = (int)$stmt->fetchColumn(); + + if ($inUse > 0) { + echo json_encode([ + 'success' => false, + 'message' => "Impossibile eliminare: l'argomento è utilizzato in $inUse scadenz" . ($inUse === 1 ? 'a' : 'e') . '.', + ]); + exit; + } + + $pdo->prepare("DELETE FROM scad_subjects WHERE id = ?")->execute([$id]); + + echo json_encode(['success' => true, 'message' => 'Argomento eliminato.']); + +} catch (Exception $e) { + echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]); +} diff --git a/public/userarea/scadenzario/subjects/ajax/save_subject.php b/public/userarea/scadenzario/subjects/ajax/save_subject.php new file mode 100644 index 0000000..577a452 --- /dev/null +++ b/public/userarea/scadenzario/subjects/ajax/save_subject.php @@ -0,0 +1,59 @@ +getConnection(); + + $id = isset($_POST['id']) && is_numeric($_POST['id']) ? (int)$_POST['id'] : null; + $name = trim($_POST['name'] ?? ''); + $color = trim($_POST['color'] ?? ''); + + if ($name === '') { + echo json_encode(['success' => false, 'message' => 'Il nome è obbligatorio.']); + exit; + } + if (mb_strlen($name) > 100) { + echo json_encode(['success' => false, 'message' => 'Il nome supera 100 caratteri.']); + exit; + } + if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $color)) { + $color = '#6c757d'; + } + + // Uniqueness check + if ($id) { + $stmt = $pdo->prepare("SELECT id FROM scad_subjects WHERE name = ? AND id <> ?"); + $stmt->execute([$name, $id]); + } else { + $stmt = $pdo->prepare("SELECT id FROM scad_subjects WHERE name = ?"); + $stmt->execute([$name]); + } + if ($stmt->fetch()) { + echo json_encode(['success' => false, 'message' => 'Esiste già un argomento con questo nome.']); + exit; + } + + if ($id) { + $stmt = $pdo->prepare("UPDATE scad_subjects SET name = ?, color = ? WHERE id = ?"); + $stmt->execute([$name, $color, $id]); + $savedId = $id; + } else { + $stmt = $pdo->prepare("INSERT INTO scad_subjects (name, color) VALUES (?, ?)"); + $stmt->execute([$name, $color]); + $savedId = (int)$pdo->lastInsertId(); + } + + echo json_encode([ + 'success' => true, + 'message' => $id ? 'Argomento aggiornato.' : 'Argomento creato.', + 'id' => $savedId, + 'name' => $name, + 'color' => $color, + ]); + +} catch (Exception $e) { + echo json_encode(['success' => false, 'message' => 'Errore: ' . $e->getMessage()]); +} diff --git a/public/userarea/scadenzario/subjects/index.php b/public/userarea/scadenzario/subjects/index.php new file mode 100644 index 0000000..9baf598 --- /dev/null +++ b/public/userarea/scadenzario/subjects/index.php @@ -0,0 +1,282 @@ + +getConnection(); + +$subjects = $pdo->query(" + SELECT s.*, + (SELECT COUNT(*) FROM scad_deadlines d WHERE d.subject_id = s.id) AS deadline_count, + (SELECT COUNT(*) FROM scad_deadlines d WHERE d.subject_id = s.id AND d.status <> 'completed') AS open_count + FROM scad_subjects s + ORDER BY s.name +")->fetchAll(PDO::FETCH_ASSOC); +?> + + + + + + scadenzario -> userarea + $baseHref = dirname(dirname($scriptDir)) . '/'; + ?> + + + + Scadenzario - Argomenti + + + + +
+ + +
+
+ + + +
+
+
Argomenti
+
+ + Scadenzario + + +
+
+
+ +
+ +

Nessun argomento definito.
Clicca "Nuovo Argomento" per aggiungere il primo.

+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
ColoreNomeScadenzeAperteAzioni
+
+ + + + + +
+
+
+ +
+
+ +
+
+ +
+ + + + + + + +