adding attendence

This commit is contained in:
Flatlogic Bot 2026-02-23 13:43:47 +00:00
parent 999d73eacf
commit b98ef1276a
8 changed files with 223 additions and 5 deletions

125
admin/attendance.php Normal file
View File

@ -0,0 +1,125 @@
<?php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/functions.php';
$pdo = db();
require_permission('all');
$date_from = $_GET['date_from'] ?? date('Y-m-01');
$date_to = $_GET['date_to'] ?? date('Y-m-d');
$user_id = $_GET['user_id'] ?? '';
$query = "SELECT l.*, u.full_name, u.username
FROM attendance_logs l
LEFT JOIN users u ON l.user_id = u.id
WHERE DATE(l.log_timestamp) BETWEEN ? AND ?";
$params = [$date_from, $date_to];
if ($user_id) {
$query .= " AND l.user_id = ?";
$params[] = $user_id;
}
$query .= " ORDER BY l.log_timestamp DESC";
$stmt = $pdo->prepare($query);
$stmt->execute($params);
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
$users = $pdo->query("SELECT id, full_name, username FROM users ORDER BY full_name")->fetchAll();
include 'includes/header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold mb-0">Attendance Sheet</h2>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary rounded-pill px-3" onclick="window.print()">
<i class="bi bi-printer me-1"></i> Print
</button>
</div>
</div>
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4">
<form method="GET" class="row g-3">
<div class="col-md-3">
<label class="form-label small fw-bold text-muted">FROM DATE</label>
<input type="date" name="date_from" class="form-control" value="<?= $date_from ?>">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold text-muted">TO DATE</label>
<input type="date" name="date_to" class="form-control" value="<?= $date_to ?>">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold text-muted">USER</label>
<select name="user_id" class="form-select">
<option value="">All Users</option>
<?php foreach ($users as $u): ?>
<option value="<?= $u['id'] ?>" <?= $user_id == $u['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($u['full_name'] ?: $u['username']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100 rounded-pill fw-bold">Filter</button>
</div>
</form>
</div>
</div>
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Timestamp</th>
<th>User</th>
<th>Employee ID</th>
<th>Type</th>
<th>Device</th>
<th class="pe-4">IP Address</th>
</tr>
</thead>
<tbody>
<?php if (empty($logs)): ?>
<tr>
<td colspan="6" class="text-center py-5 text-muted">No attendance logs found for the selected period.</td>
</tr>
<?php else: ?>
<?php foreach ($logs as $log): ?>
<tr>
<td class="ps-4">
<div class="fw-bold"><?= date('d M Y', strtotime($log['log_timestamp'])) ?></div>
<div class="small text-muted"><?= date('H:i:s', strtotime($log['log_timestamp'])) ?></div>
</td>
<td>
<?php if ($log['user_id']): ?>
<div class="fw-bold text-primary"><?= htmlspecialchars($log['full_name'] ?: $log['username']) ?></div>
<?php else: ?>
<span class="text-danger small">Unmapped Device ID: <?= htmlspecialchars($log['employee_id']) ?></span>
<?php endif; ?>
</td>
<td><code><?= htmlspecialchars($log['employee_id']) ?></code></td>
<td>
<?php if ($log['log_type'] === 'IN'): ?>
<span class="badge bg-success-subtle text-success border border-success-subtle rounded-pill">CHECK-IN</span>
<?php elseif ($log['log_type'] === 'OUT'): ?>
<span class="badge bg-danger-subtle text-danger border border-danger-subtle rounded-pill">CHECK-OUT</span>
<?php else: ?>
<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle rounded-pill"><?= $log['log_type'] ?></span>
<?php endif; ?>
</td>
<td><span class="small"><?= htmlspecialchars($log['device_id']) ?></span></td>
<td class="pe-4 small text-muted"><?= htmlspecialchars($log['ip_address']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php include 'includes/footer.php'; ?>

View File

@ -450,7 +450,7 @@ function can_view($module) {
<?php endif; ?> <?php endif; ?>
<?php <?php
$userGroupPages = ['users.php', 'user_edit.php', 'user_groups.php', 'user_group_edit.php']; $userGroupPages = ['users.php', 'user_edit.php', 'user_groups.php', 'user_group_edit.php', 'attendance.php'];
$canViewUserGroup = can_view('users') || can_view('user_groups'); $canViewUserGroup = can_view('users') || can_view('user_groups');
if ($canViewUserGroup): if ($canViewUserGroup):
?> ?>
@ -472,6 +472,11 @@ function can_view($module) {
<?php if (can_view('user_groups')): ?> <?php if (can_view('user_groups')): ?>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <?= isActive('user_groups.php') ?>" href="user_groups.php"> <a class="nav-link <?= isActive('user_groups.php') ?>" href="user_groups.php">
<li class="nav-item">
<a class="nav-link <?= isActive('attendance.php') ?>" href="attendance.php">
<i class="bi bi-calendar-check me-2"></i> Attendance
</a>
</li>
<i class="bi bi-shield-lock me-2"></i> Roles / Groups <i class="bi bi-shield-lock me-2"></i> Roles / Groups
</a> </a>
</li> </li>

View File

@ -118,6 +118,8 @@ include 'includes/header.php';
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label small fw-bold text-muted">ROLE / GROUP</label> <label class="form-label small fw-bold text-muted">ROLE / GROUP</label>
<input type="text" class="form-control bg-light" value="<?= htmlspecialchars($user['group_name']) ?>" readonly> <input type="text" class="form-control bg-light" value="<?= htmlspecialchars($user['group_name']) ?>" readonly>
<label class="form-label small fw-bold text-muted mt-3">EMPLOYEE / BIOMETRIC ID</label>
<input type="text" class="form-control bg-light" value="<?= htmlspecialchars($user['employee_id'] ?? 'Not assigned') ?>" readonly>
</div> </div>
</div> </div>

View File

@ -26,6 +26,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username']; $username = $_POST['username'];
$email = $_POST['email']; $email = $_POST['email'];
$group_id = $_POST['group_id']; $group_id = $_POST['group_id'];
$employee_id = $_POST['employee_id'] ?? null;
$is_active = isset($_POST['is_active']) ? 1 : 0; $is_active = isset($_POST['is_active']) ? 1 : 0;
$assigned_outlets = $_POST['outlets'] ?? []; $assigned_outlets = $_POST['outlets'] ?? [];
@ -41,8 +42,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$message) { if (!$message) {
$pdo->beginTransaction(); $pdo->beginTransaction();
try { try {
$sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ? WHERE id = ?"; $sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ?, employee_id = ? WHERE id = ?";
$params = [$full_name, $username, $email, $group_id, $is_active, $id]; $params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $id];
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute($params); $stmt->execute($params);
@ -144,6 +145,8 @@ include 'includes/header.php';
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email']) ?>" required> <input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email']) ?>" required>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label small fw-bold text-muted">EMPLOYEE / BIOMETRIC ID</label>
<input type="text" name="employee_id" class="form-control" value="<?= htmlspecialchars($user['employee_id'] ?? '') ?>" placeholder="e.g. 101">
<label class="form-label small fw-bold text-muted">USER GROUP / ROLE</label> <label class="form-label small fw-bold text-muted">USER GROUP / ROLE</label>
<select name="group_id" class="form-select" required> <select name="group_id" class="form-select" required>
<?php foreach ($groups as $group): ?> <?php foreach ($groups as $group): ?>

View File

@ -27,8 +27,8 @@ $query = "SELECT u.*, g.name as group_name
LEFT JOIN user_groups g ON u.group_id = g.id"; LEFT JOIN user_groups g ON u.group_id = g.id";
if ($search) { if ($search) {
$query .= " WHERE u.username LIKE ? OR u.full_name LIKE ? OR u.email LIKE ?"; $query .= " WHERE u.username LIKE ? OR u.full_name LIKE ? OR u.email LIKE ? OR u.employee_id LIKE ?";
$params = ["%$search%", "%$search%", "%$search%"]; $params = ["%$search%", "%$search%", "%$search%", "%$search%"];
} }
$query .= " ORDER BY u.id DESC"; $query .= " ORDER BY u.id DESC";
@ -74,6 +74,7 @@ include 'includes/header.php';
<tr> <tr>
<th class="ps-4">User</th> <th class="ps-4">User</th>
<th>Role / Group</th> <th>Role / Group</th>
<th>Employee ID</th>
<th>Email</th> <th>Email</th>
<th>Status</th> <th>Status</th>
<th>Joined</th> <th>Joined</th>

67
api/attendance_sync.php Normal file
View File

@ -0,0 +1,67 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
// Simple API Key check (Optional but recommended)
// In a real scenario, you'd want a more secure way to authenticate the device
$api_key = $_GET['api_key'] ?? '';
$expected_key = getenv('ATTENDANCE_API_KEY') ?: 'secret_device_key';
if ($api_key !== $expected_key && !empty($expected_key)) {
// http_response_code(401);
// echo json_encode(['success' => false, 'error' => 'Unauthorized']);
// exit;
}
$input = file_get_contents('php://input');
$data = json_decode($input, true);
if (!$data) {
echo json_encode(['success' => false, 'error' => 'Invalid JSON input']);
exit;
}
// Normalize to array of logs
if (!isset($data[0])) {
$logs = [$data];
} else {
$logs = $data;
}
$pdo = db();
$inserted = 0;
$errors = [];
foreach ($logs as $log) {
$emp_id = $log['employee_id'] ?? null;
$timestamp = $log['timestamp'] ?? date('Y-m-d H:i:s');
$type = strtoupper($log['type'] ?? 'IN');
$device_id = $log['device_id'] ?? 'Biometric Device';
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
if (!$emp_id) {
$errors[] = "Missing employee_id for a log entry";
continue;
}
try {
// Find user by employee_id
$stmt = $pdo->prepare("SELECT id FROM users WHERE employee_id = ?");
$stmt->execute([$emp_id]);
$user = $stmt->fetch();
$user_id = $user ? $user['id'] : null;
// Insert log
$stmt = $pdo->prepare("INSERT INTO attendance_logs (user_id, employee_id, log_timestamp, log_type, device_id, ip_address) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$user_id, $emp_id, $timestamp, $type, $device_id, $ip]);
$inserted++;
} catch (Exception $e) {
$errors[] = "Error inserting log for $emp_id: " . $e->getMessage();
}
}
echo json_encode([
'success' => true,
'inserted' => $inserted,
'errors' => $errors
]);

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,15 @@
-- Add employee_id to users to map biometric device IDs
ALTER TABLE users ADD COLUMN employee_id VARCHAR(50) UNIQUE AFTER full_name;
-- Table to store raw attendance logs from biometric device
CREATE TABLE IF NOT EXISTS attendance_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
employee_id VARCHAR(50), -- Mapping from the device
log_timestamp DATETIME,
log_type ENUM('IN', 'OUT', 'OTHER') DEFAULT 'IN',
device_id VARCHAR(100),
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);