346 lines
16 KiB
PHP
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';
|
|
?>
|