2006 lines
119 KiB
PHP
2006 lines
119 KiB
PHP
<?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'); ?>
|
||
<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 = $employee['auth_avatar'] ?? null;
|
||
?>
|
||
|
||
<!-- HEADER -->
|
||
<div class="profile-header">
|
||
<div class="profile-avatar">
|
||
<?php if ($avatar): ?>
|
||
<img src="<?= htmlspecialchars($avatar) ?>" alt="avatar">
|
||
<?php else: ?>
|
||
<?= htmlspecialchars($initials !== '' ? $initials : '?') ?>
|
||
<?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 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 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'); ?>
|
||
|
||
<?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 ({'<':'<','>':'>','&':'&','"':'"'})[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 ({'<':'<','>':'>','&':'&','"':'"'})[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) + ' · ' + 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>
|