362 lines
13 KiB
PHP
362 lines
13 KiB
PHP
<?php
|
||
session_start();
|
||
require '../config.php';
|
||
require '../include_header.php';
|
||
?>
|
||
|
||
<body>
|
||
<?php require '../include_topbar.php';?>
|
||
<?php require '../include_sidebar.php';?>
|
||
|
||
<!-- MAIN CONTENT -->
|
||
<main id="content" class="content py-15">
|
||
<div class="container-fluid">
|
||
|
||
<!-- Page header -->
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-6 gap-3">
|
||
<div>
|
||
<h1 class="fs-3 mb-1">Expired / Near Expiry Products</h1>
|
||
<p class="mb-0">Monitor and manage products approaching or past expiry dates</p>
|
||
</div>
|
||
<div class="d-flex align-items-center gap-3">
|
||
<label class="mb-0 text-nowrap fw-semibold">Warehouse</label>
|
||
<select id="warehouse" class="form-select" style="min-width:200px;" required>
|
||
<option value="">Select warehouse...</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main content -->
|
||
<div id="overview_content">
|
||
|
||
<!-- Summary stats -->
|
||
<div class="row mb-5 g-5">
|
||
<div class="col-xl-3 col-md-6 col-12">
|
||
<div class="card bg-danger bg-opacity-10 border-danger border-opacity-25">
|
||
<div class="card-body p-5">
|
||
<span class="icon-shape icon-md bg-danger text-white rounded-2">
|
||
<i class="ti ti-trash fs-4"></i>
|
||
</span>
|
||
<div class="d-flex flex-column mt-3">
|
||
<span class="fs-2 fw-bold mb-0 d-block" id="stat_expired">—</span>
|
||
<span class="fs-5">Expired Products</span>
|
||
<small class="text-muted">Requires immediate action</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-xl-3 col-md-6 col-12">
|
||
<div class="card bg-danger bg-opacity-10 border-danger border-opacity-25">
|
||
<div class="card-body p-5">
|
||
<span class="icon-shape icon-md bg-danger text-white rounded-2">
|
||
<i class="ti ti-alert-triangle fs-4"></i>
|
||
</span>
|
||
<div class="d-flex flex-column mt-3">
|
||
<span class="fs-2 fw-bold mb-0 d-block" id="stat_critical">—</span>
|
||
<span class="fs-5">Critical (≤7 days)</span>
|
||
<small class="text-muted">Expiring within a week</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-xl-3 col-md-6 col-12">
|
||
<div class="card bg-warning bg-opacity-10 border-warning border-opacity-25">
|
||
<div class="card-body p-5">
|
||
<span class="icon-shape icon-md bg-warning text-white rounded-2">
|
||
<i class="ti ti-clock fs-4"></i>
|
||
</span>
|
||
<div class="d-flex flex-column mt-3">
|
||
<span class="fs-2 fw-bold mb-0 d-block" id="stat_warning">—</span>
|
||
<span class="fs-5">Warning (8–14 days)</span>
|
||
<small class="text-muted">Expiring in 2 weeks</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-xl-3 col-md-6 col-12">
|
||
<div class="card bg-warning bg-opacity-10 border-warning border-opacity-25">
|
||
<div class="card-body p-5">
|
||
<span class="icon-shape icon-md bg-warning text-white rounded-2">
|
||
<i class="ti ti-calendar-clock fs-4"></i>
|
||
</span>
|
||
<div class="d-flex flex-column mt-3">
|
||
<span class="fs-2 fw-bold mb-0 d-block" id="stat_caution">—</span>
|
||
<span class="fs-5">Caution (15–30 days)</span>
|
||
<small class="text-muted">Monitor closely</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filter -->
|
||
<div class="row g-5 mb-5">
|
||
<div class="col-12">
|
||
<div class="card">
|
||
<div class="card-body p-5">
|
||
<h2 class="fs-4 mb-4">Filter Products</h2>
|
||
<div class="row g-5">
|
||
<div class="col-lg-7">
|
||
<div class="position-relative">
|
||
<input type="search" id="search_expired" class="form-control ps-9"
|
||
placeholder="Search by product name, SKU, or lot number…">
|
||
<span class="position-absolute top-25 ms-4">
|
||
<i class="ti ti-search text-muted"></i>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-5">
|
||
<select class="form-select" id="filter_status">
|
||
<option value="">All Status</option>
|
||
<option value="expired">Expired</option>
|
||
<option value="critical">Critical</option>
|
||
<option value="warning">Warning</option>
|
||
<option value="caution">Caution</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Expired table -->
|
||
<div class="row g-5 mb-5">
|
||
<div class="col-12">
|
||
<div class="card">
|
||
<div class="card-body p-5">
|
||
<h2 class="fs-4 mb-2">Expired Products</h2>
|
||
<p class="mb-4">Products that have passed their expiry date and need to be removed</p>
|
||
<div class="table-responsive">
|
||
<table class="table mb-0 text-nowrap table-hover table-centered" id="tbl_expired">
|
||
<thead class="table-danger border-light">
|
||
<tr>
|
||
<th>Product</th>
|
||
<th>SKU</th>
|
||
<th>Lot Number</th>
|
||
<th>Location</th>
|
||
<th>Expiry Date</th>
|
||
<th>Days Overdue</th>
|
||
<th>Quantity</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Near expiry table -->
|
||
<div class="row g-5 mb-5">
|
||
<div class="col-12">
|
||
<div class="card">
|
||
<div class="card-body p-5">
|
||
<h2 class="fs-4 mb-2">Near Expiry Products</h2>
|
||
<p class="mb-4">Products approaching their expiry date that may need attention</p>
|
||
<div class="table-responsive">
|
||
<table class="table mb-0 text-nowrap table-hover table-centered" id="tbl_near_expiry">
|
||
<thead class="table-warning border-light">
|
||
<tr>
|
||
<th>Product</th>
|
||
<th>SKU</th>
|
||
<th>Lot Number</th>
|
||
<th>Location</th>
|
||
<th>Expiry Date</th>
|
||
<th>Days Remaining</th>
|
||
<th>Quantity</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /overview_content -->
|
||
|
||
</div>
|
||
</main>
|
||
|
||
<?php require "../include_ending.php";?>
|
||
|
||
<script>
|
||
var all_items = [];
|
||
var search_term = '';
|
||
var filter_status = '';
|
||
|
||
|
||
// ── Fetch + populate ─────────────────────────────────────────────────
|
||
function retrieve_expired_stock() {
|
||
|
||
var warehouse_id = $('#warehouse').val();
|
||
if (!warehouse_id) return;
|
||
|
||
return ajax_request({
|
||
url: "<?php echo $server_url?>ics/api/engine_report/expired_stock.php",
|
||
autoPrepare: true,
|
||
checkRequired: 0,
|
||
action: 'read',
|
||
debugMode: 0,
|
||
onSuccess: function(res) {
|
||
|
||
var data = res.output;
|
||
|
||
$('#stat_expired').text(data.expired);
|
||
$('#stat_critical').text(data.critical);
|
||
$('#stat_warning').text(data.warning);
|
||
$('#stat_caution').text(data.caution);
|
||
|
||
all_items = data.items || [];
|
||
render_tables();
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
// ── Render both tables from filtered data ────────────────────────────
|
||
function render_tables() {
|
||
|
||
var kw = search_term.toLowerCase();
|
||
var fs = filter_status;
|
||
|
||
var filtered = all_items.filter(function(item) {
|
||
var match_search = !kw ||
|
||
item.product_name.toLowerCase().includes(kw) ||
|
||
item.product_sku.toLowerCase().includes(kw) ||
|
||
(item.lot_number || '').toLowerCase().includes(kw);
|
||
var match_status = !fs || item.status === fs;
|
||
return match_search && match_status;
|
||
});
|
||
|
||
render_expired(filtered.filter(function(i) { return i.status === 'expired'; }));
|
||
render_near_expiry(filtered.filter(function(i) { return i.status !== 'expired'; }));
|
||
}
|
||
|
||
|
||
function status_badge(status) {
|
||
var map = {
|
||
expired: '<span class="badge bg-danger">Expired</span>',
|
||
critical: '<span class="badge bg-danger">Critical (≤7 days)</span>',
|
||
warning: '<span class="badge bg-warning text-dark">Warning (8–14 days)</span>',
|
||
caution: '<span class="badge bg-warning text-dark">Caution (15–30 days)</span>',
|
||
};
|
||
return map[status] || '';
|
||
}
|
||
|
||
|
||
function render_expired(items) {
|
||
if (items.length === 0) {
|
||
$('table#tbl_expired tbody').html(
|
||
'<tr><td colspan="8" class="text-center py-5 text-muted">No expired products.</td></tr>'
|
||
);
|
||
return;
|
||
}
|
||
var body = '';
|
||
$.each(items, function(i, item) {
|
||
body += `<tr>
|
||
<td class="py-3">${item.product_name}</td>
|
||
<td class="py-3">${item.product_sku}</td>
|
||
<td class="py-3">${item.lot_number || '<span class="text-muted">—</span>'}</td>
|
||
<td class="py-3">${item.zone}-${item.aisle}-${item.rack}</td>
|
||
<td class="py-3">${item.expiry_date}</td>
|
||
<td class="py-3 text-danger fw-semibold">${Math.abs(item.days_remaining)} days</td>
|
||
<td class="py-3">${item.quantity}</td>
|
||
<td class="py-3">${status_badge(item.status)}</td>
|
||
</tr>`;
|
||
});
|
||
$('table#tbl_expired tbody').html(body);
|
||
}
|
||
|
||
|
||
function render_near_expiry(items) {
|
||
if (items.length === 0) {
|
||
$('table#tbl_near_expiry tbody').html(
|
||
'<tr><td colspan="8" class="text-center py-5 text-muted">No near-expiry products.</td></tr>'
|
||
);
|
||
return;
|
||
}
|
||
var body = '';
|
||
$.each(items, function(i, item) {
|
||
body += `<tr>
|
||
<td class="py-3">${item.product_name}</td>
|
||
<td class="py-3">${item.product_sku}</td>
|
||
<td class="py-3">${item.lot_number || '<span class="text-muted">—</span>'}</td>
|
||
<td class="py-3">${item.zone}-${item.aisle}-${item.rack}</td>
|
||
<td class="py-3">${item.expiry_date}</td>
|
||
<td class="py-3 text-warning fw-semibold">${item.days_remaining} days</td>
|
||
<td class="py-3">${item.quantity}</td>
|
||
<td class="py-3">${status_badge(item.status)}</td>
|
||
</tr>`;
|
||
});
|
||
$('table#tbl_near_expiry tbody').html(body);
|
||
}
|
||
|
||
|
||
// ── Search + filter ──────────────────────────────────────────────────
|
||
function debounce(fn, ms) {
|
||
var t;
|
||
return function() { clearTimeout(t); t = setTimeout(fn, ms); };
|
||
}
|
||
|
||
$('#search_expired').on('input', debounce(function() {
|
||
search_term = $(this).val().trim();
|
||
render_tables();
|
||
}, 200));
|
||
|
||
$('#filter_status').on('change', function() {
|
||
filter_status = $(this).val();
|
||
render_tables();
|
||
});
|
||
|
||
|
||
// ── Load warehouse dropdown ───────────────────────────────────────────
|
||
function retrieve_warehouse() {
|
||
return ajax_request({
|
||
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
|
||
autoPrepare: true,
|
||
checkRequired: 0,
|
||
action: 'read',
|
||
onSuccess: function(res) {
|
||
var option = '';
|
||
$.each(res.output, function(key, item) {
|
||
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
|
||
});
|
||
$('#warehouse').html(option);
|
||
|
||
// Auto-select first warehouse on page load
|
||
if (res.output && res.output.length > 0) {
|
||
$('#warehouse').val(res.output[0].id);
|
||
retrieve_expired_stock();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
// ── Boot ─────────────────────────────────────────────────────────────
|
||
$(async function() {
|
||
try {
|
||
await retrieve_warehouse();
|
||
} catch(e) {
|
||
console.log(e);
|
||
}
|
||
});
|
||
|
||
$('#warehouse').on('change', function() {
|
||
retrieve_expired_stock();
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
|
||
</html>
|