user profile

This commit is contained in:
2026-05-14 16:09:39 +03:00
parent fa2f293835
commit d155d1cbab
55 changed files with 5691 additions and 144 deletions
+530
View File
@@ -0,0 +1,530 @@
<?php
include('include/headscript.php');
$db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection();
/* ==========================================
PAGE DATA
========================================== */
$sql = "
SELECT tt.*,
(SELECT COUNT(*) FROM employee_trainings et WHERE et.training_topic_id = tt.id) AS trainings_count
FROM training_topics tt
ORDER BY tt.sort_order ASC, tt.name ASC
";
$topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<title>Gestione Corsi di Formazione - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<style>
body { font-size: 1.05rem; background: #f8fafc; }
.card { border-radius: 16px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); }
.back-dashboard {
background-color: #cfe3ff !important; color: #1f2d3d !important;
border: 1px solid #bcd4f4 !important; border-radius: 10px;
font-weight: 600; padding: 10px 18px;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease-in-out;
}
.back-dashboard:hover { background-color: #b9d3ff !important; transform: translateY(-2px); }
.btn-add { background-color: #0d6efd; color: #fff; border-radius: 8px; padding: 10px 20px; font-weight: 500; }
.btn-add:hover { background-color: #0b5ed7; transform: scale(1.02); }
.table thead { background-color: #cfe3ff; color: #1f2d3d; }
.modal-content { border-radius: 16px; }
#tabellaTopics thead th { text-align: center; vertical-align: middle; }
.badge-status { padding: 0.25rem 0.6rem; border-radius: 999px; font-size: 0.8rem; font-weight: 600; }
.badge-status.active { background-color: #d1fae5; color: #065f46; }
.badge-status.inactive { background-color: #e5e7eb; color: #374151; }
.description-cell {
max-width: 280px; white-space: nowrap; overflow: hidden;
text-overflow: ellipsis; text-align: left;
}
.num-pill {
display: inline-block; padding: 2px 10px; border-radius: 999px;
background: #eef2ff; color: #3730a3; font-weight: 600; font-size: 0.85rem;
}
@media (max-width: 767.98px) {
.card-header { flex-direction: column; align-items: flex-start !important; gap: .5rem; }
.back-dashboard { width: 100%; }
.btn-add { width: 100%; }
}
.tt-card {
border: 1px solid #e2e8f0;
border-radius: 14px;
padding: 14px 16px;
margin-bottom: 12px;
background: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
}
.tt-card-title {
font-size: 1.1rem;
font-weight: 600;
color: #1f2937;
margin: 0 0 4px 0;
word-break: break-word;
}
.tt-card-desc {
color: #475569;
font-size: 0.95rem;
margin: 0 0 10px 0;
word-break: break-word;
}
.tt-card-meta {
display: flex;
flex-wrap: wrap;
gap: 8px 14px;
font-size: 0.85rem;
color: #64748b;
margin-bottom: 12px;
}
.tt-card-meta b { color: #1f2937; font-weight: 600; }
.tt-card-actions {
display: flex;
gap: 8px;
}
.tt-card-actions .btn { flex: 1; }
.tt-empty {
text-align: center;
color: #94a3b8;
padding: 24px 0;
}
</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">
<div class="card p-3">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<h5 class="mb-0">Gestione Corsi di Formazione</h5>
<button type="button" class="btn back-dashboard" onclick="location.href='production_dashboard.php'">
↩️ Torna alla Dashboard
</button>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
<h6 class="fw-semibold mb-0">Elenco Corsi / Training Topics</h6>
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#addTopicModal">
Aggiungi Corso
</button>
</div>
<!-- DESKTOP / TABLET ≥768px: TABLE -->
<div class="table-responsive d-none d-md-block">
<table id="tabellaTopics" class="table table-striped align-middle text-center" style="width:100%;">
<thead>
<tr>
<th>ID</th>
<th>Nome</th>
<th>Descrizione</th>
<th>Frequenza<br>(mesi)</th>
<th>Promemoria<br>(giorni)</th>
<th>Ordine</th>
<th>Stato</th>
<th>Formazioni</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($topics as $row): ?>
<?php
$id = (int)$row['id'];
$name = $row['name'] ?? '';
$description = $row['description'] ?? '';
$freq = $row['default_frequency_months'];
$rem = (int)($row['default_reminder_days'] ?? 30);
$sortOrder = (int)($row['sort_order'] ?? 999);
$isActive = (int)($row['is_active'] ?? 1);
$isMandatory = (int)($row['is_mandatory'] ?? 0);
$cnt = (int)($row['trainings_count'] ?? 0);
$statusClass = $isActive === 1 ? 'active' : 'inactive';
$statusLabel = $isActive === 1 ? 'Attivo' : 'Inattivo';
?>
<tr>
<td><?= $id ?></td>
<td class="fw-semibold text-start">
<?= htmlspecialchars($name) ?>
<?php if ($isMandatory === 1): ?>
<span class="badge bg-warning text-dark ms-1" title="Obbligatorio per tutti"> Obbl.</span>
<?php endif; ?>
</td>
<td class="description-cell" title="<?= htmlspecialchars($description, ENT_QUOTES) ?>">
<?= $description !== '' ? htmlspecialchars($description) : '-' ?>
</td>
<td>
<?php if ($freq === null || $freq === ''): ?>
<span class="text-muted">una tantum</span>
<?php else: ?>
<span class="num-pill"><?= (int)$freq ?></span>
<?php endif; ?>
</td>
<td><span class="num-pill"><?= $rem ?></span></td>
<td><?= $sortOrder ?></td>
<td><span class="badge-status <?= $statusClass ?>"><?= $statusLabel ?></span></td>
<td><?= $cnt ?></td>
<td>
<button class="btn btn-sm btn-outline-secondary edit-topic"
data-id="<?= $id ?>"
data-name="<?= htmlspecialchars($name, ENT_QUOTES) ?>"
data-description="<?= htmlspecialchars($description, ENT_QUOTES) ?>"
data-freq="<?= $freq === null ? '' : (int)$freq ?>"
data-rem="<?= $rem ?>"
data-sort_order="<?= $sortOrder ?>"
data-is_active="<?= $isActive ?>"
data-is_mandatory="<?= $isMandatory ?>">
✏️ Modifica
</button>
<button class="btn btn-sm btn-outline-danger delete-topic"
data-id="<?= $id ?>"
data-name="<?= htmlspecialchars($name, ENT_QUOTES) ?>"
data-count="<?= $cnt ?>">
🗑️ Cancella
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- MOBILE <768px: CARDS -->
<div class="d-block d-md-none">
<?php if (empty($topics)): ?>
<div class="tt-empty">Nessun corso presente</div>
<?php endif; ?>
<?php foreach ($topics as $row): ?>
<?php
$id = (int)$row['id'];
$name = $row['name'] ?? '';
$description = $row['description'] ?? '';
$freq = $row['default_frequency_months'];
$rem = (int)($row['default_reminder_days'] ?? 30);
$sortOrder = (int)($row['sort_order'] ?? 999);
$isActive = (int)($row['is_active'] ?? 1);
$isMandatory = (int)($row['is_mandatory'] ?? 0);
$cnt = (int)($row['trainings_count'] ?? 0);
$statusClass = $isActive === 1 ? 'active' : 'inactive';
$statusLabel = $isActive === 1 ? 'Attivo' : 'Inattivo';
$freqLabel = ($freq === null || $freq === '') ? 'una tantum' : ((int)$freq . ' mesi');
?>
<div class="tt-card">
<h6 class="tt-card-title">
<?= htmlspecialchars($name) ?>
<?php if ($isMandatory === 1): ?>
<span class="badge bg-warning text-dark ms-1" title="Obbligatorio per tutti"> Obbl.</span>
<?php endif; ?>
</h6>
<?php if ($description !== ''): ?>
<p class="tt-card-desc"><?= htmlspecialchars($description) ?></p>
<?php endif; ?>
<div class="tt-card-meta">
<span><span class="badge-status <?= $statusClass ?>"><?= $statusLabel ?></span></span>
<span><b>Frequenza:</b> <?= htmlspecialchars($freqLabel) ?></span>
<span><b>Promemoria:</b> <?= $rem ?> gg</span>
<span><b>Formazioni:</b> <?= $cnt ?></span>
<span><b>Ordine:</b> <?= $sortOrder ?></span>
</div>
<div class="tt-card-actions">
<button class="btn btn-sm btn-outline-secondary edit-topic"
data-id="<?= $id ?>"
data-name="<?= htmlspecialchars($name, ENT_QUOTES) ?>"
data-description="<?= htmlspecialchars($description, ENT_QUOTES) ?>"
data-freq="<?= $freq === null ? '' : (int)$freq ?>"
data-rem="<?= $rem ?>"
data-sort_order="<?= $sortOrder ?>"
data-is_active="<?= $isActive ?>"
data-is_mandatory="<?= $isMandatory ?>">
✏️ Modifica
</button>
<button class="btn btn-sm btn-outline-danger delete-topic"
data-id="<?= $id ?>"
data-name="<?= htmlspecialchars($name, ENT_QUOTES) ?>"
data-count="<?= $cnt ?>">
🗑️ Cancella
</button>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<!-- ADD -->
<div class="modal fade" id="addTopicModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg modal-fullscreen-sm-down">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title">Aggiungi Corso</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="addTopicForm">
<div class="mb-3">
<label class="form-label fw-semibold">Nome</label>
<input type="text" class="form-control" id="addName" name="name" placeholder="es. Sicurezza antincendio" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Descrizione</label>
<textarea class="form-control" id="addDescription" name="description" rows="3" placeholder="Opzionale"></textarea>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Frequenza aggiornamento</label>
<select class="form-select" id="addFreq" name="default_frequency_months">
<option value="" selected>Una tantum (nessun aggiornamento)</option>
<option value="3">3 mesi</option>
<option value="6">6 mesi</option>
<option value="12">12 mesi (1 anno)</option>
<option value="18">18 mesi</option>
<option value="24">24 mesi (2 anni)</option>
<option value="36">36 mesi (3 anni)</option>
<option value="48">48 mesi (4 anni)</option>
<option value="60">60 mesi (5 anni)</option>
</select>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Promemoria (giorni prima della scadenza)</label>
<input type="number" class="form-control" id="addRem" name="default_reminder_days" value="30" min="0">
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Ordine</label>
<input type="number" class="form-control" id="addSortOrder" name="sort_order" value="999" min="0">
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Stato</label>
<select class="form-select" id="addIsActive" name="is_active">
<option value="1" selected>Attivo</option>
<option value="0">Inattivo</option>
</select>
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="addIsMandatory" value="1">
<label class="form-check-label fw-semibold" for="addIsMandatory">
Obbligatorio per tutti i dipendenti
</label>
<div class="small text-muted">
Se attivo, i dipendenti senza registrazione di questo corso compaiono come "Non presente" nello storico.
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Salva</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- EDIT -->
<div class="modal fade" id="editTopicModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg modal-fullscreen-sm-down">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title">Modifica Corso</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editTopicForm">
<input type="hidden" id="editTopicId">
<div class="mb-3">
<label class="form-label fw-semibold">Nome</label>
<input type="text" class="form-control" id="editName" name="name" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Descrizione</label>
<textarea class="form-control" id="editDescription" name="description" rows="3"></textarea>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Frequenza aggiornamento</label>
<select class="form-select" id="editFreq" name="default_frequency_months">
<option value="">Una tantum (nessun aggiornamento)</option>
<option value="3">3 mesi</option>
<option value="6">6 mesi</option>
<option value="12">12 mesi (1 anno)</option>
<option value="18">18 mesi</option>
<option value="24">24 mesi (2 anni)</option>
<option value="36">36 mesi (3 anni)</option>
<option value="48">48 mesi (4 anni)</option>
<option value="60">60 mesi (5 anni)</option>
</select>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Promemoria (giorni prima della scadenza)</label>
<input type="number" class="form-control" id="editRem" name="default_reminder_days" min="0">
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Ordine</label>
<input type="number" class="form-control" id="editSortOrder" name="sort_order" min="0">
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Stato</label>
<select class="form-select" id="editIsActive" name="is_active">
<option value="1">Attivo</option>
<option value="0">Inattivo</option>
</select>
</div>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="editIsMandatory" value="1">
<label class="form-check-label fw-semibold" for="editIsMandatory">
Obbligatorio per tutti i dipendenti
</label>
<div class="small text-muted">
Se attivo, i dipendenti senza registrazione di questo corso compaiono come "Non presente" nello storico.
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Salva Modifiche</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?php include('jsinclude.php'); ?>
<script>
$(document).ready(function() {
$('#tabellaTopics').DataTable({
order: [[5, 'asc'], [1, 'asc']],
pageLength: 25,
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json',
emptyTable: 'Nessun corso presente'
}
});
function ajaxPost(url, payload, successTitle, errorFallback) {
return fetch(url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: payload.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({ icon: "success", title: successTitle, confirmButtonColor: "#3085d6" })
.then(() => location.reload());
} else {
Swal.fire({ icon: "error", title: "Errore", text: data.message || errorFallback });
}
})
.catch(err => {
Swal.fire({ icon: "error", title: "Errore", text: "Errore di comunicazione." });
console.error(err);
});
}
$("#addTopicForm").on("submit", function(e) {
e.preventDefault();
const p = new URLSearchParams();
p.append('name', $("#addName").val().trim());
p.append('description', $("#addDescription").val().trim());
p.append('default_frequency_months', $("#addFreq").val());
p.append('default_reminder_days', $("#addRem").val());
p.append('sort_order', $("#addSortOrder").val());
p.append('is_active', $("#addIsActive").val());
p.append('is_mandatory', $("#addIsMandatory").is(':checked') ? '1' : '0');
ajaxPost("ajax/training_topics/save.php", p, "Salvato!", "Impossibile salvare il corso.");
});
$(document).on("click", ".edit-topic", function() {
const b = $(this);
const rawFreq = b.data("freq");
const freqStr = (rawFreq === '' || rawFreq === null || rawFreq === undefined) ? '' : String(rawFreq);
if (freqStr !== '' && $("#editFreq option[value='" + freqStr + "']").length === 0) {
$("#editFreq").append('<option value="' + freqStr + '">' + freqStr + ' mesi</option>');
}
$("#editTopicId").val(b.data("id"));
$("#editName").val(b.data("name"));
$("#editDescription").val(b.data("description"));
$("#editFreq").val(freqStr);
$("#editRem").val(b.data("rem"));
$("#editSortOrder").val(b.data("sort_order"));
$("#editIsActive").val(String(b.data("is_active")));
$("#editIsMandatory").prop('checked', String(b.data("is_mandatory")) === '1');
$("#editTopicModal").modal("show");
});
$("#editTopicForm").on("submit", function(e) {
e.preventDefault();
const p = new URLSearchParams();
p.append('id', $("#editTopicId").val());
p.append('name', $("#editName").val().trim());
p.append('description', $("#editDescription").val().trim());
p.append('default_frequency_months', $("#editFreq").val());
p.append('default_reminder_days', $("#editRem").val());
p.append('sort_order', $("#editSortOrder").val());
p.append('is_active', $("#editIsActive").val());
p.append('is_mandatory', $("#editIsMandatory").is(':checked') ? '1' : '0');
ajaxPost("ajax/training_topics/save.php", p, "Aggiornato!", "Impossibile aggiornare il corso.");
});
$(document).on("click", ".delete-topic", function() {
const id = $(this).data("id");
const name = $(this).data("name");
const cnt = parseInt($(this).data("count")) || 0;
if (cnt > 0) {
Swal.fire({
icon: "warning",
title: "Impossibile cancellare",
text: "Il corso \"" + name + "\" ha " + cnt + " registrazione/i di formazione. Cancella prima le registrazioni."
});
return;
}
Swal.fire({
title: "Confermi la cancellazione?",
text: name ? ("Corso: " + name) : "Il corso verrà cancellato.",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#d33",
cancelButtonColor: "#6c757d",
confirmButtonText: "Sì, cancella",
cancelButtonText: "Annulla"
}).then((result) => {
if (!result.isConfirmed) return;
const p = new URLSearchParams();
p.append('id', id);
ajaxPost("ajax/training_topics/delete.php", p, "Cancellato!", "Impossibile cancellare il corso.");
});
});
});
</script>
</body>
</html>