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';
}
?>
Profilo Dipendente - = htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?>
= $isOwnProfile
? 'Il tuo profilo dipendente non è ancora stato creato'
: 'Dipendente non trovato' ?>
= $isOwnProfile
? 'Contatta il responsabile HR per la creazione del tuo profilo.'
: 'Verifica l\'ID o torna alla lista dipendenti.' ?>
Nome
= valOrDash($employee['first_name']) ?>
Cognome
= valOrDash($employee['last_name']) ?>
Codice Dipendente
= valOrDash($employee['employee_code']) ?>
Data di Assunzione
= fmtDate($employee['hire_date']) ?>
Indirizzo
= valOrDash($employee['address']) ?>
Reparto
= valOrDash($deptName) ?>
Mansione
= valOrDash($jobName) ?>
Stato
= htmlspecialchars($status['label']) ?>
Utente collegato
= htmlspecialchars($employee['auth_username']) ?>
= htmlspecialchars($employee['auth_email']) ?>
Nessun utente collegato
Nessun documento
= $canEdit
? 'Carica il primo documento (mansionario, contratto, regolamento ecc.).'
: 'Nessun documento disponibile al momento.' ?>
| Categoria |
Nome File |
Dimensione |
Caricato da |
Data |
Azioni |
| = categoryLabel((string)$d['category']) ?> |
= htmlspecialchars($d['original_name']) ?>
= htmlspecialchars($d['notes']) ?>
|
= fmtFileSize($d['size'] !== null ? (int)$d['size'] : null) ?> |
= htmlspecialchars($upBy) ?> |
= fmtDate(substr((string)$d['created_at'], 0, 10)) ?> |
⬇️ Scarica
|
= categoryLabel((string)$d['category']) ?>
= fmtDate(substr((string)$d['created_at'], 0, 10)) ?>
= htmlspecialchars($d['original_name']) ?>
= htmlspecialchars($d['notes']) ?>
Dim.: = fmtFileSize($d['size'] !== null ? (int)$d['size'] : null) ?>
Da: = htmlspecialchars($upBy) ?>
Nessun DPI assegnato
= $canEdit
? 'Aggiungi il primo dispositivo di protezione individuale.'
: 'Nessun DPI consegnato al momento.' ?>
| DPI |
Data Consegna |
Consegnato da |
Note |
Azioni |
| = htmlspecialchars($p['item_name']) ?> |
= fmtDate($p['delivery_date']) ?> |
= valOrDash($p['delivered_by']) ?> |
= valOrDash($p['notes']) ?> |
|
🦺 = htmlspecialchars($p['item_name']) ?>
= fmtDate($p['delivery_date']) ?>
Consegnato da: = htmlspecialchars($p['delivered_by']) ?>
Note: = htmlspecialchars($p['notes']) ?>
⚠️ = count($missingMandatoryTopics) ?>
obbligator= count($missingMandatoryTopics) === 1 ? 'ia non presente' : 'ie non presenti' ?>:
= htmlspecialchars($mt['name']) ?>
Nessuna formazione registrata
= $canEdit
? 'Aggiungi la prima registrazione di formazione (iniziale o aggiornamento).'
: 'Nessuna formazione registrata al momento.' ?>
| Corso |
Tipo |
Completato |
Prossimo agg. |
Stato |
Allegati |
Azioni |
'storico', 'label' => 'Storico', 'class' => 'secondary'];
$typeLabel = $t['training_type'] === 'refresher' ? 'Aggiornamento' : 'Iniziale';
?>
| = htmlspecialchars($t['topic_name']) ?> |
= $typeLabel ?> |
= fmtDate($t['completed_date']) ?> |
= fmtDate($t['next_due_date']) ?> |
= $s['label'] ?> |
= (int)$t['attachments_count'] ?> |
|
'storico', 'label' => 'Storico', 'class' => 'secondary'];
$typeLabel = $t['training_type'] === 'refresher' ? 'Aggiornamento' : 'Iniziale';
?>
📖 = htmlspecialchars($t['topic_name']) ?>
= $s['label'] ?>
Tipo: = $typeLabel ?>
Completato: = fmtDate($t['completed_date']) ?>
Prossimo: = fmtDate($t['next_due_date']) ?>
0): ?>
Allegati: = (int)$t['attachments_count'] ?>