|
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/ | |
|
Path: /home2/outerorb/emp.outerorbittech.in/admin/leaves.php
Size: 18.98 KB
Permissions: 0666
<?php
require __DIR__ . '/../includes/helpers.php';
require_admin();
ensure_leave_tables();
ensure_employees_table();
$roleLabel = 'Admin';$pdo = db();
$flash = flash();
$admin = current_admin();
$year = isset($_GET['year']) ? (int) $_GET['year'] : (int) date('Y');
// ---------- POST: approve / reject / add manual leave ----------
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verify_csrf($_POST['csrf_token'] ?? '')) {
redirect_with_message('leaves.php?year=' . $year, 'Session expired.', 'error');
}
$action = $_POST['action'] ?? '';
$id = (int) ($_POST['id'] ?? 0);
if (in_array($action, ['approve', 'reject'], true) && $id) {
$newStatus = $action === 'approve' ? 'approved' : 'rejected';
$notes = sanitize_text($_POST['admin_notes'] ?? '');
$app = $pdo->prepare('SELECT * FROM leave_applications WHERE id = ? LIMIT 1');
$app->execute([$id]);
$app = $app->fetch();
if ($app && $app['status'] === 'pending') {
$pdo->prepare(
'UPDATE leave_applications SET status=?, admin_notes=?, reviewed_by=?, reviewed_at=NOW(), updated_at=NOW() WHERE id=?'
)->execute([$newStatus, $notes ?: null, $admin['id'] ?? null, $id]);
// On approval: deduct from leave balance
if ($newStatus === 'approved') {
$yr = (int) date('Y', strtotime($app['from_date']));
$pdo->prepare(
'INSERT INTO leave_balances (employee_id, leave_type_id, year, allocated_days, used_days)
VALUES (?, ?, ?,
(SELECT annual_days FROM leave_types WHERE id = ?),
?)
ON DUPLICATE KEY UPDATE used_days = used_days + ?'
)->execute([
$app['employee_id'], $app['leave_type_id'], $yr,
$app['leave_type_id'], $app['total_days'],
$app['total_days'],
]);
// Auto-fill attendance_logs for leave days
$start = new DateTime($app['from_date']);
$end = new DateTime($app['to_date']);
ensure_attendance_tables();
while ($start <= $end) {
$ds = $start->format('Y-m-d');
$dow = (int) $start->format('N');
if ($dow <= 5) { // weekdays only
$pdo->prepare(
'INSERT INTO attendance_logs (employee_id, attend_date, status, marked_by)
VALUES (?,?,\'on_leave\',?)
ON DUPLICATE KEY UPDATE status=\'on_leave\', marked_by=VALUES(marked_by), updated_at=NOW()'
)->execute([$app['employee_id'], $ds, $admin['id'] ?? null]);
}
$start->modify('+1 day');
}
}
redirect_with_message('leaves.php?year=' . $year, 'Leave application ' . $newStatus . '.');
}
}
// Admin manually adds a leave application on behalf of an employee
if ($action === 'add') {
$empId = (int) ($_POST['employee_id'] ?? 0);
$ltId = (int) ($_POST['leave_type_id'] ?? 0);
$fromDate = sanitize_text($_POST['from_date'] ?? '');
$toDate = sanitize_text($_POST['to_date'] ?? '');
$reason = sanitize_text($_POST['reason'] ?? '');
$addStatus = in_array($_POST['add_status'] ?? '', ['pending','approved','rejected']) ? $_POST['add_status'] : 'approved';
if ($empId && $ltId && $fromDate && $toDate) {
$emp = $pdo->prepare('SELECT department FROM employees WHERE id = ? LIMIT 1');
$emp->execute([$empId]);
$dept = ($emp->fetch())['department'] ?? null;
$days = count_working_days($fromDate, $toDate, $dept);
$pdo->prepare(
'INSERT INTO leave_applications (employee_id, leave_type_id, from_date, to_date, total_days, reason, status, reviewed_by, reviewed_at)
VALUES (?,?,?,?,?,?,?,?,NOW())'
)->execute([$empId, $ltId, $fromDate, $toDate, $days, $reason ?: null, $addStatus, $admin['id'] ?? null]);
$newId = (int) $pdo->lastInsertId();
if ($addStatus === 'approved') {
$yr = (int) date('Y', strtotime($fromDate));
$pdo->prepare(
'INSERT INTO leave_balances (employee_id, leave_type_id, year, allocated_days, used_days)
VALUES (?,?,?,(SELECT annual_days FROM leave_types WHERE id=?),?)
ON DUPLICATE KEY UPDATE used_days=used_days+?'
)->execute([$empId, $ltId, $yr, $ltId, $days, $days]);
ensure_attendance_tables();
$s = new DateTime($fromDate); $e = new DateTime($toDate);
while ($s <= $e) {
$ds = $s->format('Y-m-d');
$dow = (int) $s->format('N');
if ($dow <= 5) {
$pdo->prepare(
'INSERT INTO attendance_logs (employee_id, attend_date, status, marked_by)
VALUES (?,?,\'on_leave\',?)
ON DUPLICATE KEY UPDATE status=\'on_leave\', updated_at=NOW()'
)->execute([$empId, $ds, $admin['id'] ?? null]);
}
$s->modify('+1 day');
}
}
redirect_with_message('leaves.php?year=' . $year, 'Leave entry added.');
}
}
}
// ---------- Filters ----------
$statusFilter = sanitize_text($_GET['status'] ?? '');
$deptFilter = sanitize_text($_GET['dept'] ?? '');
$where = ['YEAR(la.from_date) = :year'];
$params = [':year' => $year];
if ($statusFilter !== '') {
$where[] = 'la.status = :status';
$params[':status'] = $statusFilter;
}
if ($deptFilter !== '') {
$where[] = 'e.department = :dept';
$params[':dept'] = $deptFilter;
}
$allowedDepts = allowed_departments_for_admin();
if (!empty($allowedDepts)) {
$phs = [];
foreach ($allowedDepts as $i => $d) { $k = ':d'.$i; $phs[] = $k; $params[$k] = $d; }
$where[] = 'e.department IN (' . implode(',', $phs) . ')';
}
$sql = 'SELECT la.*, e.first_name, e.last_name, e.department, lt.name AS leave_type_name, lt.code
FROM leave_applications la
JOIN employees e ON e.id = la.employee_id
JOIN leave_types lt ON lt.id = la.leave_type_id
WHERE ' . implode(' AND ', $where) . '
ORDER BY la.created_at DESC';
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$applications = $stmt->fetchAll();
$pendingCount = array_sum(array_map(fn($a) => $a['status'] === 'pending' ? 1 : 0, $applications));
// For add form
$allEmployees = $pdo->query("SELECT id, first_name, last_name, department FROM employees WHERE deleted_at IS NULL AND employee_status='active' ORDER BY first_name")->fetchAll();
$leaveTypes = list_leave_types(true);
$departments = list_departments();
function e(string $v): string { return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); }
$statusBadge = [
'pending' => 'background:#fef3c7; color:#92400e;',
'approved' => 'background:#d1fae5; color:#065f46;',
'rejected' => 'background:#fee2e2; color:#991b1b;',
'cancelled' => 'background:#f3f4f6; color:#6b7280;',
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Leave Applications — HRMS</title>
<link rel="stylesheet" href="../assets/css/style.css?v=<?php echo filemtime(__DIR__ . '/../assets/css/style.css'); ?>" />
<link rel="stylesheet" href="../assets/css/polish.css?v=<?php echo filemtime(__DIR__ . '/../assets/css/polish.css'); ?>" />
</head>
<body class="dashboard-layout">
<div class="app-wrapper">
<?php require '_sidebar.php'; ?>
<div class="main-content">
<div class="top-bar">
<div class="top-bar-left">
<button class="sidebar-toggle" id="sidebarToggle" aria-label="Toggle sidebar">☰</button>
<div>
<h1 class="page-title">Leaves</h1>
<div class="breadcrumb-nav">
<span>Manage employee leaves</span>
</div>
</div>
</div>
<div class="top-bar-right">
<a href="dashboard.php" style="color: #cbd5e1; text-decoration: none; padding: 8px 12px; border-radius: 6px; transition: background 0.15s; font-size: 13px; font-weight: 600;">← Dashboard</a>
</div>
</div>
<div class="dashboard-container" style="max-width: 1200px;">
<div class="container">
<div class="header">
<div class="brand">
<div class="brand-title">
<h1 style="margin:0;">Leave Applications</h1>
<p class="helper" style="margin:2px 0 0;"><?php echo $pendingCount > 0 ? $pendingCount . ' pending approval' : 'All caught up'; ?></p>
</div>
</div>
<div class="actions">
<a class="badge" href="leave_types.php">Leave Types</a>
<a class="badge" href="attendance.php">Attendance</a>
<a class="badge" href="dashboard.php">Dashboard</a>
<a class="badge" href="logout.php">Logout</a>
</div>
</div>
<?php if ($flash): ?>
<div class="alert <?php echo $flash['type'] === 'error' ? 'alert-error' : 'alert-success'; ?>">
<?php echo e($flash['message']); ?>
</div>
<?php endif; ?>
<!-- Add manual leave entry -->
<details class="card" style="margin-bottom:20px;">
<summary style="cursor:pointer; font-weight:700; padding:4px 0;">+ Add Leave Entry</summary>
<form method="POST" style="margin-top:16px;">
<input type="hidden" name="csrf_token" value="<?php echo e(csrf_token()); ?>" />
<input type="hidden" name="action" value="add" />
<div style="display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:12px;">
<div class="form-group" style="margin:0;">
<label>Employee *</label>
<select name="employee_id" class="form-control" required>
<option value="">Select employee</option>
<?php foreach ($allEmployees as $emp): ?>
<option value="<?php echo (int)$emp['id']; ?>"><?php echo e($emp['first_name'].' '.$emp['last_name'].' ('.$emp['department'].')'); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="margin:0;">
<label>Leave Type *</label>
<select name="leave_type_id" class="form-control" required>
<option value="">Select type</option>
<?php foreach ($leaveTypes as $lt): ?>
<option value="<?php echo (int)$lt['id']; ?>"><?php echo e($lt['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="margin:0;">
<label>From *</label>
<input type="date" name="from_date" class="form-control" required />
</div>
<div class="form-group" style="margin:0;">
<label>To *</label>
<input type="date" name="to_date" class="form-control" required />
</div>
<div class="form-group" style="margin:0;">
<label>Status</label>
<select name="add_status" class="form-control">
<option value="approved">Approved</option>
<option value="pending">Pending</option>
</select>
</div>
<div class="form-group" style="margin:0;">
<label>Reason</label>
<input type="text" name="reason" class="form-control" placeholder="Optional" />
</div>
</div>
<button type="submit" class="btn btn-primary" style="margin-top:14px;">Add Leave</button>
</form>
</details>
<!-- Filters -->
<div class="card" style="margin-bottom:16px; padding:12px 16px;">
<form method="GET" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
<select name="year" class="form-control" style="height:28px; font-size:.8rem; margin:0; width:auto;">
<?php for ($y = date('Y') + 1; $y >= date('Y') - 3; $y--): ?>
<option value="<?php echo $y; ?>" <?php echo $y === $year ? 'selected' : ''; ?>><?php echo $y; ?></option>
<?php endfor; ?>
</select>
<select name="status" class="form-control" style="height:28px; font-size:.8rem; margin:0; width:auto;">
<option value="">All Statuses</option>
<?php foreach (['pending','approved','rejected','cancelled'] as $s): ?>
<option value="<?php echo $s; ?>" <?php echo $statusFilter === $s ? 'selected' : ''; ?>><?php echo ucfirst($s); ?></option>
<?php endforeach; ?>
</select>
<select name="dept" class="form-control" style="height:28px; font-size:.8rem; margin:0; width:auto;">
<option value="">All Departments</option>
<?php foreach ($departments as $dept): ?>
<option value="<?php echo e($dept['name']); ?>" <?php echo $deptFilter === $dept['name'] ? 'selected' : ''; ?>>
<?php echo e($dept['name']); ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-primary" style="height:28px; padding:0 14px; font-size:.8rem;">Filter</button>
</form>
</div>
<div class="card">
<?php if (empty($applications)): ?>
<p style="color:#6b7280; text-align:center; padding:30px 0;">No leave applications found for the selected filters.</p>
<?php else: ?>
<table style="width:100%; border-collapse:collapse; font-size:.875rem;">
<thead>
<tr style="background:#f9fafb;">
<?php foreach (['Employee','Department','Leave Type','From','To','Days','Status','Reason','Actions'] as $h): ?>
<th style="padding:10px 12px; text-align:left; border-bottom:1px solid #e5e7eb; white-space:nowrap;"><?php echo $h; ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($applications as $app): ?>
<tr>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6;">
<?php echo e($app['first_name'] . ' ' . $app['last_name']); ?>
</td>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6; color:#6b7280;"><?php echo e($app['department']); ?></td>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6;">
<?php echo e($app['leave_type_name']); ?> <code style="font-size:.75rem;"><?php echo e($app['code']); ?></code>
</td>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6;"><?php echo date('d M', strtotime($app['from_date'])); ?></td>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6;"><?php echo date('d M', strtotime($app['to_date'])); ?></td>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6; text-align:center;"><?php echo $app['total_days']; ?></td>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6;">
<span style="display:inline-block; padding:2px 8px; border-radius:999px; font-size:.75rem; font-weight:600; <?php echo $statusBadge[$app['status']] ?? ''; ?>">
<?php echo ucfirst($app['status']); ?>
</span>
</td>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6; max-width:180px; color:#6b7280; font-size:.82rem;">
<?php echo $app['reason'] ? e($app['reason']) : '—'; ?>
</td>
<td style="padding:9px 12px; border-bottom:1px solid #f3f4f6; white-space:nowrap;">
<?php if ($app['status'] === 'pending'): ?>
<form method="POST" style="display:inline;">
<input type="hidden" name="csrf_token" value="<?php echo e(csrf_token()); ?>" />
<input type="hidden" name="action" value="approve" />
<input type="hidden" name="id" value="<?php echo (int)$app['id']; ?>" />
<button type="submit" style="background:#d1fae5; border:none; color:#065f46; padding:3px 10px; border-radius:4px; cursor:pointer; font-size:.8rem;">Approve</button>
</form>
<form method="POST" style="display:inline;" onsubmit="return confirm('Reject this leave?');">
<input type="hidden" name="csrf_token" value="<?php echo e(csrf_token()); ?>" />
<input type="hidden" name="action" value="reject" />
<input type="hidden" name="id" value="<?php echo (int)$app['id']; ?>" />
<button type="submit" style="background:#fee2e2; border:none; color:#991b1b; padding:3px 10px; border-radius:4px; cursor:pointer; font-size:.8rem;">Reject</button>
</form>
<?php else: ?>
<span style="color:#9ca3af; font-size:.8rem;"><?php echo ucfirst($app['status']); ?></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<script>
const sidebar = document.getElementById('sidebar');
const sidebarToggle = document.getElementById('sidebarToggle');
const sidebarOverlay = document.getElementById('sidebarOverlay');
sidebarToggle?.addEventListener('click', () => {
sidebar.classList.toggle('open');
sidebarOverlay.classList.toggle('open');
});
sidebarOverlay?.addEventListener('click', () => {
sidebar.classList.remove('open');
sidebarOverlay.classList.remove('open');
});
</script>
</body>
</html>