add paginations

This commit is contained in:
Flatlogic Bot 2026-03-27 09:36:07 +00:00
parent 4ee179ebfe
commit 1b3577b917
5 changed files with 237 additions and 30 deletions

View File

@ -1,6 +1,7 @@
<?php
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/accounting_functions.php'; // Include accounting helpers
require_once __DIR__ . '/includes/pagination.php';
if (!canView('expenses')) {
redirect('index.php');
@ -152,28 +153,56 @@ $date_to = $_GET['date_to'] ?? date('Y-m-t');
$category_filter = $_GET['category_id'] ?? '';
$search = $_GET['search'] ?? '';
// Build WHERE clause
$whereConditions = ["e.date BETWEEN ? AND ?"];
$params = [$date_from, $date_to];
if ($category_filter) {
$whereConditions[] = "e.category_id = ?";
$params[] = $category_filter;
}
if ($search) {
$whereConditions[] = "(e.description LIKE ? OR e.vendor LIKE ? OR e.reference LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$whereClause = implode(' AND ', $whereConditions);
// Pagination
$page = $_GET['page'] ?? 1;
$perPage = 10;
// Count Total Items & Sum Total Amount
$countSql = "SELECT COUNT(*) as count, SUM(amount) as total_amount FROM expenses e WHERE $whereClause";
$countStmt = db()->prepare($countSql);
$countStmt->execute($params);
$countResult = $countStmt->fetch(PDO::FETCH_ASSOC);
$totalExpenses = $countResult['count'];
$grandTotalAmount = $countResult['total_amount'] ?? 0;
$pagination = getPagination($page, $totalExpenses, $perPage);
// Fetch Items
$sql = "SELECT e.*, c.name as category_name, u.username as created_by_name
FROM expenses e
LEFT JOIN expense_categories c ON e.category_id = c.id
LEFT JOIN users u ON e.user_id = u.id
WHERE e.date BETWEEN ? AND ?";
$params = [$date_from, $date_to];
WHERE $whereClause
ORDER BY e.date DESC, e.id DESC
LIMIT ? OFFSET ?";
if ($category_filter) {
$sql .= " AND e.category_id = ?";
$params[] = $category_filter;
}
if ($search) {
$sql .= " AND (e.description LIKE ? OR e.vendor LIKE ? OR e.reference LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$sql .= " ORDER BY e.date DESC, e.id DESC";
// Add LIMIT/OFFSET to params
$params[] = $pagination['limit'];
$params[] = $pagination['offset'];
$stmt = db()->prepare($sql);
$stmt->execute($params);
foreach ($params as $k => $v) {
$type = is_int($v) ? PDO::PARAM_INT : PDO::PARAM_STR;
$stmt->bindValue($k + 1, $v, $type);
}
$stmt->execute();
$expenses = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch Categories for Dropdown
@ -299,9 +328,16 @@ if (isset($_SESSION['success'])) {
</td>
</tr>
<?php endforeach; ?>
<tr class="bg-light fw-bold">
<td colspan="4" class="text-end">الإجمالي:</td>
<td class="text-danger"><?= number_format(array_sum(array_column($expenses, 'amount')), 2) ?></td>
<!-- Page Total -->
<tr class="bg-light">
<td colspan="4" class="text-end fw-bold">إجمالي الصفحة:</td>
<td class="text-danger fw-bold"><?= number_format(array_sum(array_column($expenses, 'amount')), 2) ?></td>
<td colspan="3"></td>
</tr>
<!-- Grand Total -->
<tr class="bg-light border-top-0">
<td colspan="4" class="text-end fw-bold">الإجمالي الكلي (للبحث الحالي):</td>
<td class="text-danger fw-bold"><?= number_format($grandTotalAmount, 2) ?></td>
<td colspan="3"></td>
</tr>
<?php endif; ?>
@ -309,6 +345,9 @@ if (isset($_SESSION['success'])) {
</table>
</div>
</div>
<div class="card-footer bg-white">
<?= renderPagination($pagination['current_page'], $pagination['total_pages']) ?>
</div>
</div>
<!-- Modal -->

View File

@ -1,5 +1,6 @@
<?php
require_once 'includes/header.php';
require_once __DIR__ . '/includes/pagination.php';
// Check Permission
if (!canView('hr_employees')) {
@ -85,6 +86,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Fetch Departments for Dropdown
$departments = db()->query("SELECT * FROM hr_departments ORDER BY name")->fetchAll();
// Pagination
$page = $_GET['page'] ?? 1;
$perPage = 10;
$totalEmployees = db()->query("SELECT COUNT(*) FROM hr_employees")->fetchColumn();
$pagination = getPagination($page, $totalEmployees, $perPage);
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
@ -224,7 +231,7 @@ $departments = db()->query("SELECT * FROM hr_departments ORDER BY name")->fetchA
</div>
<!-- List View -->
<div class="card shadow-sm">
<div class="card shadow-sm mb-4">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
@ -240,8 +247,16 @@ $departments = db()->query("SELECT * FROM hr_departments ORDER BY name")->fetchA
</thead>
<tbody>
<?php
$sql = "SELECT e.*, d.name as dept_name FROM hr_employees e LEFT JOIN hr_departments d ON e.department_id = d.id ORDER BY e.first_name";
$stmt = db()->query($sql);
$sql = "SELECT e.*, d.name as dept_name
FROM hr_employees e
LEFT JOIN hr_departments d ON e.department_id = d.id
ORDER BY e.first_name
LIMIT ? OFFSET ?";
$stmt = db()->prepare($sql);
$stmt->bindValue(1, $pagination['limit'], PDO::PARAM_INT);
$stmt->bindValue(2, $pagination['offset'], PDO::PARAM_INT);
$stmt->execute();
while ($row = $stmt->fetch()):
?>
<tr>
@ -307,6 +322,9 @@ $departments = db()->query("SELECT * FROM hr_departments ORDER BY name")->fetchA
</table>
</div>
</div>
<div class="card-footer bg-white">
<?= renderPagination($pagination['current_page'], $pagination['total_pages']) ?>
</div>
</div>
<script>

100
includes/pagination.php Normal file
View File

@ -0,0 +1,100 @@
<?php
/**
* Calculate pagination parameters.
*
* @param int $page Current page number (1-based)
* @param int $total Total number of items
* @param int $perPage Items per page (default 10)
* @return array ['offset' => int, 'limit' => int, 'total_pages' => int, 'current_page' => int]
*/
function getPagination($page, $total, $perPage = 10) {
$page = max(1, (int)$page);
$perPage = max(1, (int)$perPage);
$totalPages = ceil($total / $perPage);
// Ensure page doesn't exceed total pages (unless total is 0)
if ($totalPages > 0 && $page > $totalPages) {
$page = $totalPages;
}
$offset = ($page - 1) * $perPage;
return [
'offset' => $offset,
'limit' => $perPage,
'total_pages' => $totalPages,
'current_page' => $page
];
}
/**
* Helper to build pagination link with existing query params
*/
function getPaginationLink($page, $queryParams = []) {
$params = array_merge($_GET, $queryParams);
$params['page'] = $page;
return '?' . http_build_query($params);
}
/**
* Render pagination links using Bootstrap 5.
*
* @param int $currentPage
* @param int $totalPages
* @return string HTML for pagination
*/
function renderPagination($currentPage, $totalPages) {
if ($totalPages <= 1) {
return '';
}
$html = '<nav aria-label="Page navigation" class="mt-4"><ul class="pagination justify-content-center">';
// Previous Button
if ($currentPage > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . getPaginationLink($currentPage - 1) . '">السابق</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">السابق</span></li>';
}
// Page Numbers
// Simple logic: Show all if <= 7, else show start, end, and around current
// For simplicity in this iteration, let's show a sliding window or just simple list
// Let's do a simple sliding window of 5
$start = max(1, $currentPage - 2);
$end = min($totalPages, $currentPage + 2);
if ($start > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . getPaginationLink(1) . '">1</a></li>';
if ($start > 2) {
$html .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
}
for ($i = $start; $i <= $end; $i++) {
$active = ($i == $currentPage) ? 'active' : '';
if ($i == $currentPage) {
$html .= '<li class="page-item active"><span class="page-link">' . $i . '</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . getPaginationLink($i) . '">' . $i . '</a></li>';
}
}
if ($end < $totalPages) {
if ($end < $totalPages - 1) {
$html .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
$html .= '<li class="page-item"><a class="page-link" href="' . getPaginationLink($totalPages) . '">' . $totalPages . '</a></li>';
}
// Next Button
if ($currentPage < $totalPages) {
$html .= '<li class="page-item"><a class="page-link" href="' . getPaginationLink($currentPage + 1) . '">التالي</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">التالي</span></li>';
}
$html .= '</ul></nav>';
return $html;
}

View File

@ -1,6 +1,7 @@
<?php
ob_start();
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/pagination.php';
if (!canView('meetings')) {
redirect('index.php');
@ -67,27 +68,60 @@ $date_to = $_GET['date_to'] ?? date('Y-m-t');
$status_filter = $_GET['status'] ?? '';
$search = $_GET['search'] ?? '';
$sql = "SELECT m.*, u.username as created_by_name
FROM meetings m
LEFT JOIN users u ON m.created_by = u.id
WHERE DATE(m.start_time) BETWEEN ? AND ?";
// Base WHERE conditions
$whereConditions = ["DATE(m.start_time) BETWEEN ? AND ?"];
$params = [$date_from, $date_to];
if ($status_filter) {
$sql .= " AND m.status = ?";
$whereConditions[] = "m.status = ?";
$params[] = $status_filter;
}
if ($search) {
$sql .= " AND (m.title LIKE ? OR m.description LIKE ? OR m.location LIKE ?)";
$whereConditions[] = "(m.title LIKE ? OR m.description LIKE ? OR m.location LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$sql .= " ORDER BY m.start_time ASC";
$whereClause = implode(' AND ', $whereConditions);
// Pagination
$page = $_GET['page'] ?? 1;
$perPage = 10;
// Count Total Items
$countSql = "SELECT COUNT(*) FROM meetings m WHERE $whereClause";
$countStmt = db()->prepare($countSql);
$countStmt->execute($params);
$totalMeetings = $countStmt->fetchColumn();
$pagination = getPagination($page, $totalMeetings, $perPage);
// Fetch Items with Limit
$sql = "SELECT m.*, u.username as created_by_name
FROM meetings m
LEFT JOIN users u ON m.created_by = u.id
WHERE $whereClause
ORDER BY m.start_time ASC
LIMIT ? OFFSET ?";
// Add LIMIT and OFFSET to params
$params[] = $pagination['limit'];
$params[] = $pagination['offset'];
$stmt = db()->prepare($sql);
$stmt->execute($params);
// Bind params manually because limit/offset must be integers
// But wait, $params is mixed string/int.
// PDO::execute($params) treats all as strings which is fine for limit/offset in MySQL usually,
// but strictly speaking, LIMIT/OFFSET should be ints.
// Let's bind all params.
foreach ($params as $k => $v) {
// 1-based index
$type = is_int($v) ? PDO::PARAM_INT : PDO::PARAM_STR;
$stmt->bindValue($k + 1, $v, $type);
}
$stmt->execute();
$meetings = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (isset($_SESSION['success'])) {
@ -244,6 +278,9 @@ if (isset($_SESSION['success'])) {
</table>
</div>
</div>
<div class="card-footer bg-white">
<?= renderPagination($pagination['current_page'], $pagination['total_pages']) ?>
</div>
</div>
<!-- Modal -->

View File

@ -1,5 +1,6 @@
<?php
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/pagination.php';
if (!canView('users')) {
redirect('index.php');
@ -174,7 +175,16 @@ if (isset($_SESSION['error'])) {
unset($_SESSION['error']);
}
$stmt = db()->query("SELECT * FROM users ORDER BY created_at DESC");
// Pagination
$page = $_GET['page'] ?? 1;
$perPage = 10;
$totalUsers = db()->query("SELECT COUNT(*) FROM users")->fetchColumn();
$pagination = getPagination($page, $totalUsers, $perPage);
$stmt = db()->prepare("SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?");
$stmt->bindValue(1, $pagination['limit'], PDO::PARAM_INT);
$stmt->bindValue(2, $pagination['offset'], PDO::PARAM_INT);
$stmt->execute();
$users = $stmt->fetchAll();
// Fetch permissions for all users
@ -278,6 +288,9 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
</table>
</div>
</div>
<div class="card-footer bg-white">
<?= renderPagination($pagination['current_page'], $pagination['total_pages']) ?>
</div>
</div>
<!-- User Modal -->