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([]); }