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, jsr.name AS job_sub_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, ar.name AS auth_role_name, ar.display_name AS auth_role_display_name 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 job_sub_roles jsr ON jsr.id = e.job_sub_role_id LEFT JOIN auth_users au ON au.id = e.auth_user_id LEFT JOIN auth_roles ar ON ar.id = au.role_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; /* ========================================== EMPLOYEE JOB ROLES / SUB ROLES (multi assignment) ========================================== */ $employeeSubRoles = []; $employeeJobRoleNames = []; $employeeSubRoleNames = []; $employeeSubRoleIds = []; $employeeSubRolesByRole = []; if ($employee) { $stmt = $pdo->prepare(" SELECT ejsr.job_sub_role_id, ejsr.is_primary, jsr.name AS job_sub_role_name, jsr.job_role_id, jr.name AS job_role_name FROM employee_job_sub_roles ejsr INNER JOIN job_sub_roles jsr ON jsr.id = ejsr.job_sub_role_id LEFT JOIN job_roles jr ON jr.id = jsr.job_role_id WHERE ejsr.employee_id = :eid ORDER BY ejsr.is_primary DESC, jr.sort_order ASC, jr.name ASC, jsr.sort_order ASC, jsr.name ASC "); $stmt->execute(['eid' => $employeeId]); $employeeSubRoles = $stmt->fetchAll(PDO::FETCH_ASSOC); // Fallback: if the bridge table is empty but legacy employees.job_sub_role_id is filled, show the legacy value. if (!$employeeSubRoles && !empty($employee['job_sub_role_id'])) { $stmt = $pdo->prepare(" SELECT jsr.id AS job_sub_role_id, 1 AS is_primary, jsr.name AS job_sub_role_name, jsr.job_role_id, jr.name AS job_role_name FROM job_sub_roles jsr LEFT JOIN job_roles jr ON jr.id = jsr.job_role_id WHERE jsr.id = :sid LIMIT 1 "); $stmt->execute(['sid' => (int)$employee['job_sub_role_id']]); $legacySubRole = $stmt->fetch(PDO::FETCH_ASSOC); if ($legacySubRole) { $employeeSubRoles = [$legacySubRole]; } } foreach ($employeeSubRoles as $sr) { $employeeSubRoleIds[] = (int)$sr['job_sub_role_id']; if (!empty($sr['job_role_name'])) { $employeeJobRoleNames[(int)$sr['job_role_id']] = $sr['job_role_name']; } if (!empty($sr['job_sub_role_name'])) { $employeeSubRoleNames[(int)$sr['job_sub_role_id']] = $sr['job_sub_role_name']; } $roleKey = (int)($sr['job_role_id'] ?? 0); if (!isset($employeeSubRolesByRole[$roleKey])) { $employeeSubRolesByRole[$roleKey] = [ 'job_role_name' => $sr['job_role_name'] ?: 'Senza mansione', 'items' => [], ]; } $employeeSubRolesByRole[$roleKey]['items'][] = $sr; } } /* ========================================== 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 + Required by sub role) ========================================== */ $ppeList = []; $ppeItemsAll = []; $requiredPpeList = []; $assignedPpeIds = []; if ($employee) { // Assigned PPE history from the normalized table. $stmt = $pdo->prepare(" SELECT epi.*, pi.name AS ppe_name, pi.category AS ppe_category, pi.photo AS ppe_photo, pi.standard_reference, pi.validity_months FROM employee_ppe_items epi INNER JOIN ppe_items pi ON pi.id = epi.ppe_item_id WHERE epi.employee_id = :eid ORDER BY CASE epi.status WHEN 'assigned' THEN 1 WHEN 'expired' THEN 2 WHEN 'damaged' THEN 3 WHEN 'lost' THEN 4 WHEN 'returned' THEN 5 ELSE 9 END, epi.assigned_date DESC, epi.created_at DESC "); $stmt->execute(['eid' => $employeeId]); $ppeList = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($ppeList as $p) { if (($p['status'] ?? '') === 'assigned') { $assignedPpeIds[(int)$p['ppe_item_id']] = true; } } // All active PPE for manual assignment dropdown. if ($canEdit) { $ppeItemsAll = $pdo->query(" SELECT id, name, category, standard_reference, validity_months FROM ppe_items WHERE is_active = 1 ORDER BY sort_order ASC, name ASC ")->fetchAll(PDO::FETCH_ASSOC); } // Required PPE based on all employee sub roles. // DISTINCT avoids duplicated PPE when two sub roles require the same item. $stmt = $pdo->prepare(" SELECT pi.id, pi.name, pi.category, pi.photo, pi.standard_reference, pi.validity_months, GROUP_CONCAT(DISTINCT CONCAT(COALESCE(jr.name, 'Senza mansione'), ' / ', jsr.name) ORDER BY jr.sort_order ASC, jr.name ASC, jsr.sort_order ASC, jsr.name ASC SEPARATOR ' | ') AS source_sub_roles FROM employee_job_sub_roles ejsr INNER JOIN job_sub_roles jsr ON jsr.id = ejsr.job_sub_role_id LEFT JOIN job_roles jr ON jr.id = jsr.job_role_id INNER JOIN job_sub_role_ppe_items jsp ON jsp.job_sub_role_id = ejsr.job_sub_role_id INNER JOIN ppe_items pi ON pi.id = jsp.ppe_item_id WHERE ejsr.employee_id = :eid AND jsp.is_active = 1 AND pi.is_active = 1 GROUP BY pi.id, pi.name, pi.category, pi.photo, pi.standard_reference, pi.validity_months ORDER BY pi.category ASC, pi.name ASC "); $stmt->execute(['eid' => $employeeId]); $requiredPpeList = $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) : []; $jobSubRolesAll = $isHrManager ? $pdo->query(" SELECT jsr.id, jsr.job_role_id, jsr.name, jr.name AS job_role_name FROM job_sub_roles jsr LEFT JOIN job_roles jr ON jr.id = jsr.job_role_id WHERE jsr.is_active = 1 ORDER BY jr.sort_order ASC, jr.name ASC, jsr.sort_order ASC, jsr.name ASC ")->fetchAll(PDO::FETCH_ASSOC) : []; $jobSubRoleToRoleMap = []; foreach ($jobSubRolesAll as $sr) { $jobSubRoleToRoleMap[(int)$sr['id']] = (int)$sr['job_role_id']; } $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'); ?>

user avatar

Codice:
Reparto
🏢
Mansioni / Sottomansioni
💼 🧩
Nessuna sottomansione associata
Ruolo accesso
🔐
Stato
Nome
Cognome
Codice Dipendente
Data di Assunzione
Indirizzo
Telefono
Email
Reparto
Mansioni / Sottomansioni
💼
🧩
Nessuna mansione/sottomansione associata
Stato
Utente collegato
Nessun utente collegato
Nessun documento

Categoria Nome File Dimensione Caricato da Data Azioni
⬇️ Scarica
Dim.: Da:
⬇️ Scarica
🦺 DPI richiesti dalle sottomansioni associate — calcolati su sottomansion
·
Da:
Assegnato Mancante
Nessun DPI obbligatorio configurato per le sottomansioni associate al dipendente.
Nessuna sottomansione associata al dipendente: non è possibile suggerire DPI obbligatori.
Nessun DPI assegnato

DPI Categoria Data Consegna Scadenza Consegnato da Stato Note Azioni
🦺
Categoria: Consegna: Scadenza: Consegnato da: Note:
⚠️ obbligator:
Nessuna formazione registrata

'storico', 'label' => 'Storico', 'class' => 'secondary']; $typeLabel = $t['training_type'] === 'refresher' ? 'Aggiornamento' : 'Iniziale'; ?>
Corso Tipo Completato Prossimo agg. Stato Allegati Azioni
'storico', 'label' => 'Storico', 'class' => 'secondary']; $typeLabel = $t['training_type'] === 'refresher' ? 'Aggiornamento' : 'Iniziale'; ?>
📖
Tipo: Completato: Prossimo: 0): ?> Allegati: