« Back to History
leave_requests.php
|
20260921_175631.php
Initial Domain Snapshot
Copy Code
<?php /* ============================================================================= File: /erp/leave_requests.php Title: Simple Leave Request System (Submit + List + Accept/Reject) Scope: Page-local only. No global/base changes. ============================================================================= */ error_reporting(E_ALL); ini_set('display_errors', 1); /* -------- AUTH + PDO -------- */ require __DIR__ . '/modules/auth/auth.php'; require_login(); $u = auth_user(); $company_id = (int)$u['company_id']; $user_id = (int)$u['id']; $role = strtolower((string)($u['role'] ?? 'employee')); // owner/admin/manager/employee $pdo = $GLOBALS['pdo'] ?? null; if (!$pdo) { require __DIR__ . '/core/db.php'; } /* -------- HELPERS -------- */ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function is_reviewer($role){ return in_array($role, ['owner','admin','manager'], true); } /* -------- TABLE (LOCAL AUTO-CREATE) -------- */ $pdo->exec(" CREATE TABLE IF NOT EXISTS leave_requests ( id BIGINT PRIMARY KEY AUTO_INCREMENT, company_id BIGINT NOT NULL, user_id BIGINT NOT NULL, from_date DATE NOT NULL, to_date DATE NOT NULL, leave_type VARCHAR(30) NOT NULL, reason TEXT NULL, status ENUM('Pending','Approved','Rejected') NOT NULL DEFAULT 'Pending', reviewed_by BIGINT NULL, reviewed_at DATETIME NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_company_status (company_id, status), INDEX idx_company_user (company_id, user_id), INDEX idx_company_date (company_id, from_date, to_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); /* -------- CSRF -------- */ if (session_status() !== PHP_SESSION_ACTIVE) session_start(); if (empty($_SESSION['csrf'])) { $_SESSION['csrf'] = bin2hex(random_bytes(16)); } $CSRF = $_SESSION['csrf']; /* -------- HANDLE CREATE -------- */ $msg = ''; if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && ($_POST['action'] ?? '') === 'create') { if (!hash_equals($CSRF, $_POST['csrf'] ?? '')) { $msg = 'Security check failed.'; } else { $from_date = trim($_POST['from_date'] ?? ''); $to_date = trim($_POST['to_date'] ?? ''); $leave_type = trim($_POST['leave_type'] ?? 'Other'); $reason = trim($_POST['reason'] ?? ''); // Basic validations if ($from_date === '' || $to_date === '') { $msg = 'Please choose From and To dates.'; } elseif (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $from_date) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $to_date)) { $msg = 'Invalid date format.'; } elseif (strtotime($from_date) === false || strtotime($to_date) === false) { $msg = 'Invalid date value.'; } elseif (strtotime($from_date) > strtotime($to_date)) { $msg = 'From Date cannot be after To Date.'; } else { $st = $pdo->prepare("INSERT INTO leave_requests (company_id, user_id, from_date, to_date, leave_type, reason, status) VALUES (?, ?, ?, ?, ?, ?, 'Pending')"); $st->execute([$company_id, $user_id, $from_date, $to_date, $leave_type, $reason]); $msg = 'Leave request submitted.'; } } } /* -------- HANDLE APPROVE/REJECT -------- */ $act = $_GET['act'] ?? ''; $id = isset($_GET['id']) ? (int)$_GET['id'] : 0; if ($id > 0 && in_array($act, ['approve','reject'], true)) { if (!hash_equals($CSRF, $_GET['csrf'] ?? '')) { $msg = 'Security check failed.'; } elseif (!is_reviewer($role)) { http_response_code(403); $msg = 'You are not allowed to perform this action.'; } else { // Only update if belongs to same company and still Pending $newStatus = ($act === 'approve') ? 'Approved' : 'Rejected'; $st = $pdo->prepare("UPDATE leave_requests SET status=?, reviewed_by=?, reviewed_at=NOW() WHERE id=? AND company_id=? AND status='Pending'"); $st->execute([$newStatus, $user_id, $id, $company_id]); $msg = ($st->rowCount() > 0) ? "Request $newStatus." : "No pending request found / already processed."; } } /* -------- FETCH LIST -------- */ $filter_status = $_GET['status'] ?? 'All'; // All/Pending/Approved/Rejected $params = [$company_id]; $where = "lr.company_id = ?"; if (!is_reviewer($role)) { $where .= " AND lr.user_id = ?"; $params[] = $user_id; } if (in_array($filter_status, ['Pending','Approved','Rejected'], true)) { $where .= " AND lr.status = ?"; $params[] = $filter_status; } $q = " SELECT lr.*, u.name AS emp_name, DATEDIFF(lr.to_date, lr.from_date) + 1 AS days_count, r.name AS reviewer_name FROM leave_requests lr LEFT JOIN users u ON u.id = lr.user_id LEFT JOIN users r ON r.id = lr.reviewed_by WHERE $where ORDER BY lr.created_at DESC, lr.id DESC "; $rows = $pdo->prepare($q); $rows->execute($params); $data = $rows->fetchAll(PDO::FETCH_ASSOC); /* -------- UI (Mister Manager Palette) -------- */ /* Use global header/footer. No page-level <style> now. */ require_once __DIR__ . '/partials/header.php'; ?> <div class="container leave-requests"> <div class="headerbar" style="margin-bottom:12px;"> <h1 style="margin:0 0 0.5rem;color:var(--primary, #34A853);">Leave Requests</h1> <form method="get" class="filter-form" style="display:grid;grid-auto-flow:column;gap:8px;align-items:center"> <input type="hidden" name="csrf" value="<?php echo h($CSRF); ?>"> <label class="small" for="status" style="margin:0">Status</label> <select name="status" id="status" onchange="this.form.submit()"> <?php $opts = ['All','Pending','Approved','Rejected']; foreach($opts as $o){ $sel = ($o === $filter_status) ? 'selected' : ''; echo "<option $sel>".h($o)."</option>"; } ?> </select> </form> </div> <?php if ($msg): ?> <div class="notice"><?php echo h($msg); ?></div> <?php endif; ?> <!-- ========== CREATE FORM ========== --> <div class="card" style="margin-bottom:16px"> <form method="post" onsubmit="return animateSubmit(this)" class="form-grid"> <input type="hidden" name="csrf" value="<?php echo h($CSRF); ?>"> <input type="hidden" name="action" value="create"> <div> <label>From Date</label> <input type="date" name="from_date" required> </div> <div> <label>To Date</label> <input type="date" name="to_date" required> </div> <div> <label>Leave Type</label> <select name="leave_type" required> <?php foreach (['Casual','Sick','Paid','Unpaid','Other'] as $t): ?> <option value="<?php echo h($t); ?>"><?php echo h($t); ?></option> <?php endforeach; ?> </select> </div> <div> <label>Reason (optional)</label> <input type="text" name="reason" placeholder="Short reason..."> </div> <div class="form-actions" style="grid-column:1/-1;margin-top:12px;display:flex;gap:8px;align-items:center"> <button class="btn btn--primary" type="submit" id="submitBtn">Submit Request</button> <button class="btn btn--muted" type="reset">Reset</button> <span class="small" style="align-self:center;">Approvals by Owner/Admin/Manager</span> </div> </form> </div> <!-- ========== LIST ========== --> <div class="card"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px"> <div class="small"> Viewing: <strong><?php echo h($filter_status); ?></strong> <?php if (!is_reviewer($role)): ?> • My Requests <?php else: ?> • Company-wide <?php endif; ?> </div> </div> <div class="tablewrap"> <table class="table"> <thead> <tr> <th>Employee</th> <th>Period</th> <th>Days</th> <th>Type</th> <th>Status</th> <th>Reviewed By</th> <th>Reason</th> <th>Actions</th> </tr> </thead> <tbody> <?php if (!$data): ?> <tr><td colspan="8" class="small">No leave requests yet.</td></tr> <?php else: foreach($data as $r): $badge = 'pending'; if ($r['status']==='Approved') $badge='approved'; if ($r['status']==='Rejected') $badge='rejected'; ?> <tr> <td><?php echo h($r['emp_name'] ?: 'Employee #'.$r['user_id']); ?></td> <td> <?php echo h($r['from_date']); ?> → <?php echo h($r['to_date']); ?><br> <span class="small">Raised: <?php echo h(substr($r['created_at'],0,19)); ?></span> </td> <td><?php echo (int)$r['days_count']; ?></td> <td><?php echo h($r['leave_type']); ?></td> <td><span class="badge <?php echo $badge; ?>"><?php echo h($r['status']); ?></span></td> <td><?php echo h($r['reviewer_name'] ?: '-'); ?></td> <td><?php echo h($r['reason'] ?: '-'); ?></td> <td class="actions"> <?php if (is_reviewer($role) && $r['status']==='Pending'): ?> <a class="btn btn--primary" style="padding:6px 10px" href="?act=approve&id=<?php echo (int)$r['id']; ?>&csrf=<?php echo h($CSRF); ?>&status=<?php echo h($filter_status); ?>" onclick="return confirm('Approve this request?')">Approve</a> <a class="btn btn--danger" style="padding:6px 10px" href="?act=reject&id=<?php echo (int)$r['id']; ?>&csrf=<?php echo h($CSRF); ?>&status=<?php echo h($filter_status); ?>" onclick="return confirm('Reject this request?')">Reject</a> <?php else: ?> <span class="small">—</span> <?php endif; ?> </td> </tr> <?php endforeach; endif; ?> </tbody> </table> </div> </div> </div> <script> // Tiny submit animation function animateSubmit(form){ var btn = form.querySelector('#submitBtn'); if(!btn) return true; var txt = btn.textContent; btn.disabled = true; btn.style.transform = 'translateY(-1px)'; btn.style.boxShadow = '0 6px 16px rgba(52,168,83,.25)'; btn.textContent = 'Submitting...'; setTimeout(function(){ btn.textContent = txt; btn.disabled=false; }, 900); return true; } </script> <?php /* footer include (closes body/html and loads global scripts) */ require_once __DIR__ . '/partials/footer.php'; ?>