diff --git a/public/userarea/ajax/trainings/bulk_update_deadline.php b/public/userarea/ajax/trainings/bulk_update_deadline.php new file mode 100644 index 0000000..a44a348 --- /dev/null +++ b/public/userarea/ajax/trainings/bulk_update_deadline.php @@ -0,0 +1,104 @@ + false, 'message' => 'Metodo non consentito.']); + exit; +} + +// $pdo and $currentUserId from hr_auth_check.php + +$completedDate = trim($_POST['completed_date'] ?? ''); +$ids = $_POST['training_ids'] ?? []; + +if (!is_array($ids)) { + $ids = []; +} +$ids = array_values(array_unique(array_filter(array_map('intval', $ids), fn($v) => $v > 0))); + +if ($completedDate === '' || !DateTime::createFromFormat('Y-m-d', $completedDate)) { + echo json_encode(['success' => false, 'message' => 'Indicare una data valida.']); + exit; +} +if (empty($ids)) { + echo json_encode(['success' => false, 'message' => 'Selezionare almeno un record.']); + exit; +} + +try { + $pdo->beginTransaction(); + + // Load each record with its topic default frequency + $rowStmt = $pdo->prepare(" + SELECT et.id, et.employee_id, et.completed_date, et.next_due_date, + et.update_frequency_months, tt.default_frequency_months + FROM employee_trainings et + JOIN training_topics tt ON tt.id = et.training_topic_id + WHERE et.id = :id + "); + $upd = $pdo->prepare(" + UPDATE employee_trainings + SET completed_date = :cd, next_due_date = :nd, updated_at = NOW() + WHERE id = :id + "); + $logStmt = $pdo->prepare(" + INSERT INTO employee_training_log + (employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at) + VALUES + (:eid, :tid, 'updated', :field, :old_v, :new_v, :cb, NOW()) + "); + + $updated = 0; + foreach ($ids as $id) { + $rowStmt->execute(['id' => $id]); + $row = $rowStmt->fetch(PDO::FETCH_ASSOC); + if (!$row) { + continue; + } + + // Effective frequency: per-record override, else topic default + $effFreq = $row['update_frequency_months'] !== null + ? (int)$row['update_frequency_months'] + : ($row['default_frequency_months'] !== null ? (int)$row['default_frequency_months'] : null); + + $nextDue = null; + if ($effFreq !== null && $effFreq > 0) { + $d = DateTime::createFromFormat('Y-m-d', $completedDate); + if ($d) { + $d->modify('+' . $effFreq . ' months'); + $nextDue = $d->format('Y-m-d'); + } + } + + $upd->execute(['cd' => $completedDate, 'nd' => $nextDue, 'id' => $id]); + + if ((string)$row['completed_date'] !== (string)$completedDate) { + $logStmt->execute([ + 'eid' => $row['employee_id'], 'tid' => $id, 'field' => 'completed_date', + 'old_v' => $row['completed_date'], 'new_v' => $completedDate, 'cb' => $currentUserId, + ]); + } + if ((string)$row['next_due_date'] !== (string)$nextDue) { + $logStmt->execute([ + 'eid' => $row['employee_id'], 'tid' => $id, 'field' => 'next_due_date', + 'old_v' => $row['next_due_date'], 'new_v' => $nextDue, 'cb' => $currentUserId, + ]); + } + $updated++; + } + + $pdo->commit(); + echo json_encode([ + 'success' => true, + 'updated' => $updated, + 'message' => $updated . ' record aggiornat' . ($updated === 1 ? 'o' : 'i') . '.', + ]); +} catch (Exception $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/ajax/trainings/calendar_events.php b/public/userarea/ajax/trainings/calendar_events.php new file mode 100644 index 0000000..8589b75 --- /dev/null +++ b/public/userarea/ajax/trainings/calendar_events.php @@ -0,0 +1,92 @@ + et.completed_date + OR (et2.completed_date = et.completed_date AND et2.id > et.id)) + )"; + + if ($start && $end) { + $where[] = "et.next_due_date >= :start AND et.next_due_date <= :end"; + $params['start'] = $start; + $params['end'] = $end; + } + if ($fDept > 0) { $where[] = "e.department_id = :did"; $params['did'] = $fDept; } + if ($fTopic > 0) { $where[] = "et.training_topic_id = :tid"; $params['tid'] = $fTopic; } + if ($fEmp > 0) { $where[] = "et.employee_id = :eid"; $params['eid'] = $fEmp; } + + $whereSql = 'WHERE ' . implode(' AND ', $where); + + $stmt = $pdo->prepare(" + SELECT et.id, et.employee_id, et.next_due_date, et.reminder_days, + tt.name AS topic_name, tt.default_reminder_days AS topic_default_rem, + e.first_name, e.last_name + FROM employee_trainings et + JOIN training_topics tt ON tt.id = et.training_topic_id + JOIN employees e ON e.id = et.employee_id + $whereSql + "); + $stmt->execute($params); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $today = new DateTime('today'); + $events = []; + + foreach ($rows as $r) { + $rem = $r['reminder_days'] !== null + ? (int)$r['reminder_days'] + : ($r['topic_default_rem'] !== null ? (int)$r['topic_default_rem'] : 30); + + $due = DateTime::createFromFormat('Y-m-d', $r['next_due_date']); + if (!$due) continue; + $daysLeft = (int)$today->diff($due)->format('%r%a'); + + if ($daysLeft < 0) { $code = 'expired'; $color = '#dc3545'; } + elseif ($daysLeft <= $rem){ $code = 'due_soon'; $color = '#e8930c'; } + else { $code = 'compliant'; $color = '#198754'; } + + if ($fStatus !== '' && $fStatus !== $code) continue; + + $name = trim($r['first_name'] . ' ' . $r['last_name']); + $events[] = [ + 'id' => (int)$r['id'], + 'title' => $name . ' β€” ' . $r['topic_name'], + 'start' => $r['next_due_date'], + 'allDay' => true, + 'backgroundColor' => $color, + 'borderColor' => $color, + 'url' => 'employee-profile.php?id=' . (int)$r['employee_id'] . '#tab-training', + ]; + } + + echo json_encode($events); +} catch (Exception $e) { + echo json_encode([]); +} diff --git a/public/userarea/ajax/trainings/save_bulk_training.php b/public/userarea/ajax/trainings/save_bulk_training.php new file mode 100644 index 0000000..ea38252 --- /dev/null +++ b/public/userarea/ajax/trainings/save_bulk_training.php @@ -0,0 +1,131 @@ + false, 'message' => 'Metodo non consentito.']); + exit; +} + +// $pdo and $currentUserId from hr_auth_check.php + +$topicId = (int)($_POST['training_topic_id'] ?? 0); +$completedDate = trim($_POST['completed_date'] ?? ''); +$deliveredBy = trim($_POST['delivered_by'] ?? ''); +$description = trim($_POST['description'] ?? ''); +$trainingType = trim($_POST['training_type'] ?? 'initial'); +$freqRaw = $_POST['update_frequency_months'] ?? ''; +$remRaw = $_POST['reminder_days'] ?? ''; +$employeeIds = $_POST['employee_ids'] ?? []; + +if (!is_array($employeeIds)) { + $employeeIds = []; +} +$employeeIds = array_values(array_unique(array_filter(array_map('intval', $employeeIds), fn($v) => $v > 0))); + +if ($topicId <= 0) { + echo json_encode(['success' => false, 'message' => 'Selezionare un corso.']); + exit; +} +if ($completedDate === '' || !DateTime::createFromFormat('Y-m-d', $completedDate)) { + echo json_encode(['success' => false, 'message' => 'La data di completamento Γ¨ obbligatoria.']); + exit; +} +if (empty($employeeIds)) { + echo json_encode(['success' => false, 'message' => 'Selezionare almeno un dipendente.']); + exit; +} +if (!in_array($trainingType, ['initial', 'refresher'], true)) { + $trainingType = 'initial'; +} + +$topicStmt = $pdo->prepare("SELECT default_frequency_months, default_reminder_days FROM training_topics WHERE id = :id"); +$topicStmt->execute(['id' => $topicId]); +$topic = $topicStmt->fetch(PDO::FETCH_ASSOC); +if (!$topic) { + echo json_encode(['success' => false, 'message' => 'Corso non trovato.']); + exit; +} + +$freq = ($freqRaw === '' || $freqRaw === null) ? null : max(0, (int)$freqRaw); +$rem = ($remRaw === '' || $remRaw === null) ? null : max(0, (int)$remRaw); + +/* Effective frequency β†’ next_due_date (same for every employee: same date + same frequency) */ +$effFreq = $freq !== null ? $freq : ($topic['default_frequency_months'] !== null ? (int)$topic['default_frequency_months'] : null); +$nextDue = null; +if ($effFreq !== null && $effFreq > 0) { + $d = DateTime::createFromFormat('Y-m-d', $completedDate); + if ($d) { + $d->modify('+' . (int)$effFreq . ' months'); + $nextDue = $d->format('Y-m-d'); + } +} + +$deliveredBy = $deliveredBy !== '' ? $deliveredBy : null; +$description = $description !== '' ? $description : null; + +try { + $pdo->beginTransaction(); + + // Only insert for employees that actually exist + $checkEmp = $pdo->prepare("SELECT id FROM employees WHERE id = :id"); + + $ins = $pdo->prepare(" + INSERT INTO employee_trainings + (employee_id, training_topic_id, completed_date, + delivered_by, description, + training_type, update_frequency_months, reminder_days, next_due_date, + created_by, created_at, updated_at) + VALUES + (:eid, :tid, :completed_date, + :delivered_by, :description, + :training_type, :freq, :rem, :next_due, + :cb, NOW(), NOW()) + "); + $logStmt = $pdo->prepare(" + INSERT INTO employee_training_log + (employee_id, training_id, action, field, old_value, new_value, changed_by, changed_at) + VALUES + (:eid, :tid, 'created', NULL, NULL, NULL, :cb, NOW()) + "); + + $created = 0; + foreach ($employeeIds as $eid) { + $checkEmp->execute(['id' => $eid]); + if (!$checkEmp->fetchColumn()) { + continue; + } + $ins->execute([ + 'eid' => $eid, + 'tid' => $topicId, + 'completed_date' => $completedDate, + 'delivered_by' => $deliveredBy, + 'description' => $description, + 'training_type' => $trainingType, + 'freq' => $freq, + 'rem' => $rem, + 'next_due' => $nextDue, + 'cb' => $currentUserId, + ]); + $newId = (int)$pdo->lastInsertId(); + $logStmt->execute(['eid' => $eid, 'tid' => $newId, 'cb' => $currentUserId]); + $created++; + } + + $pdo->commit(); + echo json_encode([ + 'success' => true, + 'created' => $created, + 'message' => $created . ' formazion' . ($created === 1 ? 'e registrata' : 'i registrate') . '.', + ]); +} catch (Exception $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + echo json_encode(['success' => false, 'message' => $e->getMessage()]); +} diff --git a/public/userarea/include/navbar.php b/public/userarea/include/navbar.php index 0379ea7..e173b41 100644 --- a/public/userarea/include/navbar.php +++ b/public/userarea/include/navbar.php @@ -321,6 +321,11 @@ Storico Formazione +
  • + + Calendario Formazione + +
  • diff --git a/public/userarea/training_calendar.php b/public/userarea/training_calendar.php new file mode 100644 index 0000000..42cfb5e --- /dev/null +++ b/public/userarea/training_calendar.php @@ -0,0 +1,217 @@ +getConnection(); + +/* ========================================== + PERMISSIONS (mirror trainings.php) + ========================================== */ +$isHrManager = Auth::user()->hasRole('Admin') + || Auth::user()->hasRole('Superuser') + || Auth::user()->hasRole('employee-hr') + || Auth::user()->hasRole('manager'); + +if (!$isHrManager) { + header('Location: employee-profile.php'); + exit; +} + +/* Dropdown data */ +$employees = $pdo->query(" + SELECT id, first_name, last_name, employee_code + FROM employees + ORDER BY last_name, first_name +")->fetchAll(PDO::FETCH_ASSOC); +$topics = $pdo->query(" + SELECT id, name FROM training_topics WHERE is_active = 1 ORDER BY sort_order, name +")->fetchAll(PDO::FETCH_ASSOC); +$departments = $pdo->query(" + SELECT id, name FROM departments WHERE is_active = 1 ORDER BY sort_order, name +")->fetchAll(PDO::FETCH_ASSOC); +?> + + + + + + + + + + + + Calendario Formazione - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?> + + + + + +
    + + + +
    +
    +
    +
    +
    πŸ“… Calendario Formazione
    +
    + + πŸ“š Storico Formazione + + +
    +
    + +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    +
    +
    + + +
    +
    Scaduto
    +
    Da aggiornare
    +
    Conforme
    +
    + +
    +
    +
    +
    +
    + + +
    + + + + + + diff --git a/public/userarea/trainings.php b/public/userarea/trainings.php index 3a13667..435d537 100644 --- a/public/userarea/trainings.php +++ b/public/userarea/trainings.php @@ -151,12 +151,13 @@ if ($fType === '' || $fType === 'initial') { /* Dropdown data */ $employees = $pdo->query(" - SELECT id, first_name, last_name, employee_code + SELECT id, first_name, last_name, employee_code, department_id FROM employees ORDER BY last_name, first_name ")->fetchAll(PDO::FETCH_ASSOC); $topics = $pdo->query(" - SELECT id, name FROM training_topics WHERE is_active = 1 ORDER BY sort_order, name + 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); $departments = $pdo->query(" SELECT id, name, color FROM departments WHERE is_active = 1 ORDER BY sort_order, name @@ -180,6 +181,9 @@ function fmtDate(?string $d): string { + + +