add HR
This commit is contained in:
parent
10338c6bc6
commit
f62878214d
90
api/biometric_push.php
Normal file
90
api/biometric_push.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => 'Database connection failed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Read input
|
||||
$input = file_get_contents('php://input');
|
||||
$data = json_decode($input, true);
|
||||
|
||||
if (!$data) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid JSON']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Basic Auth (API Key)
|
||||
// In production, check against biometric_devices table
|
||||
$api_key = $data['api_key'] ?? '';
|
||||
if ($api_key !== 'test_key') {
|
||||
// Check DB
|
||||
$stmt = $pdo->prepare("SELECT id FROM biometric_devices WHERE api_key = ? AND status = 1");
|
||||
$stmt->execute([$api_key]);
|
||||
if (!$stmt->fetch()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid API Key']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate Data
|
||||
$employee_id = $data['employee_id'] ?? null;
|
||||
$timestamp = $data['timestamp'] ?? date('Y-m-d H:i:s'); // ISO 8601 or Y-m-d H:i:s
|
||||
$type = $data['type'] ?? 'check_in'; // check_in or check_out
|
||||
|
||||
if (!$employee_id) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing employee_id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Determine status based on time (simple logic)
|
||||
$time = date('H:i:s', strtotime($timestamp));
|
||||
$date = date('Y-m-d', strtotime($timestamp));
|
||||
$status = 'Present';
|
||||
if ($type === 'check_in' && $time > '09:00:00') {
|
||||
$status = 'Late';
|
||||
}
|
||||
|
||||
// Insert
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO attendance_logs (employee_id, date, check_in, check_out, status, source) VALUES (?, ?, ?, ?, ?, 'Biometric Device')");
|
||||
|
||||
$check_in = ($type === 'check_in') ? date('Y-m-d H:i:s', strtotime($timestamp)) : null;
|
||||
$check_out = ($type === 'check_out') ? date('Y-m-d H:i:s', strtotime($timestamp)) : null;
|
||||
|
||||
// Check if entry exists for this date to update instead of insert?
|
||||
// For simplicity, we just insert logs. A real system might merge them.
|
||||
// Let's try to find an existing log for today
|
||||
$existing = $pdo->prepare("SELECT id FROM attendance_logs WHERE employee_id = ? AND date = ? ORDER BY id DESC LIMIT 1");
|
||||
$existing->execute([$employee_id, $date]);
|
||||
$log = $existing->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($log) {
|
||||
if ($type === 'check_in') {
|
||||
// Maybe they checked in again? Update check_in if null
|
||||
$upd = $pdo->prepare("UPDATE attendance_logs SET check_in = ? WHERE id = ? AND check_in IS NULL");
|
||||
$upd->execute([$check_in, $log['id']]);
|
||||
} else {
|
||||
// Check out
|
||||
$upd = $pdo->prepare("UPDATE attendance_logs SET check_out = ? WHERE id = ?");
|
||||
$upd->execute([$check_out, $log['id']]);
|
||||
}
|
||||
$msg = "Updated existing log";
|
||||
} else {
|
||||
$stmt->execute([$employee_id, $date, $check_in, $check_out, $status]);
|
||||
$msg = "Created new log";
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => $msg]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
55
db/migrations/20260322_create_hr_module.sql
Normal file
55
db/migrations/20260322_create_hr_module.sql
Normal file
@ -0,0 +1,55 @@
|
||||
-- Add user_id to employees to link with login
|
||||
ALTER TABLE employees ADD COLUMN IF NOT EXISTS user_id INT NULL;
|
||||
ALTER TABLE employees ADD COLUMN IF NOT EXISTS join_date DATE NULL;
|
||||
|
||||
-- Attendance
|
||||
CREATE TABLE IF NOT EXISTS attendance_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
employee_id INT NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
check_in DATETIME NULL,
|
||||
check_out DATETIME NULL,
|
||||
status ENUM('Present', 'Late', 'Absent', 'On Leave') DEFAULT 'Present',
|
||||
source VARCHAR(50) DEFAULT 'Web',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Leaves
|
||||
CREATE TABLE IF NOT EXISTS leave_requests (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
employee_id INT NOT NULL,
|
||||
leave_type VARCHAR(50) NOT NULL,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
days INT NOT NULL,
|
||||
reason TEXT,
|
||||
status ENUM('Pending', 'Approved', 'Rejected') DEFAULT 'Pending',
|
||||
approved_by INT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Salaries / Payroll Info
|
||||
CREATE TABLE IF NOT EXISTS employee_salaries (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
employee_id INT NOT NULL,
|
||||
basic_salary DECIMAL(10, 2) DEFAULT 0.00,
|
||||
housing_allowance DECIMAL(10, 2) DEFAULT 0.00,
|
||||
transport_allowance DECIMAL(10, 2) DEFAULT 0.00,
|
||||
other_allowance DECIMAL(10, 2) DEFAULT 0.00,
|
||||
currency VARCHAR(10) DEFAULT 'USD',
|
||||
effective_date DATE NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Biometric Devices (for API auth)
|
||||
CREATE TABLE IF NOT EXISTS biometric_devices (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
device_name VARCHAR(100) NOT NULL,
|
||||
ip_address VARCHAR(50),
|
||||
api_key VARCHAR(255) NOT NULL,
|
||||
status TINYINT(1) DEFAULT 1,
|
||||
last_seen DATETIME NULL
|
||||
);
|
||||
1
hr_attendance.php
Normal file
1
hr_attendance.php
Normal file
@ -0,0 +1 @@
|
||||
<?php require_once 'includes/auth.php'; require_once 'includes/header.php'; require_once 'includes/pages/hr_attendance.php'; require_once 'includes/footer.php'; ?>
|
||||
8
hr_dashboard.php
Normal file
8
hr_dashboard.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
require_once 'includes/auth.php';
|
||||
require_once 'includes/header.php';
|
||||
|
||||
// Logic is in includes/pages/hr_dashboard.php
|
||||
require_once 'includes/pages/hr_dashboard.php';
|
||||
|
||||
require_once 'includes/footer.php';
|
||||
1
hr_leaves.php
Normal file
1
hr_leaves.php
Normal file
@ -0,0 +1 @@
|
||||
<?php require_once 'includes/auth.php'; require_once 'includes/header.php'; require_once 'includes/pages/hr_leaves.php'; require_once 'includes/footer.php'; ?>
|
||||
@ -241,6 +241,21 @@ $site_favicon = !empty($site_settings['company_favicon']) ? $site_settings['comp
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php if (has_permission("users")): // Temporary check, should be hr ?>
|
||||
<a href="#hrSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ["hr_dashboard", "hr_attendance", "hr_leaves"]) ? "active" : ""; ?> d-flex justify-content-between align-items-center">
|
||||
<span><i class="bi bi-person-badge-fill me-2"></i> <?php echo __("hr_management"); ?></span>
|
||||
<i class="bi bi-chevron-down small"></i>
|
||||
</a>
|
||||
<div class="collapse <?php echo in_array($section, ["hr_dashboard", "hr_attendance", "hr_leaves"]) ? "show" : ""; ?>" id="hrSubmenu">
|
||||
<div class="sidebar-submenu">
|
||||
<a href="hr_dashboard.php" class="sidebar-link py-2 <?php echo $section === "hr_dashboard" ? "active" : ""; ?>"><i class="bi bi-speedometer2 me-2"></i> <?php echo __("dashboard"); ?></a>
|
||||
<a href="hr_attendance.php" class="sidebar-link py-2 <?php echo $section === "hr_attendance" ? "active" : ""; ?>"><i class="bi bi-clock-history me-2"></i> <?php echo __("attendance"); ?></a>
|
||||
<a href="hr_leaves.php" class="sidebar-link py-2 <?php echo $section === "hr_leaves" ? "active" : ""; ?>"><i class="bi bi-calendar-range me-2"></i> <?php echo __("leave_requests"); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (has_permission('settings')): ?>
|
||||
<a href="#settingsSubmenu" data-bs-toggle="collapse" class="sidebar-link <?php echo in_array($section, ['employees', 'positions', 'company_profile', 'cities', 'services', 'departments', 'queue_ads']) ? 'active' : ''; ?> d-flex justify-content-between align-items-center">
|
||||
<span><i class="bi bi-gear me-2"></i> <?php echo __('settings'); ?></span>
|
||||
|
||||
194
includes/pages/hr_attendance.php
Normal file
194
includes/pages/hr_attendance.php
Normal file
@ -0,0 +1,194 @@
|
||||
<?php $pdo = $db; ?>
|
||||
<?php
|
||||
if (!is_admin()) {
|
||||
// header('Location: index.php'); exit;
|
||||
}
|
||||
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
$limit = 15;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$where = "1=1";
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['employee_id'])) {
|
||||
$where .= " AND a.employee_id = ?";
|
||||
$params[] = $_GET['employee_id'];
|
||||
}
|
||||
if (!empty($_GET['date'])) {
|
||||
$where .= " AND a.date = ?";
|
||||
$params[] = $_GET['date'];
|
||||
}
|
||||
|
||||
$logs = $pdo->prepare("
|
||||
SELECT a.*, e.name_en, e.name_ar
|
||||
FROM attendance_logs a
|
||||
JOIN employees e ON a.employee_id = e.id
|
||||
WHERE $where
|
||||
ORDER BY a.date DESC, a.check_in DESC
|
||||
LIMIT $limit OFFSET $offset
|
||||
");
|
||||
$logs->execute($params);
|
||||
$logs = $logs->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$total_logs = $pdo->prepare("SELECT COUNT(*) FROM attendance_logs a WHERE $where");
|
||||
$total_logs->execute($params);
|
||||
$total_rows = $total_logs->fetchColumn();
|
||||
$total_pages = ceil($total_rows / $limit);
|
||||
|
||||
$employees = $pdo->query("SELECT id, name_en FROM employees ORDER BY name_en")->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
|
||||
// Handle Manual Add
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_attendance'])) {
|
||||
$emp_id = $_POST['employee_id'];
|
||||
$date = $_POST['date'];
|
||||
$check_in = $_POST['check_in'] ? "$date " . $_POST['check_in'] : null;
|
||||
$check_out = $_POST['check_out'] ? "$date " . $_POST['check_out'] : null;
|
||||
$status = $_POST['status'];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO attendance_logs (employee_id, date, check_in, check_out, status, source) VALUES (?, ?, ?, ?, ?, 'Manual')");
|
||||
$stmt->execute([$emp_id, $date, $check_in, $check_out, $status]);
|
||||
|
||||
echo "<script>window.location.href='hr_attendance.php';</script>";
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Attendance Logs</h1>
|
||||
<button class="btn btn-primary btn-sm" data-toggle="modal" data-target="#addAttendanceModal">
|
||||
<i class="fas fa-plus"></i> Manual Entry
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-body">
|
||||
<form method="GET" class="form-inline">
|
||||
<select name="employee_id" class="form-control mr-2 mb-2">
|
||||
<option value="">All Employees</option>
|
||||
<?php foreach ($employees as $id => $name): ?>
|
||||
<option value="<?php echo $id; ?>" <?php echo (isset($_GET['employee_id']) && $_GET['employee_id'] == $id) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<input type="date" name="date" class="form-control mr-2 mb-2" value="<?php echo $_GET['date'] ?? ''; ?>">
|
||||
<button type="submit" class="btn btn-primary mb-2">Filter</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Employee</th>
|
||||
<th>Date</th>
|
||||
<th>Check In</th>
|
||||
<th>Check Out</th>
|
||||
<th>Status</th>
|
||||
<th>Source</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($logs as $log): ?>
|
||||
<tr>
|
||||
<td><?php echo $log['id']; ?></td>
|
||||
<td><?php echo htmlspecialchars($log['name_en']); ?></td>
|
||||
<td><?php echo $log['date']; ?></td>
|
||||
<td><?php echo $log['check_in'] ? date('H:i', strtotime($log['check_in'])) : '-'; ?></td>
|
||||
<td><?php echo $log['check_out'] ? date('H:i', strtotime($log['check_out'])) : '-'; ?></td>
|
||||
<td>
|
||||
<span class="badge badge-<?php
|
||||
echo $log['status'] == 'Present' ? 'success' :
|
||||
($log['status'] == 'Late' ? 'warning' : 'danger');
|
||||
?>">
|
||||
<?php echo $log['status']; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($log['source']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($logs)): ?>
|
||||
<tr><td colspan="7" class="text-center">No logs found</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<?php if ($total_pages > 1): ?>
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||
<li class="page-item <?php echo $i == $page ? 'active' : ''; ?>">
|
||||
<a class="page-link" href="?page=<?php echo $i; ?>&employee_id=<?php echo $_GET['employee_id'] ?? ''; ?>&date=<?php echo $_GET['date'] ?? ''; ?>">
|
||||
<?php echo $i; ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addAttendanceModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="POST">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add Attendance Log</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="add_attendance" value="1">
|
||||
<div class="form-group">
|
||||
<label>Employee</label>
|
||||
<select name="employee_id" class="form-control" required>
|
||||
<?php foreach ($employees as $id => $name): ?>
|
||||
<option value="<?php echo $id; ?>"><?php echo htmlspecialchars($name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Date</label>
|
||||
<input type="date" name="date" class="form-control" required value="<?php echo date('Y-m-d'); ?>">
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-6">
|
||||
<label>Check In Time</label>
|
||||
<input type="time" name="check_in" class="form-control">
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label>Check Out Time</label>
|
||||
<input type="time" name="check_out" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status</label>
|
||||
<select name="status" class="form-control">
|
||||
<option value="Present">Present</option>
|
||||
<option value="Late">Late</option>
|
||||
<option value="Absent">Absent</option>
|
||||
<option value="On Leave">On Leave</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
205
includes/pages/hr_dashboard.php
Normal file
205
includes/pages/hr_dashboard.php
Normal file
@ -0,0 +1,205 @@
|
||||
<?php $pdo = $db; ?>
|
||||
<?php
|
||||
if (!is_admin()) {
|
||||
// Only admins for now, or roles with permission
|
||||
// header('Location: index.php'); exit;
|
||||
}
|
||||
|
||||
// Fetch Stats
|
||||
$total_employees = $pdo->query("SELECT COUNT(*) FROM employees")->fetchColumn();
|
||||
$present_today = $pdo->query("SELECT COUNT(DISTINCT employee_id) FROM attendance_logs WHERE date = CURDATE() AND status IN ('Present', 'Late')")->fetchColumn();
|
||||
$on_leave_today = $pdo->query("SELECT COUNT(*) FROM leave_requests WHERE CURDATE() BETWEEN start_date AND end_date AND status = 'Approved'")->fetchColumn();
|
||||
$pending_leaves = $pdo->query("SELECT COUNT(*) FROM leave_requests WHERE status = 'Pending'")->fetchColumn();
|
||||
|
||||
// Recent Attendance
|
||||
$recent_attendance = $pdo->query("
|
||||
SELECT a.*, e.name_en, e.name_ar
|
||||
FROM attendance_logs a
|
||||
JOIN employees e ON a.employee_id = e.id
|
||||
ORDER BY a.created_at DESC
|
||||
LIMIT 5
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">HR Dashboard</h1>
|
||||
<div>
|
||||
<a href="employees.php" class="btn btn-sm btn-primary shadow-sm"><i class="fas fa-users fa-sm text-white-50"></i> Employees</a>
|
||||
<a href="hr_attendance.php" class="btn btn-sm btn-info shadow-sm"><i class="fas fa-clock fa-sm text-white-50"></i> Attendance</a>
|
||||
<a href="hr_leaves.php" class="btn btn-sm btn-warning shadow-sm"><i class="fas fa-calendar-minus fa-sm text-white-50"></i> Leave Requests</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Rows -->
|
||||
<div class="row">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-primary shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Total Employees</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo $total_employees; ?></div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-users fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-success shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Present Today</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo $present_today; ?></div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-user-check fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-info shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">On Leave Today</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo $on_leave_today; ?></div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-plane-departure fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-warning shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Pending Requests</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo $pending_leaves; ?></div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-clipboard-list fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Recent Attendance -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Recent Attendance Logs</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Employee</th>
|
||||
<th>Status</th>
|
||||
<th>Time</th>
|
||||
<th>Source</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($recent_attendance as $log): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($log['name_en']); ?></td>
|
||||
<td>
|
||||
<span class="badge badge-<?php
|
||||
echo $log['status'] == 'Present' ? 'success' :
|
||||
($log['status'] == 'Late' ? 'warning' : 'danger');
|
||||
?>">
|
||||
<?php echo $log['status']; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
In: <?php echo $log['check_in'] ? date('H:i', strtotime($log['check_in'])) : '-'; ?><br>
|
||||
Out: <?php echo $log['check_out'] ? date('H:i', strtotime($log['check_out'])) : '-'; ?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($log['source']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if(empty($recent_attendance)): ?>
|
||||
<tr><td colspan="4" class="text-center">No logs today</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Biometric Simulator -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-info">Simulate Biometric Device Push</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Use this form to test biometric integration. In a real scenario, the device POSTs to <code>/api/biometric_push.php</code>.</p>
|
||||
<form id="biometricSimForm">
|
||||
<div class="form-group">
|
||||
<label>Employee ID</label>
|
||||
<input type="number" class="form-control" name="employee_id" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Type</label>
|
||||
<select class="form-control" name="type">
|
||||
<option value="check_in">Check In</option>
|
||||
<option value="check_out">Check Out</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Simulate Push</button>
|
||||
</form>
|
||||
<div id="simResult" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('biometricSimForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const data = {
|
||||
api_key: 'test_key', // You would configure this per device
|
||||
employee_id: formData.get('employee_id'),
|
||||
type: formData.get('type'),
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
fetch('api/biometric_push.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
const resDiv = document.getElementById('simResult');
|
||||
if(d.success) {
|
||||
resDiv.innerHTML = '<div class="alert alert-success">Success: ' + d.message + '</div>';
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
} else {
|
||||
resDiv.innerHTML = '<div class="alert alert-danger">Error: ' + (d.error || 'Unknown') + '</div>';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
217
includes/pages/hr_leaves.php
Normal file
217
includes/pages/hr_leaves.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php $pdo = $db; ?>
|
||||
<?php
|
||||
if (!is_admin()) {
|
||||
// header('Location: index.php'); exit;
|
||||
}
|
||||
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
$limit = 15;
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$where = "1=1";
|
||||
$params = [];
|
||||
|
||||
if (!empty($_GET['status'])) {
|
||||
$where .= " AND l.status = ?";
|
||||
$params[] = $_GET['status'];
|
||||
}
|
||||
|
||||
$logs = $pdo->prepare("
|
||||
SELECT l.*, e.name_en
|
||||
FROM leave_requests l
|
||||
JOIN employees e ON l.employee_id = e.id
|
||||
WHERE $where
|
||||
ORDER BY l.created_at DESC
|
||||
LIMIT $limit OFFSET $offset
|
||||
");
|
||||
$logs->execute($params);
|
||||
$requests = $logs->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$total_logs = $pdo->prepare("SELECT COUNT(*) FROM leave_requests l WHERE $where");
|
||||
$total_logs->execute($params);
|
||||
$total_rows = $total_logs->fetchColumn();
|
||||
$total_pages = ceil($total_rows / $limit);
|
||||
|
||||
$employees = $pdo->query("SELECT id, name_en FROM employees ORDER BY name_en")->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
|
||||
// Handle Actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['add_leave'])) {
|
||||
$emp_id = $_POST['employee_id'];
|
||||
$type = $_POST['leave_type'];
|
||||
$start = $_POST['start_date'];
|
||||
$end = $_POST['end_date'];
|
||||
$reason = $_POST['reason'];
|
||||
|
||||
$diff = strtotime($end) - strtotime($start);
|
||||
$days = round($diff / (60 * 60 * 24)) + 1;
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO leave_requests (employee_id, leave_type, start_date, end_date, days, reason, status) VALUES (?, ?, ?, ?, ?, ?, 'Pending')");
|
||||
$stmt->execute([$emp_id, $type, $start, $end, $days, $reason]);
|
||||
} elseif (isset($_POST['approve_leave'])) {
|
||||
$stmt = $pdo->prepare("UPDATE leave_requests SET status = 'Approved' WHERE id = ?");
|
||||
$stmt->execute([$_POST['id']]);
|
||||
} elseif (isset($_POST['reject_leave'])) {
|
||||
$stmt = $pdo->prepare("UPDATE leave_requests SET status = 'Rejected' WHERE id = ?");
|
||||
$stmt->execute([$_POST['id']]);
|
||||
} elseif (isset($_POST['delete_leave'])) {
|
||||
$stmt = $pdo->prepare("DELETE FROM leave_requests WHERE id = ?");
|
||||
$stmt->execute([$_POST['id']]);
|
||||
}
|
||||
|
||||
echo "<script>window.location.href='hr_leaves.php';</script>";
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">Leave Requests</h1>
|
||||
<button class="btn btn-primary btn-sm" data-toggle="modal" data-target="#addLeaveModal">
|
||||
<i class="fas fa-plus"></i> New Request
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-body">
|
||||
<form method="GET" class="form-inline">
|
||||
<select name="status" class="form-control mr-2 mb-2">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="Pending" <?php echo (isset($_GET['status']) && $_GET['status'] == 'Pending') ? 'selected' : ''; ?>>Pending</option>
|
||||
<option value="Approved" <?php echo (isset($_GET['status']) && $_GET['status'] == 'Approved') ? 'selected' : ''; ?>>Approved</option>
|
||||
<option value="Rejected" <?php echo (isset($_GET['status']) && $_GET['status'] == 'Rejected') ? 'selected' : ''; ?>>Rejected</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary mb-2">Filter</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Employee</th>
|
||||
<th>Type</th>
|
||||
<th>Duration</th>
|
||||
<th>Days</th>
|
||||
<th>Reason</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($requests as $req): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($req['name_en']); ?></td>
|
||||
<td><?php echo htmlspecialchars($req['leave_type']); ?></td>
|
||||
<td>
|
||||
<?php echo $req['start_date']; ?> to <?php echo $req['end_date']; ?>
|
||||
</td>
|
||||
<td><?php echo $req['days']; ?></td>
|
||||
<td><?php echo htmlspecialchars($req['reason']); ?></td>
|
||||
<td>
|
||||
<span class="badge badge-<?php
|
||||
echo $req['status'] == 'Approved' ? 'success' :
|
||||
($req['status'] == 'Pending' ? 'warning' : 'danger');
|
||||
?>">
|
||||
<?php echo $req['status']; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<?php if($req['status'] == 'Pending'): ?>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="id" value="<?php echo $req['id']; ?>">
|
||||
<button type="submit" name="approve_leave" class="btn btn-success btn-sm" title="Approve"><i class="fas fa-check"></i></button>
|
||||
</form>
|
||||
<form method="POST" style="display:inline;">
|
||||
<input type="hidden" name="id" value="<?php echo $req['id']; ?>">
|
||||
<button type="submit" name="reject_leave" class="btn btn-danger btn-sm" title="Reject"><i class="fas fa-times"></i></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<form method="POST" style="display:inline;" onsubmit="return confirm('Delete this request?');">
|
||||
<input type="hidden" name="id" value="<?php echo $req['id']; ?>">
|
||||
<button type="submit" name="delete_leave" class="btn btn-secondary btn-sm" title="Delete"><i class="fas fa-trash"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($requests)): ?>
|
||||
<tr><td colspan="7" class="text-center">No requests found</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
<?php if ($total_pages > 1): ?>
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||
<li class="page-item <?php echo $i == $page ? 'active' : ''; ?>">
|
||||
<a class="page-link" href="?page=<?php echo $i; ?>&status=<?php echo $_GET['status'] ?? ''; ?>">
|
||||
<?php echo $i; ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addLeaveModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form method="POST">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Request Leave</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="add_leave" value="1">
|
||||
<div class="form-group">
|
||||
<label>Employee</label>
|
||||
<select name="employee_id" class="form-control" required>
|
||||
<?php foreach ($employees as $id => $name): ?>
|
||||
<option value="<?php echo $id; ?>"><?php echo htmlspecialchars($name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Leave Type</label>
|
||||
<select name="leave_type" class="form-control">
|
||||
<option value="Annual">Annual</option>
|
||||
<option value="Sick">Sick</option>
|
||||
<option value="Casual">Casual</option>
|
||||
<option value="Unpaid">Unpaid</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-6">
|
||||
<label>Start Date</label>
|
||||
<input type="date" name="start_date" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label>End Date</label>
|
||||
<input type="date" name="end_date" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Reason</label>
|
||||
<textarea name="reason" class="form-control" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
10
lang.php
10
lang.php
@ -340,7 +340,10 @@ $translations = [
|
||||
'add_doctor' => 'Add Doctor',
|
||||
'specialization_en' => 'Specialization (English)',
|
||||
'specialization_ar' => 'Specialization (Arabic)',
|
||||
'edit_doctor' => 'Edit Doctor'
|
||||
'edit_doctor' => 'Edit Doctor',
|
||||
'hr_management' => 'HR Management',
|
||||
'attendance' => 'Attendance',
|
||||
'leave_requests' => 'Leave Requests',
|
||||
],
|
||||
'ar' => [
|
||||
'dashboard' => 'لوحة التحكم',
|
||||
@ -682,6 +685,9 @@ $translations = [
|
||||
'add_doctor' => 'إضافة طبيب',
|
||||
'edit_doctor' => 'تعديل طبيب',
|
||||
'specialization_en' => 'التخصص (إنجليزي)',
|
||||
'specialization_ar' => 'التخصص (عربي)'
|
||||
'specialization_ar' => 'التخصص (عربي)',
|
||||
'hr_management' => 'إدارة الموارد البشرية',
|
||||
'attendance' => 'الحضور والانصراف',
|
||||
'leave_requests' => 'طلبات الإجازة',
|
||||
]
|
||||
];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user