1246 lines
71 KiB
PHP
1246 lines
71 KiB
PHP
<?php include('../include/headscript.php'); ?>
|
|
<?php
|
|
$db = DBHandlerSelect::getInstance();
|
|
$pdo = $db->getConnection();
|
|
|
|
// 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; }
|
|
}
|
|
|
|
// Optional filter: limit to deadlines assigned to the current user (directly OR via department)
|
|
$filterMy = !empty($_GET['filter_my']);
|
|
$filterMyEmployee = null;
|
|
if ($filterMy) {
|
|
$st = $pdo->prepare("SELECT id, department FROM employees WHERE auth_user_id = ? LIMIT 1");
|
|
$st->execute([(int)$iduserlogin]);
|
|
$filterMyEmployee = $st->fetch(PDO::FETCH_ASSOC) ?: null;
|
|
if (!$filterMyEmployee) { $filterMy = false; }
|
|
}
|
|
|
|
$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
|
|
";
|
|
$where = [];
|
|
$params = [];
|
|
if ($filterSubjectId) {
|
|
$where[] = "d.subject_id = ?";
|
|
$params[] = $filterSubjectId;
|
|
}
|
|
if ($filterMy && $filterMyEmployee) {
|
|
$where[] = "(d.id IN (SELECT deadline_id FROM scad_deadline_employee WHERE employee_id = ?)"
|
|
. " OR (? <> '' AND FIND_IN_SET(?, REPLACE(d.departments, ', ', ',')) > 0))";
|
|
$params[] = (int)$filterMyEmployee['id'];
|
|
$params[] = (string)($filterMyEmployee['department'] ?? '');
|
|
$params[] = (string)($filterMyEmployee['department'] ?? '');
|
|
}
|
|
if (!empty($where)) {
|
|
$sql .= " WHERE " . implode(' AND ', $where);
|
|
}
|
|
$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');
|
|
?>
|
|
<!doctype html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<?php
|
|
$scriptDir = dirname($_SERVER['SCRIPT_NAME']);
|
|
// Go up from /userarea/scadenzario to /userarea/
|
|
$baseHref = dirname($scriptDir) . '/';
|
|
?>
|
|
<base href="<?= $baseHref ?>">
|
|
<?php include('../cssinclude.php'); ?>
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet">
|
|
<link href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css" rel="stylesheet">
|
|
<title>Scadenzario - Lista Scadenze</title>
|
|
<script>if(window.innerWidth>1024)document.addEventListener('DOMContentLoaded',function(){document.getElementById('appWrapper').classList.add('toggled')})</script>
|
|
<style>
|
|
:root {
|
|
--scad-primary: #5a8fd8;
|
|
--scad-primary-hover: #4578c0;
|
|
--scad-heading: #2c3e6b;
|
|
--scad-card-bg: linear-gradient(135deg, #f0f4ff 0%, #e8eeff 100%);
|
|
--scad-card-border: #dde4f0;
|
|
--scad-red: #dc3545;
|
|
--scad-orange: #e8930c;
|
|
--scad-green: #198754;
|
|
--scad-blue: #5a8fd8;
|
|
}
|
|
.scad-card {
|
|
border: none;
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
|
overflow: hidden;
|
|
}
|
|
.scad-card .card-header {
|
|
background: var(--scad-card-bg);
|
|
border-bottom: 1px solid var(--scad-card-border);
|
|
padding: 1rem 1.25rem;
|
|
}
|
|
.scad-card .card-header h5 {
|
|
font-weight: 700;
|
|
color: var(--scad-heading);
|
|
margin: 0;
|
|
font-size: 1.1rem;
|
|
letter-spacing: -0.01em;
|
|
}
|
|
.scad-card .card-body { padding: 1.25rem; }
|
|
|
|
.btn-scad-primary {
|
|
background: var(--scad-primary);
|
|
border: none;
|
|
color: #fff;
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 0.5rem;
|
|
transition: all 0.2s;
|
|
}
|
|
.btn-scad-primary:hover {
|
|
background: var(--scad-primary-hover);
|
|
color: #fff;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(90,143,216,0.35);
|
|
}
|
|
.btn-scad-outline {
|
|
background: transparent;
|
|
border: 1.5px solid var(--scad-primary);
|
|
color: var(--scad-primary);
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
padding: 0.45rem 1rem;
|
|
border-radius: 0.5rem;
|
|
transition: all 0.2s;
|
|
}
|
|
.btn-scad-outline:hover {
|
|
background: var(--scad-primary);
|
|
color: #fff;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Action buttons */
|
|
.btn-action {
|
|
width: 32px; height: 32px;
|
|
display: inline-flex; align-items: center; justify-content: center;
|
|
border-radius: 0.4rem; border: none;
|
|
transition: all 0.2s; font-size: 0.78rem;
|
|
}
|
|
.btn-action-edit { background: #eef3ff; color: var(--scad-primary); }
|
|
.btn-action-edit:hover { background: var(--scad-primary); color: #fff; transform: translateY(-1px); box-shadow: 0 3px 8px rgba(90,143,216,0.3); }
|
|
.btn-action-complete { background: #e8f5e9; color: var(--scad-green); }
|
|
.btn-action-complete:hover { background: var(--scad-green); color: #fff; transform: translateY(-1px); box-shadow: 0 3px 8px rgba(25,135,84,0.3); }
|
|
.btn-action-delete { background: #fff0f0; color: var(--scad-red); }
|
|
.btn-action-delete:hover { background: var(--scad-red); color: #fff; transform: translateY(-1px); box-shadow: 0 3px 8px rgba(220,53,69,0.3); }
|
|
|
|
/* Status badges */
|
|
.badge-status {
|
|
font-weight: 600; font-size: 0.75rem;
|
|
padding: 0.35em 0.65em; border-radius: 2rem;
|
|
display: inline-block; white-space: nowrap;
|
|
}
|
|
.badge-attiva { background: #e8eeff; color: #3a6bb5; }
|
|
.badge-scaduta { background: #fde8e8; color: #b91c1c; }
|
|
.badge-in-scadenza{ background: #fef3cd; color: #92600a; }
|
|
.badge-completata { background: #d1f2e0; color: #0f5132; }
|
|
|
|
/* Row coloring */
|
|
#deadlinesTable tbody tr.row-overdue { background-color: #fff5f5; }
|
|
#deadlinesTable tbody tr.row-approaching { background-color: #fffbeb; }
|
|
#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;
|
|
font-size: 0.85rem;
|
|
border-color: #d0d9e8;
|
|
min-width: 160px;
|
|
}
|
|
.filter-bar .form-select:focus {
|
|
border-color: var(--scad-primary);
|
|
box-shadow: 0 0 0 0.2rem rgba(90,143,216,0.15);
|
|
}
|
|
|
|
/* Modal groups */
|
|
.form-section-title {
|
|
font-size: 0.8rem;
|
|
font-weight: 700;
|
|
color: var(--scad-heading);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
margin-bottom: 0.75rem;
|
|
padding-bottom: 0.4rem;
|
|
border-bottom: 2px solid #e8eeff;
|
|
}
|
|
.modal-backdrop { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; }
|
|
.modal { position: fixed; }
|
|
.modal-content { background: #fff !important; }
|
|
.modal-body { background: #fff !important; }
|
|
.modal-footer { background: #fff !important; }
|
|
.modal-header {
|
|
background: var(--scad-card-bg);
|
|
border-bottom: 1px solid var(--scad-card-border);
|
|
}
|
|
.modal-title { font-weight: 700; color: var(--scad-heading); }
|
|
|
|
/* Empty state */
|
|
.empty-state { text-align: center; padding: 3rem 1rem; color: #8e99b0; }
|
|
.empty-state i { font-size: 2.5rem; margin-bottom: 0.75rem; color: #c5cfe0; display: block; }
|
|
|
|
/* Table text overflow */
|
|
#deadlinesTable td { max-width: 250px; overflow: hidden; text-overflow: ellipsis; }
|
|
#deadlinesTable td:first-child { max-width: 150px; }
|
|
|
|
/* Attachment list in modal */
|
|
.att-item {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0.5rem 0.75rem; background: #f8f9fa; border-radius: 0.4rem;
|
|
margin-bottom: 0.4rem; font-size: 0.85rem;
|
|
}
|
|
.att-item .att-name { color: var(--scad-heading); font-weight: 500; word-break: break-all; }
|
|
.att-item .att-actions { display: flex; gap: 0.4rem; flex-shrink: 0; margin-left: 0.5rem; }
|
|
.att-item .att-actions a, .att-item .att-actions button {
|
|
width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;
|
|
border-radius: 0.3rem; border: none; font-size: 0.75rem; transition: all 0.15s;
|
|
}
|
|
.att-item .att-download { background: #eef3ff; color: var(--scad-primary); text-decoration: none; }
|
|
.att-item .att-download:hover { background: var(--scad-primary); color: #fff; }
|
|
.att-item .att-remove { background: #fff0f0; color: var(--scad-red); cursor: pointer; }
|
|
.att-item .att-remove:hover { background: var(--scad-red); color: #fff; }
|
|
|
|
/* Select2 tweaks */
|
|
.select2-container--bootstrap-5 .select2-selection { min-height: 38px; border-color: #d0d9e8; }
|
|
|
|
/* Mobile cards */
|
|
.deadline-card {
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
|
padding: 1rem 1rem 0.85rem;
|
|
margin-bottom: 1rem;
|
|
transition: box-shadow 0.2s;
|
|
border-left: 5px solid var(--scad-blue);
|
|
background: #fff;
|
|
}
|
|
.deadline-card:active { box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
.deadline-card.card-overdue { border-left-color: var(--scad-red); background: #fff8f8; border-color: #fcd5d5; }
|
|
.deadline-card.card-approaching { border-left-color: var(--scad-orange); background: #fffdf5; border-color: #fce9b8; }
|
|
.deadline-card.card-completed { border-left-color: #adb5bd; opacity: 0.55; border-color: #dee2e6; }
|
|
.deadline-card .card-topic {
|
|
font-weight: 700; font-size: 0.95rem; color: var(--scad-heading);
|
|
margin-bottom: 0.35rem; line-height: 1.3;
|
|
}
|
|
.deadline-card .card-meta {
|
|
font-size: 0.8rem; color: #6c757d;
|
|
display: flex; flex-wrap: wrap; gap: 0.3rem 1rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
.deadline-card .card-meta i { width: 14px; text-align: center; }
|
|
.deadline-card .card-actions {
|
|
display: flex; gap: 0.5rem; align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.header-menu-btn {
|
|
width: 38px !important;
|
|
height: 38px !important;
|
|
padding: 0 !important;
|
|
display: inline-flex !important;
|
|
align-items: center !important;
|
|
justify-content: center !important;
|
|
line-height: 1 !important;
|
|
font-size: 1rem !important;
|
|
position: relative;
|
|
}
|
|
.header-menu-btn i {
|
|
display: block;
|
|
line-height: 1;
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
}
|
|
.header-menu-btn::after { display: none !important; }
|
|
|
|
@media (max-width: 575.98px) {
|
|
.scad-card .card-header { flex-direction: column; gap: 0.75rem; align-items: flex-start !important; }
|
|
.header-actions { width: 100%; justify-content: flex-end; }
|
|
.header-actions #btnAddDeadline { flex: 1; justify-content: center; }
|
|
.filter-bar .form-select { width: 100%; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrapper" id="appWrapper">
|
|
<?php include('../include/navbar.php'); ?>
|
|
<?php include('../include/topbar.php'); ?>
|
|
<div class="page-wrapper">
|
|
<div class="page-content">
|
|
|
|
<?php include(__DIR__ . '/include/my_deadlines_widget.php'); ?>
|
|
|
|
<?php if ($filterSubjectId): ?>
|
|
<div class="subject-filter-banner">
|
|
<div>
|
|
<i class="fa-solid fa-filter me-2"></i>
|
|
<strong>Storico per argomento:</strong>
|
|
<?= htmlspecialchars($filterSubjectName, ENT_QUOTES, 'UTF-8') ?>
|
|
<span class="text-muted ms-2">(tutte le scadenze — aperte e chiuse)</span>
|
|
</div>
|
|
<a href="scadenzario/index.php" class="btn btn-sm btn-light border">
|
|
<i class="fa-solid fa-xmark me-1"></i> Rimuovi filtro
|
|
</a>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($filterMy): ?>
|
|
<div class="subject-filter-banner">
|
|
<div>
|
|
<i class="fa-solid fa-user-check me-2"></i>
|
|
<strong>Le mie scadenze</strong>
|
|
<span class="text-muted ms-2">(assegnate a me o al reparto <?= htmlspecialchars($filterMyEmployee['department'] ?? '—', ENT_QUOTES, 'UTF-8') ?>)</span>
|
|
</div>
|
|
<a href="scadenzario/index.php" class="btn btn-sm btn-light border">
|
|
<i class="fa-solid fa-xmark me-1"></i> Tutte le scadenze
|
|
</a>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Filter Toolbar -->
|
|
<div class="filter-toolbar mb-3">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<button type="button" class="btn btn-scad-outline d-inline-flex align-items-center gap-2 flex-shrink-0" data-bs-toggle="modal" data-bs-target="#filtersModal">
|
|
<i class="fa-solid fa-filter"></i>
|
|
<span>Filtri</span>
|
|
<span id="filterCountBadge" class="badge bg-primary rounded-pill d-none" style="font-size:0.7rem">0</span>
|
|
</button>
|
|
<button id="btnResetFilters" type="button" class="btn btn-light border d-inline-flex align-items-center justify-content-center gap-1 flex-shrink-0" title="Reset filtri" style="min-width:38px;height:38px">
|
|
<i class="fa-solid fa-rotate-left"></i>
|
|
<span class="d-none d-sm-inline">Reset</span>
|
|
</button>
|
|
<span id="activeFiltersSummary" class="text-muted small text-truncate d-none d-md-inline"></span>
|
|
</div>
|
|
<div id="activeFiltersSummaryMobile" class="text-muted small d-md-none mt-1" style="padding-left:0.25rem"></div>
|
|
</div>
|
|
|
|
<!-- Filters Modal -->
|
|
<div class="modal fade" id="filtersModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="fa-solid fa-filter me-2"></i>Filtri scadenze</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Stato</label>
|
|
<select id="filterStatus" class="form-select">
|
|
<?php if ($filterSubjectId): ?>
|
|
<option value="" selected>Tutti</option>
|
|
<option value="non-completata">Non completate</option>
|
|
<?php else: ?>
|
|
<option value="non-completata" selected>Non completate</option>
|
|
<option value="">Tutti</option>
|
|
<?php endif; ?>
|
|
<option value="attiva">Attive</option>
|
|
<option value="in-scadenza">In scadenza</option>
|
|
<option value="scaduta">Scadute</option>
|
|
<option value="completata">Completate</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Argomento</label>
|
|
<select id="filterSubject" class="form-select">
|
|
<option value="">Tutti</option>
|
|
<?php foreach ($subjects as $s): ?>
|
|
<option value="<?= (int)$s['id'] ?>" <?= $filterSubjectId === (int)$s['id'] ? 'selected' : '' ?>><?= htmlspecialchars($s['name'], ENT_QUOTES, 'UTF-8') ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Reparto</label>
|
|
<select id="filterDepartment" class="form-select">
|
|
<option value="">Tutti</option>
|
|
<?php foreach ($departments as $dept): ?>
|
|
<option value="<?= htmlspecialchars($dept, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($dept, ENT_QUOTES, 'UTF-8') ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Responsabile</label>
|
|
<select id="filterEmployee" class="form-select">
|
|
<option value="">Tutti</option>
|
|
<?php foreach ($employees as $emp): ?>
|
|
<option value="<?= htmlspecialchars(trim($emp['first_name'] . ' ' . $emp['last_name']), ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars(trim($emp['first_name'] . ' ' . $emp['last_name']), ENT_QUOTES, 'UTF-8') ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Data scadenza (intervallo)</label>
|
|
<input type="text" class="form-control" id="filterDueRange" placeholder="da — a" readonly>
|
|
</div>
|
|
<div class="mb-0">
|
|
<label class="form-label fw-semibold">Data ultimo controllo (intervallo)</label>
|
|
<input type="text" class="form-control" id="filterCheckRange" placeholder="da — a" readonly>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-light border" id="btnResetFiltersModal">
|
|
<i class="fa-solid fa-rotate-left me-1"></i> Reset
|
|
</button>
|
|
<button type="button" class="btn btn-scad-primary" data-bs-dismiss="modal">
|
|
<i class="fa-solid fa-check me-1"></i> Applica
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Card -->
|
|
<div class="card scad-card">
|
|
<div class="card-header d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
<h5 class="d-none d-md-flex align-items-center"><i class="fa-solid fa-calendar-check me-2"></i>Lista Scadenze</h5>
|
|
<div class="header-actions d-flex gap-2 flex-wrap">
|
|
<!-- Desktop (≥md): tutte le azioni visibili -->
|
|
<a href="scadenzario/subjects/index.php" class="btn btn-scad-outline d-none d-md-inline-flex align-items-center gap-2">
|
|
<i class="fa-solid fa-tags"></i><span>Argomenti</span>
|
|
</a>
|
|
<a href="scadenzario/calendar.php" class="btn btn-scad-outline d-none d-md-inline-flex align-items-center gap-2">
|
|
<i class="fa-solid fa-calendar-days"></i><span>Calendario</span>
|
|
</a>
|
|
<button class="btn btn-scad-outline d-none d-md-inline-flex align-items-center gap-2" id="btnStampa">
|
|
<i class="fa-solid fa-print"></i><span>Stampa</span>
|
|
</button>
|
|
|
|
<button class="btn btn-scad-primary d-inline-flex align-items-center gap-2" id="btnAddDeadline">
|
|
<i class="fa-solid fa-plus"></i><span>Nuova Scadenza</span>
|
|
</button>
|
|
|
|
<!-- Mobile (<md): menu a tendina con le altre azioni -->
|
|
<div class="dropdown d-md-none">
|
|
<button class="btn btn-scad-outline header-menu-btn" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Altre azioni">
|
|
<i class="fa-solid fa-bars"></i>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li><a class="dropdown-item d-flex align-items-center gap-2" href="scadenzario/subjects/index.php"><i class="fa-solid fa-tags"></i> Argomenti</a></li>
|
|
<li><a class="dropdown-item d-flex align-items-center gap-2" href="scadenzario/calendar.php"><i class="fa-solid fa-calendar-days"></i> Calendario</a></li>
|
|
<li><button type="button" class="dropdown-item d-flex align-items-center gap-2" id="btnStampaMobile"><i class="fa-solid fa-print"></i> Stampa</button></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<?php if (count($deadlines) === 0): ?>
|
|
<div class="empty-state">
|
|
<i class="fa-solid fa-calendar-xmark"></i>
|
|
<p>Nessuna scadenza registrata.<br>Clicca <strong>"Nuova Scadenza"</strong> per aggiungere la prima.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
|
|
<?php
|
|
// Pre-compute status for each row (used by both views)
|
|
$processedDeadlines = [];
|
|
foreach ($deadlines as $row) {
|
|
$dueDate = $row['due_date'];
|
|
$nDays = (int)$row['notification_days'];
|
|
$approachDate = date('Y-m-d', strtotime($today . ' + ' . $nDays . ' days'));
|
|
$isCompleted = $row['status'] === 'completed';
|
|
$isOverdue = !$isCompleted && $dueDate < $today;
|
|
$isApproaching = !$isCompleted && !$isOverdue && $dueDate <= $approachDate;
|
|
|
|
if ($isCompleted) { $statusLabel = 'Completata'; $statusClass = 'badge-completata'; $statusKey = 'completata'; $rowClass = 'row-completed'; $cardClass = 'card-completed'; }
|
|
elseif ($isOverdue) { $statusLabel = 'Scaduta'; $statusClass = 'badge-scaduta'; $statusKey = 'scaduta'; $rowClass = 'row-overdue'; $cardClass = 'card-overdue'; }
|
|
elseif ($isApproaching) { $statusLabel = 'In scadenza'; $statusClass = 'badge-in-scadenza'; $statusKey = 'in-scadenza'; $rowClass = 'row-approaching'; $cardClass = 'card-approaching'; }
|
|
else { $statusLabel = 'Attiva'; $statusClass = 'badge-attiva'; $statusKey = 'attiva'; $rowClass = ''; $cardClass = ''; }
|
|
|
|
$row['_dueFmt'] = date('d/m/Y', strtotime($dueDate));
|
|
$row['_checkFmt'] = $row['check_date'] ? date('d/m/Y', strtotime($row['check_date'])) : '—';
|
|
$row['_statusLabel'] = $statusLabel;
|
|
$row['_statusClass'] = $statusClass;
|
|
$row['_statusKey'] = $statusKey;
|
|
$row['_rowClass'] = $rowClass;
|
|
$row['_cardClass'] = $cardClass;
|
|
$row['_isCompleted'] = $isCompleted;
|
|
|
|
// Merge assigned departments + employee departments
|
|
$allDepts = [];
|
|
if (!empty($row['reparti_assegnati'])) $allDepts = array_map('trim', explode(',', $row['reparti_assegnati']));
|
|
if (!empty($row['reparti_persone'])) $allDepts = array_merge($allDepts, array_map('trim', explode(',', $row['reparti_persone'])));
|
|
$row['reparti'] = implode(', ', array_unique(array_filter($allDepts)));
|
|
|
|
$processedDeadlines[] = $row;
|
|
}
|
|
?>
|
|
|
|
<!-- MOBILE: Cards (visible < md) -->
|
|
<div class="d-md-none" id="mobileCards">
|
|
<?php foreach ($processedDeadlines as $row): ?>
|
|
<div class="deadline-card <?= $row['_cardClass'] ?>"
|
|
data-id="<?= (int)$row['id'] ?>"
|
|
data-status="<?= $row['_statusKey'] ?>"
|
|
data-subject-id="<?= (int)($row['subject_id'] ?? 0) ?>"
|
|
<?php if (!empty($row['subject_color'])): ?>data-subject-color="<?= htmlspecialchars($row['subject_color'], ENT_QUOTES, 'UTF-8') ?>" style="--subject-color: <?= htmlspecialchars($row['subject_color'], ENT_QUOTES, 'UTF-8') ?>"<?php endif; ?>
|
|
data-department="<?= htmlspecialchars($row['reparti'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
|
data-employees="<?= htmlspecialchars($row['responsabili'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
|
data-due-date="<?= htmlspecialchars($row['due_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
|
data-check-date="<?= htmlspecialchars($row['check_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
|
|
<?php if (!empty($row['subject_name'])): ?>
|
|
<div class="mb-1"><span class="subject-chip" style="background: <?= htmlspecialchars($row['subject_color'] ?: '#6c757d', ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($row['subject_name'], ENT_QUOTES, 'UTF-8') ?></span></div>
|
|
<?php endif; ?>
|
|
<div class="d-flex justify-content-between align-items-start mb-1">
|
|
<a href="scadenzario/detail.php?id=<?= (int)$row['id'] ?>" class="card-topic text-decoration-none"><?= htmlspecialchars($row['topic'], ENT_QUOTES, 'UTF-8') ?></a>
|
|
<span class="badge-status <?= $row['_statusClass'] ?> ms-2 flex-shrink-0"><?= $row['_statusLabel'] ?></span>
|
|
</div>
|
|
<div class="card-meta">
|
|
<span><i class="fa-regular fa-calendar"></i> <?= $row['_dueFmt'] ?></span>
|
|
<?php if ($row['reparti']): ?>
|
|
<span><i class="fa-regular fa-building"></i> <?= htmlspecialchars($row['reparti'], ENT_QUOTES, 'UTF-8') ?></span>
|
|
<?php endif; ?>
|
|
<?php if ($row['responsabili']): ?>
|
|
<span><i class="fa-regular fa-user"></i> <?= htmlspecialchars($row['responsabili'], ENT_QUOTES, 'UTF-8') ?></span>
|
|
<?php endif; ?>
|
|
<?php if ($row['law_regulation']): ?>
|
|
<span><i class="fa-regular fa-file-lines"></i> <?= htmlspecialchars($row['law_regulation'], ENT_QUOTES, 'UTF-8') ?></span>
|
|
<?php endif; ?>
|
|
<?php if ((int)$row['attachment_count'] > 0): ?>
|
|
<span><i class="fa-solid fa-paperclip"></i> <?= (int)$row['attachment_count'] ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php if (!$row['_isCompleted']): ?>
|
|
<div class="card-actions">
|
|
<div class="d-flex gap-1">
|
|
<button class="btn-action btn-action-edit btn-edit" title="Modifica"><i class="fa-solid fa-pen"></i></button>
|
|
<button class="btn-action btn-action-complete btn-complete" title="Completa"><i class="fa-solid fa-check"></i></button>
|
|
<button class="btn-action btn-action-delete btn-delete" title="Elimina"><i class="fa-solid fa-trash"></i></button>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<!-- DESKTOP: DataTable (visible >= md) -->
|
|
<div class="d-none d-md-block">
|
|
<div class="table-responsive">
|
|
<table id="deadlinesTable" class="table table-hover align-middle mb-0" style="width:100%">
|
|
<thead>
|
|
<tr>
|
|
<th>Argomento</th>
|
|
<th>Dettaglio</th>
|
|
<th class="d-none d-lg-table-cell">Legge/Art.</th>
|
|
<th>Scadenza</th>
|
|
<th class="d-none d-lg-table-cell">Verifica</th>
|
|
<th>Responsabili</th>
|
|
<th>Stato</th>
|
|
<th class="text-center" style="width:120px">Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($processedDeadlines as $row): ?>
|
|
<tr class="<?= $row['_rowClass'] ?>"
|
|
data-id="<?= (int)$row['id'] ?>"
|
|
data-status="<?= $row['_statusKey'] ?>"
|
|
data-subject-id="<?= (int)($row['subject_id'] ?? 0) ?>"
|
|
<?php if (!empty($row['subject_color'])): ?>data-subject-color="<?= htmlspecialchars($row['subject_color'], ENT_QUOTES, 'UTF-8') ?>" style="--subject-color: <?= htmlspecialchars($row['subject_color'], ENT_QUOTES, 'UTF-8') ?>"<?php endif; ?>
|
|
data-department="<?= htmlspecialchars($row['reparti'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
|
data-employees="<?= htmlspecialchars($row['responsabili'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
|
data-due-date="<?= htmlspecialchars($row['due_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>"
|
|
data-check-date="<?= htmlspecialchars($row['check_date'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
|
|
<td>
|
|
<?php if (!empty($row['subject_name'])): ?>
|
|
<span class="subject-chip" style="background: <?= htmlspecialchars($row['subject_color'] ?: '#6c757d', ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($row['subject_name'], ENT_QUOTES, 'UTF-8') ?></span>
|
|
<?php else: ?>
|
|
<span class="text-muted">—</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<a href="scadenzario/detail.php?id=<?= (int)$row['id'] ?>" class="fw-semibold text-decoration-none" style="color:var(--scad-heading)"><?= htmlspecialchars($row['topic'], ENT_QUOTES, 'UTF-8') ?></a>
|
|
<?php if ((int)$row['attachment_count'] > 0): ?>
|
|
<span class="text-muted ms-1" title="<?= (int)$row['attachment_count'] ?> allegati"><i class="fa-solid fa-paperclip"></i> <?= (int)$row['attachment_count'] ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="d-none d-lg-table-cell text-muted"><?= htmlspecialchars($row['law_regulation'] ?? '—', ENT_QUOTES, 'UTF-8') ?></td>
|
|
<td><span class="text-nowrap"><?= $row['_dueFmt'] ?></span></td>
|
|
<td class="d-none d-lg-table-cell text-muted"><?= $row['_checkFmt'] ?></td>
|
|
<td>
|
|
<?php if ($row['reparti']): ?><span class="text-muted"><i class="fa-regular fa-building me-1"></i><?= htmlspecialchars($row['reparti'], ENT_QUOTES, 'UTF-8') ?></span><?php endif; ?>
|
|
<?php if ($row['reparti'] && $row['responsabili']): ?><br><?php endif; ?>
|
|
<?php if ($row['responsabili']): ?><span><i class="fa-regular fa-user me-1"></i><?= htmlspecialchars($row['responsabili'], ENT_QUOTES, 'UTF-8') ?></span><?php endif; ?>
|
|
<?php if (!$row['reparti'] && !$row['responsabili']): ?>—<?php endif; ?>
|
|
</td>
|
|
<td><span class="badge-status <?= $row['_statusClass'] ?>"><?= $row['_statusLabel'] ?></span></td>
|
|
<td class="text-center">
|
|
<?php if (!$row['_isCompleted']): ?>
|
|
<div class="d-inline-flex gap-1">
|
|
<button class="btn-action btn-action-edit btn-edit" title="Modifica"><i class="fa-solid fa-pen"></i></button>
|
|
<button class="btn-action btn-action-complete btn-complete" title="Completa"><i class="fa-solid fa-check"></i></button>
|
|
<button class="btn-action btn-action-delete btn-delete" title="Elimina"><i class="fa-solid fa-trash"></i></button>
|
|
</div>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
<?php include('../include/footer.php'); ?>
|
|
</div>
|
|
|
|
<!-- Deadline Modal -->
|
|
<div class="modal fade" id="deadlineModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg modal-fullscreen-sm-down">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="modalTitle">Nuova Scadenza</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
|
</div>
|
|
<form id="deadlineForm">
|
|
<div class="modal-body">
|
|
<input type="hidden" id="dlId" name="id" value="">
|
|
|
|
<!-- Group 1: Informazioni principali -->
|
|
<div class="form-section-title">Informazioni principali</div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-6">
|
|
<label for="dlSubject" class="form-label fw-semibold">Argomento</label>
|
|
<div class="d-flex gap-2">
|
|
<select class="form-select" id="dlSubject" name="subject_id" style="flex:1">
|
|
<option value="">— Nessuno —</option>
|
|
<?php foreach ($subjects as $s): ?>
|
|
<option value="<?= (int)$s['id'] ?>" data-color="<?= htmlspecialchars($s['color'], ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($s['name'], ENT_QUOTES, 'UTF-8') ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<a href="scadenzario/subjects/index.php" target="_blank" class="btn btn-scad-outline" title="Gestisci argomenti">
|
|
<i class="fa-solid fa-gear"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<label for="dlLaw" class="form-label fw-semibold">Legge / Articolo</label>
|
|
<input type="text" class="form-control" id="dlLaw" name="law_regulation" maxlength="500" placeholder="es. D.Lgs. 81/2008, D.M. 10.03.1998...">
|
|
</div>
|
|
<div class="col-12">
|
|
<label for="dlTopic" class="form-label fw-semibold">Dettaglio <span class="text-danger">*</span></label>
|
|
<textarea class="form-control" id="dlTopic" name="topic" required maxlength="500" rows="2" placeholder="es. Verifica estintori, Autorizzazione trasporto rifiuti..."></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Group 2: Date e frequenza -->
|
|
<div class="form-section-title">Date e frequenza</div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12 col-md-4">
|
|
<label for="dlRecurrence" class="form-label fw-semibold">Periodicità</label>
|
|
<select class="form-select" id="dlRecurrence" name="recurrence_type">
|
|
<option value="once">Una tantum</option>
|
|
<option value="monthly">Mensile</option>
|
|
<option value="quarterly">Trimestrale</option>
|
|
<option value="semiannual">Semestrale</option>
|
|
<option value="annual">Annuale</option>
|
|
<option value="biennial">Biennale</option>
|
|
<option value="triennial">Triennale</option>
|
|
<option value="quadriennial">Quadriennale</option>
|
|
<option value="quinquennial">Quinquennale</option>
|
|
<option value="decennial">Decennale</option>
|
|
<option value="quindecennial">Quindicennale</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<label for="dlDocDate" class="form-label fw-semibold">Data documento</label>
|
|
<input type="date" class="form-control" id="dlDocDate" name="document_date">
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<label for="dlDueDate" class="form-label fw-semibold">Data scadenza <span class="text-danger">*</span></label>
|
|
<input type="date" class="form-control" id="dlDueDate" name="due_date" required>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<label for="dlCheckDate" class="form-label fw-semibold">Data ultimo controllo</label>
|
|
<input type="date" class="form-control" id="dlCheckDate" name="check_date">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Group 3: Responsabili -->
|
|
<div class="form-section-title">Responsabili</div>
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12">
|
|
<label for="dlDepartments" class="form-label fw-semibold">Reparti</label>
|
|
<select class="form-select" id="dlDepartments" name="department_names[]" multiple>
|
|
<?php foreach ($departments as $dept): ?>
|
|
<option value="<?= htmlspecialchars($dept, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($dept, ENT_QUOTES, 'UTF-8') ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<div class="form-text">Tutto il reparto sarà responsabile</div>
|
|
</div>
|
|
<div class="col-12">
|
|
<label for="dlEmployees" class="form-label fw-semibold">Singoli responsabili</label>
|
|
<select class="form-select" id="dlEmployees" name="employee_ids[]" multiple>
|
|
<?php foreach ($employees as $emp): ?>
|
|
<option value="<?= (int)$emp['id'] ?>">
|
|
<?= htmlspecialchars($emp['first_name'] . ' ' . $emp['last_name'], ENT_QUOTES, 'UTF-8') ?><?php if ($emp['department']): ?> (<?= htmlspecialchars($emp['department'], ENT_QUOTES, 'UTF-8') ?>)<?php endif; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Group 4: Dettagli aggiuntivi -->
|
|
<div class="form-section-title">Dettagli aggiuntivi</div>
|
|
<div class="row g-3">
|
|
<div class="col-12 col-md-4">
|
|
<label for="dlNotifDays" class="form-label fw-semibold">Giorni preavviso</label>
|
|
<input type="number" class="form-control" id="dlNotifDays" name="notification_days" value="7" min="1" max="365">
|
|
</div>
|
|
<div class="col-12 col-md-8">
|
|
<label for="dlStorage" class="form-label fw-semibold">Luogo archiviazione</label>
|
|
<input type="text" class="form-control" id="dlStorage" name="storage_location" maxlength="500" placeholder="es. Armadio A3, Server/Documenti/Sicurezza...">
|
|
</div>
|
|
<div class="col-12">
|
|
<label for="dlNotes" class="form-label fw-semibold">Note</label>
|
|
<textarea class="form-control" id="dlNotes" name="notes" rows="3" placeholder="es. Scadenza 09/06/2026, Attività in appalto a Ditta specializzata..."></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Group 5: Allegati -->
|
|
<div class="form-section-title mt-4">Allegati</div>
|
|
<div id="attachmentsList" class="mb-3"></div>
|
|
<div class="row g-3">
|
|
<div class="col-12">
|
|
<label for="dlFiles" class="form-label fw-semibold">Carica file</label>
|
|
<input type="file" class="form-control" id="dlFiles" multiple>
|
|
<div class="form-text">Puoi selezionare più file contemporaneamente</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
|
|
<button type="submit" class="btn btn-scad-primary">
|
|
<i class="fa-solid fa-check me-1"></i> Salva
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php include('../jsinclude.php'); ?>
|
|
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
|
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/i18n/it.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/it.js"></script>
|
|
<script>
|
|
$(document).ready(function() {
|
|
|
|
// --- Flatpickr date range ---
|
|
function formatDate(d) {
|
|
return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
|
|
}
|
|
var fpOpts = {
|
|
mode: 'range',
|
|
dateFormat: 'd/m/Y',
|
|
locale: 'it',
|
|
allowInput: false,
|
|
onChange: function() {
|
|
if (table) table.draw();
|
|
filterCards();
|
|
if (typeof updateFilterBadge === 'function') updateFilterBadge();
|
|
}
|
|
};
|
|
var fpDue = flatpickr('#filterDueRange', fpOpts);
|
|
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...',
|
|
allowClear: true,
|
|
dropdownParent: $('#deadlineModal .modal-body'),
|
|
language: 'it',
|
|
width: '100%'
|
|
});
|
|
|
|
$('#dlEmployees').select2({
|
|
theme: 'bootstrap-5',
|
|
placeholder: 'Seleziona persone...',
|
|
allowClear: true,
|
|
dropdownParent: $('#deadlineModal .modal-body'),
|
|
language: 'it',
|
|
width: '100%'
|
|
});
|
|
|
|
// --- Auto-calc due_date from document_date + recurrence ---
|
|
var RECURRENCE_OFFSETS = {
|
|
monthly: { months: 1 },
|
|
quarterly: { months: 3 },
|
|
semiannual: { months: 6 },
|
|
annual: { years: 1 },
|
|
biennial: { years: 2 },
|
|
triennial: { years: 3 },
|
|
quadriennial: { years: 4 },
|
|
quinquennial: { years: 5 },
|
|
decennial: { years: 10 },
|
|
quindecennial:{ years: 15 }
|
|
};
|
|
function computeDueDate() {
|
|
var docVal = document.getElementById('dlDocDate').value;
|
|
var recurrence = document.getElementById('dlRecurrence').value;
|
|
var offset = RECURRENCE_OFFSETS[recurrence];
|
|
if (!docVal || !offset) return;
|
|
var d = new Date(docVal + 'T00:00:00');
|
|
if (isNaN(d.getTime())) return;
|
|
if (offset.months) d.setMonth(d.getMonth() + offset.months);
|
|
if (offset.years) d.setFullYear(d.getFullYear() + offset.years);
|
|
var iso = d.getFullYear() + '-' +
|
|
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
|
String(d.getDate()).padStart(2, '0');
|
|
document.getElementById('dlDueDate').value = iso;
|
|
}
|
|
$('#dlDocDate, #dlRecurrence').on('change', computeDueDate);
|
|
|
|
// --- DataTables custom filters ---
|
|
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
|
|
if (settings.nTable.id !== 'deadlinesTable') return true;
|
|
var row = settings.aoData[dataIndex].nTr;
|
|
var statusFilter = $('#filterStatus').val();
|
|
var deptFilter = $('#filterDepartment').val();
|
|
|
|
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;
|
|
var dueVal = row.getAttribute('data-due-date') || '';
|
|
if (dueDates.length >= 1 && dueVal < formatDate(dueDates[0])) return false;
|
|
if (dueDates.length >= 2 && dueVal > formatDate(dueDates[1])) return false;
|
|
var chkDates = fpCheck.selectedDates;
|
|
var chkVal = row.getAttribute('data-check-date') || '';
|
|
if (chkDates.length >= 1 && (!chkVal || chkVal < formatDate(chkDates[0]))) return false;
|
|
if (chkDates.length >= 2 && (!chkVal || chkVal > formatDate(chkDates[1]))) return false;
|
|
return true;
|
|
});
|
|
|
|
// --- DataTables init ---
|
|
var table = null;
|
|
if ($('#deadlinesTable').length) {
|
|
table = $('#deadlinesTable').DataTable({
|
|
order: [[3, 'asc']],
|
|
pageLength: 25,
|
|
language: {
|
|
url: 'https://cdn.datatables.net/plug-ins/1.13.7/i18n/it-IT.json'
|
|
},
|
|
columnDefs: [
|
|
{ orderable: false, targets: -1 }
|
|
]
|
|
});
|
|
}
|
|
|
|
// --- Filters ---
|
|
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;
|
|
var dueVal = $(this).data('due-date') || '';
|
|
if (dueDates.length >= 1 && dueVal < formatDate(dueDates[0])) show = false;
|
|
if (dueDates.length >= 2 && dueVal > formatDate(dueDates[1])) show = false;
|
|
var chkDates = fpCheck.selectedDates;
|
|
var chkVal = $(this).data('check-date') || '';
|
|
if (chkDates.length >= 1 && (!chkVal || chkVal < formatDate(chkDates[0]))) show = false;
|
|
if (chkDates.length >= 2 && (!chkVal || chkVal > formatDate(chkDates[1]))) show = false;
|
|
$(this).toggle(show);
|
|
});
|
|
}
|
|
|
|
function updateFilterBadge() {
|
|
var active = 0;
|
|
var summary = [];
|
|
var st = $('#filterStatus').val();
|
|
if (st && st !== 'non-completata') { active++; summary.push($('#filterStatus option:selected').text()); }
|
|
var subj = $('#filterSubject').val();
|
|
if (subj) { active++; summary.push('Argomento: ' + $('#filterSubject option:selected').text()); }
|
|
var dept = $('#filterDepartment').val();
|
|
if (dept) { active++; summary.push('Reparto: ' + dept); }
|
|
var emp = $('#filterEmployee').val();
|
|
if (emp) { active++; summary.push('Responsabile: ' + emp); }
|
|
if (fpDue.selectedDates.length) { active++; summary.push('Scadenza: ' + $('#filterDueRange').val()); }
|
|
if (fpCheck.selectedDates.length) { active++; summary.push('Controllo: ' + $('#filterCheckRange').val()); }
|
|
|
|
var $badge = $('#filterCountBadge');
|
|
if (active > 0) $badge.text(active).removeClass('d-none');
|
|
else $badge.addClass('d-none');
|
|
|
|
var summaryText = summary.length ? summary.slice(0, 2).join(' • ') + (summary.length > 2 ? ' +' + (summary.length - 2) : '') : '';
|
|
$('#activeFiltersSummary, #activeFiltersSummaryMobile').text(summaryText);
|
|
}
|
|
|
|
$('#filterStatus, #filterDepartment, #filterEmployee, #filterSubject').on('change', function() {
|
|
if (table) table.draw();
|
|
filterCards();
|
|
updateFilterBadge();
|
|
});
|
|
function resetFilters() {
|
|
$('#filterStatus').val('non-completata');
|
|
$('#filterDepartment').val('');
|
|
$('#filterEmployee').val('');
|
|
$('#filterSubject').val('');
|
|
fpDue.clear();
|
|
fpCheck.clear();
|
|
if (table) table.draw();
|
|
filterCards();
|
|
updateFilterBadge();
|
|
}
|
|
$('#btnResetFilters, #btnResetFiltersModal').on('click', resetFilters);
|
|
|
|
// Apply URL filters (e.g. from dashboard widgets)
|
|
(function () {
|
|
var qs = new URLSearchParams(window.location.search);
|
|
var qStatus = qs.get('filter_status');
|
|
if (qStatus) $('#filterStatus').val(qStatus);
|
|
})();
|
|
|
|
// Apply default filter on load
|
|
if (table) table.draw();
|
|
filterCards();
|
|
updateFilterBadge();
|
|
|
|
// --- Modal ---
|
|
var modal = new bootstrap.Modal(document.getElementById('deadlineModal'));
|
|
var form = document.getElementById('deadlineForm');
|
|
|
|
// Add
|
|
document.getElementById('btnAddDeadline').addEventListener('click', function() {
|
|
form.reset();
|
|
document.getElementById('dlId').value = '';
|
|
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([]);
|
|
modal.show();
|
|
});
|
|
|
|
// Save
|
|
var isSaving = false;
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
if (isSaving) return;
|
|
isSaving = true;
|
|
var saveBtn = form.querySelector('[type="submit"]');
|
|
saveBtn.disabled = true;
|
|
saveBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin me-1"></i> Salvataggio...';
|
|
var formData = new FormData(form);
|
|
|
|
fetch('scadenzario/ajax/save_deadline.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
var deadlineId = data.id;
|
|
var fileInput = document.getElementById('dlFiles');
|
|
if (fileInput.files.length > 0) {
|
|
// Upload files
|
|
var fileData = new FormData();
|
|
fileData.append('deadline_id', deadlineId);
|
|
for (var i = 0; i < fileInput.files.length; i++) {
|
|
fileData.append('files[]', fileInput.files[i]);
|
|
}
|
|
return fetch('scadenzario/ajax/upload_attachment.php', { method: 'POST', body: fileData })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(upData) {
|
|
modal.hide();
|
|
Swal.fire({ icon: 'success', title: 'Salvato', text: data.message + ' ' + upData.message, timer: 2000, showConfirmButton: false })
|
|
.then(function() { location.reload(); });
|
|
});
|
|
} else {
|
|
modal.hide();
|
|
Swal.fire({ icon: 'success', title: 'Salvato', text: data.message, timer: 1500, showConfirmButton: false })
|
|
.then(function() { location.reload(); });
|
|
}
|
|
} else {
|
|
Swal.fire('Errore', data.message, 'error');
|
|
isSaving = false; saveBtn.disabled = false;
|
|
saveBtn.innerHTML = '<i class="fa-solid fa-check me-1"></i> Salva';
|
|
}
|
|
})
|
|
.catch(function() {
|
|
Swal.fire('Errore', 'Errore di connessione.', 'error');
|
|
isSaving = false; saveBtn.disabled = false;
|
|
saveBtn.innerHTML = '<i class="fa-solid fa-check me-1"></i> Salva';
|
|
});
|
|
});
|
|
|
|
// Render attachments list
|
|
function renderAttachments(attachments) {
|
|
var container = document.getElementById('attachmentsList');
|
|
if (!attachments || attachments.length === 0) {
|
|
container.innerHTML = '<div class="text-muted" style="font-size:0.85rem">Nessun allegato</div>';
|
|
return;
|
|
}
|
|
container.innerHTML = attachments.map(function(a) {
|
|
return '<div class="att-item" data-att-id="' + a.id + '">' +
|
|
'<span class="att-name"><i class="fa-solid fa-paperclip me-1"></i>' + $('<span>').text(a.original_name).html() + '</span>' +
|
|
'<span class="att-actions">' +
|
|
'<a href="scadenzario/ajax/download_attachment.php?id=' + a.id + '" class="att-download" title="Scarica"><i class="fa-solid fa-download"></i></a>' +
|
|
'<button type="button" class="att-remove" title="Elimina" data-att-id="' + a.id + '"><i class="fa-solid fa-xmark"></i></button>' +
|
|
'</span></div>';
|
|
}).join('');
|
|
}
|
|
|
|
// Delete attachment
|
|
$(document).on('click', '.att-remove', function(e) {
|
|
e.preventDefault();
|
|
var btn = $(this);
|
|
var attId = btn.data('att-id');
|
|
Swal.fire({
|
|
title: 'Eliminare allegato?',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#dc3545',
|
|
cancelButtonText: 'Annulla',
|
|
confirmButtonText: 'Elimina'
|
|
}).then(function(result) {
|
|
if (result.isConfirmed) {
|
|
fetch('scadenzario/ajax/delete_attachment.php?id=' + attId)
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
btn.closest('.att-item').remove();
|
|
if ($('#attachmentsList .att-item').length === 0) {
|
|
renderAttachments([]);
|
|
}
|
|
} else {
|
|
Swal.fire('Errore', data.message, 'error');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Edit
|
|
$(document).on('click', '.btn-edit', function() {
|
|
var id = $(this).closest('[data-id]').data('id');
|
|
fetch('scadenzario/ajax/get_deadline.php?id=' + id)
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (!data.success) { Swal.fire('Errore', data.message, 'error'); return; }
|
|
var d = data.data;
|
|
document.getElementById('dlId').value = d.id;
|
|
$('#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';
|
|
document.getElementById('dlDocDate').value = d.document_date || '';
|
|
document.getElementById('dlDueDate').value = d.due_date || '';
|
|
document.getElementById('dlCheckDate').value = d.check_date || '';
|
|
document.getElementById('dlNotifDays').value = d.notification_days || 7;
|
|
document.getElementById('dlStorage').value = d.storage_location || '';
|
|
document.getElementById('dlNotes').value = d.notes || '';
|
|
document.getElementById('dlFiles').value = '';
|
|
document.getElementById('modalTitle').textContent = 'Modifica Scadenza';
|
|
$('#dlDepartments').val(d.department_names || []).trigger('change');
|
|
$('#dlEmployees').val(d.employee_ids.map(String)).trigger('change');
|
|
renderAttachments(d.attachments || []);
|
|
modal.show();
|
|
})
|
|
.catch(function() { Swal.fire('Errore', 'Errore di connessione.', 'error'); });
|
|
});
|
|
|
|
// Complete
|
|
$(document).on('click', '.btn-complete', function() {
|
|
var el = $(this).closest('[data-id]');
|
|
var id = el.data('id');
|
|
Swal.fire({
|
|
title: 'Completare la scadenza?',
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#198754',
|
|
cancelButtonText: 'Annulla',
|
|
confirmButtonText: 'Completa'
|
|
}).then(function(result) {
|
|
if (result.isConfirmed) {
|
|
fetch('scadenzario/ajax/complete_deadline.php?id=' + id)
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
Swal.fire({ icon: 'success', title: 'Completata', text: data.message, timer: 2500, showConfirmButton: false })
|
|
.then(function() { location.reload(); });
|
|
} else {
|
|
Swal.fire('Errore', data.message, 'error');
|
|
}
|
|
})
|
|
.catch(function() { Swal.fire('Errore', 'Errore di connessione.', 'error'); });
|
|
}
|
|
});
|
|
});
|
|
|
|
// Delete
|
|
$(document).on('click', '.btn-delete', function() {
|
|
var el = $(this).closest('[data-id]');
|
|
var id = el.data('id');
|
|
Swal.fire({
|
|
title: 'Eliminare la scadenza?',
|
|
text: 'Questa azione non può essere annullata.',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#dc3545',
|
|
cancelButtonText: 'Annulla',
|
|
confirmButtonText: 'Elimina'
|
|
}).then(function(result) {
|
|
if (result.isConfirmed) {
|
|
fetch('scadenzario/ajax/delete_deadline.php?id=' + id)
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
Swal.fire({ icon: 'success', title: 'Eliminata', text: data.message, timer: 1500, showConfirmButton: false })
|
|
.then(function() { location.reload(); });
|
|
} else {
|
|
Swal.fire('Errore', data.message, 'error');
|
|
}
|
|
})
|
|
.catch(function() { Swal.fire('Errore', 'Errore di connessione.', 'error'); });
|
|
}
|
|
});
|
|
});
|
|
|
|
// Auto-open edit modal from ?edit=ID
|
|
var urlParams = new URLSearchParams(window.location.search);
|
|
var editId = urlParams.get('edit');
|
|
if (editId) {
|
|
history.replaceState(null, '', 'scadenzario/index.php');
|
|
fetch('scadenzario/ajax/get_deadline.php?id=' + editId)
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (!data.success) return;
|
|
var d = data.data;
|
|
document.getElementById('dlId').value = d.id;
|
|
$('#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';
|
|
document.getElementById('dlDocDate').value = d.document_date || '';
|
|
document.getElementById('dlDueDate').value = d.due_date || '';
|
|
document.getElementById('dlCheckDate').value = d.check_date || '';
|
|
document.getElementById('dlNotifDays').value = d.notification_days || 7;
|
|
document.getElementById('dlStorage').value = d.storage_location || '';
|
|
document.getElementById('dlNotes').value = d.notes || '';
|
|
document.getElementById('dlFiles').value = '';
|
|
document.getElementById('modalTitle').textContent = 'Modifica Scadenza';
|
|
$('#dlDepartments').val(d.department_names || []).trigger('change');
|
|
$('#dlEmployees').val(d.employee_ids.map(String)).trigger('change');
|
|
renderAttachments(d.attachments || []);
|
|
modal.show();
|
|
});
|
|
}
|
|
|
|
// Stampa
|
|
function doStampa() {
|
|
var params = [];
|
|
var s = $('#filterStatus').val();
|
|
var d = $('#filterDepartment').val();
|
|
var emp = $('#filterEmployee').val();
|
|
if (s) params.push('status=' + encodeURIComponent(s));
|
|
if (d) params.push('department=' + encodeURIComponent(d));
|
|
if (emp) params.push('employee=' + encodeURIComponent(emp));
|
|
var dueDates = fpDue.selectedDates;
|
|
if (dueDates.length >= 1) params.push('due_from=' + formatDate(dueDates[0]));
|
|
if (dueDates.length >= 2) params.push('due_to=' + formatDate(dueDates[1]));
|
|
var chkDates = fpCheck.selectedDates;
|
|
if (chkDates.length >= 1) params.push('check_from=' + formatDate(chkDates[0]));
|
|
if (chkDates.length >= 2) params.push('check_to=' + formatDate(chkDates[1]));
|
|
window.open('scadenzario/print.php' + (params.length ? '?' + params.join('&') : ''), '_blank');
|
|
}
|
|
document.getElementById('btnStampa').addEventListener('click', doStampa);
|
|
var btnStampaMobile = document.getElementById('btnStampaMobile');
|
|
if (btnStampaMobile) btnStampaMobile.addEventListener('click', doStampa);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|