38929-vm/lab.php
2026-03-03 16:57:49 +00:00

346 lines
16 KiB
PHP

<?php
$title = 'lab';
require_once __DIR__ . '/includes/header.php';
// View check is handled by header.php global check.
// Filters
$status_filter = $_GET['status'] ?? '';
$branch_filter = $_GET['branch_id'] ?? '';
$from_date = $_GET['from_date'] ?? '';
$to_date = $_GET['to_date'] ?? '';
$search = $_GET['search'] ?? '';
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
if ($page < 1) $page = 1;
$limit = 10;
$offset = ($page - 1) * $limit;
$sql_base = " FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
LEFT JOIN branches b ON o.branch_id = b.id
WHERE 1=1";
$params = [];
if ($status_filter) {
$sql_base .= " AND o.status = ?";
$params[] = $status_filter;
}
if ($branch_filter) {
$sql_base .= " AND o.branch_id = ?";
$params[] = $branch_filter;
}
if ($from_date) {
$sql_base .= " AND DATE(o.created_at) >= ?";
$params[] = $from_date;
}
if ($to_date) {
$sql_base .= " AND DATE(o.created_at) <= ?";
$params[] = $to_date;
}
if ($search) {
$sql_base .= " AND (o.order_number LIKE ? OR c.phone LIKE ? OR c.name_en LIKE ? OR c.name_ar LIKE ?)";
$search_param = "%$search%";
$params[] = $search_param;
$params[] = $search_param;
$params[] = $search_param;
$params[] = $search_param;
}
// Total count for pagination
$count_sql = "SELECT COUNT(*) " . $sql_base;
$stmt_count = db()->prepare($count_sql);
$stmt_count->execute($params);
$total_items = $stmt_count->fetchColumn();
$total_pages = ceil($total_items / $limit);
// Final data query
$sql = "SELECT o.*, c.name_en as customer_name_en, c.name_ar as customer_name_ar, c.phone as customer_phone,
b.name_en as branch_name_en, b.name_ar as branch_name_ar " . $sql_base . " ORDER BY o.created_at DESC LIMIT $limit OFFSET $offset";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$orders = $stmt->fetchAll();
$branches = db()->query("SELECT id, name_en, name_ar FROM branches")->fetchAll();
?>
<div class="card p-4 shadow-sm border-0" style="border-radius: 20px;">
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4">
<div>
<h5 class="fw-bold mb-0"><?= __('lab') ?? 'Lab Module' ?></h5>
<p class="text-muted small mb-0"><?= __('manage_orders_across_outlets') ?? 'Manage orders across all outlets' ?></p>
</div>
</div>
<!-- Filters -->
<form action="" method="GET" class="row g-3 mb-4">
<div class="col-md-3">
<input type="text" name="search" class="form-control" placeholder="<?= __('search_placeholder') ?>" value="<?= htmlspecialchars($search) ?>" style="border-radius: 12px;">
</div>
<div class="col-md-2">
<select name="branch_id" class="form-select border-radius-12" style="border-radius: 12px;" onchange="this.form.submit()">
<option value=""><?= __('all_branches') ?? 'All Branches' ?></option>
<?php foreach($branches as $b): ?>
<option value="<?= $b['id'] ?>" <?= $branch_filter == $b['id'] ? 'selected' : '' ?>>
<?= $lang === 'ar' ? ($b['name_ar'] ?: $b['name_en']) : $b['name_en'] ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<select name="status" class="form-select border-radius-12" style="border-radius: 12px;" onchange="this.form.submit()">
<option value=""><?= __('all_status') ?? 'All Status' ?></option>
<option value="received" <?= $status_filter == 'received' ? 'selected' : '' ?>><?= __('received') ?></option>
<option value="processing" <?= $status_filter == 'processing' ? 'selected' : '' ?>><?= __('processing') ?></option>
<option value="ready" <?= $status_filter == 'ready' ? 'selected' : '' ?>><?= __('ready') ?></option>
<option value="delivered" <?= $status_filter == 'delivered' ? 'selected' : '' ?>><?= __('delivered') ?></option>
<option value="cancelled" <?= $status_filter == 'cancelled' ? 'selected' : '' ?>><?= __('cancelled') ?></option>
</select>
</div>
<div class="col-md-5 d-flex gap-2">
<input type="date" name="from_date" class="form-control" value="<?= $from_date ?>" style="border-radius: 12px;" title="<?= __('from_date') ?>">
<input type="date" name="to_date" class="form-control" value="<?= $to_date ?>" style="border-radius: 12px;" title="<?= __('to_date') ?>">
<button type="submit" class="btn btn-light shadow-sm" style="border-radius: 12px;"><i class="bi bi-search"></i></button>
</div>
</form>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="bg-light">
<tr>
<th class="ps-4">#</th>
<th><?= __('order_number') ?? 'Order #' ?></th>
<th><?= __('outlet') ?? 'Outlet' ?></th>
<th><?= __('customer') ?></th>
<th><?= __('status') ?></th>
<th><?= __('date') ?></th>
<th class="text-end pe-4"><?= __('actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($orders as $order): ?>
<tr>
<td class="ps-4"><?= $order['id'] ?></td>
<td class="fw-bold"><?= $order['order_number'] ?></td>
<td>
<span class="badge bg-light text-dark border fw-normal">
<?= $lang === 'ar' ? ($order['branch_name_ar'] ?: $order['branch_name_en']) : $order['branch_name_en'] ?>
</span>
</td>
<td>
<div class="fw-bold"><?= $lang === 'ar' ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></div>
<div class="small text-muted"><?= $order['customer_phone'] ?></div>
</td>
<td><span class="badge bg-<?= getStatusColor($order['status']) ?> rounded-pill px-3 py-2"><?= __($order['status']) ?></span></td>
<td class="small text-muted"><?= date('d/m/Y H:i', strtotime($order['created_at'])) ?></td>
<td class="text-end pe-4">
<div class="d-flex gap-1 justify-content-end">
<button class="btn btn-sm btn-light border-0 p-2 text-primary view-items-btn shadow-sm" data-id="<?= $order['id'] ?>" title="<?= __('view_items') ?>" style="border-radius: 8px;">
<i class="bi bi-eye-fill"></i>
</button>
<?php if (has_permission('edit')): ?>
<button class="btn btn-sm btn-light border-0 p-2 text-info status-change-btn shadow-sm" data-id="<?= $order['id'] ?>" data-status="<?= $order['status'] ?>" title="<?= __('update_status') ?>" style="border-radius: 8px;">
<i class="bi bi-arrow-repeat"></i>
</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($orders)): ?>
<tr>
<td colspan="7" class="text-center py-5 text-muted">
<i class="bi bi-flask fs-1 d-block mb-3 opacity-25"></i>
<?= __('no_orders_found') ?? 'No orders found' ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center">
<li class="page-item <?= $page <= 1 ? 'disabled' : '' ?>">
<a class="page-link shadow-sm" href="?<?= http_build_query(array_merge($_GET, ['page' => $page - 1])) ?>" style="border-radius: 8px 0 0 8px;"><?= __('previous') ?></a>
</li>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?= $page == $i ? 'active' : '' ?>">
<a class="page-link shadow-sm" href="?<?= http_build_query(array_merge($_GET, ['page' => $i])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
<li class="page-item <?= $page >= $total_pages ? 'disabled' : '' ?>">
<a class="page-link shadow-sm" href="?<?= http_build_query(array_merge($_GET, ['page' => $page + 1])) ?>" style="border-radius: 0 8px 8px 0;"><?= __('next') ?></a>
</li>
</ul>
</nav>
<?php endif; ?>
</div>
<!-- View Items Modal -->
<div class="modal fade" id="itemsModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 rounded-4 shadow-lg">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold"><?= __('order') ?> #<span id="displayOrderNumber"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div id="itemsContainer">
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status"></div>
</div>
</div>
</div>
<div class="modal-footer border-0">
<button type="button" class="btn btn-light rounded-3 shadow-sm" data-bs-dismiss="modal"><?= __('close') ?></button>
</div>
</div>
</div>
</div>
<!-- Status Update Modal -->
<?php if (has_permission('edit')): ?>
<div class="modal fade" id="statusModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 rounded-4 shadow-lg">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold"><?= __('update_status') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<input type="hidden" id="modalOrderId">
<div class="row g-2">
<div class="col-6">
<button class="btn btn-outline-secondary w-100 py-3 rounded-4 status-opt shadow-sm" data-status="received"><?= __('received') ?></button>
</div>
<div class="col-6">
<button class="btn btn-outline-primary w-100 py-3 rounded-4 status-opt shadow-sm" data-status="processing"><?= __('processing') ?></button>
</div>
<div class="col-6">
<button class="btn btn-outline-success w-100 py-3 rounded-4 status-opt shadow-sm" data-status="ready"><?= __('ready') ?></button>
</div>
<div class="col-6">
<button class="btn btn-outline-dark w-100 py-3 rounded-4 status-opt shadow-sm" data-status="delivered"><?= __('delivered') ?></button>
</div>
<div class="col-12 mt-2">
<button class="btn btn-outline-danger w-100 py-3 rounded-4 status-opt shadow-sm" data-status="cancelled"><?= __('cancelled') ?></button>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const statusModalEl = document.getElementById('statusModal');
const statusModal = statusModalEl ? new bootstrap.Modal(statusModalEl) : null;
const itemsModal = new bootstrap.Modal(document.getElementById('itemsModal'));
// View Items Logic
document.querySelectorAll('.view-items-btn').forEach(btn => {
btn.onclick = async () => {
const id = btn.dataset.id;
document.getElementById('displayOrderNumber').innerText = '...';
document.getElementById('itemsContainer').innerHTML = `
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status"></div>
</div>
`;
itemsModal.show();
try {
const res = await fetch(`api/get_order_items.php?id=${id}`);
const data = await res.json();
if (data.success) {
document.getElementById('displayOrderNumber').innerText = data.order_number;
let html = `<ul class="list-group list-group-flush">`;
data.items.forEach(item => {
const itemName = '<?= $lang ?>' === 'ar' ? (item.item_ar || item.item_en) : item.item_en;
html += `
<li class="list-group-item d-flex justify-content-between align-items-center py-3 border-0 border-bottom">
<div class="fw-bold text-dark">${itemName}</div>
<span class="badge bg-primary rounded-pill px-3 fs-6 shadow-sm">${item.quantity}</span>
</li>
`;
});
html += `</ul>`;
document.getElementById('itemsContainer').innerHTML = html;
} else {
document.getElementById('itemsContainer').innerHTML = `<div class="alert alert-danger">${data.error}</div>`;
}
} catch (e) {
document.getElementById('itemsContainer').innerHTML = `<div class="alert alert-danger">Error loading items</div>`;
}
};
});
// Status Change Logic
document.querySelectorAll('.status-change-btn').forEach(btn => {
btn.onclick = () => {
if (!statusModal) return;
const id = btn.dataset.id;
const currentStatus = btn.dataset.status;
document.getElementById('modalOrderId').value = id;
document.querySelectorAll('.status-opt').forEach(opt => {
opt.classList.remove('active');
if (opt.dataset.status === currentStatus) opt.classList.add('active');
});
statusModal.show();
};
});
document.querySelectorAll('.status-opt').forEach(btn => {
btn.onclick = async () => {
const id = document.getElementById('modalOrderId').value;
const status = btn.dataset.status;
try {
const res = await fetch('api/update_order_status.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, status })
});
const data = await res.json();
if (data.success) {
location.reload();
} else {
alert(data.error);
}
} catch (e) {
alert('Error updating status');
}
};
});
});
</script>
<style>
.status-opt.active {
background-color: var(--bs-primary);
color: white;
border-color: var(--bs-primary);
}
.status-opt:hover {
transform: translateY(-2px);
transition: all 0.2s;
}
</style>
<?php
if (!function_exists('getStatusColor')) { function getStatusColor($status) {
return [
'received' => 'secondary',
'processing' => 'primary',
'ready' => 'success',
'delivered' => 'dark',
'cancelled' => 'danger',
][$status] ?? 'info';
}}
require_once __DIR__ . '/includes/footer.php';
?>