Server : LiteSpeed
System : Linux terra.hostitbro.com 5.14.0-611.54.3.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 7 16:31:24 EDT 2026 x86_64
User : outerorb ( 1091)
PHP Version : 8.1.34
Disable Function : mail
Directory :  /home2/outerorb/emp.outerorbittech.in/admin/

📁 Create New:
⬆️ Upload File:
Current Dir [ Writable ] Root [ Writable ]


OR Upload from URL:
URL: Save as:

📄 File: attendance.php

Path: /home2/outerorb/emp.outerorbittech.in/admin/attendance.php

Size: 40.58 KB

Permissions: 0666

<?php
require __DIR__ . '/../includes/helpers.php';
require_admin();
ensure_attendance_tables();
ensure_employees_table();
ensure_holidays_table();

$roleLabel = 'Admin';
$pdo   = db();
$flash = flash();
$admin = current_admin();

// Get filters
$year  = isset($_GET['year'])  ? (int) $_GET['year']  : (int) date('Y');
$month = isset($_GET['month']) ? (int) $_GET['month'] : (int) date('m');
$employeeId = isset($_GET['employee_id']) ? (int) $_GET['employee_id'] : 0;
$departmentFilter = isset($_GET['department']) ? sanitize_text($_GET['department']) : '';
$searchQuery = isset($_GET['q']) ? sanitize_text($_GET['q']) : '';
$fromDate = isset($_GET['from_date']) ? sanitize_text($_GET['from_date']) : '';
$toDate = isset($_GET['to_date']) ? sanitize_text($_GET['to_date']) : '';

// validate date range input; if invalid or empty, fallback to current month options
$isFromValid = DateTime::createFromFormat('Y-m-d', $fromDate) !== false;
$isToValid   = DateTime::createFromFormat('Y-m-d', $toDate) !== false;

if ($isFromValid) { $fromDate = DateTime::createFromFormat('Y-m-d', $fromDate)->format('Y-m-d'); } else { $fromDate = ''; }
if ($isToValid)   { $toDate   = DateTime::createFromFormat('Y-m-d', $toDate)->format('Y-m-d'); }   else { $toDate = ''; }

if ($fromDate && !$toDate) { $toDate = $fromDate; }
if ($toDate && !$fromDate) { $fromDate = $toDate; }
if ($fromDate && $toDate && $fromDate > $toDate) { $tmp=$fromDate; $fromDate=$toDate; $toDate=$tmp; }

if ($month < 1)  { $month = 12; $year--; }
if ($month > 12) { $month =  1; $year++; }

// Validate month/year
if ($year < 2020 || $year > 2030) { $year = (int) date('Y'); }
if ($month < 1 || $month > 12) { $month = (int) date('m'); }

// If date range was not provided, default to selected month range.
if ($fromDate === '' || $toDate === '') {
    $fromDate = date('Y-m-01', strtotime("{$year}-{$month}-01"));
    $toDate = date('Y-m-t', strtotime("{$year}-{$month}-01"));
}

// Get employees for dropdown
$empWhere = ['e.deleted_at IS NULL', "e.employee_status = 'Active'"];
$empParams = [];
$allowedDepts = allowed_departments_for_admin();
if (!empty($allowedDepts)) {
    $phs = [];
    foreach ($allowedDepts as $i => $d) { $k = ':d'.$i; $phs[] = $k; $empParams[$k] = $d; }
    $empWhere[] = 'e.department IN (' . implode(',', $phs) . ')';
}
if ($departmentFilter !== '') {
    $empWhere[] = 'e.department = :dept';
    $empParams[':dept'] = $departmentFilter;
}
$empSql = 'SELECT e.id, e.first_name, e.last_name, e.department, e.card_number FROM employees e WHERE ' . implode(' AND ', $empWhere) . ' ORDER BY e.first_name, e.last_name';
$empStmt = $pdo->prepare($empSql);
$empStmt->execute($empParams);
$allEmployees = $empStmt->fetchAll();

// Get selected employee details
$selectedEmployee = null;
if ($employeeId > 0) {
    foreach ($allEmployees as $emp) {
        if ($emp['id'] == $employeeId) {
            $selectedEmployee = $emp;
            break;
        }
    }
}

// Build attendance query by date range
$where = ['al.attend_date BETWEEN :from_date AND :to_date'];
$params = [':from_date' => $fromDate, ':to_date' => $toDate];

if ($employeeId > 0) {
    $where[] = 'al.employee_id = :emp_id';
    $params[':emp_id'] = $employeeId;
} elseif ($departmentFilter !== '') {
    $where[] = 'e.department = :dept';
    $params[':dept'] = $departmentFilter;
} elseif (!empty($allowedDepts)) {
    $phs = [];
    foreach ($allowedDepts as $i => $d) { $k = ':d'.$i; $phs[] = $k; $params[$k] = $d; }
    $where[] = 'e.department IN (' . implode(',', $phs) . ')';
}

if ($searchQuery !== '') {
    $where[] = '(CONCAT(e.first_name, " ", e.last_name) LIKE :q_name OR e.card_number LIKE :q_card OR CAST(e.id AS CHAR) LIKE :q_id)';
    $params[':q_name'] = "%{$searchQuery}%";
    $params[':q_card'] = "%{$searchQuery}%";
    $params[':q_id'] = "%{$searchQuery}%";
}

$sql = 'SELECT al.*, e.first_name, e.last_name, e.card_number, e.department
        FROM attendance_logs al
        JOIN employees e ON e.id = al.employee_id
        WHERE ' . implode(' AND ', $where) . '
        ORDER BY e.first_name, e.last_name, al.attend_date, al.punch_in';

$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$attendanceRecords = $stmt->fetchAll();

// Group by employee for display
$groupedRecords = [];
foreach ($attendanceRecords as $record) {
    $empId = $record['employee_id'];
    if (!isset($groupedRecords[$empId])) {
        $groupedRecords[$empId] = [
            'employee' => [
                'id' => $record['employee_id'],
                'name' => trim($record['first_name'] . ' ' . $record['last_name']),
                'card_number' => $record['card_number'],
                'department' => $record['department']
            ],
            'records' => []
        ];
    }
    $groupedRecords[$empId]['records'][] = $record;
}

// Handle POST actions (import, save)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!verify_csrf($_POST['csrf_token'] ?? '')) {
        redirect_with_message('attendance.php', 'Session expired.', 'error');
    }

    $action = $_POST['action'] ?? '';

    // Save single attendance record
    if ($action === 'save') {
        $empId  = (int) ($_POST['employee_id'] ?? 0);
        $date   = sanitize_text($_POST['attend_date'] ?? '');
        $status = in_array($_POST['status'] ?? '', ['present','absent','half_day','wfh','holiday','on_leave'])
                    ? $_POST['status'] : 'present';
        $punchIn  = sanitize_text($_POST['punch_in']  ?? '') ?: null;
        $punchOut = sanitize_text($_POST['punch_out'] ?? '') ?: null;
        $notes    = sanitize_text($_POST['notes'] ?? '') ?: null;

        if ($empId && $date) {
            $shift  = get_shift_for_department(null);
            $isLate = 0;
            if ($punchIn && $status === 'present') {
                $graceSec = ($shift['grace_minutes'] ?? 15) * 60;
                $shiftStart = strtotime(date('Y-m-d') . ' ' . $shift['start_time']);
                $punchTs    = strtotime(date('Y-m-d') . ' ' . $punchIn);
                $isLate = ($punchTs > $shiftStart + $graceSec) ? 1 : 0;
            }

            $pdo->prepare(
                'INSERT INTO attendance_logs (employee_id, attend_date, status, punch_in, punch_out, is_late, notes, marked_by)
                 VALUES (?,?,?,?,?,?,?,?)
                 ON DUPLICATE KEY UPDATE status=VALUES(status), punch_in=VALUES(punch_in),
                     punch_out=VALUES(punch_out), is_late=VALUES(is_late), notes=VALUES(notes),
                     marked_by=VALUES(marked_by), updated_at=NOW()'
            )->execute([$empId, $date, $status, $punchIn, $punchOut, $isLate, $notes, $admin['id'] ?? null]);
        }
        redirect_with_message('attendance.php?' . http_build_query($_GET), 'Attendance saved.');
    }

    // Bulk import from CSV/XLS/XLSX
    if ($action === 'import') {
        $res = handle_upload('attendance_file', ['text/csv','application/vnd.ms-excel','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'], 10 * 1024 * 1024);
        if (!empty($res['error'])) {
            redirect_with_message('attendance.php', 'Import failed: ' . $res['error'], 'error');
        }
        $path = $res['path'];
        $imported = 0; $skipped = 0; $unmatched = 0;

        $origName = $_FILES['attendance_file']['name'] ?? '';
        $ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));

        // XLS/XLSX processing
        if (in_array($ext, ['xls','xlsx'], true)) {
            $vendor = __DIR__ . '/../vendor/autoload.php';
            if (!file_exists($vendor)) {
                redirect_with_message('attendance.php', 'Please install PHPSpreadsheet: run `composer require phpoffice/phpspreadsheet` in the project root.', 'error');
            }
            require $vendor;
            try {
                $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($path);
                $sheet = $spreadsheet->getActiveSheet();
                $rows = $sheet->toArray(null, true, true, true);

                if (empty($rows) || count($rows) < 1) {
                    redirect_with_message('attendance.php', 'Import failed: spreadsheet appears empty.', 'error');
                }

                // Find header row
                $rowsList = array_values($rows);
                $header = null; $headerRowIndex = null;
                $scanLimit = min(120, count($rowsList));
                $normalize = function($cell){
                    $k = strtolower(trim(preg_replace('/[^a-z0-9_ ]+/', '', (string)$cell)));
                    return str_replace(' ', '_', $k);
                };

                for ($i = 0; $i < $scanLimit; $i++) {
                    $r = array_values($rowsList[$i]);
                    $norms = array_map($normalize, $r);
                    if (in_array('card_no', $norms, true) || in_array('card', $norms, true) || in_array('cardnumber', $norms, true)) {
                        $header = $r; $headerRowIndex = $i; break;
                    }
                    if ((in_array('s_no', $norms, true) || in_array('sno', $norms, true)) &&
                        (in_array('emp_code', $norms, true) || in_array('emp_name', $norms, true) || in_array('empname', $norms, true))) {
                        $header = $r; $headerRowIndex = $i; break;
                    }
                }

                if ($header === null) {
                    for ($i = 0; $i < $scanLimit; $i++) {
                        $r = array_values($rowsList[$i]);
                        foreach ($r as $cell) {
                            if ($cell === null) continue;
                            if (mb_stripos((string)$cell, 'card') !== false || mb_stripos((string)$cell, 'card no') !== false || mb_stripos((string)$cell, 'badge') !== false) {
                                $header = $r; $headerRowIndex = $i; break 2;
                            }
                        }
                    }
                }
                if ($header === null) { $header = array_values($rowsList[0]); $headerRowIndex = 0; }

                $map = []; $rawHeader = [];
                foreach ($header as $i => $h) { $map[$normalize($h)] = $i; $rawHeader[$i] = (string)$h; }

                $cardIdx = null; $inIdx = null; $outIdx = null; $dateIdx = null;
                foreach ($rawHeader as $i => $h) {
                    if ($cardIdx === null && mb_stripos($h, 'card') !== false) $cardIdx = $i;
                    if ($inIdx === null && (mb_stripos($h, 'in') !== false || mb_stripos($h, 'in time') !== false)) $inIdx = $i;
                    if ($outIdx === null && (mb_stripos($h, 'out') !== false || mb_stripos($h, 'out time') !== false)) $outIdx = $i;
                    if ($dateIdx === null && mb_stripos($h, 'date') !== false && mb_stripos($h, 'time') === false) $dateIdx = $i;
                }

                $cardCandidates = ['card_number','card_no','card','cardnumber','cardno','badge_number','badge_no','badge','employee_card','emp_card','card_id','uid','emp_code'];
                if ($cardIdx === null) { foreach ($cardCandidates as $c) { if (isset($map[$c])) { $cardIdx = $map[$c]; break; } } }
                $inCandidates = ['punch_in','time_in','in_time','in','check_in','clock_in','punch','time'];
                if ($inIdx === null) { foreach ($inCandidates as $c) { if (isset($map[$c])) { $inIdx = $map[$c]; break; } } }
                $outCandidates = ['punch_out','time_out','out_time','out','check_out','clock_out','punch'];
                if ($outIdx === null) { foreach ($outCandidates as $c) { if (isset($map[$c])) { $outIdx = $map[$c]; break; } } }
                if ($dateIdx === null) { foreach ($map as $k => $i) { if (strpos($k, 'date') !== false && strpos($k,'time') === false) { $dateIdx = $i; break; } } }

                if ($cardIdx === null) {
                    $found = implode(', ', array_keys($map));
                    redirect_with_message('attendance.php', 'Import failed: could not find a Card Number column. Found headers: ' . $found, 'error');
                }

                $started = false; $rowIndex = 0;
                foreach ($rows as $r) {
                    if ($rowIndex++ <= $headerRowIndex) { continue; }
                    $vals = array_values($r);
                    $cardRaw = $vals[$cardIdx] ?? '';
                    $card = trim((string)$cardRaw);
                    if ($card === '' && is_numeric($cardRaw)) { $card = (string)$cardRaw; }
                    if (strpos($card, '.') !== false) { $card = preg_replace('/\.0+$/', '', $card); }
                    $card = preg_replace('/[^0-9A-Za-z]/', '', $card);
                    if ($card === '') { $skipped++; continue; }

                    $rawIn = trim($vals[$inIdx] ?? '');
                    $rawOut = trim($vals[$outIdx] ?? '');
                    $rawDate = trim($vals[$dateIdx] ?? '');

                    $attDate = null; $punchIn = null; $punchOut = null;
                    if ($rawIn !== '') { $ts = strtotime($rawIn); if ($ts !== false) { $attDate = date('Y-m-d', $ts); $punchIn = date('H:i:s', $ts); } }
                    if ($rawOut !== '') { $ts = strtotime($rawOut); if ($ts !== false) { $attDate = $attDate ?: date('Y-m-d', $ts); $punchOut = date('H:i:s', $ts); } }
                    if ($attDate === null && $rawDate !== '') { $dts = strtotime($rawDate); if ($dts !== false) $attDate = date('Y-m-d', $dts); }
                    if ($attDate === null) { $skipped++; continue; }

                    $stmt = $pdo->prepare('SELECT id, department FROM employees WHERE card_number = ? LIMIT 1');
                    $stmt->execute([$card]);
                    $emp = $stmt->fetch();
                    if (!$emp && ctype_digit($card)) {
                        $stmt = $pdo->prepare('SELECT id, department FROM employees WHERE id = ? LIMIT 1');
                        $stmt->execute([(int)$card]);
                        $emp = $stmt->fetch();
                    }
                    if (!$emp) { $unmatched++; continue; }

                    $shift = get_shift_for_department($emp['department'] ?? null);
                    $isLate = 0;
                    if ($punchIn && isset($shift['start_time'])) {
                        $graceSec = ($shift['grace_minutes'] ?? 15) * 60;
                        $shiftStart = strtotime($attDate . ' ' . $shift['start_time']);
                        $pTs = strtotime($attDate . ' ' . $punchIn);
                        if ($pTs !== false && $pTs > $shiftStart + $graceSec) $isLate = 1;
                    }

                    $pdo->prepare(
                        'INSERT INTO attendance_logs (employee_id, attend_date, status, punch_in, punch_out, is_late, marked_by)
                         VALUES (?,?,?,?,?,?,?)
                         ON DUPLICATE KEY UPDATE status=VALUES(status), punch_in=COALESCE(VALUES(punch_in), punch_in),
                             punch_out=COALESCE(VALUES(punch_out), punch_out), is_late=VALUES(is_late), marked_by=VALUES(marked_by), updated_at=NOW()'
                    )->execute([$emp['id'], $attDate, 'present', $punchIn, $punchOut, $isLate, $admin['id'] ?? null]);
                    $imported++;
                }

                $msg = "Imported: $imported; Skipped: $skipped; Unmatched cards: $unmatched";
                redirect_with_message('attendance.php?' . http_build_query($_GET), $msg);
            } catch (Throwable $e) {
                redirect_with_message('attendance.php', 'Import failed: ' . $e->getMessage(), 'error');
            }
        }

        // CSV processing
        if (($fh = @fopen($path, 'r')) !== false) {
            $headers = fgetcsv($fh);
            if ($headers === false) { fclose($fh); redirect_with_message('attendance.php', 'Import failed: empty file or not a CSV.', 'error'); }

            $rowsPreview = [$headers];
            $previewLimit = 80;
            for ($i = 1; $i < $previewLimit; $i++) {
                $peek = fgetcsv($fh);
                if ($peek === false) break;
                $rowsPreview[] = $peek;
            }

            $header = null; $headerIndex = 0;
            $cardCandidates = ['card_number','card_no','card','cardnumber','cardno','badge_number','badge_no','badge','employee_card','emp_card','card_id','uid'];
            foreach ($rowsPreview as $ri => $rrow) {
                $norms = array_map($normalize ?? function($v) {
                    $k = strtolower(trim(preg_replace('/[^a-z0-9_ ]+/', '', (string)$v)));
                    return str_replace(' ', '_', $k);
                }, $rrow);
                if (in_array('card_no', $norms, true) || in_array('card', $norms, true) || in_array('cardnumber', $norms, true)) { $header = $rrow; $headerIndex = $ri; break; }
                if ((in_array('s_no', $norms, true) || in_array('sno', $norms, true)) && (in_array('emp_code', $norms, true) || in_array('emp_name', $norms, true) || in_array('empname', $norms, true))) { $header = $rrow; $headerIndex = $ri; break; }
                foreach ($cardCandidates as $c) { if (in_array($c, $norms, true)) { $header = $rrow; $headerIndex = $ri; break 2; } }
            }
            if ($header === null) { $header = $rowsPreview[0]; $headerIndex = 0; }

            $map = [];
            foreach ($header as $i => $h) { $map[$normalize($h)] = $i; }

            $cardIdx = null;
            foreach ($cardCandidates as $c) { if (isset($map[$c])) { $cardIdx = $map[$c]; break; } }
            if ($cardIdx === null) {
                foreach ($map as $k => $i) { if (strpos($k, 'card') !== false || strpos($k,'badge') !== false || (strpos($k,'id')!==false && strpos($k,'date')===false)) { $cardIdx = $i; break; } }
            }

            $inCandidates = ['punch_in','time_in','in_time','in','check_in','clock_in','punch','time'];
            $inIdx = null; foreach ($inCandidates as $c) { if (isset($map[$c])) { $inIdx = $map[$c]; break; } }
            $outCandidates = ['punch_out','time_out','out_time','out','check_out','clock_out','punch'];
            $outIdx = null; foreach ($outCandidates as $c) { if (isset($map[$c])) { $outIdx = $map[$c]; break; } }
            $dateIdx = null; foreach ($map as $k => $i) { if (strpos($k, 'date') !== false && strpos($k,'time') === false) { $dateIdx = $i; break; } }

            if ($cardIdx === null) { fclose($fh); $found = implode(', ', array_keys($map)); redirect_with_message('attendance.php', 'Import failed: could not find a Card Number column. Found headers: ' . $found, 'error'); }

            rewind($fh);
            for ($s = 0; $s <= $headerIndex; $s++) { if (fgetcsv($fh) === false) break; }

            while (($row = fgetcsv($fh)) !== false) {
                $cardRaw = $row[$cardIdx] ?? '';
                $card = trim((string)$cardRaw);
                if ($card === '' && is_numeric($cardRaw)) { $card = (string)$cardRaw; }
                if (strpos($card, '.') !== false) { $card = preg_replace('/\.0+$/', '', $card); }
                $card = preg_replace('/[^0-9A-Za-z]/', '', $card);
                if ($card === '') { $skipped++; continue; }

                $rawIn  = trim($row[$inIdx]  ?? '');
                $rawOut = trim($row[$outIdx] ?? '');
                $rawDate = trim($row[$dateIdx] ?? '');

                $attDate = null; $punchIn = null; $punchOut = null;
                if ($rawIn !== '') { $ts = strtotime($rawIn); if ($ts !== false) { $attDate = date('Y-m-d', $ts); $punchIn = date('H:i:s', $ts); } }
                if ($rawOut !== '') { $ts = strtotime($rawOut); if ($ts !== false) { $attDate = $attDate ?: date('Y-m-d', $ts); $punchOut = date('H:i:s', $ts); } }
                if ($attDate === null && $rawDate !== '') { $dts = strtotime($rawDate); if ($dts !== false) $attDate = date('Y-m-d', $dts); }
                if ($attDate === null) { $skipped++; continue; }

                $stmt = $pdo->prepare('SELECT id, department FROM employees WHERE card_number = ? LIMIT 1');
                $stmt->execute([$card]);
                $emp = $stmt->fetch();
                if (!$emp && ctype_digit($card)) {
                    $stmt = $pdo->prepare('SELECT id, department FROM employees WHERE id = ? LIMIT 1');
                    $stmt->execute([(int)$card]);
                    $emp = $stmt->fetch();
                }
                if (!$emp) { $unmatched++; continue; }

                $shift = get_shift_for_department($emp['department'] ?? null);
                $isLate = 0;
                if ($punchIn && isset($shift['start_time'])) {
                    $graceSec = ($shift['grace_minutes'] ?? 15) * 60;
                    $shiftStart = strtotime($attDate . ' ' . $shift['start_time']);
                    $pTs = strtotime($attDate . ' ' . $punchIn);
                    if ($pTs !== false && $pTs > $shiftStart + $graceSec) $isLate = 1;
                }

                $pdo->prepare(
                    'INSERT INTO attendance_logs (employee_id, attend_date, status, punch_in, punch_out, is_late, marked_by)
                     VALUES (?,?,?,?,?,?,?)
                     ON DUPLICATE KEY UPDATE status=VALUES(status), punch_in=COALESCE(VALUES(punch_in), punch_in),
                         punch_out=COALESCE(VALUES(punch_out), punch_out), is_late=VALUES(is_late), marked_by=VALUES(marked_by), updated_at=NOW()'
                )->execute([$emp['id'], $attDate, 'present', $punchIn, $punchOut, $isLate, $admin['id'] ?? null]);
                $imported++;
            }
            fclose($fh);

            $msg = "Imported: $imported; Skipped: $skipped; Unmatched cards: $unmatched";
            redirect_with_message('attendance.php?' . http_build_query($_GET), $msg);
        }
        redirect_with_message('attendance.php', 'Import failed: could not read uploaded file.', 'error');
    }
}

// Get departments for filter
$departments = list_departments();

// Helper function
function e(?string $v): string { return $v === null ? '' : htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); }
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Attendance Management - <?php echo e($roleLabel); ?></title>
    <link rel="stylesheet" href="../assets/css/style.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <style>
        /* Override container centering for left alignment */
        .container {
            align-items: flex-start !important;
            max-width: 1300px !important;
            width: 95vw !important;
        }

        /* Make filters and import side by side */
        .filters-and-import {
            display: flex;
            gap: 20px;
            align-items: flex-start;
            margin-bottom: 20px;
            width: 100%;
            max-width: 100%;
            box-sizing: border-box;
        }

        .filters-section {
            flex: 1.1;
            min-width: 250px;
            max-width: 700px;
            background: rgba(255, 255, 255, 0.05);
            border-radius: 8px;
            padding: 18px;
            border: 1px solid rgba(255, 255, 255, 0.1);
            box-sizing: border-box;
        }

        .import-section {
            flex: 0.9;
            min-width: 220px;
            max-width: 440px;
            background: rgba(255, 255, 255, 0.05);
            border-radius: 8px;
            padding: 20px;
            border: 1px solid rgba(255, 255, 255, 0.1);
            box-sizing: border-box;
        }
        .filters-section {
            background: rgba(255, 255, 255, 0.05);
            border-radius: 8px;
            padding: 20px;
            margin-bottom: 20px;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }
        .filters-row {
            display: flex;
            gap: 15px;
            align-items: center;
            flex-wrap: wrap;
        }
        .filter-group {
            display: flex;
            flex-direction: column;
            min-width: 150px;
        }
        .filter-group label {
            font-size: 14px;
            font-weight: 500;
            margin-bottom: 5px;
            color: #bfdbfe;
        }
        .filter-group select,
        .filter-group input {
            padding: 8px 12px;
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 6px;
            background: rgba(255, 255, 255, 0.05);
            color: #ffffff;
            font-size: 14px;
        }
        .filter-group select:focus,
        .filter-group input:focus {
            outline: none;
            border-color: #3b82f6;
            box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
        }
        .btn-filter {
            padding: 8px 16px;
            background: #3b82f6;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.2s;
        }
        .btn-filter:hover {
            background: #2563eb;
        }
        .attendance-table-container {
            background: rgba(255, 255, 255, 0.05);
            backdrop-filter: blur(10px);
            border-radius: 12px;
            border: 1px solid rgba(255, 255, 255, 0.1);
            overflow: hidden;
            margin-top: 20px;
        }

        .attendance-main-table {
            width: 100%;
            border-collapse: collapse;
        }

        .attendance-main-table thead {
            background: rgba(255, 255, 255, 0.08);
        }

        .attendance-main-table th {
            padding: 1rem;
            text-align: left;
            color: #cbd5e1;
            font-size: 0.875rem;
            font-weight: 600;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }

        .attendance-main-table td {
            padding: 1rem;
            border-bottom: 1px solid rgba(255, 255, 255, 0.05);
            color: #e2e8f0;
        }

        .attendance-main-table tbody tr:hover {
            background: rgba(255, 255, 255, 0.03);
        }

        .employee-info {
            display: flex;
            flex-direction: column;
        }

        .employee-name {
            font-weight: 600;
            color: #e5e7eb;
        }

        .employee-card {
            color: #bfdbfe;
            font-size: 0.875rem;
        }

        .status-badge {
            display: inline-block;
            padding: 0.25rem 0.75rem;
            border-radius: 6px;
            font-size: 0.75rem;
            font-weight: 600;
        }

        .status-present {
            background: rgba(16, 185, 129, 0.2);
            color: #10b981;
        }

        .status-absent {
            background: rgba(239, 68, 68, 0.2);
            color: #ef4444;
        }

        .status-half-day {
            background: rgba(245, 158, 11, 0.2);
            color: #f59e0b;
        }

        .status-work-from-home {
            background: rgba(37, 99, 235, 0.2);
            color: #2563eb;
        }

        .status-holiday {
            background: rgba(139, 92, 246, 0.2);
            color: #8b5cf6;
        }

        .status-on-leave {
            background: rgba(156, 163, 175, 0.2);
            color: #9ca3af;
        }

        .time-display {
            font-family: 'Courier New', monospace;
            font-weight: 500;
            color: #93c5fd;
        }

        .no-time {
            color: #64748b;
        }

        .status-late {
            color: #ef4444;
            font-weight: 600;
        }
        .import-section {
            background: rgba(255, 255, 255, 0.05);
            border-radius: 8px;
            padding: 20px;
            margin-bottom: 20px;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }
        .import-form {
            display: flex;
            gap: 15px;
            align-items: center;
            flex-wrap: wrap;
        }
        .import-form input[type="file"] {
            flex: 1;
            min-width: 200px;
        }
        .btn-import {
            padding: 8px 16px;
            background: #10b981;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.2s;
        }
        .btn-import:hover {
            background: #059669;
        }
        .month-nav {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 20px;
            margin-bottom: 20px;
        }
        .month-nav a {
            color: #3b82f6;
            text-decoration: none;
            padding: 8px 16px;
            border-radius: 6px;
            transition: background-color 0.2s;
        }
        .month-nav a:hover {
            background: rgba(59, 130, 246, 0.1);
        }
        .current-month {
            font-size: 18px;
            font-weight: 600;
            color: #e5e7eb;
        }
        @media (max-width: 768px) {
            .container {
                width: 100% !important;
            }
            .filters-and-import {
                flex-direction: column;
            }
            .filters-row {
                flex-direction: column;
                align-items: stretch;
            }
            .filter-group {
                min-width: auto;
            }
            .import-form {
                flex-direction: column;
                align-items: stretch;
            }
            .attendance-main-table {
                font-size: 0.875rem;
            }
            .attendance-main-table th,
            .attendance-main-table td {
                padding: 0.75rem;
            }
            .employee-info {
                flex-direction: row;
                justify-content: space-between;
                align-items: center;
            }
            .employee-card {
                font-size: 0.75rem;
            }
        }
    </style>
</head>
<body class="dashboard-layout">
    <?php include __DIR__ . '/_sidebar.php'; ?>

    <div class="main-content">
        <div class="container">
            <h1><i class="fas fa-calendar-check"></i> Attendance Management</h1>

            <?php if ($flash): ?>
                <div class="alert alert-<?php echo $flash['type']; ?>">
                    <?php echo e($flash['message']); ?>
                </div>
            <?php endif; ?>

            <!-- Month Navigation -->
            <div class="month-nav">
                <?php
                $prevMonth = $month - 1; $prevYear = $year;
                if ($prevMonth < 1) { $prevMonth = 12; $prevYear--; }
                $nextMonth = $month + 1; $nextYear = $year;
                if ($nextMonth > 12) { $nextMonth = 1; $nextYear++; }

                $queryParams = $_GET;
                unset($queryParams['from_date'], $queryParams['to_date']);
                ?>
                <a href="?<?php echo http_build_query(array_merge($queryParams, ['year' => $prevYear, 'month' => $prevMonth])); ?>">
                    <i class="fas fa-chevron-left"></i> Previous
                </a>
                <div class="current-month">
                    <?php echo date('F Y', mktime(0, 0, 0, $month, 1, $year)); ?>
                </div>
                <a href="?<?php echo http_build_query(array_merge($queryParams, ['year' => $nextYear, 'month' => $nextMonth])); ?>">
                    Next <i class="fas fa-chevron-right"></i>
                </a>
            </div>

            <!-- Filters and Import Section -->
            <div class="filters-and-import">
            <div class="filters-section">
                <form method="GET" class="filters-row">
                    <input type="hidden" name="year" value="<?php echo $year; ?>">
                    <input type="hidden" name="month" value="<?php echo $month; ?>">

                    <div class="filter-group">
                        <label for="employee_id">Employee:</label>
                        <select name="employee_id" id="employee_id">
                            <option value="">All Employees</option>
                            <?php foreach ($allEmployees as $emp): ?>
                                <option value="<?php echo $emp['id']; ?>" <?php echo $employeeId == $emp['id'] ? 'selected' : ''; ?>>
                                    <?php echo e(trim($emp['first_name'] . ' ' . $emp['last_name'])); ?>
                                    <?php if ($emp['card_number']): ?> (<?php echo e($emp['card_number']); ?>)<?php endif; ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="filter-group">
                        <label for="department">Department:</label>
                        <select name="department" id="department">
                            <option value="">All Departments</option>
                            <?php foreach ($departments as $dept): ?>
                                <option value="<?php echo e($dept); ?>" <?php echo $departmentFilter === $dept ? 'selected' : ''; ?>>
                                    <?php echo e($dept); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </div>

                    <div class="filter-group">
                        <label for="from_date">From Date:</label>
                        <input type="date" name="from_date" id="from_date" value="<?php echo e($fromDate); ?>">
                    </div>
                    <div class="filter-group">
                        <label for="to_date">To Date:</label>
                        <input type="date" name="to_date" id="to_date" value="<?php echo e($toDate); ?>">
                    </div>

                    <div class="filter-group">
                        <label for="q">Search:</label>
                        <input type="text" name="q" id="q" value="<?php echo e($searchQuery); ?>" placeholder="Name, ID, or Card #">
                    </div>

                    <div class="filter-group">
                        <label>&nbsp;</label>
                        <button type="submit" class="btn-filter">
                            <i class="fas fa-search"></i> Filter
                        </button>
                    </div>
                </form>
            </div>

            <!-- Import Section -->
            <div class="import-section">
                <h3><i class="fas fa-upload"></i> Import Attendance Data</h3>
                <form method="POST" enctype="multipart/form-data" class="import-form">
                    <input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
                    <input type="hidden" name="action" value="import">
                    <input type="file" name="attendance_file" accept=".csv,.xls,.xlsx" required>
                    <button type="submit" class="btn-import">
                        <i class="fas fa-upload"></i> Import
                    </button>
                </form>
                <p style="margin-top: 10px; font-size: 14px; color: #bfdbfe;">
                    Upload CSV, XLS, or XLSX file with columns: Card Number, Punch In, Punch Out, Date
                </p>
            </div>
            </div>

            <!-- Attendance Records -->
            <?php if (empty($attendanceRecords)): ?>
                <div class="no-records">
                    <i class="fas fa-calendar-times" style="font-size: 48px; margin-bottom: 20px; opacity: 0.5;"></i>
                    <p>No attendance records found for the selected filters.</p>
                </div>
            <?php else: ?>
                <div class="attendance-table-container">
                    <table class="attendance-main-table">
                        <thead>
                            <tr>
                                <th>Employee</th>
                                <th>Department</th>
                                <th>Date</th>
                                <th>Status</th>
                                <th>Punch In</th>
                                <th>Punch Out</th>
                                <th>Late</th>
                                <th>Notes</th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ($attendanceRecords as $record): ?>
                                <tr>
                                    <td>
                                        <div class="employee-info">
                                            <div class="employee-name"><?php echo e($record['first_name'] . ' ' . $record['last_name']); ?></div>
                                            <div class="employee-card"><?php if ($record['card_number']): ?>Card: <?php echo e($record['card_number']); ?><?php endif; ?></div>
                                        </div>
                                    </td>
                                    <td><?php echo e($record['department']); ?></td>
                                    <td><?php echo date('d M Y', strtotime($record['attend_date'])); ?></td>
                                    <td>
                                        <span class="status-badge status-<?php echo str_replace('_', '-', $record['status']); ?>">
                                            <?php
                                            $statusLabels = [
                                                'present' => 'Present',
                                                'absent' => 'Absent',
                                                'half_day' => 'Half Day',
                                                'wfh' => 'Work from Home',
                                                'holiday' => 'Holiday',
                                                'on_leave' => 'On Leave'
                                            ];
                                            echo $statusLabels[$record['status']] ?? ucfirst(str_replace('_', ' ', $record['status']));
                                            ?>
                                        </span>
                                    </td>
                                    <td>
                                        <?php if ($record['punch_in']): ?>
                                            <span class="time-display"><?php echo date('H:i', strtotime($record['punch_in'])); ?></span>
                                        <?php else: ?>
                                            <span class="no-time">—</span>
                                        <?php endif; ?>
                                    </td>
                                    <td>
                                        <?php if ($record['punch_out']): ?>
                                            <span class="time-display"><?php echo date('H:i', strtotime($record['punch_out'])); ?></span>
                                        <?php else: ?>
                                            <span class="no-time">—</span>
                                        <?php endif; ?>
                                    </td>
                                    <td>
                                        <?php if ($record['is_late']): ?>
                                            <span class="status-late">Yes</span>
                                        <?php else: ?>
                                            <span class="no-time">No</span>
                                        <?php endif; ?>
                                    </td>
                                    <td><?php echo e($record['notes'] ?? ''); ?></td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                </div>
            <?php endif; ?>
        </div>
    </div>

    <script>
        // Auto-submit form when filters change
        document.getElementById('employee_id').addEventListener('change', function() {
            this.form.submit();
        });
        document.getElementById('department').addEventListener('change', function() {
            this.form.submit();
        });

        // Search on enter key
        document.getElementById('q').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                this.form.submit();
            }
        });
    </script>
</body>
</html>



← Back to Directory Edit File 🔒 Chmod

WP File Manager