« Back to History
attendance_entryDateWise.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* ============================================================================= File: /erp/attendance_entryDateWise.php Purpose: Spreadsheet Matrix Attendance Entry — Complete Auto Fill with Print Totals ========================================================================== */ header('X-Frame-Options: SAMEORIGIN'); error_reporting(E_ALL); ini_set('display_errors', '1'); $require_activity_helper = __DIR__ . '/helpers/activity_helper.php'; if (file_exists($require_activity_helper)) require_once $require_activity_helper; require __DIR__ . '/modules/auth/page_acl.php'; $ctx = page_require_access('manual_attendance'); $USER = $ctx['user']; $COMPANY_ID = (int)$ctx['company_id']; $USER_ID = (int)$USER['id']; $pdo = isset($ctx['pdo']) && $ctx['pdo'] instanceof PDO ? $ctx['pdo'] : null; if (!$pdo) { require __DIR__ . '/core/db.php'; } /* ===== Helpers Utility Tools ===== */ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function qv($k,$d=null){ return isset($_REQUEST[$k])? (is_array($_REQUEST[$k])?$_REQUEST[$k]:trim((string)$_REQUEST[$k])) : $d; } function pv($k,$d=null){ return isset($_POST[$k])? (is_array($_POST[$k])?$_POST[$k]:trim((string)$_POST[$k])) : $d; } function jexit($arr){ header('Content-Type: application/json; charset=utf-8'); echo json_encode($arr); exit; } /* ============================================================================= 2. INTERACTIVE AJAX PROCESSING GATEWAY ========================================================================== */ if (qv('ajax') === '1') { $act = qv('act',''); try { if ($act === 'departments') { $stm = $pdo->prepare(" SELECT department_name FROM company_employee_master WHERE company_id = ? AND is_active = 1 AND department_name <> '' GROUP BY department_name ORDER BY department_name ASC "); $stm->execute([$COMPANY_ID]); $items = $stm->fetchAll(PDO::FETCH_ASSOC); jexit(['ok' => true, 'items' => $items]); } if ($act === 'get_matrix_grid') { $month = qv('month'); $period = qv('period'); $dept_name = trim(qv('dept_name')); if (empty($dept_name)) throw new Exception("Operational Department text argument missing."); $rule_stmt = $pdo->prepare("SELECT halfday_calculation_type FROM attendance_rules_master WHERE company_id = ? AND department_name = ? LIMIT 1"); $rule_stmt->execute([$COMPANY_ID, $dept_name]); $calc_rule = $rule_stmt->fetchColumn() ?: 'half_day_salary'; [$start_day, $end_day] = array_map('intval', explode('-', $period)); [$year, $month_num] = array_map('intval', explode('-', $month)); $days_in_month = cal_days_in_month(CAL_GREGORIAN, $month_num, $year); if ($end_day > $days_in_month) $end_day = $days_in_month; $dates_array = []; for ($d = $start_day; $d <= $end_day; $d++) { $dates_array[] = sprintf('%02d-%02d-%04d', $d, $month_num, $year); } $emp_stmt = $pdo->prepare(" SELECT id, name, employee_code FROM company_employee_master WHERE company_id = ? AND department_name = ? AND is_active = 1 ORDER BY name ASC "); $emp_stmt->execute([$COMPANY_ID, $dept_name]); $employees = $emp_stmt->fetchAll(PDO::FETCH_ASSOC); $start_iso = sprintf('%04d-%02d-%02d', $year, $month_num, $start_day); $end_iso = sprintf('%04d-%02d-%02d', $year, $month_num, $end_day); $attendance_matrix = []; if (!empty($employees)) { $att_stmt = $pdo->prepare("SELECT employee_id, attendance_date, status FROM employee_attendance_daily WHERE company_id = ? AND attendance_date BETWEEN ? AND ?"); $att_stmt->execute([$COMPANY_ID, $start_iso, $end_iso]); $raw_att = $att_stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($raw_att as $row) { $date_formatted = date('d-m-Y', strtotime($row['attendance_date'])); $attendance_matrix[$row['employee_id']][$date_formatted] = $row['status']; } } jexit([ 'ok' => true, 'dates' => $dates_array, 'employees' => $employees, 'matrix' => $attendance_matrix, 'calc_rule' => $calc_rule ]); } if ($act === 'save_batch_matrix') { $matrix_json = isset($_POST['matrix_json']) ? trim($_POST['matrix_json']) : ''; $dept_name = trim(pv('dept_name')); $month = pv('month'); $period = pv('period'); if(empty($dept_name) || empty($month) || empty($period)) { throw new Exception("Missing required core grid parameters."); } if($matrix_json === '') { throw new Exception("Payload data string missing."); } $matrix_payload = json_decode($matrix_json, true); if (!is_array($matrix_payload)) { throw new Exception("Matrix data parsing failure."); } [$start_day, $end_day] = array_map('intval', explode('-', $period)); [$year, $month_num] = array_map('intval', explode('-', $month)); $start_iso = sprintf('%04d-%02d-%02d', $year, $month_num, $start_day); $end_iso = sprintf('%04d-%02d-%02d', $year, $month_num, $end_day); $pdo->beginTransaction(); $emp_list_stmt = $pdo->prepare("SELECT id FROM company_employee_master WHERE company_id = ? AND department_name = ? AND is_active = 1"); $emp_list_stmt->execute([$COMPANY_ID, $dept_name]); $scoped_db_emp_ids = $emp_list_stmt->fetchAll(PDO::FETCH_COLUMN); if (empty($scoped_db_emp_ids)) { $scoped_db_emp_ids = [0]; } $in_clause_placeholders = implode(',', array_fill(0, count($scoped_db_emp_ids), '?')); $del_query = " DELETE FROM employee_attendance_daily WHERE company_id = ? AND attendance_date BETWEEN ? AND ? AND employee_id IN ($in_clause_placeholders) "; $del_params = array_merge([$COMPANY_ID, $start_iso, $end_iso], $scoped_db_emp_ids); $del = $pdo->prepare($del_query); $del->execute($del_params); $ins = $pdo->prepare(" INSERT INTO employee_attendance_daily (company_id, employee_id, attendance_date, status, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW()) "); $status_map = ['P' => 'Present', 'PP' => 'DoublePresent', 'A' => 'Absent', 'H' => 'Halfday']; $insert_count = 0; foreach ($matrix_payload as $emp_id => $dates_obj) { $emp_id = (int)$emp_id; if (!in_array($emp_id, $scoped_db_emp_ids)) continue; foreach ($dates_obj as $date_str => $char_code) { $char_code = strtoupper(trim($char_code)); if ($char_code === '') { $char_code = 'A'; } if (isset($status_map[$char_code])) { $d = DateTime::createFromFormat('d-m-Y', $date_str); $db_date = $d ? $d->format('Y-m-d') : ''; if (!empty($db_date)) { $ins->execute([$COMPANY_ID, $emp_id, $db_date, $status_map[$char_code]]); $insert_count++; } } } } $pdo->commit(); jexit([ 'ok' => true, 'inserted_rows' => $insert_count, 'msg' => 'Total ' . $insert_count . ' records database me safely commit ho gaye!' ]); } if ($act === 'update_dept_rule') { $dept_name = trim(pv('dept_name')); $rule_type = pv('rule_type'); if (empty($dept_name)) throw new Exception("Department reference specification error."); $stmt = $pdo->prepare("INSERT INTO attendance_rules_master (company_id, department_name, halfday_calculation_type, updated_at) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE halfday_calculation_type = ?, updated_at = NOW()"); $stmt->execute([$COMPANY_ID, $dept_name, $rule_type, $rule_type]); jexit(['ok' => true, 'msg' => 'Rules synchronized.']); } } catch (Throwable $e) { if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack(); jexit(['ok' => false, 'msg' => 'Database Error: ' . $e->getMessage()]); } } require_once __DIR__ . '/partials/header.php'; ?> <div class="container-fluid py-4 section-to-hide-print"> <div class="card shadow-sm border-0 mb-4 bg-white"> <div class="card-body py-3 d-flex justify-content-between align-items-center flex-wrap gap-2"> <div> <h4 class="fw-bold mb-0 text-dark">Live Excel-Style Attendance Grid</h4> <p class="text-muted small mb-0">Review sheet entries, manage personnel inclusions and click save to commit live changes.</p> </div> <div id="liveSyncBadge" class="badge bg-secondary px-3 py-2 d-flex align-items-center font-monospace"> <i class="bi bi-pencil-square me-1"></i> AUTO BATCH ACTIVE </div> </div> </div> <div class="card shadow-sm border-0 mb-4"> <div class="card-body bg-light rounded"> <div class="row g-3 align-items-end"> <div class="col-md-2"> <label class="form-label small fw-bold text-uppercase">Working Month</label> <input type="month" id="ctrl_month" class="form-control fw-bold" value="<?= date('Y-m') ?>" onchange="renderAttendanceMatrixGrid()"> </div> <div class="col-md-2"> <label class="form-label small fw-bold text-uppercase">Target Period</label> <select id="ctrl_period" class="form-select fw-bold" onchange="renderAttendanceMatrixGrid()"> <option value="1-15">1 to 15 (First Half)</option> <option value="16-31">16 to End (Second Half)</option> </select> </div> <div class="col-md-4"> <label class="form-label small fw-bold text-uppercase">Operational Department</label> <select id="ctrl_dept" class="form-select border-primary fw-bold" onchange="renderAttendanceMatrixGrid()"> <option value="">-- Choose Working Department Group --</option> </select> </div> <div class="col-md-4"> <label class="form-label small fw-bold text-danger text-uppercase">Half-Day Calculation Setting</label> <select id="ctrl_rule" class="form-select border-danger bg-white fw-bold" onchange="persistDepartmentRuleSetting()"> <option value="half_day_salary">Deduct Salary (Compute as 0.5 Day)</option> <option value="full_day_salary">Give Full Salary (Compute as 1.0 Full Day)</option> </select> </div> </div> </div> </div> <div id="macroControlsCard" class="card border-primary mb-4 shadow-sm d-none"> <div class="card-header bg-primary text-white fw-bold py-2"> <i class="bi bi-lightning-charge-fill me-1"></i> Live Grid Mass Auto Fill Operations Panel </div> <div class="card-body bg-white py-3"> <div class="row g-3 align-items-center"> <div class="col-md-3"> <div class="input-group"> <span class="input-group-text bg-light fw-bold small text-uppercase">Half Day</span> <input type="text" id="macro_hd_dates" class="form-control font-monospace" placeholder="e.g. 3,10"> <button class="btn btn-warning fw-bold" type="button" onclick="executeMacroFill('H')">Apply H</button> </div> </div> <div class="col-md-4"> <div class="input-group"> <span class="input-group-text bg-light fw-bold small text-uppercase">Holiday Target</span> <select id="macro_hol_date_select" class="form-select font-monospace"></select> <select id="macro_hol_status_select" class="form-select fw-bold border-info text-info"> <option value="P">Present (P)</option> <option value="PP">Present (PP)</option> <option value="A">Absent (A)</option> <option value="H">Halfday (H)</option> </select> <button class="btn btn-info text-dark fw-bold" type="button" onclick="executeMacroHolidayFillDropdown()">Apply Hol</button> </div> </div> <div class="col-md-3"> <div class="btn-group w-100 shadow-sm"> <button type="button" class="btn btn-outline-success fw-bold btn-sm" onclick="executeMacroFill('P')">Fill P</button> <button type="button" class="btn btn-success fw-bold text-white btn-sm" onclick="executeMacroFill('PP')">Fill PP</button> </div> </div> <div class="col-md-2 text-end"> <button type="button" class="btn btn-danger fw-bold w-100" onclick="clearEmptyGridBlocksFields()"> <i class="bi bi-eraser-fill"></i> CLEAR EMPTY </button> </div> </div> </div> </div> </div> <div class="container-fluid print-container"> <form id="attendanceBatchSaveForm" autocomplete="off" onsubmit="return false;"> <div class="card shadow-sm border-0 p-0 mb-4 card-print-override"> <div class="table-responsive border rounded bg-white table-print-wrapper"> <table id="matrixTable" class="table table-bordered align-middle text-center mb-0" style="min-width: 800px;"> <thead> <tr><th class="p-3 bg-dark text-white">Grid Mappings Matrix</th></tr> </thead> <tbody> <tr><td class="p-4 text-muted font-weight-bold">Please select targeted department filters parameters above to populate matrix tracking grid.</td></tr> </tbody> </table> </div> </div> <div id="saveActionContainer" class="card shadow-sm border-0 mb-4 d-none section-to-hide-print"> <div class="card-body bg-light text-end py-3 d-flex justify-content-between align-items-center"> <div> <button type="button" class="btn btn-dark btn-lg px-4 fw-bold shadow-sm" onclick="printAttendanceReportGrid();"> <i class="bi bi-printer-fill me-1"></i> PRINT SHEET </button> </div> <div> <span class="text-muted me-3 small font-monospace">* Empty values will automatically save as Absent (A).</span> <button type="button" id="saveMatrixBtn" class="btn btn-success btn-lg px-5 fw-bold shadow" onclick="commitEntireMatrixToServer()"> <i class="bi bi-cloud-arrow-up-fill me-1"></i> COMMIT & SAVE ATTENDANCE SHEET </button> </div> </div> </div> </form> </div> <script> const el = id => document.getElementById(id); document.addEventListener('DOMContentLoaded', async () => { try { const res = await fetch('?ajax=1&act=departments').then(r=>r.json()); if(res.ok) { res.items.forEach(d => { el('ctrl_dept').add(new Option(d.department_name, d.department_name)); }); } } catch(err) { console.error(err); } }); let serverDatesRef = []; let serverEmployeesRef = []; async function renderAttendanceMatrixGrid() { const month = el('ctrl_month').value; const period = el('ctrl_period').value; const deptName = el('ctrl_dept').value; if(!deptName || !month) { el('macroControlsCard').classList.add('d-none'); el('saveActionContainer').classList.add('d-none'); el('matrixTable').innerHTML = '<tbody><tr><td class="p-4 text-muted font-weight-bold">Please select targeted department filters parameters above to populate matrix tracking grid.</td></tr></tbody>'; return; } try { const res = await fetch(`?ajax=1&act=get_matrix_grid&month=${month}&period=${period}&dept_name=${encodeURIComponent(deptName)}`); const data = await res.json(); if(data.ok) { serverDatesRef = data.dates; serverEmployeesRef = data.employees; el('ctrl_rule').value = data.calc_rule; const holDateSel = el('macro_hol_date_select'); holDateSel.innerHTML = ''; serverDatesRef.forEach(dStr => { const dayNum = parseInt(dStr.split('-')[0]); holDateSel.add(new Option(`Day ${dayNum} (${dStr})`, dayNum)); }); const table = el('matrixTable'); table.innerHTML = ''; if(serverEmployeesRef.length === 0) { table.innerHTML = `<tbody><tr><td class="p-4 text-danger fw-bold bg-light-danger text-center">No active employee list found inside department specifications.</td></tr></tbody>`; el('macroControlsCard').classList.add('d-none'); el('saveActionContainer').classList.add('d-none'); return; } el('macroControlsCard').classList.remove('d-none'); el('saveActionContainer').classList.remove('d-none'); let theadHtml = `<tr> <th class="bg-dark text-white text-center font-monospace shadow-sm position-sticky start-0 z-index-top-override" style="min-width:120px; width: 140px;"> <div class="form-check justify-content-center d-flex align-items-center gap-1 my-0 section-to-hide-print"> <input class="form-check-input check-master-emp shadow-none cursor-pointer" type="checkbox" id="master_emp_toggle" checked onchange="toggleAllEmployeesGridScope(this)"> <label class="form-check-label small fw-bold font-monospace mb-0 text-warning cursor-pointer" for="master_emp_toggle">Date</label> </div> </th>`; serverEmployeesRef.forEach(emp => { theadHtml += ` <th class="bg-dark text-white p-2 text-wrap font-monospace employee-col-header-${emp.id}" style="min-width: 130px;"> <div class="form-check d-flex align-items-center justify-content-center gap-2 mb-1 border-bottom border-secondary pb-1 section-to-hide-print"> <input class="form-check-input emp-scope-checkbox cursor-pointer" type="checkbox" data-emp-id="${emp.id}" id="scope_check_${emp.id}" checked onchange="toggleSingleEmployeeColumnScope(${emp.id}, this.checked)"> <label class="form-check-label text-success fw-bold mb-0 cursor-pointer small font-monospace" for="scope_check_${emp.id}">Include</label> </div> <div class="emp-meta-wrapper">${emp.name}<br><small class="text-muted text-uppercase" style="font-size:0.75rem;">${emp.employee_code || ''}</small></div> </th>`; }); theadHtml += `</tr>`; table.innerHTML += `<thead class="table-dark">${theadHtml}</thead>`; let tbodyHtml = ''; serverDatesRef.forEach(dStr => { tbodyHtml += `<tr> <td class="bg-light fw-bold text-secondary font-monospace position-sticky start-0 border-end-2 shadow-sm bg-white z-index-mid-override">${dStr}</td>`; serverEmployeesRef.forEach(emp => { const existingStatus = data.matrix[emp.id]?.[dStr] || ''; let displayChar = ''; if(existingStatus === 'Present') displayChar = 'P'; if(existingStatus === 'DoublePresent') displayChar = 'PP'; if(existingStatus === 'Absent') displayChar = 'A'; if(existingStatus === 'Halfday') displayChar = 'H'; let cellBg = ''; if(displayChar === 'P') cellBg = 'bg-cell-p'; if(displayChar === 'PP') cellBg = 'bg-cell-pp'; if(displayChar === 'A') cellBg = 'bg-cell-a'; if(displayChar === 'H') cellBg = 'bg-cell-h'; tbodyHtml += ` <td class="${cellBg} p-0 transition-all text-center align-middle grid-column-emp-${emp.id}" id="cell_box_${emp.id}_${dStr}"> <input type="text" class="matrix-grid-input font-monospace text-center uppercase input-emp-id-${emp.id}" maxLength="2" value="${displayChar}" id="input_${emp.id}_${dStr}" data-emp-id="${emp.id}" data-date="${dStr}" onclick="this.select();" onkeydown="handleGridKeystrokeNavigation(event, this)" oninput="evaluateInputStylesLive(this)"> </td>`; }); tbodyHtml += `</tr>`; }); let tfootHtml = `<tr class="bg-dark text-white fw-bold summary-row-footer shadow-sm position-sticky bottom-0"> <td class="p-3 font-monospace text-warning position-sticky start-0 bg-dark z-index-mid-override align-middle" style="font-size:0.85rem;">TOTAL COUNT<br>& TOTAL DAYS</td>`; serverEmployeesRef.forEach(emp => { tfootHtml += ` <td class="p-2 font-monospace align-middle grid-column-emp-${emp.id}" id="summary_panel_emp_${emp.id}" style="font-size:0.8rem; min-width: 130px; line-height:1.4;"> <div class="text-success text-sum-p">P: <span id="sum_p_${emp.id}">0</span> | PP: <span id="sum_pp_${emp.id}">0</span></div> <div class="text-danger text-sum-a">A: <span id="sum_a_${emp.id}">0</span> | H: <span id="sum_h_${emp.id}">0</span></div> <div class="text-warning text-sum-total border-top border-secondary mt-1 pt-1 fw-bold" style="font-size:0.85rem;">Total: <span id="sum_pct_${emp.id}">0 Days</span></div> </td>`; }); tfootHtml += `</tr>`; table.innerHTML += `<tbody>${tbodyHtml}</tbody><tfoot>${tfootHtml}</tfoot>`; recalculateLiveGridTotals(); } } catch(err) { console.error(err); } } function handleGridKeystrokeNavigation(e, node) { const key = e.key.toUpperCase(); const empId = node.getAttribute('data-emp-id'); const currentDateStr = node.getAttribute('data-date'); const currentIndex = serverDatesRef.indexOf(currentDateStr); if (key === 'ENTER') { e.preventDefault(); shiftFocusToNextRowCell(empId, currentIndex); return; } if (['ARROWUP', 'ARROWDOWN', 'TAB', 'BACKSPACE', 'DELETE'].includes(key)) { return; } if (!['P', 'A', 'H', '2'].includes(key)) { e.preventDefault(); return; } e.preventDefault(); const currentVal = node.value.toUpperCase(); if (key === '2') { node.value = 'PP'; } else if (key === 'P') { node.value = (currentVal === 'P') ? 'PP' : 'P'; } else { node.value = key; } evaluateInputStylesLive(node); shiftFocusToNextRowCell(empId, currentIndex); } function shiftFocusToNextRowCell(empId, currentIndex) { if (currentIndex !== -1 && currentIndex + 1 < serverDatesRef.length) { const nextDateStr = serverDatesRef[currentIndex + 1]; const nextInput = el(`input_${empId}_${nextDateStr}`); if (nextInput && !nextInput.disabled) { nextInput.focus(); nextInput.select(); } } } function toggleSingleEmployeeColumnScope(empId, isChecked) { const cells = document.querySelectorAll(`.grid-column-emp-${empId}`); const headers = document.querySelectorAll(`.employee-col-header-${empId}`); cells.forEach(td => { const inp = td.querySelector('input'); if (isChecked) { td.style.opacity = '1'; td.style.backgroundColor = ''; if (inp) inp.disabled = false; } else { td.style.opacity = '0.4'; td.style.backgroundColor = '#e9ecef'; if (inp) inp.disabled = true; } }); headers.forEach(th => { const meta = th.querySelector('.emp-meta-wrapper'); if (meta) meta.style.opacity = isChecked ? '1' : '0.4'; }); } function toggleAllEmployeesGridScope(masterNode) { const checkboxes = document.querySelectorAll('.emp-scope-checkbox'); checkboxes.forEach(cb => { cb.checked = masterNode.checked; toggleSingleEmployeeColumnScope(cb.getAttribute('data-emp-id'), masterNode.checked); }); } function evaluateInputStylesLive(inputNode) { let v = inputNode.value.toUpperCase().trim(); if (!['', 'P', 'PP', 'A', 'H'].includes(v)) { inputNode.value = ''; const parentTd = inputNode.parentElement; parentTd.className = `p-0 transition-all text-center align-middle grid-column-emp-${inputNode.getAttribute('data-emp-id')}`; recalculateLiveGridTotals(); return; } const val = inputNode.value.toUpperCase(); const parentTd = inputNode.parentElement; parentTd.className = `p-0 transition-all text-center align-middle grid-column-emp-${inputNode.getAttribute('data-emp-id')}`; if(val === 'P') parentTd.classList.add('bg-cell-p'); if(val === 'PP') parentTd.classList.add('bg-cell-pp'); if(val === 'A') parentTd.classList.add('bg-cell-a'); if(val === 'H') parentTd.classList.add('bg-cell-h'); recalculateLiveGridTotals(); } function recalculateLiveGridTotals() { if (!serverEmployeesRef || serverEmployeesRef.length === 0) return; serverEmployeesRef.forEach(emp => { let countP = 0, countPP = 0, countA = 0, countH = 0; serverDatesRef.forEach(dStr => { const inp = el(`input_${emp.id}_${dStr}`); if (inp) { let v = inp.value.toUpperCase().trim(); if (v === 'P') countP++; else if (v === 'PP') countPP++; else if (v === 'A') countA++; else if (v === 'H') countH++; else countA++; } }); let netPresentWeight = (countP * 1.0) + (countPP * 2.0) + (countH * 0.5); if (el(`sum_p_${emp.id}`)) el(`sum_p_${emp.id}`).innerText = countP; if (el(`sum_pp_${emp.id}`)) el(`sum_pp_${emp.id}`).innerText = countPP; if (el(`sum_a_${emp.id}`)) el(`sum_a_${emp.id}`).innerText = countA; if (el(`sum_h_${emp.id}`)) el(`sum_h_${emp.id}`).innerText = countH; if (el(`sum_pct_${emp.id}`)) el(`sum_pct_${emp.id}`).innerText = netPresentWeight + ' Days'; }); } function clearEmptyGridBlocksFields() { serverEmployeesRef.forEach(emp => { const cb = el(`scope_check_${emp.id}`); if(cb && cb.checked) { serverDatesRef.forEach(dStr => { const inp = el(`input_${emp.id}_${dStr}`); if(inp) { inp.value = ''; const parentTd = inp.parentElement; parentTd.className = `p-0 transition-all text-center align-middle grid-column-emp-${emp.id}`; } }); } }); recalculateLiveGridTotals(); } function executeMacroFill(targetChar) { let targetDays = []; if(targetChar === 'H') { const rawInp = el('macro_hd_dates').value.trim(); if(!rawInp) { alert("Dates values missing references."); return; } targetDays = rawInp.split(',').map(x => parseInt(x.trim())).filter(x => !isNaN(x)); } serverEmployeesRef.forEach(emp => { const cb = el(`scope_check_${emp.id}`); if(cb && cb.checked) { serverDatesRef.forEach(dStr => { const dayNum = parseInt(dStr.split('-')[0]); const inputField = el(`input_${emp.id}_${dStr}`); if(inputField) { if(targetChar === 'H' && targetDays.includes(dayNum)) { inputField.value = 'H'; evaluateInputStylesLive(inputField); } else if((targetChar === 'P' || targetChar === 'PP') && !inputField.value.trim()) { inputField.value = targetChar; evaluateInputStylesLive(inputField); } } }); } }); recalculateLiveGridTotals(); } function executeMacroHolidayFillDropdown() { const targetDay = parseInt(el('macro_hol_date_select').value); const targetStatus = el('macro_hol_status_select').value; if(isNaN(targetDay)) { alert("Please select a valid date parameter."); return; } serverEmployeesRef.forEach(emp => { const cb = el(`scope_check_${emp.id}`); if(cb && cb.checked) { serverDatesRef.forEach(dStr => { const dayNum = parseInt(dStr.split('-')[0]); if(dayNum === targetDay) { const fullInput = el(`input_${emp.id}_${dStr}`); if(fullInput) { fullInput.value = targetStatus; evaluateInputStylesLive(fullInput); } } }); } }); recalculateLiveGridTotals(); } async function commitEntireMatrixToServer() { const btn = el('saveMatrixBtn'); const badge = el('liveSyncBadge'); let activeSelectionFound = false; serverEmployeesRef.forEach(emp => { const cb = el(`scope_check_${emp.id}`); if(cb && cb.checked) activeSelectionFound = true; }); if(!activeSelectionFound) { alert("Operation Aborted: Kam se kam ek Employee selected hona chahiye."); return; } btn.disabled = true; btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1" role="status"></span> TRANSMITTING ATTENDANCE MATRIX...'; badge.className = "badge bg-warning px-3 py-2 d-flex align-items-center font-monospace text-dark"; badge.innerHTML = '<i class="bi bi-cloud-upload-fill me-1"></i> READING CURRENT UI VIEW STATE...'; let matrixDataMap = {}; serverEmployeesRef.forEach(emp => { const cb = el(`scope_check_${emp.id}`); if(cb && cb.checked) { matrixDataMap[emp.id] = {}; serverDatesRef.forEach(dStr => { const inp = el(`input_${emp.id}_${dStr}`); let valToSend = inp ? inp.value.toUpperCase().trim() : ''; matrixDataMap[emp.id][dStr] = valToSend; }); } }); const fd = new FormData(); fd.append('dept_name', el('ctrl_dept').value); fd.append('month', el('ctrl_month').value); fd.append('period', el('ctrl_period').value); fd.append('matrix_json', JSON.stringify(matrixDataMap)); try { const response = await fetch('?ajax=1&act=save_batch_matrix', { method: 'POST', body: fd }); const txt = await response.text(); console.log("Diagnostic Raw Response Output: ", txt); const res = JSON.parse(txt); if(res.ok) { badge.className = "badge bg-success px-3 py-2 d-flex align-items-center font-monospace"; badge.innerHTML = '<i class="bi bi-cloud-check-fill me-1"></i> ROW SAVED: ' + (res.inserted_rows || 0); alert(res.msg); renderAttendanceMatrixGrid(); } else { alert("Database Rejection Notice: " + res.msg); badge.className = "badge bg-danger px-3 py-2 d-flex align-items-center font-monospace text-white"; badge.innerHTML = '<i class="bi bi-exclamation-triangle-fill me-1"></i> TRANSACTION REJECTED'; } } catch(e) { console.error("Critical Exception: ", e); alert("System Error. Review browser debugging streams inside F12 console."); } btn.disabled = false; btn.innerHTML = '<i class="bi bi-cloud-arrow-up-fill me-1"></i> COMMIT & SAVE ATTENDANCE SHEET'; } async function persistDepartmentRuleSetting() { const deptName = el('ctrl_dept').value; const ruleVal = el('ctrl_rule').value; if(!deptName) return; const fd = new FormData(); fd.append('dept_name', deptName); fd.append('rule_type', ruleVal); try { await fetch('?ajax=1&act=update_dept_rule', { method: 'POST', body: fd }); } catch(e) { console.error(e); } } function printAttendanceReportGrid() { const dept = el('ctrl_dept').value || 'N/A'; const month = el('ctrl_month').value || 'N/A'; const period = el('ctrl_period').value || 'N/A'; let printWin = window.open('', '', 'width=1200,height=800'); let htmlContent = ` <html> <head> <title>Attendance Matrix Printout</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; padding: 20px; background:#fff; color:#000; } .header { text-align: center; margin-bottom: 20px; } .header h2 { margin: 0 0 5px 0; text-transform: uppercase; letter-spacing: 1px; font-size: 18px; } .header p { margin: 0; font-size: 12px; color: #333; font-family: monospace; } table { width: 100%; border-collapse: collapse; margin-top: 10px; table-layout: fixed; } th, td { border: 1px solid #666; padding: 5px 3px; text-align: center; font-size: 11px; font-weight: bold; color: #000; } th { background-color: #f2f2f2 !important; color: #000 !important; } .print-text-success { color: #198754 !important; } .print-text-danger { color: #dc3545 !important; } .print-text-warning { color: #b58105 !important; font-size: 12px; font-weight: 900; } </style> </head> <body> <div class="header"> <h2>Employee Attendance Matrix Report</h2> <p>Department: ${dept} | Month: ${month} | Period Range: ${period}</p> </div> <table> <thead> <tr> <th style="width: 80px;">Date</th>`; serverEmployeesRef.forEach(emp => { const cb = el(`scope_check_${emp.id}`); if(cb && cb.checked) { htmlContent += `<th>${emp.name}<br><span style="font-size:9px; color:#444; font-weight:normal;">${emp.employee_code || ''}</span></th>`; } }); htmlContent += `</tr></thead><tbody>`; serverDatesRef.forEach(dStr => { htmlContent += `<tr><td>${dStr}</td>`; serverEmployeesRef.forEach(emp => { const cb = el(`scope_check_${emp.id}`); if(cb && cb.checked) { const inp = el(`input_${emp.id}_${dStr}`); const val = inp ? inp.value.toUpperCase().trim() : ''; htmlContent += `<td>${val}</td>`; } }); htmlContent += `</tr>`; }); // Ink-saving Clean White/Gray Print Summary Block Added htmlContent += `<tr style="background-color: #fafafa !important;"> <td style="font-size:10px; font-family:monospace; font-weight:bold; background-color:#eeeeee;">TOTAL COUNT<br>& DAYS</td>`; serverEmployeesRef.forEach(emp => { const cb = el(`scope_check_${emp.id}`); if(cb && cb.checked) { const pVal = el(`sum_p_${emp.id}`) ? el(`sum_p_${emp.id}`).innerText : '0'; const ppVal = el(`sum_pp_${emp.id}`) ? el(`sum_pp_${emp.id}`).innerText : '0'; const aVal = el(`sum_a_${emp.id}`) ? el(`sum_a_${emp.id}`).innerText : '0'; const hVal = el(`sum_h_${emp.id}`) ? el(`sum_h_${emp.id}`).innerText : '0'; const totalDays = el(`sum_pct_${emp.id}`) ? el(`sum_pct_${emp.id}`).innerText : '0 Days'; htmlContent += ` <td style="font-size:10px; font-family:monospace; text-align:center; padding:4px; line-height:1.3;"> <div class="print-text-success">P:${pVal} | PP:${ppVal}</div> <div class="print-text-danger">A:${aVal} | H:${hVal}</div> <div class="print-text-warning" style="border-top:1px solid #ccc; margin-top:2px; pt-1;">${totalDays}</div> </td>`; } }); htmlContent += `</tr>`; htmlContent += `</tbody></table></body></html>`; printWin.document.write(htmlContent); printWin.document.close(); setTimeout(() => { printWin.focus(); printWin.print(); printWin.close(); }, 450); } </script> <style> .matrix-grid-input { width: 100%; height: 38px; border: 0 !important; background: transparent !important; font-weight: bold; outline: none !important; font-size: 1rem; color: #000; } .matrix-grid-input:focus { background-color: rgba(13, 110, 253, 0.15) !important; } .bg-cell-p { background-color: rgba(25, 135, 84, 0.18) !important; } .bg-cell-pp { background-color: rgba(10, 90, 50, 0.35) !important; } .bg-cell-a { background-color: rgba(220, 53, 69, 0.22) !important; } .bg-cell-h { background-color: rgba(255, 193, 7, 0.3) !important; } .bg-light-danger { background-color: rgba(220, 53, 69, 0.08) !important; } .transition-all { transition: all 0.2s ease-in-out; } .border-end-2 { border-right: 3px solid #343a40 !important; } table#matrixTable td { padding: 0 !important; vertical-align: middle; } .cursor-pointer { cursor: pointer; } .z-index-top-override { z-index: 10 !important; } .z-index-mid-override { z-index: 9 !important; } .summary-row-footer td { background-color: #212529 !important; color: #fff !important; border-top: 3px double #000 !important; } </style> <?php require_once __DIR__ . '/partials/footer.php'; ?>