add paginations to all modules

This commit is contained in:
Flatlogic Bot 2026-03-27 09:55:11 +00:00
parent 1b3577b917
commit 7828e2ad4e
10 changed files with 437 additions and 50 deletions

View File

@ -2,6 +2,7 @@
require_once 'db/config.php'; require_once 'db/config.php';
require_once 'includes/header.php'; require_once 'includes/header.php';
require_once 'includes/accounting_functions.php'; require_once 'includes/accounting_functions.php';
require_once 'includes/pagination.php';
// Check permission // Check permission
$user_id = $_SESSION['user_id'] ?? 0; $user_id = $_SESSION['user_id'] ?? 0;
@ -60,18 +61,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
// Pagination and Filtering setup // Pagination and Filtering setup
$page = isset($_GET['p']) ? (int)$_GET['p'] : 1; $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; // Standardized to 'page'
if ($page < 1) $page = 1;
$limit = 10; $limit = 10;
$offset = ($page - 1) * $limit; $offset = ($page - 1) * $limit;
$search = $_GET['search'] ?? ''; $search = $_GET['search'] ?? '';
$date_from = $_GET['date_from'] ?? ''; $date_from = $_GET['date_from'] ?? '';
$date_to = $_GET['date_to'] ?? ''; $date_to = $_GET['date_to'] ?? '';
// Fetch ledger data with filters // Fetch ledger data with filters using optimized functions
$ledger_all = get_full_ledger_filtered($search, $date_from, $date_to); $totalFiltered = get_ledger_count($search, $date_from, $date_to);
$total_items = count($ledger_all); $ledger = get_ledger_paginated($search, $date_from, $date_to, $limit, $offset);
$total_pages = ceil($total_items / $limit);
$ledger = array_slice($ledger_all, $offset, $limit);
?> ?>
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
@ -237,6 +239,9 @@ $ledger = array_slice($ledger_all, $offset, $limit);
<table class="table table-hover table-bordered text-right align-middle table-sm"> <table class="table table-hover table-bordered text-right align-middle table-sm">
<thead class="table-light"><tr><th>التاريخ</th><th>الوصف</th><th>المرجع</th><th>الحساب</th><th>مدين</th><th>دائن</th><th>الإجراءات</th></tr></thead> <thead class="table-light"><tr><th>التاريخ</th><th>الوصف</th><th>المرجع</th><th>الحساب</th><th>مدين</th><th>دائن</th><th>الإجراءات</th></tr></thead>
<tbody> <tbody>
<?php if(empty($ledger)): ?>
<tr><td colspan="7" class="text-center">لا توجد قيود.</td></tr>
<?php endif; ?>
<?php foreach ($ledger as $row): ?> <?php foreach ($ledger as $row): ?>
<tr> <tr>
<td><?= htmlspecialchars($row['date']) ?></td> <td><?= htmlspecialchars($row['date']) ?></td>
@ -262,15 +267,8 @@ $ledger = array_slice($ledger_all, $offset, $limit);
</table> </table>
</div> </div>
<nav> <!-- Pagination -->
<ul class="pagination pagination-sm justify-content-center"> <?php renderPagination($page, $totalFiltered, $limit); ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?= $i == $page ? 'active' : '' ?>">
<a class="page-link" href="?p=<?= $i ?>&search=<?= urlencode($search) ?>&date_from=<?= urlencode($date_from) ?>&date_to=<?= urlencode($date_to) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
<?php <?php
require_once 'includes/header.php'; require_once 'includes/header.php';
require_once 'includes/pagination.php';
if (!canView('hr_attendance')) { if (!canView('hr_attendance')) {
echo "<div class='alert alert-danger'>ليس لديك صلاحية للوصول إلى هذه الصفحة.</div>"; echo "<div class='alert alert-danger'>ليس لديك صلاحية للوصول إلى هذه الصفحة.</div>";
@ -44,13 +45,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_attendance'])) {
} }
} }
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
// Count Total Employees
$countStmt = db()->prepare("SELECT COUNT(*) FROM hr_employees WHERE status = 'active'");
$countStmt->execute();
$totalFiltered = $countStmt->fetchColumn();
// Fetch Employees and their attendance for the selected date // Fetch Employees and their attendance for the selected date
$sql = "SELECT e.id, e.first_name, e.last_name, e.job_title, $sql = "SELECT e.id, e.first_name, e.last_name, e.job_title,
a.id as att_id, a.status, a.check_in, a.check_out, a.notes a.id as att_id, a.status, a.check_in, a.check_out, a.notes
FROM hr_employees e FROM hr_employees e
LEFT JOIN hr_attendance a ON e.id = a.employee_id AND a.date = ? LEFT JOIN hr_attendance a ON e.id = a.employee_id AND a.date = ?
WHERE e.status = 'active' WHERE e.status = 'active'
ORDER BY e.first_name"; ORDER BY e.first_name
LIMIT $limit OFFSET $offset";
$stmt = db()->prepare($sql); $stmt = db()->prepare($sql);
$stmt->execute([$date]); $stmt->execute([$date]);
$records = $stmt->fetchAll(); $records = $stmt->fetchAll();
@ -74,7 +87,7 @@ $records = $stmt->fetchAll();
<div class="alert alert-success"><?= htmlspecialchars($success) ?></div> <div class="alert alert-success"><?= htmlspecialchars($success) ?></div>
<?php endif; ?> <?php endif; ?>
<div class="card shadow-sm"> <div class="card shadow-sm border-0">
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover align-middle"> <table class="table table-hover align-middle">
@ -90,6 +103,11 @@ $records = $stmt->fetchAll();
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php if (empty($records)): ?>
<tr>
<td colspan="7" class="text-center py-4 text-muted">لا يوجد موظفين نشطين.</td>
</tr>
<?php endif; ?>
<?php foreach ($records as $row): ?> <?php foreach ($records as $row): ?>
<tr class="<?= !$row['att_id'] ? 'table-light text-muted' : '' ?>"> <tr class="<?= !$row['att_id'] ? 'table-light text-muted' : '' ?>">
<td class="fw-bold"><?= htmlspecialchars($row['first_name'] . ' ' . $row['last_name']) ?></td> <td class="fw-bold"><?= htmlspecialchars($row['first_name'] . ' ' . $row['last_name']) ?></td>
@ -158,6 +176,9 @@ $records = $stmt->fetchAll();
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination -->
<?php renderPagination($page, $totalFiltered, $limit); ?>
</div> </div>
</div> </div>
@ -212,16 +233,18 @@ $records = $stmt->fetchAll();
<script> <script>
const attModal = document.getElementById('attModal'); const attModal = document.getElementById('attModal');
attModal.addEventListener('show.bs.modal', event => { if (attModal) {
const button = event.relatedTarget; attModal.addEventListener('show.bs.modal', event => {
const button = event.relatedTarget;
document.getElementById('modalEmpId').value = button.getAttribute('data-id'); document.getElementById('modalEmpId').value = button.getAttribute('data-id');
document.getElementById('modalEmpName').textContent = button.getAttribute('data-name'); document.getElementById('modalEmpName').textContent = button.getAttribute('data-name');
document.getElementById('modalStatus').value = button.getAttribute('data-status'); document.getElementById('modalStatus').value = button.getAttribute('data-status');
document.getElementById('modalIn').value = button.getAttribute('data-in'); document.getElementById('modalIn').value = button.getAttribute('data-in');
document.getElementById('modalOut').value = button.getAttribute('data-out'); document.getElementById('modalOut').value = button.getAttribute('data-out');
document.getElementById('modalNotes').value = button.getAttribute('data-notes'); document.getElementById('modalNotes').value = button.getAttribute('data-notes');
}); });
}
</script> </script>
<?php require_once 'includes/footer.php'; ?> <?php require_once 'includes/footer.php'; ?>

View File

@ -1,5 +1,6 @@
<?php <?php
require_once 'includes/header.php'; require_once 'includes/header.php';
require_once 'includes/pagination.php';
if (!canView('hr_leaves')) { if (!canView('hr_leaves')) {
echo "<div class='alert alert-danger'>ليس لديك صلاحية للوصول إلى هذه الصفحة.</div>"; echo "<div class='alert alert-danger'>ليس لديك صلاحية للوصول إلى هذه الصفحة.</div>";
@ -64,14 +65,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Fetch Employees for Dropdown // Fetch Employees for Dropdown
$employees = db()->query("SELECT id, first_name, last_name FROM hr_employees WHERE status = 'active' ORDER BY first_name")->fetchAll(); $employees = db()->query("SELECT id, first_name, last_name FROM hr_employees WHERE status = 'active' ORDER BY first_name")->fetchAll();
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
// Fetch Leaves based on Tab // Fetch Leaves based on Tab
$where_clause = $tab === 'pending' ? "WHERE l.status = 'pending'" : "WHERE 1=1"; $where_clause = $tab === 'pending' ? "WHERE l.status = 'pending'" : "WHERE 1=1";
// Count Total
$countSql = "SELECT COUNT(*) FROM hr_leaves l $where_clause";
$countStmt = db()->query($countSql);
$totalFiltered = $countStmt->fetchColumn();
$sql = "SELECT l.*, e.first_name, e.last_name, u.full_name as approver_name $sql = "SELECT l.*, e.first_name, e.last_name, u.full_name as approver_name
FROM hr_leaves l FROM hr_leaves l
JOIN hr_employees e ON l.employee_id = e.id JOIN hr_employees e ON l.employee_id = e.id
LEFT JOIN users u ON l.approved_by = u.id LEFT JOIN users u ON l.approved_by = u.id
$where_clause $where_clause
ORDER BY l.created_at DESC"; ORDER BY l.created_at DESC
LIMIT $limit OFFSET $offset";
$requests = db()->query($sql)->fetchAll(); $requests = db()->query($sql)->fetchAll();
?> ?>
@ -196,6 +210,9 @@ $requests = db()->query($sql)->fetchAll();
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination -->
<?php renderPagination($page, $totalFiltered, $limit); ?>
</div> </div>
</div> </div>

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'includes/header.php'; require_once 'includes/header.php';
require_once 'includes/accounting_functions.php'; // Include accounting helpers require_once 'includes/accounting_functions.php'; // Include accounting helpers
require_once 'includes/pagination.php';
if (!canView('hr_payroll')) { if (!canView('hr_payroll')) {
echo "<div class='alert alert-danger'>ليس لديك صلاحية للوصول إلى هذه الصفحة.</div>"; echo "<div class='alert alert-danger'>ليس لديك صلاحية للوصول إلى هذه الصفحة.</div>";
@ -117,19 +118,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
} }
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
// Fetch Payroll Records // Fetch Payroll Records
$sql = "SELECT p.*, e.first_name, e.last_name, e.job_title $sql = "SELECT p.*, e.first_name, e.last_name, e.job_title
FROM hr_payroll p FROM hr_payroll p
JOIN hr_employees e ON p.employee_id = e.id JOIN hr_employees e ON p.employee_id = e.id
WHERE p.month = ? AND p.year = ? WHERE p.month = ? AND p.year = ?
ORDER BY e.first_name"; ORDER BY e.first_name
LIMIT $limit OFFSET $offset";
$stmt = db()->prepare($sql); $stmt = db()->prepare($sql);
$stmt->execute([$month, $year]); $stmt->execute([$month, $year]);
$payrolls = $stmt->fetchAll(); $payrolls = $stmt->fetchAll();
// Calculate Totals // Count Total for Pagination
$total_salaries = 0; $countSql = "SELECT COUNT(*) FROM hr_payroll WHERE month = ? AND year = ?";
foreach ($payrolls as $p) $total_salaries += $p['net_salary']; $countStmt = db()->prepare($countSql);
$countStmt->execute([$month, $year]);
$totalFiltered = $countStmt->fetchColumn();
// Calculate Grand Total Salaries (for the stats card)
$sumSql = "SELECT SUM(net_salary) FROM hr_payroll WHERE month = ? AND year = ?";
$sumStmt = db()->prepare($sumSql);
$sumStmt->execute([$month, $year]);
$total_salaries = $sumStmt->fetchColumn() ?: 0;
?> ?>
@ -233,6 +249,9 @@ foreach ($payrolls as $p) $total_salaries += $p['net_salary'];
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination -->
<?php renderPagination($page, $totalFiltered, $limit); ?>
</div> </div>
</div> </div>
@ -298,14 +317,16 @@ foreach ($payrolls as $p) $total_salaries += $p['net_salary'];
<script> <script>
const editPayModal = document.getElementById('editPayModal'); const editPayModal = document.getElementById('editPayModal');
editPayModal.addEventListener('show.bs.modal', event => { if (editPayModal) {
const button = event.relatedTarget; editPayModal.addEventListener('show.bs.modal', event => {
document.getElementById('payId').value = button.getAttribute('data-id'); const button = event.relatedTarget;
document.getElementById('payName').textContent = button.getAttribute('data-name'); document.getElementById('payId').value = button.getAttribute('data-id');
document.getElementById('payBonus').value = button.getAttribute('data-bonus'); document.getElementById('payName').textContent = button.getAttribute('data-name');
document.getElementById('payDeduct').value = button.getAttribute('data-deduct'); document.getElementById('payBonus').value = button.getAttribute('data-bonus');
document.getElementById('payStatus').value = button.getAttribute('data-status'); document.getElementById('payDeduct').value = button.getAttribute('data-deduct');
}); document.getElementById('payStatus').value = button.getAttribute('data-status');
});
}
</script> </script>
<?php require_once 'includes/footer.php'; ?> <?php require_once 'includes/footer.php'; ?>

View File

@ -1,5 +1,6 @@
<?php <?php
require_once 'includes/header.php'; require_once 'includes/header.php';
require_once 'includes/pagination.php';
$error = ''; $error = '';
$success = ''; $success = '';
@ -100,13 +101,26 @@ if (isset($_GET['my_tasks'])) {
$params[] = $_SESSION['user_id']; $params[] = $_SESSION['user_id'];
} }
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
// Count filtered
$countQuery = "SELECT COUNT(*) FROM inbound_mail m $where";
$countStmt = db()->prepare($countQuery);
$countStmt->execute($params);
$totalFiltered = $countStmt->fetchColumn();
$query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name, $query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name,
(SELECT GROUP_CONCAT(display_name SEPARATOR '|||') FROM inbound_attachments WHERE mail_id = m.id) as attachment_names (SELECT GROUP_CONCAT(display_name SEPARATOR '|||') FROM inbound_attachments WHERE mail_id = m.id) as attachment_names
FROM inbound_mail m FROM inbound_mail m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id LEFT JOIN users u ON m.assigned_to = u.id
$where $where
ORDER BY m.date_registered DESC, m.id DESC"; ORDER BY m.date_registered DESC, m.id DESC
LIMIT $limit OFFSET $offset";
$stmt = db()->prepare($query); $stmt = db()->prepare($query);
$stmt->execute($params); $stmt->execute($params);
@ -290,6 +304,9 @@ if (isset($_GET['id'])) {
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination -->
<?php renderPagination($page, $totalFiltered, $limit); ?>
</div> </div>
</div> </div>

View File

@ -49,6 +49,64 @@ function get_full_ledger_filtered($search = '', $date_from = '', $date_to = '')
return $stmt->fetchAll(PDO::FETCH_ASSOC); return $stmt->fetchAll(PDO::FETCH_ASSOC);
} }
function get_ledger_count($search = '', $date_from = '', $date_to = '') {
$db = db();
$sql = "SELECT COUNT(*)
FROM accounting_journal j
JOIN accounting_entries e ON j.id = e.journal_id
WHERE 1=1";
$params = [];
if ($search) {
$sql .= " AND (j.description LIKE ? OR j.reference LIKE ? OR e.account_name LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
if ($date_from) {
$sql .= " AND j.date >= ?";
$params[] = $date_from;
}
if ($date_to) {
$sql .= " AND j.date <= ?";
$params[] = $date_to;
}
$stmt = $db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchColumn();
}
function get_ledger_paginated($search = '', $date_from = '', $date_to = '', $limit = 10, $offset = 0) {
$db = db();
$sql = "SELECT j.id, j.date, j.description, j.reference, e.account_name, e.debit, e.credit
FROM accounting_journal j
JOIN accounting_entries e ON j.id = e.journal_id
WHERE 1=1";
$params = [];
if ($search) {
$sql .= " AND (j.description LIKE ? OR j.reference LIKE ? OR e.account_name LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
if ($date_from) {
$sql .= " AND j.date >= ?";
$params[] = $date_from;
}
if ($date_to) {
$sql .= " AND j.date <= ?";
$params[] = $date_to;
}
$sql .= " ORDER BY j.date DESC, j.id DESC LIMIT $limit OFFSET $offset";
$stmt = $db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
function get_trial_balance() { function get_trial_balance() {
$db = db(); $db = db();
$stmt = $db->query("SELECT account_name, SUM(debit) as total_debit, SUM(credit) as total_credit $stmt = $db->query("SELECT account_name, SUM(debit) as total_debit, SUM(credit) as total_credit

View File

@ -1,5 +1,6 @@
<?php <?php
require_once 'includes/header.php'; require_once 'includes/header.php';
require_once 'includes/pagination.php';
$error = ''; $error = '';
$success = ''; $success = '';
@ -95,13 +96,27 @@ if (isset($_GET['status_id']) && !empty($_GET['status_id'])) {
$params[] = $_GET['status_id']; $params[] = $_GET['status_id'];
} }
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
// Count filtered
$countQuery = "SELECT COUNT(*) FROM outbound_mail m $where";
$countStmt = db()->prepare($countQuery);
$countStmt->execute($params);
$totalFiltered = $countStmt->fetchColumn();
$query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name, $query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name,
(SELECT GROUP_CONCAT(display_name SEPARATOR '|||') FROM outbound_attachments WHERE mail_id = m.id) as attachment_names (SELECT GROUP_CONCAT(display_name SEPARATOR '|||') FROM outbound_attachments WHERE mail_id = m.id) as attachment_names
FROM outbound_mail m FROM outbound_mail m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id LEFT JOIN users u ON m.assigned_to = u.id
$where $where
ORDER BY m.date_registered DESC, m.id DESC"; ORDER BY m.date_registered DESC, m.id DESC
LIMIT $limit OFFSET $offset";
$stmt = db()->prepare($query); $stmt = db()->prepare($query);
$stmt->execute($params); $stmt->execute($params);
@ -270,6 +285,9 @@ if (isset($_GET['id'])) {
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination -->
<?php renderPagination($page, $totalFiltered, $limit); ?>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
<?php <?php
require_once __DIR__ . '/includes/header.php'; require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/pagination.php';
if (!canView('stock_in')) { if (!canView('stock_in')) {
echo '<div class="alert alert-danger">عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.</div>'; echo '<div class="alert alert-danger">عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.</div>';
@ -57,6 +58,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll(); $stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll();
$items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll(); $items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
// Pagination for History
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$where = "WHERE t.transaction_type = 'in'";
$params = [];
// Count Total
$countQuery = "SELECT COUNT(*) FROM stock_transactions t $where";
$countStmt = db()->prepare($countQuery);
$countStmt->execute($params);
$totalFiltered = $countStmt->fetchColumn();
// Fetch History
$historyQuery = "
SELECT t.*, i.name as item_name, s.name as store_name, u.full_name as user_name
FROM stock_transactions t
JOIN stock_items i ON t.item_id = i.id
JOIN stock_stores s ON t.store_id = s.id
LEFT JOIN users u ON t.user_id = u.id
$where
ORDER BY t.created_at DESC
LIMIT $limit OFFSET $offset
";
$stmt = db()->prepare($historyQuery);
$stmt->execute($params);
$history = $stmt->fetchAll();
?> ?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
@ -80,9 +111,12 @@ $items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="row justify-content-center"> <div class="row justify-content-center mb-5">
<div class="col-md-8"> <div class="col-md-8">
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-header bg-success text-white">
<h5 class="mb-0"><i class="fas fa-plus-circle me-2"></i> تسجيل توريد جديد</h5>
</div>
<div class="card-body p-4"> <div class="card-body p-4">
<form method="POST"> <form method="POST">
<div class="row"> <div class="row">
@ -133,4 +167,48 @@ $items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
</div> </div>
</div> </div>
<!-- History Table -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white py-3">
<h5 class="mb-0 fw-bold"><i class="fas fa-history me-2 text-secondary"></i> سجل عمليات التوريد</h5>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">#</th>
<th>الصنف</th>
<th>المستودع</th>
<th>الكمية</th>
<th>بواسطة</th>
<th>التاريخ</th>
<th>المرجع</th>
</tr>
</thead>
<tbody>
<?php if (empty($history)): ?>
<tr>
<td colspan="7" class="text-center py-4 text-muted">لا توجد عمليات توريد سابقة.</td>
</tr>
<?php else: ?>
<?php foreach ($history as $h): ?>
<tr>
<td class="ps-4"><?= $h['id'] ?></td>
<td class="fw-bold"><?= htmlspecialchars($h['item_name']) ?></td>
<td><?= htmlspecialchars($h['store_name']) ?></td>
<td class="text-success fw-bold" dir="ltr">+<?= number_format($h['quantity'], 2) ?></td>
<td><?= htmlspecialchars($h['user_name'] ?? '-') ?></td>
<td><?= date('Y-m-d H:i', strtotime($h['created_at'])) ?></td>
<td><small class="text-muted"><?= htmlspecialchars($h['reference'] ?? '-') ?></small></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Pagination -->
<?php renderPagination($page, $totalFiltered, $limit); ?>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?> <?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -1,5 +1,6 @@
<?php <?php
require_once __DIR__ . '/includes/header.php'; require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/pagination.php';
if (!canView('stock_items')) { if (!canView('stock_items')) {
echo '<div class="alert alert-danger">عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.</div>'; echo '<div class="alert alert-danger">عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.</div>';
@ -51,15 +52,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
} }
// Pagination & Search
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$where = "WHERE 1=1";
$params = [];
if (isset($_GET['search']) && !empty($_GET['search'])) {
$where .= " AND (i.name LIKE ? OR i.sku LIKE ?)";
$search = "%" . $_GET['search'] . "%";
$params = array_merge($params, [$search, $search]);
}
if (isset($_GET['category_id']) && !empty($_GET['category_id'])) {
$where .= " AND i.category_id = ?";
$params[] = $_GET['category_id'];
}
// Count Total
$countQuery = "SELECT COUNT(*) FROM stock_items i $where";
$countStmt = db()->prepare($countQuery);
$countStmt->execute($params);
$totalFiltered = $countStmt->fetchColumn();
// Fetch Items with Category Name and Total Quantity // Fetch Items with Category Name and Total Quantity
$query = " $query = "
SELECT i.*, c.name as category_name, SELECT i.*, c.name as category_name,
(SELECT SUM(quantity) FROM stock_quantities q WHERE q.item_id = i.id) as total_quantity (SELECT SUM(quantity) FROM stock_quantities q WHERE q.item_id = i.id) as total_quantity
FROM stock_items i FROM stock_items i
LEFT JOIN stock_categories c ON i.category_id = c.id LEFT JOIN stock_categories c ON i.category_id = c.id
$where
ORDER BY i.name ASC ORDER BY i.name ASC
LIMIT $limit OFFSET $offset
"; ";
$items = db()->query($query)->fetchAll(); $stmt = db()->prepare($query);
$stmt->execute($params);
$items = $stmt->fetchAll();
// Fetch Categories for Dropdown // Fetch Categories for Dropdown
$categories = db()->query("SELECT * FROM stock_categories ORDER BY name ASC")->fetchAll(); $categories = db()->query("SELECT * FROM stock_categories ORDER BY name ASC")->fetchAll();
@ -89,6 +120,31 @@ $categories = db()->query("SELECT * FROM stock_categories ORDER BY name ASC")->f
</div> </div>
<?php endif; ?> <?php endif; ?>
<!-- Filter Bar -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body p-3">
<form method="GET" class="row g-2 align-items-center">
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text bg-white border-end-0"><i class="fas fa-search text-muted"></i></span>
<input type="text" name="search" class="form-control border-start-0" placeholder="بحث باسم الصنف أو الرمز (SKU)..." value="<?= htmlspecialchars($_GET['search'] ?? '') ?>">
</div>
</div>
<div class="col-md-4">
<select name="category_id" class="form-select" onchange="this.form.submit()">
<option value="">جميع التصنيفات</option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= (isset($_GET['category_id']) && $_GET['category_id'] == $cat['id']) ? 'selected' : '' ?>><?= $cat['name'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3 text-end">
<button type="submit" class="btn btn-light w-100">تصفية</button>
</div>
</form>
</div>
</div>
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-body p-0"> <div class="card-body p-0">
<div class="table-responsive"> <div class="table-responsive">
@ -105,6 +161,14 @@ $categories = db()->query("SELECT * FROM stock_categories ORDER BY name ASC")->f
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php if (empty($items)): ?>
<tr>
<td colspan="7" class="text-center py-5 text-muted">
<i class="fas fa-boxes fa-3x mb-3 opacity-20"></i>
<p>لا يوجد أصناف مطابقة.</p>
</td>
</tr>
<?php endif; ?>
<?php foreach ($items as $item): ?> <?php foreach ($items as $item): ?>
<tr> <tr>
<td class="ps-4 fw-bold"><?= htmlspecialchars($item['name']) ?></td> <td class="ps-4 fw-bold"><?= htmlspecialchars($item['name']) ?></td>
@ -136,6 +200,9 @@ $categories = db()->query("SELECT * FROM stock_categories ORDER BY name ASC")->f
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination -->
<?php renderPagination($page, $totalFiltered, $limit); ?>
</div> </div>
</div> </div>
@ -201,10 +268,14 @@ $categories = db()->query("SELECT * FROM stock_categories ORDER BY name ASC")->f
<script> <script>
let itemModal; let itemModal;
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
itemModal = new bootstrap.Modal(document.getElementById('itemModal')); var modalEl = document.getElementById('itemModal');
if (modalEl) {
itemModal = new bootstrap.Modal(modalEl);
}
}); });
function openItemModal(action, data = null) { function openItemModal(action, data = null) {
if (!itemModal) return;
document.getElementById('modalAction').value = action; document.getElementById('modalAction').value = action;
document.getElementById('itemModalLabel').textContent = action === 'add' ? 'إضافة صنف جديد' : 'تعديل الصنف'; document.getElementById('itemModalLabel').textContent = action === 'add' ? 'إضافة صنف جديد' : 'تعديل الصنف';

View File

@ -1,5 +1,6 @@
<?php <?php
require_once __DIR__ . '/includes/header.php'; require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/includes/pagination.php';
if (!canView('stock_out')) { if (!canView('stock_out')) {
echo '<div class="alert alert-danger">عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.</div>'; echo '<div class="alert alert-danger">عذراً، ليس لديك صلاحية الوصول لهذه الصفحة.</div>';
@ -57,6 +58,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll(); $stores = db()->query("SELECT * FROM stock_stores ORDER BY name ASC")->fetchAll();
$items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll(); $items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
// Pagination for History
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$where = "WHERE t.transaction_type IN ('out', 'damage')";
$params = [];
// Count Total
$countQuery = "SELECT COUNT(*) FROM stock_transactions t $where";
$countStmt = db()->prepare($countQuery);
$countStmt->execute($params);
$totalFiltered = $countStmt->fetchColumn();
// Fetch History
$historyQuery = "
SELECT t.*, i.name as item_name, s.name as store_name, u.full_name as user_name
FROM stock_transactions t
JOIN stock_items i ON t.item_id = i.id
JOIN stock_stores s ON t.store_id = s.id
LEFT JOIN users u ON t.user_id = u.id
$where
ORDER BY t.created_at DESC
LIMIT $limit OFFSET $offset
";
$stmt = db()->prepare($historyQuery);
$stmt->execute($params);
$history = $stmt->fetchAll();
?> ?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
@ -80,9 +111,12 @@ $items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="row justify-content-center"> <div class="row justify-content-center mb-5">
<div class="col-md-8"> <div class="col-md-8">
<div class="card shadow-sm border-0"> <div class="card shadow-sm border-0">
<div class="card-header bg-danger text-white">
<h5 class="mb-0"><i class="fas fa-minus-circle me-2"></i> تسجيل عملية صرف</h5>
</div>
<div class="card-body p-4"> <div class="card-body p-4">
<form method="POST"> <form method="POST">
<div class="row"> <div class="row">
@ -141,4 +175,56 @@ $items = db()->query("SELECT * FROM stock_items ORDER BY name ASC")->fetchAll();
</div> </div>
</div> </div>
<!-- History Table -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white py-3">
<h5 class="mb-0 fw-bold"><i class="fas fa-history me-2 text-secondary"></i> سجل عمليات الصرف</h5>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">#</th>
<th>النوع</th>
<th>الصنف</th>
<th>المستودع</th>
<th>الكمية</th>
<th>بواسطة</th>
<th>التاريخ</th>
<th>المرجع</th>
</tr>
</thead>
<tbody>
<?php if (empty($history)): ?>
<tr>
<td colspan="8" class="text-center py-4 text-muted">لا توجد عمليات صرف سابقة.</td>
</tr>
<?php else: ?>
<?php foreach ($history as $h): ?>
<tr>
<td class="ps-4"><?= $h['id'] ?></td>
<td>
<?php if ($h['transaction_type'] == 'damage'): ?>
<span class="badge bg-dark">تالف</span>
<?php else: ?>
<span class="badge bg-danger">صرف</span>
<?php endif; ?>
</td>
<td class="fw-bold"><?= htmlspecialchars($h['item_name']) ?></td>
<td><?= htmlspecialchars($h['store_name']) ?></td>
<td class="text-danger fw-bold" dir="ltr">-<?= number_format($h['quantity'], 2) ?></td>
<td><?= htmlspecialchars($h['user_name'] ?? '-') ?></td>
<td><?= date('Y-m-d H:i', strtotime($h['created_at'])) ?></td>
<td><small class="text-muted"><?= htmlspecialchars($h['reference'] ?? '-') ?></small></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Pagination -->
<?php renderPagination($page, $totalFiltered, $limit); ?>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?> <?php require_once __DIR__ . '/includes/footer.php'; ?>