Files
zibo-dashboard/public/userarea/employee-profile.php
T
2026-05-26 16:05:24 +02:00

2556 lines
132 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
include('include/headscript.php');
$pdo = DBHandlerSelect::getInstance()->getConnection();
/* ==========================================
PERMISSIONS
========================================== */
$isHrManager = Auth::user()->hasRole('Admin')
|| Auth::user()->hasRole('Superuser')
|| Auth::user()->hasRole('employee-hr')
|| Auth::user()->hasRole('manager');
/* ==========================================
RESOLVE TARGET EMPLOYEE
========================================== */
$requestedId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$isOwnProfile = ($requestedId === 0);
if ($isOwnProfile) {
$stmt = $pdo->prepare("SELECT id FROM employees WHERE auth_user_id = :uid LIMIT 1");
$stmt->execute(['uid' => $iduserlogin]);
$employeeId = (int)$stmt->fetchColumn();
} else {
if (!$isHrManager) {
// Non-HR users can only view their own profile.
header('Location: employee-profile.php');
exit;
}
$employeeId = $requestedId;
}
/* ==========================================
LOAD EMPLOYEE DATA
========================================== */
$employee = null;
if ($employeeId > 0) {
$stmt = $pdo->prepare("
SELECT e.*,
d.name AS department_name,
d.color AS department_color,
jr.name AS job_role_name,
au.first_name AS auth_first_name,
au.last_name AS auth_last_name,
au.email AS auth_email,
au.username AS auth_username,
au.avatar AS auth_avatar
FROM employees e
LEFT JOIN departments d ON d.id = e.department_id
LEFT JOIN job_roles jr ON jr.id = e.job_role_id
LEFT JOIN auth_users au ON au.id = e.auth_user_id
WHERE e.id = :id
LIMIT 1
");
$stmt->execute(['id' => $employeeId]);
$employee = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
}
/* Authorization: own profile must match auth_user_id (defence in depth) */
if (!$isHrManager && $employee && (int)$employee['auth_user_id'] !== (int)$iduserlogin) {
header('Location: employee-profile.php');
exit;
}
$canEdit = $isHrManager;
/* ==========================================
DOCUMENTS (File Repository)
========================================== */
$documents = [];
if ($employee) {
$stmt = $pdo->prepare("
SELECT d.*,
TRIM(CONCAT(COALESCE(au.first_name,''),' ',COALESCE(au.last_name,''))) AS uploader_name,
au.email AS uploader_email
FROM employee_documents d
LEFT JOIN auth_users au ON au.id = d.uploaded_by
WHERE d.employee_id = :eid
ORDER BY d.created_at DESC
");
$stmt->execute(['eid' => $employeeId]);
$documents = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/* ==========================================
TRAINING HISTORY
========================================== */
$trainings = [];
$trainingTopicsAll = [];
$missingMandatoryTopics = [];
if ($employee) {
$stmt = $pdo->prepare("
SELECT et.*,
tt.name AS topic_name,
tt.default_frequency_months AS topic_default_freq,
tt.default_reminder_days AS topic_default_rem,
(SELECT COUNT(*) FROM employee_training_attachments a WHERE a.training_id = et.id) AS attachments_count
FROM employee_trainings et
JOIN training_topics tt ON tt.id = et.training_topic_id
WHERE et.employee_id = :eid
ORDER BY et.completed_date DESC, et.id DESC
");
$stmt->execute(['eid' => $employeeId]);
$trainings = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Mark the most recent record per topic — older ones are history, not "expired".
$seenTopics = [];
foreach ($trainings as &$t) {
$tid = (int)$t['training_topic_id'];
$t['_is_latest'] = !isset($seenTopics[$tid]);
$seenTopics[$tid] = true;
}
unset($t);
if ($canEdit) {
$trainingTopicsAll = $pdo->query("
SELECT id, name, default_frequency_months, default_reminder_days
FROM training_topics
WHERE is_active = 1
ORDER BY sort_order, name
")->fetchAll(PDO::FETCH_ASSOC);
}
$missingStmt = $pdo->prepare("
SELECT tt.id, tt.name
FROM training_topics tt
WHERE tt.is_active = 1 AND tt.is_mandatory = 1
AND NOT EXISTS (
SELECT 1 FROM employee_trainings et
WHERE et.employee_id = :eid AND et.training_topic_id = tt.id
)
ORDER BY tt.sort_order, tt.name
");
$missingStmt->execute(['eid' => $employeeId]);
$missingMandatoryTopics = $missingStmt->fetchAll(PDO::FETCH_ASSOC);
}
/* ==========================================
PPE (Assigned)
========================================== */
$ppeList = [];
if ($employee) {
$stmt = $pdo->prepare("
SELECT *
FROM employee_ppe
WHERE employee_id = :eid
ORDER BY delivery_date DESC, created_at DESC
");
$stmt->execute(['eid' => $employeeId]);
$ppeList = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/* ==========================================
DROPDOWN DATA FOR EDIT MODAL
========================================== */
$departments = $isHrManager
? $pdo->query("SELECT id, name FROM departments WHERE is_active = 1 ORDER BY sort_order, name")->fetchAll(PDO::FETCH_ASSOC)
: [];
$jobRoles = $isHrManager
? $pdo->query("SELECT id, name FROM job_roles WHERE is_active = 1 ORDER BY sort_order, name")->fetchAll(PDO::FETCH_ASSOC)
: [];
$authUsers = $isHrManager
? $pdo->query("SELECT id, username, first_name, last_name, email, role_id FROM auth_users ORDER BY first_name, last_name")->fetchAll(PDO::FETCH_ASSOC)
: [];
$rolesList = $isHrManager
? $pdo->query("SELECT id, name, display_name FROM auth_roles ORDER BY display_name, name")->fetchAll(PDO::FETCH_ASSOC)
: [];
/* ==========================================
HELPERS
========================================== */
function statusBadge(string $status): array
{
switch ($status) {
case 'active':
return ['label' => 'Attivo', 'class' => 'success'];
case 'inactive':
return ['label' => 'Cessato', 'class' => 'secondary'];
case 'suspended':
return ['label' => 'Sospeso', 'class' => 'warning'];
default:
return ['label' => htmlspecialchars($status), 'class' => 'secondary'];
}
}
function fmtDate(?string $d): string
{
if (!$d || $d === '0000-00-00') return '—';
$ts = strtotime($d);
return $ts ? date('d/m/Y', $ts) : '—';
}
function valOrDash($v): string
{
$v = (string)($v ?? '');
return $v !== '' ? htmlspecialchars($v) : '—';
}
function categoryLabel(string $c): string
{
switch ($c) {
case 'job_description':
return 'Mansionario';
case 'contract':
return 'Contratto';
case 'rules':
return 'Regolamento';
case 'other':
return 'Altro';
default:
return htmlspecialchars($c);
}
}
function trainingStatus(?string $nextDue, ?int $reminderDays, ?int $topicDefaultRem): array
{
if (!$nextDue) {
return ['code' => 'compliant', 'label' => 'Conforme', 'class' => 'success'];
}
$rem = $reminderDays !== null ? $reminderDays : ($topicDefaultRem !== null ? $topicDefaultRem : 30);
$today = new DateTime('today');
$due = DateTime::createFromFormat('Y-m-d', $nextDue);
if (!$due) return ['code' => 'compliant', 'label' => 'Conforme', 'class' => 'success'];
$daysLeft = (int)$today->diff($due)->format('%r%a');
if ($daysLeft < 0) return ['code' => 'expired', 'label' => 'Scaduto', 'class' => 'danger'];
if ($daysLeft <= $rem) return ['code' => 'due_soon', 'label' => 'Da aggiornare', 'class' => 'warning'];
return ['code' => 'compliant', 'label' => 'Conforme', 'class' => 'success'];
}
function fmtFileSize(?int $bytes): string
{
if ($bytes === null || $bytes <= 0) return '—';
if ($bytes < 1024) return $bytes . ' B';
if ($bytes < 1024 * 1024) return number_format($bytes / 1024, 1) . ' KB';
if ($bytes < 1024 * 1024 * 1024) return number_format($bytes / 1024 / 1024, 1) . ' MB';
return number_format($bytes / 1024 / 1024 / 1024, 1) . ' GB';
}
?>
<!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'); ?>
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/dataTables.bootstrap5.min.css">
<title>Profilo Dipendente - <?= 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>
<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);
}
.modal-content {
border-radius: 16px;
}
.profile-header {
background: linear-gradient(135deg, #cfe3ff 0%, #e0e9f8 100%);
border-radius: 16px;
padding: 24px;
margin-bottom: 20px;
display: flex;
gap: 20px;
align-items: center;
flex-wrap: wrap;
}
.profile-avatar {
width: 96px;
height: 96px;
border-radius: 50%;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.4rem;
font-weight: 700;
color: #1f2d3d;
border: 3px solid #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, .08);
flex-shrink: 0;
overflow: hidden;
}
.profile-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.profile-name {
font-size: 1.6rem;
font-weight: 700;
color: #1f2d3d;
margin: 0;
}
.profile-subtitle {
color: #475569;
margin: 4px 0 8px 0;
}
.profile-badges {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.pill {
display: inline-block;
padding: 4px 12px;
border-radius: 999px;
font-size: 0.85rem;
font-weight: 600;
}
.pill-dept {
background: rgba(0, 0, 0, .06);
color: #1f2d3d;
}
.pill-role {
background: #fff;
color: #334155;
border: 1px solid #cbd5e1;
}
.pill-status-success {
background: #d1fae5;
color: #065f46;
}
.pill-status-secondary {
background: #e5e7eb;
color: #374151;
}
.pill-status-warning {
background: #fef3c7;
color: #92400e;
}
.pill-status-danger {
background: #fee2e2;
color: #991b1b;
}
.nav-tabs {
border-bottom: 1px solid #e2e8f0;
flex-wrap: nowrap;
gap: 2px;
}
.nav-tabs .nav-link {
border: none;
color: #64748b;
font-weight: 600;
padding: 12px 18px;
border-radius: 0;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.nav-tabs .nav-link i {
font-size: 1.2rem;
}
.nav-tabs .nav-link.active {
color: #0d6efd;
border-bottom: 3px solid #0d6efd;
background: transparent;
}
.tab-burger {
display: none;
position: relative;
margin-bottom: 12px;
}
.tab-burger-btn {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
justify-content: space-between;
background: #cfe3ff;
color: #1f2d3d;
border: 1px solid #bcd4f4;
border-radius: 10px;
padding: 12px 16px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
}
.tab-burger-btn .burger-left {
display: flex;
align-items: center;
gap: 10px;
}
.tab-burger-btn i.bx-menu {
font-size: 1.4rem;
}
.tab-burger-btn .caret {
transition: transform .2s;
}
.tab-burger.open .tab-burger-btn .caret {
transform: rotate(180deg);
}
.tab-burger-menu {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0, 0, 0, .12);
padding: 6px 0;
margin: 0;
list-style: none;
z-index: 1050;
display: none;
}
.tab-burger.open .tab-burger-menu {
display: block;
}
.tab-burger-menu li a {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
color: #334155;
text-decoration: none;
font-weight: 500;
}
.tab-burger-menu li a:hover {
background: #f1f5f9;
}
.tab-burger-menu li a.active {
background: #cfe3ff;
color: #1f2d3d;
font-weight: 600;
}
@media (max-width: 767.98px) {
.nav-tabs {
display: none;
}
.tab-burger {
display: block;
}
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px 28px;
}
@media (max-width: 767.98px) {
.info-grid {
grid-template-columns: 1fr;
}
}
.info-row {
border-bottom: 1px dashed #e2e8f0;
padding-bottom: 10px;
}
.info-label {
font-size: 0.8rem;
text-transform: uppercase;
color: #64748b;
letter-spacing: 0.04em;
margin-bottom: 4px;
}
.info-value {
font-size: 1.05rem;
color: #1f2937;
font-weight: 500;
word-break: break-word;
}
.empty-profile {
text-align: center;
padding: 60px 20px;
color: #64748b;
}
.empty-profile h4 {
color: #1f2937;
}
.placeholder-section {
text-align: center;
padding: 60px 20px;
color: #94a3b8;
}
.placeholder-section .bx {
font-size: 3rem;
color: #cbd5e1;
}
.mma-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
padding: 8px 12px;
background: #fffbeb;
border: 1px solid #fcd34d;
border-radius: 10px;
font-size: 0.9rem;
}
.mma-bar-label {
color: #78350f;
margin-right: 4px;
}
.mma-chip {
display: inline-flex;
align-items: center;
gap: 5px;
background: #fff;
border: 1px solid #fcd34d;
color: #92400e;
border-radius: 999px;
padding: 3px 10px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
transition: background .15s, transform .1s;
}
.mma-chip:hover {
background: #fef3c7;
transform: translateY(-1px);
}
.mma-chip-plus {
color: #d97706;
font-weight: 700;
}
.mma-chip.mma-chip-static {
cursor: default;
}
.mma-chip.mma-chip-static:hover {
background: #fff;
transform: none;
}
.doc-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, .04);
}
.doc-card-title {
display: block;
font-size: 1.05rem;
font-weight: 600;
color: #1f2937;
text-decoration: none;
word-break: break-word;
}
.doc-card-meta {
display: flex;
flex-wrap: wrap;
gap: 6px 14px;
font-size: 0.85rem;
color: #64748b;
margin: 10px 0;
}
.doc-card-meta b {
color: #1f2937;
font-weight: 600;
}
.doc-card-actions {
display: flex;
gap: 8px;
}
.doc-card-actions .btn {
flex: 1;
}
@media (max-width: 767.98px) {
.card-header {
flex-direction: column;
align-items: flex-start !important;
gap: .5rem;
}
.back-dashboard {
width: 100%;
}
.profile-header {
padding: 16px;
gap: 14px;
}
.profile-avatar {
width: 72px;
height: 72px;
font-size: 1.8rem;
}
.profile-name {
font-size: 1.3rem;
}
}
</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">
<?= $isOwnProfile ? 'Il Mio Profilo' : 'Profilo Dipendente' ?>
</h5>
<button type="button" class="btn back-dashboard"
onclick="location.href='<?= $isHrManager ? 'employees.php' : 'production_dashboard.php' ?>'">
↩️ <?= $isHrManager ? 'Torna ai Dipendenti' : 'Torna alla Dashboard' ?>
</button>
</div>
<div class="card-body">
<?php if (!$employee): ?>
<div class="empty-profile">
<i class='bx bx-user-x' style="font-size:4rem; color:#cbd5e1;"></i>
<h4 class="mt-3">
<?= $isOwnProfile
? 'Il tuo profilo dipendente non è ancora stato creato'
: 'Dipendente non trovato' ?>
</h4>
<p class="text-muted">
<?= $isOwnProfile
? 'Contatta il responsabile HR per la creazione del tuo profilo.'
: 'Verifica l\'ID o torna alla lista dipendenti.' ?>
</p>
</div>
<?php else: ?>
<?php
$fullName = trim(($employee['first_name'] ?? '') . ' ' . ($employee['last_name'] ?? ''));
$initials = strtoupper(mb_substr($employee['first_name'] ?? '?', 0, 1) . mb_substr($employee['last_name'] ?? '', 0, 1));
$status = statusBadge((string)($employee['status'] ?? 'active'));
$deptName = $employee['department_name'] ?? null;
$deptColor = $employee['department_color'] ?? null;
$jobName = $employee['job_role_name'] ?? null;
$avatar = trim((string)($employee['auth_avatar'] ?? ''));
if ($avatar !== '') {
// If the DB stores only the filename, build the correct relative path.
if (
strpos($avatar, '/') === false &&
strpos($avatar, '\\') === false &&
!preg_match('/^https?:\/\//i', $avatar)
) {
$avatar = '../upload/users/' . $avatar;
}
}
?>
<!-- HEADER -->
<div class="profile-header">
<div class="profile-avatar">
<?php if (!empty($avatar)): ?>
<img src="<?= htmlspecialchars($avatar, ENT_QUOTES, 'UTF-8') ?>" alt="user avatar">
<?php else: ?>
<?= htmlspecialchars($initials !== '' ? $initials : '?', ENT_QUOTES, 'UTF-8') ?>
<?php endif; ?>
</div>
<div style="flex:1; min-width: 240px;">
<h2 class="profile-name"><?= htmlspecialchars($fullName !== '' ? $fullName : '—') ?></h2>
<?php if (!empty($employee['employee_code'])): ?>
<div class="profile-subtitle">
Codice: <code><?= htmlspecialchars($employee['employee_code']) ?></code>
</div>
<?php endif; ?>
<div class="profile-badges">
<?php if ($jobName): ?>
<span class="pill pill-role">💼 <?= htmlspecialchars($jobName) ?></span>
<?php endif; ?>
<?php if ($deptName): ?>
<span class="pill pill-dept" style="<?= $deptColor ? 'background:' . htmlspecialchars($deptColor, ENT_QUOTES) . '20; color:' . htmlspecialchars($deptColor, ENT_QUOTES) . ';' : '' ?>">
🏢 <?= htmlspecialchars($deptName) ?>
</span>
<?php endif; ?>
<span class="pill pill-status-<?= htmlspecialchars($status['class']) ?>">
<?= htmlspecialchars($status['label']) ?>
</span>
</div>
</div>
<?php if ($canEdit): ?>
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#editPersonalModal">
✏️ Modifica
</button>
<?php endif; ?>
</div>
<!-- TABS: desktop -->
<ul class="nav nav-tabs" role="tablist" id="profileTabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#tab-personal" data-tab-label="Anagrafica" data-tab-icon="bx-user">
<i class='bx bx-user'></i><span class="tab-label">Anagrafica</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-docs" data-tab-label="Documenti" data-tab-icon="bx-folder">
<i class='bx bx-folder'></i><span class="tab-label">Documenti</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-ppe" data-tab-label="DPI" data-tab-icon="bx-shield-quarter">
<i class='bx bx-shield-quarter'></i><span class="tab-label">DPI</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-training" data-tab-label="Formazione" data-tab-icon="bx-book-content">
<i class='bx bx-book-content'></i><span class="tab-label">Formazione</span>
</a>
</li>
</ul>
<!-- TABS: mobile burger -->
<div class="tab-burger" id="tabBurger">
<button type="button" class="tab-burger-btn" id="tabBurgerBtn">
<span class="burger-left">
<i class='bx bx-menu'></i>
<i class='bx bx-user' id="burgerIcon"></i>
<span id="burgerLabel">Anagrafica</span>
</span>
<i class='bx bx-chevron-down caret'></i>
</button>
<ul class="tab-burger-menu">
<li><a class="active" href="#" data-burger-target="#tab-personal"><i class='bx bx-user'></i> Anagrafica</a></li>
<li><a href="#" data-burger-target="#tab-docs"><i class='bx bx-folder'></i> Documenti</a></li>
<li><a href="#" data-burger-target="#tab-ppe"><i class='bx bx-shield-quarter'></i> DPI</a></li>
<li><a href="#" data-burger-target="#tab-training"><i class='bx bx-book-content'></i> Formazione</a></li>
</ul>
</div>
<div class="tab-content pt-4">
<!-- ANAGRAFICA -->
<div class="tab-pane fade show active" id="tab-personal">
<div class="info-grid">
<div class="info-row">
<div class="info-label">Nome</div>
<div class="info-value"><?= valOrDash($employee['first_name']) ?></div>
</div>
<div class="info-row">
<div class="info-label">Cognome</div>
<div class="info-value"><?= valOrDash($employee['last_name']) ?></div>
</div>
<div class="info-row">
<div class="info-label">Codice Dipendente</div>
<div class="info-value"><?= valOrDash($employee['employee_code']) ?></div>
</div>
<div class="info-row">
<div class="info-label">Data di Assunzione</div>
<div class="info-value"><?= fmtDate($employee['hire_date']) ?></div>
</div>
<div class="info-row">
<div class="info-label">Indirizzo</div>
<div class="info-value"><?= valOrDash($employee['address']) ?></div>
</div>
<div class="info-row">
<div class="info-label">Telefono</div>
<div class="info-value">
<?php if (!empty($employee['phone'])): ?>
<a href="tel:<?= htmlspecialchars($employee['phone'], ENT_QUOTES) ?>"><?= htmlspecialchars($employee['phone']) ?></a>
<?php else: ?>—<?php endif; ?>
</div>
</div>
<div class="info-row">
<div class="info-label">Email</div>
<div class="info-value">
<?php if (!empty($employee['email'])): ?>
<a href="mailto:<?= htmlspecialchars($employee['email'], ENT_QUOTES) ?>"><?= htmlspecialchars($employee['email']) ?></a>
<?php else: ?>—<?php endif; ?>
</div>
</div>
<div class="info-row">
<div class="info-label">Reparto</div>
<div class="info-value"><?= valOrDash($deptName) ?></div>
</div>
<div class="info-row">
<div class="info-label">Mansione</div>
<div class="info-value"><?= valOrDash($jobName) ?></div>
</div>
<div class="info-row">
<div class="info-label">Stato</div>
<div class="info-value"><?= htmlspecialchars($status['label']) ?></div>
</div>
<div class="info-row">
<div class="info-label">Utente collegato</div>
<div class="info-value">
<?php if (!empty($employee['auth_username'])): ?>
<?= htmlspecialchars($employee['auth_username']) ?>
<?php if (!empty($employee['auth_email'])): ?>
<small class="text-muted d-block"><?= htmlspecialchars($employee['auth_email']) ?></small>
<?php endif; ?>
<?php else: ?>
<span class="text-muted">Nessun utente collegato</span>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- DOCUMENTI -->
<div class="tab-pane fade" id="tab-docs">
<?php if ($canEdit): ?>
<div class="d-flex justify-content-end mb-3">
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#uploadDocumentModal">
⬆️ Carica Documento
</button>
</div>
<?php endif; ?>
<?php if (empty($documents)): ?>
<div class="placeholder-section">
<i class='bx bx-folder-open'></i>
<h5 class="mt-3">Nessun documento</h5>
<p>
<?= $canEdit
? 'Carica il primo documento (mansionario, contratto, regolamento ecc.).'
: 'Nessun documento disponibile al momento.' ?>
</p>
</div>
<?php else: ?>
<!-- DESKTOP TABLE -->
<div class="table-responsive d-none d-md-block">
<table id="documentsTable" class="table table-striped align-middle">
<thead style="background-color:#cfe3ff;">
<tr>
<th>Categoria</th>
<th>Nome File</th>
<th>Dimensione</th>
<th>Caricato da</th>
<th>Data</th>
<th class="text-end">Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($documents as $d): ?>
<?php
$did = (int)$d['id'];
$upBy = trim((string)($d['uploader_name'] ?? '')) !== ''
? $d['uploader_name']
: ($d['uploader_email'] ?? '—');
?>
<tr>
<td><span class="pill pill-dept"><?= categoryLabel((string)$d['category']) ?></span></td>
<td>
<a href="ajax/employee_profile/download_document.php?id=<?= $did ?>"
class="fw-semibold text-decoration-none">
<i class='bx bx-file me-1'></i><?= htmlspecialchars($d['original_name']) ?>
</a>
<?php if (!empty($d['notes'])): ?>
<div class="small text-muted"><?= htmlspecialchars($d['notes']) ?></div>
<?php endif; ?>
</td>
<td><?= fmtFileSize($d['size'] !== null ? (int)$d['size'] : null) ?></td>
<td><?= htmlspecialchars($upBy) ?></td>
<td><?= fmtDate(substr((string)$d['created_at'], 0, 10)) ?></td>
<td class="text-end">
<a class="btn btn-sm btn-outline-primary"
href="ajax/employee_profile/download_document.php?id=<?= $did ?>">⬇️ Scarica</a>
<?php if ($canEdit): ?>
<button class="btn btn-sm btn-outline-danger delete-document"
data-id="<?= $did ?>"
data-name="<?= htmlspecialchars($d['original_name'], ENT_QUOTES) ?>">
🗑️
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- MOBILE CARDS -->
<div class="d-block d-md-none">
<?php foreach ($documents as $d): ?>
<?php
$did = (int)$d['id'];
$upBy = trim((string)($d['uploader_name'] ?? '')) !== ''
? $d['uploader_name']
: ($d['uploader_email'] ?? '—');
?>
<div class="doc-card">
<div class="d-flex justify-content-between align-items-start gap-2 mb-2">
<span class="pill pill-dept"><?= categoryLabel((string)$d['category']) ?></span>
<span class="small text-muted"><?= fmtDate(substr((string)$d['created_at'], 0, 10)) ?></span>
</div>
<a href="ajax/employee_profile/download_document.php?id=<?= $did ?>"
class="doc-card-title">
<i class='bx bx-file me-1'></i><?= htmlspecialchars($d['original_name']) ?>
</a>
<?php if (!empty($d['notes'])): ?>
<div class="small text-muted mt-1"><?= htmlspecialchars($d['notes']) ?></div>
<?php endif; ?>
<div class="doc-card-meta">
<span><b>Dim.:</b> <?= fmtFileSize($d['size'] !== null ? (int)$d['size'] : null) ?></span>
<span><b>Da:</b> <?= htmlspecialchars($upBy) ?></span>
</div>
<div class="doc-card-actions">
<a class="btn btn-sm btn-outline-primary"
href="ajax/employee_profile/download_document.php?id=<?= $did ?>">⬇️ Scarica</a>
<?php if ($canEdit): ?>
<button class="btn btn-sm btn-outline-danger delete-document"
data-id="<?= $did ?>"
data-name="<?= htmlspecialchars($d['original_name'], ENT_QUOTES) ?>">
🗑️
</button>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- DPI -->
<div class="tab-pane fade" id="tab-ppe">
<?php if ($canEdit): ?>
<div class="d-flex justify-content-end mb-3">
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#ppeModal"
onclick="openPpeModal()"> Aggiungi DPI</button>
</div>
<?php endif; ?>
<?php if (empty($ppeList)): ?>
<div class="placeholder-section">
<i class='bx bx-shield-quarter'></i>
<h5 class="mt-3">Nessun DPI assegnato</h5>
<p>
<?= $canEdit
? 'Aggiungi il primo dispositivo di protezione individuale.'
: 'Nessun DPI consegnato al momento.' ?>
</p>
</div>
<?php else: ?>
<!-- DESKTOP TABLE -->
<div class="table-responsive d-none d-md-block">
<table class="table table-striped align-middle">
<thead style="background-color:#cfe3ff;">
<tr>
<th>DPI</th>
<th>Data Consegna</th>
<th>Consegnato da</th>
<th>Note</th>
<?php if ($canEdit): ?><th class="text-end">Azioni</th><?php endif; ?>
</tr>
</thead>
<tbody>
<?php foreach ($ppeList as $p): ?>
<?php $pid = (int)$p['id']; ?>
<tr>
<td class="fw-semibold"><?= htmlspecialchars($p['item_name']) ?></td>
<td><?= fmtDate($p['delivery_date']) ?></td>
<td><?= valOrDash($p['delivered_by']) ?></td>
<td><?= valOrDash($p['notes']) ?></td>
<?php if ($canEdit): ?>
<td class="text-end">
<button class="btn btn-sm btn-outline-secondary edit-ppe"
data-id="<?= $pid ?>"
data-item_name="<?= htmlspecialchars($p['item_name'], ENT_QUOTES) ?>"
data-delivery_date="<?= htmlspecialchars($p['delivery_date'] ?? '', ENT_QUOTES) ?>"
data-delivered_by="<?= htmlspecialchars($p['delivered_by'] ?? '', ENT_QUOTES) ?>"
data-notes="<?= htmlspecialchars($p['notes'] ?? '', ENT_QUOTES) ?>">✏️</button>
<button class="btn btn-sm btn-outline-danger delete-ppe"
data-id="<?= $pid ?>"
data-name="<?= htmlspecialchars($p['item_name'], ENT_QUOTES) ?>">🗑️</button>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- MOBILE CARDS -->
<div class="d-block d-md-none">
<?php foreach ($ppeList as $p): ?>
<?php $pid = (int)$p['id']; ?>
<div class="doc-card">
<div class="d-flex justify-content-between align-items-start gap-2 mb-2">
<span class="doc-card-title">🦺 <?= htmlspecialchars($p['item_name']) ?></span>
<span class="small text-muted text-nowrap"><?= fmtDate($p['delivery_date']) ?></span>
</div>
<div class="doc-card-meta">
<?php if (!empty($p['delivered_by'])): ?>
<span><b>Consegnato da:</b> <?= htmlspecialchars($p['delivered_by']) ?></span>
<?php endif; ?>
<?php if (!empty($p['notes'])): ?>
<span><b>Note:</b> <?= htmlspecialchars($p['notes']) ?></span>
<?php endif; ?>
</div>
<?php if ($canEdit): ?>
<div class="doc-card-actions">
<button class="btn btn-sm btn-outline-secondary edit-ppe"
data-id="<?= $pid ?>"
data-item_name="<?= htmlspecialchars($p['item_name'], ENT_QUOTES) ?>"
data-delivery_date="<?= htmlspecialchars($p['delivery_date'] ?? '', ENT_QUOTES) ?>"
data-delivered_by="<?= htmlspecialchars($p['delivered_by'] ?? '', ENT_QUOTES) ?>"
data-notes="<?= htmlspecialchars($p['notes'] ?? '', ENT_QUOTES) ?>">✏️ Modifica</button>
<button class="btn btn-sm btn-outline-danger delete-ppe"
data-id="<?= $pid ?>"
data-name="<?= htmlspecialchars($p['item_name'], ENT_QUOTES) ?>">🗑️</button>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- FORMAZIONE -->
<div class="tab-pane fade" id="tab-training">
<?php if (!empty($missingMandatoryTopics) || $canEdit): ?>
<div class="d-flex flex-wrap align-items-center gap-2 mb-3">
<?php if (!empty($missingMandatoryTopics)): ?>
<div class="mma-bar flex-grow-1" title="Corsi obbligatori senza registrazione">
<span class="mma-bar-label">
⚠️ <b><?= count($missingMandatoryTopics) ?></b>
obbligator<?= count($missingMandatoryTopics) === 1 ? 'ia non presente' : 'ie non presenti' ?>:
</span>
<?php foreach ($missingMandatoryTopics as $mt): ?>
<?php if ($canEdit): ?>
<button class="mma-chip add-missing-training"
data-topic_id="<?= (int)$mt['id'] ?>"
data-topic_name="<?= htmlspecialchars($mt['name'], ENT_QUOTES) ?>"
title="Registra: <?= htmlspecialchars($mt['name'], ENT_QUOTES) ?>">
<?= htmlspecialchars($mt['name']) ?> <span class="mma-chip-plus">+</span>
</button>
<?php else: ?>
<span class="mma-chip mma-chip-static"><?= htmlspecialchars($mt['name']) ?></span>
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="flex-grow-1"></div>
<?php endif; ?>
<?php if ($canEdit): ?>
<button class="btn btn-add" data-bs-toggle="modal" data-bs-target="#trainingModal" onclick="openTrainingModal()">
Aggiungi Formazione
</button>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (empty($trainings)): ?>
<div class="placeholder-section">
<i class='bx bx-book-content'></i>
<h5 class="mt-3">Nessuna formazione registrata</h5>
<p>
<?= $canEdit
? 'Aggiungi la prima registrazione di formazione (iniziale o aggiornamento).'
: 'Nessuna formazione registrata al momento.' ?>
</p>
</div>
<?php else: ?>
<!-- DESKTOP TABLE -->
<div class="table-responsive d-none d-md-block">
<table id="trainingTable" class="table table-striped align-middle">
<thead style="background-color:#cfe3ff;">
<tr>
<th>Corso</th>
<th>Tipo</th>
<th>Completato</th>
<th>Prossimo agg.</th>
<th>Stato</th>
<th>Allegati</th>
<th class="text-end">Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($trainings as $t): ?>
<?php
$tid = (int)$t['id'];
$s = !empty($t['_is_latest'])
? trainingStatus(
$t['next_due_date'] ?: null,
$t['reminder_days'] !== null ? (int)$t['reminder_days'] : null,
$t['topic_default_rem'] !== null ? (int)$t['topic_default_rem'] : null
)
: ['code' => 'storico', 'label' => 'Storico', 'class' => 'secondary'];
$typeLabel = $t['training_type'] === 'refresher' ? 'Aggiornamento' : 'Iniziale';
?>
<tr>
<td class="fw-semibold"><?= htmlspecialchars($t['topic_name']) ?></td>
<td><span class="pill pill-role"><?= $typeLabel ?></span></td>
<td><?= fmtDate($t['completed_date']) ?></td>
<td><?= fmtDate($t['next_due_date']) ?></td>
<td><span class="pill pill-status-<?= $s['class'] ?>"><?= $s['label'] ?></span></td>
<td><?= (int)$t['attachments_count'] ?></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary open-attachments"
data-id="<?= $tid ?>"
data-name="<?= htmlspecialchars($t['topic_name'], ENT_QUOTES) ?>">
📎 <?= (int)$t['attachments_count'] ?>
</button>
<button class="btn btn-sm btn-outline-info open-log"
data-id="<?= $tid ?>"
data-name="<?= htmlspecialchars($t['topic_name'], ENT_QUOTES) ?>">📜</button>
<?php if ($canEdit): ?>
<button class="btn btn-sm btn-outline-secondary edit-training"
data-id="<?= $tid ?>"
data-topic_id="<?= (int)$t['training_topic_id'] ?>"
data-completed_date="<?= htmlspecialchars($t['completed_date'] ?? '', ENT_QUOTES) ?>"
data-delivered_by="<?= htmlspecialchars($t['delivered_by'] ?? '', ENT_QUOTES) ?>"
data-description="<?= htmlspecialchars($t['description'] ?? '', ENT_QUOTES) ?>"
data-training_type="<?= htmlspecialchars($t['training_type'], ENT_QUOTES) ?>"
data-freq="<?= $t['update_frequency_months'] !== null ? (int)$t['update_frequency_months'] : '' ?>"
data-rem="<?= $t['reminder_days'] !== null ? (int)$t['reminder_days'] : '' ?>">✏️</button>
<button class="btn btn-sm btn-outline-danger delete-training"
data-id="<?= $tid ?>"
data-name="<?= htmlspecialchars($t['topic_name'], ENT_QUOTES) ?>">🗑️</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- MOBILE CARDS -->
<div class="d-block d-md-none">
<?php foreach ($trainings as $t): ?>
<?php
$tid = (int)$t['id'];
$s = !empty($t['_is_latest'])
? trainingStatus(
$t['next_due_date'] ?: null,
$t['reminder_days'] !== null ? (int)$t['reminder_days'] : null,
$t['topic_default_rem'] !== null ? (int)$t['topic_default_rem'] : null
)
: ['code' => 'storico', 'label' => 'Storico', 'class' => 'secondary'];
$typeLabel = $t['training_type'] === 'refresher' ? 'Aggiornamento' : 'Iniziale';
?>
<div class="doc-card">
<div class="d-flex justify-content-between align-items-start gap-2 mb-2">
<span class="doc-card-title">📖 <?= htmlspecialchars($t['topic_name']) ?></span>
<span class="pill pill-status-<?= $s['class'] ?>"><?= $s['label'] ?></span>
</div>
<div class="doc-card-meta">
<span><b>Tipo:</b> <?= $typeLabel ?></span>
<span><b>Completato:</b> <?= fmtDate($t['completed_date']) ?></span>
<?php if ($t['next_due_date']): ?>
<span><b>Prossimo:</b> <?= fmtDate($t['next_due_date']) ?></span>
<?php endif; ?>
<?php if ((int)$t['attachments_count'] > 0): ?>
<span><b>Allegati:</b> <?= (int)$t['attachments_count'] ?></span>
<?php endif; ?>
</div>
<div class="doc-card-actions">
<button class="btn btn-sm btn-outline-primary open-attachments"
data-id="<?= $tid ?>"
data-name="<?= htmlspecialchars($t['topic_name'], ENT_QUOTES) ?>">
📎 (<?= (int)$t['attachments_count'] ?>)
</button>
<button class="btn btn-sm btn-outline-info open-log"
data-id="<?= $tid ?>"
data-name="<?= htmlspecialchars($t['topic_name'], ENT_QUOTES) ?>">📜 Storia</button>
<?php if ($canEdit): ?>
<button class="btn btn-sm btn-outline-secondary edit-training"
data-id="<?= $tid ?>"
data-topic_id="<?= (int)$t['training_topic_id'] ?>"
data-completed_date="<?= htmlspecialchars($t['completed_date'] ?? '', ENT_QUOTES) ?>"
data-delivered_by="<?= htmlspecialchars($t['delivered_by'] ?? '', ENT_QUOTES) ?>"
data-description="<?= htmlspecialchars($t['description'] ?? '', ENT_QUOTES) ?>"
data-training_type="<?= htmlspecialchars($t['training_type'], ENT_QUOTES) ?>"
data-freq="<?= $t['update_frequency_months'] !== null ? (int)$t['update_frequency_months'] : '' ?>"
data-rem="<?= $t['reminder_days'] !== null ? (int)$t['reminder_days'] : '' ?>">✏️ Modifica</button>
<button class="btn btn-sm btn-outline-danger delete-training"
data-id="<?= $tid ?>"
data-name="<?= htmlspecialchars($t['topic_name'], ENT_QUOTES) ?>">🗑️</button>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<?php if ($employee): ?>
<!-- TRAINING CHANGE LOG MODAL -->
<div class="modal fade" id="trainingLogModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg modal-dialog-scrollable modal-fullscreen-sm-down">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title">📜 Storia: <span id="trLogSubtitle"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="trLogList">
<div class="text-center text-muted py-3">Caricamento…</div>
</div>
</div>
</div>
</div>
</div>
<style>
.log-entry {
border-left: 3px solid #cbd5e1;
padding: 10px 14px;
margin-bottom: 10px;
background: #f8fafc;
border-radius: 0 10px 10px 0;
}
.log-entry.created {
border-left-color: #16a34a;
}
.log-entry.updated {
border-left-color: #2563eb;
}
.log-entry.deleted {
border-left-color: #dc2626;
}
.log-entry.attachment_added {
border-left-color: #0891b2;
}
.log-entry.attachment_deleted {
border-left-color: #ea580c;
}
.log-when {
font-size: 0.85rem;
color: #64748b;
}
.log-who {
font-weight: 600;
color: #1f2937;
}
.log-action {
font-weight: 600;
}
.log-field {
color: #475569;
font-size: 0.95rem;
margin-top: 4px;
}
.log-field code {
background: #e2e8f0;
padding: 2px 6px;
border-radius: 4px;
}
.log-diff {
display: flex;
gap: 6px;
flex-wrap: wrap;
align-items: center;
margin-top: 4px;
font-size: 0.9rem;
}
.log-old {
background: #fee2e2;
color: #991b1b;
padding: 2px 8px;
border-radius: 4px;
text-decoration: line-through;
}
.log-new {
background: #dcfce7;
color: #166534;
padding: 2px 8px;
border-radius: 4px;
}
</style>
<!-- TRAINING ATTACHMENTS MODAL (visible to everyone with profile access) -->
<div class="modal fade" id="trainingAttachmentsModal" 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">📎 Allegati: <span id="trAttModalSubtitle"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="trAttList" class="mb-3">
<div class="text-center text-muted py-3">Caricamento…</div>
</div>
<div id="trAttUploadWrap" style="display:none;">
<hr>
<h6 class="fw-semibold mb-2">Carica nuovo allegato</h6>
<form id="trAttUploadForm" enctype="multipart/form-data">
<input type="hidden" id="trAttTrainingId" name="training_id">
<div class="mb-3">
<input type="file" class="form-control" name="file" required>
</div>
<div class="text-end">
<button type="submit" class="btn btn-add">⬆️ Carica</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($employee && $canEdit): ?>
<!-- UPLOAD DOCUMENT MODAL -->
<div class="modal fade" id="uploadDocumentModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-fullscreen-sm-down">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title">Carica Documento</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="uploadDocumentForm" enctype="multipart/form-data">
<input type="hidden" name="employee_id" value="<?= (int)$employee['id'] ?>">
<div class="mb-3">
<label class="form-label fw-semibold">Categoria</label>
<select class="form-select" name="category" required>
<option value="job_description">Mansionario</option>
<option value="contract">Contratto</option>
<option value="rules">Regolamento aziendale</option>
<option value="other" selected>Altro</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">File</label>
<input type="file" class="form-control" name="file" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Note</label>
<textarea class="form-control" name="notes" rows="2" placeholder="Opzionale"></textarea>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">⬆️ Carica</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- TRAINING MODAL (add / edit) -->
<div class="modal fade" id="trainingModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg modal-dialog-scrollable modal-fullscreen-sm-down">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title" id="trainingModalTitle">Aggiungi Formazione</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="trainingForm">
<input type="hidden" id="trainingId">
<input type="hidden" id="trainingEmployeeId" value="<?= (int)$employee['id'] ?>">
<div class="row">
<div class="col-12 col-md-8 mb-3">
<label class="form-label fw-semibold">Corso *</label>
<select class="form-select" id="trainingTopicId" required>
<option value="">— Seleziona corso —</option>
<?php foreach ($trainingTopicsAll as $tt): ?>
<option value="<?= (int)$tt['id'] ?>"
data-default-freq="<?= $tt['default_frequency_months'] !== null ? (int)$tt['default_frequency_months'] : '' ?>"
data-default-rem="<?= $tt['default_reminder_days'] !== null ? (int)$tt['default_reminder_days'] : 30 ?>">
<?= htmlspecialchars($tt['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-4 mb-3">
<label class="form-label fw-semibold">Tipo *</label>
<select class="form-select" id="trainingType">
<option value="initial">Iniziale</option>
<option value="refresher">Aggiornamento</option>
</select>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Data Completamento *</label>
<input type="date" class="form-control" id="trainingCompletedDate" required>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Erogato da</label>
<input type="text" class="form-control" id="trainingDeliveredBy" placeholder="Azienda o nome del formatore">
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Descrizione</label>
<textarea class="form-control" id="trainingDescription" rows="2"></textarea>
</div>
<hr>
<p class="text-muted small mb-2">
Frequenza e promemoria sono ereditati dal corso. Sovrascrivili solo se necessario.
</p>
<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="trainingFreq">
<option value="">Usa default del corso</option>
<option value="0">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</label>
<select class="form-select" id="trainingRem">
<option value="">Usa default del corso</option>
<option value="7">7 giorni (1 settimana)</option>
<option value="14">14 giorni (2 settimane)</option>
<option value="30">30 giorni (1 mese)</option>
<option value="60">60 giorni (2 mesi)</option>
<option value="90">90 giorni (3 mesi)</option>
<option value="180">180 giorni (6 mesi)</option>
</select>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Salva</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- PPE MODAL (add / edit) -->
<div class="modal fade" id="ppeModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-fullscreen-sm-down">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title" id="ppeModalTitle">Aggiungi DPI</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="ppeForm">
<input type="hidden" id="ppeId">
<input type="hidden" name="employee_id" id="ppeEmployeeId" value="<?= (int)$employee['id'] ?>">
<div class="mb-3">
<label class="form-label fw-semibold">DPI *</label>
<input type="text" class="form-control" id="ppeItemName" placeholder="es. Casco, Guanti, Scarpe antinfortunistiche" required>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Data Consegna</label>
<input type="date" class="form-control" id="ppeDeliveryDate">
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Consegnato da</label>
<input type="text" class="form-control" id="ppeDeliveredBy" placeholder="Nome o azienda">
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Note</label>
<textarea class="form-control" id="ppeNotes" rows="2" placeholder="Opzionale"></textarea>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Salva</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- EDIT PERSONAL DETAILS MODAL -->
<div class="modal fade" id="editPersonalModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg modal-dialog-scrollable modal-fullscreen-sm-down">
<div class="modal-content">
<div class="modal-header" style="background-color:#cfe3ff;">
<h5 class="modal-title">Modifica Anagrafica</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editPersonalForm">
<input type="hidden" id="editEmployeeId" value="<?= (int)$employee['id'] ?>">
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Nome *</label>
<input type="text" class="form-control" id="editFirstName" value="<?= htmlspecialchars($employee['first_name'] ?? '', ENT_QUOTES) ?>" required>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Cognome *</label>
<input type="text" class="form-control" id="editLastName" value="<?= htmlspecialchars($employee['last_name'] ?? '', ENT_QUOTES) ?>" required>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Codice Dipendente</label>
<input type="text" class="form-control" id="editEmployeeCode" value="<?= htmlspecialchars($employee['employee_code'] ?? '', ENT_QUOTES) ?>">
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Data di Assunzione</label>
<input type="date" class="form-control" id="editHireDate" value="<?= htmlspecialchars($employee['hire_date'] ?? '', ENT_QUOTES) ?>">
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Indirizzo</label>
<input type="text" class="form-control" id="editAddress" value="<?= htmlspecialchars($employee['address'] ?? '', ENT_QUOTES) ?>">
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Telefono</label>
<input type="tel" class="form-control" id="editPhone" value="<?= htmlspecialchars($employee['phone'] ?? '', ENT_QUOTES) ?>">
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Email</label>
<input type="email" class="form-control" id="editEmail" value="<?= htmlspecialchars($employee['email'] ?? '', ENT_QUOTES) ?>">
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Reparto</label>
<select class="form-select" id="editDepartmentId">
<option value="">— Nessuno —</option>
<?php foreach ($departments as $d): ?>
<option value="<?= (int)$d['id'] ?>" <?= ((int)($employee['department_id'] ?? 0) === (int)$d['id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($d['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Mansione</label>
<select class="form-select" id="editJobRoleId">
<option value="">— Nessuna —</option>
<?php foreach ($jobRoles as $r): ?>
<option value="<?= (int)$r['id'] ?>" <?= ((int)($employee['job_role_id'] ?? 0) === (int)$r['id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($r['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Stato</label>
<select class="form-select" id="editStatus">
<option value="active" <?= ($employee['status'] ?? '') === 'active' ? 'selected' : '' ?>>Attivo</option>
<option value="inactive" <?= ($employee['status'] ?? '') === 'inactive' ? 'selected' : '' ?>>Cessato</option>
<option value="suspended" <?= ($employee['status'] ?? '') === 'suspended' ? 'selected' : '' ?>>Sospeso</option>
</select>
</div>
<div class="col-12 col-md-6 mb-3">
<label class="form-label fw-semibold">Utente collegato (account login)</label>
<select class="form-select" id="editAuthUserId">
<option value="" data-role_id="">— Nessuno —</option>
<?php foreach ($authUsers as $u): ?>
<?php
$label = trim(($u['first_name'] ?? '') . ' ' . ($u['last_name'] ?? ''));
if ($label === '') $label = $u['username'] ?? ('#' . $u['id']);
if (!empty($u['email'])) $label .= ' (' . $u['email'] . ')';
?>
<option value="<?= (int)$u['id'] ?>"
data-role_id="<?= (int)($u['role_id'] ?? 0) ?>"
<?= ((int)($employee['auth_user_id'] ?? 0) === (int)$u['id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($label) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="mb-3" id="editRoleWrapper" style="display:none;">
<label class="form-label fw-semibold">Ruolo di accesso</label>
<select class="form-select" id="editRoleId">
<option value="">— Seleziona ruolo —</option>
<?php foreach ($rolesList as $r): ?>
<option value="<?= (int)$r['id'] ?>">
<?= htmlspecialchars($r['display_name'] ?: $r['name']) ?>
</option>
<?php endforeach; ?>
</select>
<small class="text-muted">Visibile solo quando è collegato un utente di sistema.</small>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Salva Modifiche</button>
</div>
</form>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.8/js/dataTables.bootstrap5.min.js"></script>
<script>
$(document).ready(function() {
if ($('#documentsTable').length) {
$('#documentsTable').DataTable({
pageLength: 10,
order: [
[4, 'desc']
],
language: {
search: "Cerca:",
lengthMenu: "Mostra _MENU_ righe",
info: "Mostra _START_ - _END_ di _TOTAL_ righe",
infoEmpty: "Nessuna riga disponibile",
infoFiltered: "(filtrate da _MAX_ righe totali)",
zeroRecords: "Nessun risultato trovato",
paginate: {
first: "Prima",
last: "Ultima",
next: "Avanti",
previous: "Indietro"
}
},
columnDefs: [{
targets: -1,
orderable: false,
searchable: false
}]
});
}
if ($('#trainingTable').length) {
$('#trainingTable').DataTable({
pageLength: 10,
order: [
[2, 'desc']
],
language: {
search: "Cerca:",
lengthMenu: "Mostra _MENU_ righe",
info: "Mostra _START_ - _END_ di _TOTAL_ righe",
infoEmpty: "Nessuna riga disponibile",
infoFiltered: "(filtrate da _MAX_ righe totali)",
zeroRecords: "Nessun risultato trovato",
paginate: {
first: "Prima",
last: "Ultima",
next: "Avanti",
previous: "Indietro"
}
},
columnDefs: [{
targets: -1,
orderable: false,
searchable: false
}]
});
}
});
</script>
<?php if ($employee): ?>
<script>
(function() {
var burger = document.getElementById('tabBurger');
var burgerBtn = document.getElementById('tabBurgerBtn');
if (burger && burgerBtn) {
burgerBtn.addEventListener('click', function(e) {
e.stopPropagation();
burger.classList.toggle('open');
});
document.addEventListener('click', function(e) {
if (!burger.contains(e.target)) burger.classList.remove('open');
});
}
// Mobile burger items → activate desktop tab via Bootstrap Tab API
document.querySelectorAll('.tab-burger-menu a').forEach(function(item) {
item.addEventListener('click', function(e) {
e.preventDefault();
var target = item.getAttribute('data-burger-target');
if (burger) burger.classList.remove('open');
if (!target) return;
var deskTab = document.querySelector('#profileTabs a[href="' + target + '"]');
if (deskTab && window.bootstrap && window.bootstrap.Tab) {
bootstrap.Tab.getOrCreateInstance(deskTab).show();
}
});
});
// When any desktop tab becomes active, sync burger label/icon, menu active state, and URL hash
document.querySelectorAll('#profileTabs a[data-bs-toggle="tab"]').forEach(function(el) {
el.addEventListener('shown.bs.tab', function(e) {
var target = e.target.getAttribute('href');
var label = e.target.getAttribute('data-tab-label') || '';
var icon = e.target.getAttribute('data-tab-icon') || 'bx-user';
var bl = document.getElementById('burgerLabel');
var bi = document.getElementById('burgerIcon');
if (bl) bl.textContent = label;
if (bi) bi.className = 'bx ' + icon;
document.querySelectorAll('.tab-burger-menu a').forEach(function(i) {
i.classList.toggle('active', i.getAttribute('data-burger-target') === target);
});
// Persist active tab in URL hash so reload keeps the user on the same section
if (history.replaceState) {
history.replaceState(null, '', target);
}
});
});
// On page load — activate tab from URL hash if present
if (location.hash) {
var initial = document.querySelector('#profileTabs a[href="' + location.hash + '"]');
if (initial && window.bootstrap && window.bootstrap.Tab) {
bootstrap.Tab.getOrCreateInstance(initial).show();
}
}
// ---- TRAINING CHANGE LOG MODAL ----
var ACTION_LABEL = {
'created': {
text: 'Creato',
icon: ''
},
'updated': {
text: 'Modificato',
icon: '✏️'
},
'deleted': {
text: 'Cancellato',
icon: '🗑️'
},
'attachment_added': {
text: 'Allegato aggiunto',
icon: '📎'
},
'attachment_deleted': {
text: 'Allegato cancellato',
icon: '🗑️'
}
};
var FIELD_LABEL = {
'training_topic_id': 'Corso',
'completed_date': 'Data Completamento',
'delivered_by': 'Erogato da',
'delivered_by_company': 'Erogato da',
'description': 'Descrizione',
'training_type': 'Tipo',
'update_frequency_months': 'Frequenza aggiornamento (mesi)',
'reminder_days': 'Promemoria (giorni)',
'next_due_date': 'Prossima scadenza',
'attachment': 'Allegato'
};
var TYPE_LABEL = {
'initial': 'Iniziale',
'refresher': 'Aggiornamento'
};
function fmtDateTimeIt(s) {
if (!s) return '—';
var d = new Date(String(s).replace(' ', 'T'));
if (isNaN(d.getTime())) return '—';
var pad = function(n) {
return String(n).padStart(2, '0');
};
return pad(d.getDate()) + '/' + pad(d.getMonth() + 1) + '/' + d.getFullYear() +
' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
}
function escapeHtml(s) {
return String(s == null ? '' : s).replace(/[<>&"]/g, function(c) {
return ({
'<': '&lt;',
'>': '&gt;',
'&': '&amp;',
'"': '&quot;'
})[c];
});
}
function prettyValue(field, val) {
if (val === null || val === undefined || val === '') return '—';
if (field === 'training_type') return TYPE_LABEL[val] || val;
if (field === 'completed_date' || field === 'next_due_date') {
var d = new Date(val);
if (!isNaN(d.getTime())) {
var pad = function(n) {
return String(n).padStart(2, '0');
};
return pad(d.getDate()) + '/' + pad(d.getMonth() + 1) + '/' + d.getFullYear();
}
}
return val;
}
function loadLog(trainingId) {
var listEl = document.getElementById('trLogList');
listEl.innerHTML = '<div class="text-center text-muted py-3">Caricamento…</div>';
fetch('ajax/employee_profile/get_training_log.php?training_id=' + encodeURIComponent(trainingId))
.then(function(r) {
return r.json();
})
.then(function(data) {
if (!data.success) {
listEl.innerHTML = '<div class="text-danger py-3">' + escapeHtml(data.message || 'Errore.') + '</div>';
return;
}
if (!data.entries || data.entries.length === 0) {
listEl.innerHTML = '<div class="text-muted py-3 text-center">Nessuna modifica registrata</div>';
return;
}
var html = '';
data.entries.forEach(function(e) {
var act = ACTION_LABEL[e.action] || {
text: e.action,
icon: '•'
};
var who = e.changed_by_name && e.changed_by_name.trim() !== '' ?
e.changed_by_name :
(e.changed_by_email || '—');
html += '<div class="log-entry ' + escapeHtml(e.action) + '">';
html += '<div class="d-flex justify-content-between flex-wrap gap-2">';
html += '<div class="log-action">' + act.icon + ' ' + escapeHtml(act.text) + '</div>';
html += '<div class="log-when">' + escapeHtml(fmtDateTimeIt(e.changed_at)) + '</div>';
html += '</div>';
html += '<div class="log-who">' + escapeHtml(who) + '</div>';
if (e.field) {
var fname = FIELD_LABEL[e.field] || e.field;
html += '<div class="log-field"><code>' + escapeHtml(fname) + '</code></div>';
if (e.action === 'updated') {
html += '<div class="log-diff">';
html += '<span class="log-old">' + escapeHtml(prettyValue(e.field, e.old_value)) + '</span>';
html += '<span>→</span>';
html += '<span class="log-new">' + escapeHtml(prettyValue(e.field, e.new_value)) + '</span>';
html += '</div>';
} else if (e.action === 'attachment_added') {
html += '<div class="log-diff"><span class="log-new">' + escapeHtml(e.new_value || '—') + '</span></div>';
} else if (e.action === 'attachment_deleted') {
html += '<div class="log-diff"><span class="log-old">' + escapeHtml(e.old_value || '—') + '</span></div>';
}
}
html += '</div>';
});
listEl.innerHTML = html;
})
.catch(function(err) {
listEl.innerHTML = '<div class="text-danger py-3">Errore di comunicazione.</div>';
console.error(err);
});
}
document.addEventListener('click', function(e) {
var btn = e.target.closest('.open-log');
if (!btn) return;
var id = btn.getAttribute('data-id');
var name = btn.getAttribute('data-name') || '';
document.getElementById('trLogSubtitle').textContent = name;
loadLog(id);
var modal = new bootstrap.Modal(document.getElementById('trainingLogModal'));
modal.show();
});
// ---- TRAINING ATTACHMENTS MODAL (visible to all profile viewers) ----
function fmtBytes(b) {
if (b == null || b <= 0) return '—';
if (b < 1024) return b + ' B';
if (b < 1024 * 1024) return (b / 1024).toFixed(1) + ' KB';
if (b < 1024 * 1024 * 1024) return (b / 1024 / 1024).toFixed(1) + ' MB';
return (b / 1024 / 1024 / 1024).toFixed(1) + ' GB';
}
function fmtDateIt(s) {
if (!s) return '—';
var d = new Date(String(s).replace(' ', 'T'));
if (isNaN(d.getTime())) return '—';
var dd = String(d.getDate()).padStart(2, '0');
var mm = String(d.getMonth() + 1).padStart(2, '0');
return dd + '/' + mm + '/' + d.getFullYear();
}
function loadAttachments(trainingId) {
var listEl = document.getElementById('trAttList');
var uploadWrap = document.getElementById('trAttUploadWrap');
listEl.innerHTML = '<div class="text-center text-muted py-3">Caricamento…</div>';
uploadWrap.style.display = 'none';
fetch('ajax/employee_profile/get_training_attachments.php?training_id=' + encodeURIComponent(trainingId))
.then(function(r) {
return r.json();
})
.then(function(data) {
if (!data.success) {
listEl.innerHTML = '<div class="text-danger py-3">' + (data.message || 'Errore.') + '</div>';
return;
}
if (!data.attachments || data.attachments.length === 0) {
listEl.innerHTML = '<div class="text-muted py-3 text-center">Nessun allegato</div>';
} else {
var html = '<ul class="list-group">';
data.attachments.forEach(function(a) {
var safeName = String(a.original_name).replace(/[<>&"]/g, function(c) {
return ({
'<': '&lt;',
'>': '&gt;',
'&': '&amp;',
'"': '&quot;'
})[c];
});
html += '<li class="list-group-item d-flex justify-content-between align-items-center flex-wrap gap-2">';
html += '<div>';
html += '<a href="ajax/employee_profile/download_training_attachment.php?id=' + a.id + '" class="fw-semibold text-decoration-none"><i class="bx bx-file me-1"></i>' + safeName + '</a>';
html += '<div class="small text-muted">' + fmtBytes(a.size) + ' &middot; ' + fmtDateIt(a.created_at) + '</div>';
html += '</div>';
html += '<div class="d-flex gap-2">';
html += '<a class="btn btn-sm btn-outline-primary" href="ajax/employee_profile/download_training_attachment.php?id=' + a.id + '">⬇️</a>';
if (data.can_edit) {
html += '<button class="btn btn-sm btn-outline-danger delete-tr-att" data-id="' + a.id + '" data-name="' + safeName + '">🗑️</button>';
}
html += '</div>';
html += '</li>';
});
html += '</ul>';
listEl.innerHTML = html;
}
uploadWrap.style.display = data.can_edit ? 'block' : 'none';
})
.catch(function(err) {
listEl.innerHTML = '<div class="text-danger py-3">Errore di comunicazione.</div>';
console.error(err);
});
}
document.addEventListener('click', function(e) {
var openBtn = e.target.closest('.open-attachments');
if (openBtn) {
var id = openBtn.getAttribute('data-id');
var name = openBtn.getAttribute('data-name') || '';
document.getElementById('trAttTrainingId').value = id;
document.getElementById('trAttModalSubtitle').textContent = name;
loadAttachments(id);
var modal = new bootstrap.Modal(document.getElementById('trainingAttachmentsModal'));
modal.show();
return;
}
var delBtn = e.target.closest('.delete-tr-att');
if (delBtn) {
var aid = delBtn.getAttribute('data-id');
var nm = delBtn.getAttribute('data-name') || '';
Swal.fire({
title: 'Confermi la cancellazione?',
text: 'Allegato: ' + nm,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Sì, cancella',
cancelButtonText: 'Annulla'
}).then(function(r) {
if (!r.isConfirmed) return;
var p = new URLSearchParams();
p.append('id', aid);
fetch('ajax/employee_profile/delete_training_attachment.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: p.toString()
}).then(function(rr) {
return rr.json();
}).then(function(data) {
if (data.success) {
location.reload();
} else {
Swal.fire({
icon: 'error',
title: 'Errore',
text: data.message || 'Impossibile cancellare.'
});
}
});
});
}
});
var uploadForm = document.getElementById('trAttUploadForm');
if (uploadForm) {
uploadForm.addEventListener('submit', function(e) {
e.preventDefault();
var fd = new FormData(uploadForm);
fetch('ajax/employee_profile/upload_training_attachment.php', {
method: 'POST',
body: fd
}).then(function(r) {
return r.json();
}).then(function(data) {
if (data.success) {
location.reload();
} else {
Swal.fire({
icon: 'error',
title: 'Errore',
text: data.message || 'Impossibile caricare.'
});
}
});
});
}
})();
</script>
<?php endif; ?>
<?php if ($employee && $canEdit): ?>
<script>
$(document).ready(function() {
// ---- UPLOAD DOCUMENT ----
$("#uploadDocumentForm").on("submit", function(e) {
e.preventDefault();
const fd = new FormData(this);
fetch("ajax/employee_profile/upload_document.php", {
method: "POST",
body: fd
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Caricato!",
confirmButtonColor: "#3085d6"
})
.then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Impossibile caricare il file."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Errore",
text: "Errore di comunicazione."
});
console.error(err);
});
});
// ---- TRAINING: open modal (add or edit) ----
window.openTrainingModal = function() {
$("#trainingId").val('');
$("#trainingTopicId").val('');
$("#trainingType").val('initial');
$("#trainingCompletedDate").val('');
$("#trainingDeliveredBy").val('');
$("#trainingDescription").val('');
$("#trainingFreq").val('');
$("#trainingRem").val('');
$("#trainingModalTitle").text('Aggiungi Formazione');
};
// Add training from "missing mandatory" list — pre-fill topic + type=initial
$(document).on("click", ".add-missing-training", function() {
window.openTrainingModal();
$("#trainingTopicId").val(String($(this).data("topic_id"))).trigger("change");
$("#trainingType").val('initial');
$("#trainingModal").modal("show");
});
// Update "Usa default del corso (N)" text when topic changes
$("#trainingTopicId").on("change", function() {
const opt = this.options[this.selectedIndex];
if (!opt) return;
const f = opt.getAttribute('data-default-freq') || '';
const r = opt.getAttribute('data-default-rem') || '30';
const freqDefaultLabel = f !== '' ?
'Usa default del corso (' + f + ' mesi)' :
'Usa default del corso (una tantum)';
const remDefaultLabel = 'Usa default del corso (' + r + ' giorni)';
const freqFirst = $("#trainingFreq option").first();
const remFirst = $("#trainingRem option").first();
if (freqFirst.attr('value') === '') freqFirst.text(freqDefaultLabel);
if (remFirst.attr('value') === '') remFirst.text(remDefaultLabel);
});
// Add a missing option on the fly if the stored value isn't in the predefined list
function ensureSelectOption(selectId, value, labelFmt) {
if (value === '' || value === null || value === undefined) return;
const v = String(value);
if ($(selectId + " option[value='" + v + "']").length === 0) {
$(selectId).append('<option value="' + v + '">' + labelFmt(v) + '</option>');
}
}
$(document).on("click", ".edit-training", function() {
const b = $(this);
$("#trainingId").val(b.data("id"));
$("#trainingTopicId").val(b.data("topic_id"));
$("#trainingType").val(b.data("training_type") || 'initial');
$("#trainingCompletedDate").val(b.data("completed_date"));
$("#trainingDeliveredBy").val(b.data("delivered_by"));
$("#trainingDescription").val(b.data("description"));
$("#trainingTopicId").trigger("change"); // refresh defaults first
const freqVal = b.data("freq");
const remVal = b.data("rem");
ensureSelectOption("#trainingFreq", freqVal, function(v) {
return v + ' mesi';
});
ensureSelectOption("#trainingRem", remVal, function(v) {
return v + ' giorni';
});
$("#trainingFreq").val(freqVal === '' || freqVal === undefined ? '' : String(freqVal));
$("#trainingRem").val(remVal === '' || remVal === undefined ? '' : String(remVal));
$("#trainingModalTitle").text('Modifica Formazione');
$("#trainingModal").modal("show");
});
$("#trainingForm").on("submit", function(e) {
e.preventDefault();
const p = new URLSearchParams();
p.append('id', $("#trainingId").val());
p.append('employee_id', $("#trainingEmployeeId").val());
p.append('training_topic_id', $("#trainingTopicId").val());
p.append('training_type', $("#trainingType").val());
p.append('completed_date', $("#trainingCompletedDate").val());
p.append('delivered_by', $("#trainingDeliveredBy").val().trim());
p.append('description', $("#trainingDescription").val().trim());
p.append('update_frequency_months', $("#trainingFreq").val());
p.append('reminder_days', $("#trainingRem").val());
fetch("ajax/employee_profile/save_training.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: p.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Salvato!",
confirmButtonColor: "#3085d6"
})
.then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Impossibile salvare."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Errore",
text: "Errore di comunicazione."
});
console.error(err);
});
});
$(document).on("click", ".delete-training", function() {
const id = $(this).data("id");
const name = $(this).data("name");
Swal.fire({
title: "Confermi la cancellazione?",
text: name ? ("Formazione: " + name) : "La registrazione verrà cancellata.",
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);
fetch("ajax/employee_profile/delete_training.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: p.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Cancellato!",
confirmButtonColor: "#3085d6"
})
.then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Impossibile cancellare."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Errore",
text: "Errore di comunicazione."
});
console.error(err);
});
});
});
// ---- PPE: open modal (add or edit) ----
window.openPpeModal = function() {
$("#ppeId").val('');
$("#ppeItemName").val('');
$("#ppeDeliveryDate").val('');
$("#ppeDeliveredBy").val('');
$("#ppeNotes").val('');
$("#ppeModalTitle").text('Aggiungi DPI');
};
$(document).on("click", ".edit-ppe", function() {
const b = $(this);
$("#ppeId").val(b.data("id"));
$("#ppeItemName").val(b.data("item_name"));
$("#ppeDeliveryDate").val(b.data("delivery_date"));
$("#ppeDeliveredBy").val(b.data("delivered_by"));
$("#ppeNotes").val(b.data("notes"));
$("#ppeModalTitle").text('Modifica DPI');
$("#ppeModal").modal("show");
});
$("#ppeForm").on("submit", function(e) {
e.preventDefault();
const p = new URLSearchParams();
p.append('id', $("#ppeId").val());
p.append('employee_id', $("#ppeEmployeeId").val());
p.append('item_name', $("#ppeItemName").val().trim());
p.append('delivery_date', $("#ppeDeliveryDate").val());
p.append('delivered_by', $("#ppeDeliveredBy").val().trim());
p.append('notes', $("#ppeNotes").val().trim());
fetch("ajax/employee_profile/save_ppe.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: p.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Salvato!",
confirmButtonColor: "#3085d6"
})
.then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Impossibile salvare."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Errore",
text: "Errore di comunicazione."
});
console.error(err);
});
});
$(document).on("click", ".delete-ppe", function() {
const id = $(this).data("id");
const name = $(this).data("name");
Swal.fire({
title: "Confermi la cancellazione?",
text: name ? ("DPI: " + name) : "Il DPI 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);
fetch("ajax/employee_profile/delete_ppe.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: p.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Cancellato!",
confirmButtonColor: "#3085d6"
})
.then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Impossibile cancellare."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Errore",
text: "Errore di comunicazione."
});
console.error(err);
});
});
});
// ---- DELETE DOCUMENT ----
$(document).on("click", ".delete-document", function() {
const id = $(this).data("id");
const name = $(this).data("name");
Swal.fire({
title: "Confermi la cancellazione?",
text: name ? ("Documento: " + name) : "Il documento 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);
fetch("ajax/employee_profile/delete_document.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: p.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Cancellato!",
confirmButtonColor: "#3085d6"
})
.then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Impossibile cancellare."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Errore",
text: "Errore di comunicazione."
});
console.error(err);
});
});
});
// Show/hide and preselect role when an auth_user is linked
function refreshEditRoleWrapper() {
const $opt = $("#editAuthUserId option:selected");
if ($opt.length && $opt.val() !== '') {
$("#editRoleWrapper").show();
const linkedRole = $opt.data('role_id');
if (linkedRole !== undefined && linkedRole !== '' && $("#editRoleId").val() === '') {
$("#editRoleId").val(String(linkedRole));
}
} else {
$("#editRoleWrapper").hide();
$("#editRoleId").val('');
}
}
$(document).on('change', '#editAuthUserId', refreshEditRoleWrapper);
$('#editPersonalModal').on('shown.bs.modal', refreshEditRoleWrapper);
$("#editPersonalForm").on("submit", function(e) {
e.preventDefault();
const p = new URLSearchParams();
p.append('employee_id', $("#editEmployeeId").val());
p.append('first_name', $("#editFirstName").val().trim());
p.append('last_name', $("#editLastName").val().trim());
p.append('employee_code', $("#editEmployeeCode").val().trim());
p.append('hire_date', $("#editHireDate").val());
p.append('address', $("#editAddress").val().trim());
p.append('phone', $("#editPhone").val().trim());
p.append('email', $("#editEmail").val().trim());
p.append('department_id', $("#editDepartmentId").val());
p.append('job_role_id', $("#editJobRoleId").val());
p.append('status', $("#editStatus").val());
p.append('auth_user_id', $("#editAuthUserId").val());
p.append('role_id', $("#editAuthUserId").val() ? ($("#editRoleId").val() || '') : '');
fetch("ajax/employee_profile/save_personal.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: p.toString()
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: "success",
title: "Salvato!",
confirmButtonColor: "#3085d6"
})
.then(() => location.reload());
} else {
Swal.fire({
icon: "error",
title: "Errore",
text: data.message || "Impossibile salvare."
});
}
})
.catch(err => {
Swal.fire({
icon: "error",
title: "Errore",
text: "Errore di comunicazione."
});
console.error(err);
});
});
});
</script>
<?php endif; ?>
</body>
</html>