276 lines
15 KiB
PHP
276 lines
15 KiB
PHP
<?php include('../include/headscript.php'); ?>
|
|
<?php
|
|
$db = DBHandlerSelect::getInstance();
|
|
$pdo = $db->getConnection();
|
|
$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);
|
|
?>
|
|
<!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']);
|
|
$baseHref = dirname($scriptDir) . '/';
|
|
?>
|
|
<base href="<?= $baseHref ?>">
|
|
<?php include('../cssinclude.php'); ?>
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.9/index.global.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/@fullcalendar/core@6.1.9/locales/it.global.min.js"></script>
|
|
<title>Calendario - Scadenzario</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-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.05rem;
|
|
}
|
|
.scad-card .card-body { padding: 1.25rem; }
|
|
.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; }
|
|
.scad-breadcrumb { background: transparent; padding: 0; margin-bottom: 1rem; }
|
|
.scad-breadcrumb .breadcrumb-item a { color: var(--scad-primary); text-decoration: none; font-weight: 500; }
|
|
.scad-breadcrumb .breadcrumb-item a:hover { color: var(--scad-primary-hover); }
|
|
.scad-breadcrumb .breadcrumb-item.active { color: #6c757d; font-weight: 600; }
|
|
|
|
/* FullCalendar overrides */
|
|
.fc { font-size: 0.9rem; }
|
|
.fc .fc-toolbar-title { font-size: 1.15rem; font-weight: 700; color: var(--scad-heading); }
|
|
.fc .fc-button-primary {
|
|
background: var(--scad-primary); border-color: var(--scad-primary);
|
|
font-weight: 600; font-size: 0.82rem; border-radius: 0.4rem;
|
|
}
|
|
.fc .fc-button-primary:hover { background: var(--scad-primary-hover); border-color: var(--scad-primary-hover); }
|
|
.fc .fc-button-primary:disabled { background: #9bbce6; border-color: #9bbce6; }
|
|
.fc .fc-button-primary:not(:disabled).fc-button-active { background: var(--scad-heading); border-color: var(--scad-heading); }
|
|
.fc .fc-daygrid-day-number { color: var(--scad-heading); font-weight: 500; }
|
|
.fc .fc-daygrid-day.fc-day-today { background: #f0f4ff; }
|
|
.fc .fc-event { border-radius: 0.3rem; padding: 2px 4px; font-weight: 600; cursor: pointer; }
|
|
.fc .fc-event:hover { filter: brightness(0.9); }
|
|
.fc .fc-list-event:hover td { background: #f0f4ff; }
|
|
|
|
/* Legend */
|
|
.legend { display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 1rem; }
|
|
.legend-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.82rem; color: #6c757d; }
|
|
.legend-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
|
|
|
|
@media (max-width: 767.98px) {
|
|
.fc .fc-toolbar { flex-direction: column; gap: 0.5rem; }
|
|
.fc .fc-toolbar-title { font-size: 1rem; }
|
|
/* Stack list events vertically on mobile */
|
|
.fc .fc-list-table { display: block; }
|
|
.fc .fc-list-table tbody { display: block; }
|
|
.fc .fc-list-day { display: block; }
|
|
.fc .fc-list-day th { display: block; padding: 8px 12px; }
|
|
.fc .fc-list-event { display: flex; flex-direction: column; padding: 8px 12px; border-bottom: 1px solid #e8eeff; }
|
|
.fc .fc-list-event td { display: block; border: none; padding: 0; }
|
|
.fc .fc-list-event-time { font-size: 0.75rem; color: #8e99b0; order: 2; }
|
|
.fc .fc-list-event-graphic { display: none; }
|
|
.fc .fc-list-event-title { font-size: 0.9rem; word-break: break-word; white-space: normal; order: 1; margin-bottom: 2px; }
|
|
.fc .fc-list-event-dot { display: inline-block; margin-right: 6px; }
|
|
}
|
|
</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'); ?>
|
|
|
|
<div class="d-flex gap-2 mb-3 flex-wrap align-items-center">
|
|
<button type="button" class="btn btn-scad-outline d-inline-flex align-items-center gap-2" 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" 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 mb-2" style="padding-left:0.25rem"></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 calendario</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">
|
|
<option value="non-completata" selected>Non completate</option>
|
|
<option value="">Tutti</option>
|
|
<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">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-0">
|
|
<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>
|
|
<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>
|
|
|
|
<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 mb-0"><i class="fa-solid fa-calendar-days me-2"></i>Calendario Scadenze</h5>
|
|
<div class="header-actions d-flex gap-2 flex-wrap ms-auto">
|
|
<a href="scadenzario/index.php" class="btn btn-scad-outline d-inline-flex align-items-center gap-2">
|
|
<i class="fa-solid fa-list"></i><span>Lista Scadenze</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="legend">
|
|
<div class="legend-item"><span class="legend-dot" style="background:#5a8fd8"></span> Attiva</div>
|
|
<div class="legend-item"><span class="legend-dot" style="background:#e8930c"></span> In scadenza</div>
|
|
<div class="legend-item"><span class="legend-dot" style="background:#dc3545"></span> Scaduta</div>
|
|
<div class="legend-item"><span class="legend-dot" style="background:#198754"></span> Completata</div>
|
|
</div>
|
|
<div id="calendar"></div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
<?php include('../include/footer.php'); ?>
|
|
</div>
|
|
<?php include('../jsinclude.php'); ?>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var isMobile = window.innerWidth < 768;
|
|
var calendarEl = document.getElementById('calendar');
|
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
|
locale: 'it',
|
|
initialView: isMobile ? 'listWeek' : 'dayGridMonth',
|
|
headerToolbar: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: isMobile ? 'listWeek,dayGridMonth' : 'dayGridMonth,listWeek'
|
|
},
|
|
height: 'auto',
|
|
navLinks: true,
|
|
eventSources: [{
|
|
url: 'scadenzario/ajax/get_calendar_events.php',
|
|
extraParams: function() {
|
|
return {
|
|
status: document.getElementById('filterStatus').value,
|
|
department: document.getElementById('filterDepartment').value,
|
|
employee: document.getElementById('filterEmployee').value
|
|
};
|
|
},
|
|
failure: function() {
|
|
Swal.fire('Errore', 'Impossibile caricare gli eventi.', 'error');
|
|
}
|
|
}],
|
|
eventClick: function(info) {
|
|
info.jsEvent.preventDefault();
|
|
if (info.event.url) {
|
|
window.location.href = info.event.url;
|
|
}
|
|
},
|
|
windowResize: function(view) {
|
|
if (window.innerWidth < 768) {
|
|
calendar.changeView('listWeek');
|
|
} else {
|
|
calendar.changeView('dayGridMonth');
|
|
}
|
|
}
|
|
});
|
|
calendar.render();
|
|
|
|
// Filters
|
|
function updateFilterBadge() {
|
|
var active = 0;
|
|
var summary = [];
|
|
var st = document.getElementById('filterStatus');
|
|
var stv = st.value;
|
|
if (stv && stv !== 'non-completata') { active++; summary.push(st.options[st.selectedIndex].text); }
|
|
var dept = document.getElementById('filterDepartment').value;
|
|
if (dept) { active++; summary.push('Reparto: ' + dept); }
|
|
var emp = document.getElementById('filterEmployee').value;
|
|
if (emp) { active++; summary.push('Responsabile: ' + emp); }
|
|
|
|
var badge = document.getElementById('filterCountBadge');
|
|
if (active > 0) { badge.textContent = active; badge.classList.remove('d-none'); }
|
|
else { badge.classList.add('d-none'); }
|
|
|
|
var summaryText = summary.length ? summary.slice(0, 2).join(' • ') + (summary.length > 2 ? ' +' + (summary.length - 2) : '') : '';
|
|
document.getElementById('activeFiltersSummary').textContent = summaryText;
|
|
document.getElementById('activeFiltersSummaryMobile').textContent = summaryText;
|
|
}
|
|
|
|
document.querySelectorAll('#filterStatus, #filterDepartment, #filterEmployee').forEach(function(el) {
|
|
el.addEventListener('change', function() { calendar.refetchEvents(); updateFilterBadge(); });
|
|
});
|
|
|
|
function resetFilters() {
|
|
document.getElementById('filterStatus').value = 'non-completata';
|
|
document.getElementById('filterDepartment').value = '';
|
|
document.getElementById('filterEmployee').value = '';
|
|
calendar.refetchEvents();
|
|
updateFilterBadge();
|
|
}
|
|
document.getElementById('btnResetFilters').addEventListener('click', resetFilters);
|
|
document.getElementById('btnResetFiltersModal').addEventListener('click', resetFilters);
|
|
|
|
updateFilterBadge();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|