Lot, Serial, ExpiryDate
This commit is contained in:
@@ -6,6 +6,27 @@
|
||||
|
||||
$id = (int)$data['id'];
|
||||
|
||||
// ── Business rule: min_stock < reorder_point ──────────────────────────
|
||||
// The warning tier (balance between min_stock and reorder_point) only
|
||||
// exists when min_stock is strictly less than reorder_point. Enforcing
|
||||
// this invariant keeps the low-stock report meaningful.
|
||||
$min_stock = (float) ($data['min_stock'] ?? 0);
|
||||
$reorder_point = (float) ($data['reorder_point'] ?? 0);
|
||||
|
||||
if ($min_stock < 0 || $reorder_point < 0) {
|
||||
$answer["success"] = 0;
|
||||
$answer["message"] = "Min stock and reorder point cannot be negative.";
|
||||
http_response_code(400);
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
if ($min_stock >= $reorder_point) {
|
||||
$answer["success"] = 0;
|
||||
$answer["message"] = "Min stock ({$min_stock}) must be less than reorder point ({$reorder_point}).";
|
||||
http_response_code(400);
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
/** 1. FILE HANDLING **/
|
||||
|
||||
@@ -45,11 +45,15 @@
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="min_stock" class="form-label">Min Stock</label>
|
||||
<input type="number" class="form-control" id="min_stock" placeholder="0" required>
|
||||
<input type="number" class="form-control" id="min_stock" placeholder="0" min="0" required>
|
||||
<div class="invalid-feedback" id="min_stock_error"></div>
|
||||
<small class="text-muted" id="min_stock_hint">Safety floor</small>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="reorder_point" class="form-label">Reorder Point</label>
|
||||
<input type="number" class="form-control" id="reorder_point" placeholder="0" required>
|
||||
<input type="number" class="form-control" id="reorder_point" placeholder="0" min="0" required>
|
||||
<div class="invalid-feedback" id="reorder_point_error"></div>
|
||||
<small class="text-muted" id="reorder_point_hint">Must be greater than min stock</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -203,6 +207,46 @@
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function validate_stock_thresholds() {
|
||||
|
||||
var min_stock = parseFloat($('#min_stock').val() || 0);
|
||||
var reorder_point = parseFloat($('#reorder_point').val() || 0);
|
||||
|
||||
// Reset both fields to clean state
|
||||
$('#min_stock, #reorder_point').removeClass('is-invalid');
|
||||
$('#min_stock_error, #reorder_point_error').text('');
|
||||
$('#min_stock_hint, #reorder_point_hint').show();
|
||||
|
||||
// Helper: mark a field invalid and hide its hint
|
||||
function flag(field, msg) {
|
||||
$('#' + field).addClass('is-invalid');
|
||||
$('#' + field + '_error').text(msg);
|
||||
$('#' + field + '_hint').hide();
|
||||
}
|
||||
|
||||
if (min_stock < 0) {
|
||||
flag('min_stock', 'Cannot be negative.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reorder_point < 0) {
|
||||
flag('reorder_point', 'Cannot be negative.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (min_stock >= reorder_point) {
|
||||
flag('reorder_point', 'Must be greater than min stock (' + min_stock + ').');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Live feedback as the user types
|
||||
$('#min_stock, #reorder_point').on('input', validate_stock_thresholds);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -41,19 +41,27 @@
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="aisle_from" class="form-label">Aisle From</label>
|
||||
<input type="text" class="form-control" id="aisle_from" placeholder="Enter aisle form" required>
|
||||
<input type="text" class="form-control" id="aisle_from" placeholder="e.g. 1 or A" required
|
||||
pattern="^([0-9]+|[A-Za-z])$" title="Enter a number (e.g. 1) or a single letter (e.g. A)">
|
||||
<div class="form-text">Number (1–99) or letter (A–Z)</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="aisle_to" class="form-label">Aisle To</label>
|
||||
<input type="text" class="form-control" id="aisle_to" placeholder="Enter aisle to" required>
|
||||
<input type="text" class="form-control" id="aisle_to" placeholder="e.g. 5 or E" required
|
||||
pattern="^([0-9]+|[A-Za-z])$" title="Enter a number (e.g. 5) or a single letter (e.g. E)">
|
||||
<div class="form-text">Must be same type as Aisle From</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="rack_from" class="form-label">Rack From</label>
|
||||
<input type="text" class="form-control" id="rack_from" placeholder="Enter rack form" required>
|
||||
<input type="text" class="form-control" id="rack_from" placeholder="e.g. 1 or A" required
|
||||
pattern="^([0-9]+|[A-Za-z])$" title="Enter a number (e.g. 1) or a single letter (e.g. A)">
|
||||
<div class="form-text">Number (1–99) or letter (A–Z)</div>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="rack_to" class="form-label">Rack To</label>
|
||||
<input type="text" class="form-control" id="rack_to" placeholder="Enter rack to" required>
|
||||
<input type="text" class="form-control" id="rack_to" placeholder="e.g. 10 or J" required
|
||||
pattern="^([0-9]+|[A-Za-z])$" title="Enter a number (e.g. 10) or a single letter (e.g. J)">
|
||||
<div class="form-text">Must be same type as Rack From</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,6 +93,45 @@
|
||||
<script>
|
||||
function manage_storage() {
|
||||
|
||||
// Validate that aisle from/to and rack from/to are same type
|
||||
var af = $('#aisle_from').val().trim().toUpperCase();
|
||||
var at = $('#aisle_to').val().trim().toUpperCase();
|
||||
var rf = $('#rack_from').val().trim().toUpperCase();
|
||||
var rt = $('#rack_to').val().trim().toUpperCase();
|
||||
|
||||
var isNumeric = function(v) { return /^[0-9]+$/.test(v); };
|
||||
var isAlpha = function(v) { return /^[A-Z]$/.test(v); };
|
||||
|
||||
if (af && at) {
|
||||
if (isNumeric(af) !== isNumeric(at)) {
|
||||
alert('Aisle From and Aisle To must both be numbers or both be letters.');
|
||||
return;
|
||||
}
|
||||
if (isAlpha(af) && isAlpha(at) && af.charCodeAt(0) > at.charCodeAt(0)) {
|
||||
alert('Aisle From must be less than or equal to Aisle To.');
|
||||
return;
|
||||
}
|
||||
if (isNumeric(af) && isNumeric(at) && parseInt(af) > parseInt(at)) {
|
||||
alert('Aisle From must be less than or equal to Aisle To.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (rf && rt) {
|
||||
if (isNumeric(rf) !== isNumeric(rt)) {
|
||||
alert('Rack From and Rack To must both be numbers or both be letters.');
|
||||
return;
|
||||
}
|
||||
if (isAlpha(rf) && isAlpha(rt) && rf.charCodeAt(0) > rt.charCodeAt(0)) {
|
||||
alert('Rack From must be less than or equal to Rack To.');
|
||||
return;
|
||||
}
|
||||
if (isNumeric(rf) && isNumeric(rt) && parseInt(rf) > parseInt(rt)) {
|
||||
alert('Rack From must be less than or equal to Rack To.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/manage_storage.php",
|
||||
autoPrepare: true,
|
||||
|
||||
+201
-177
@@ -2,7 +2,6 @@
|
||||
session_start();
|
||||
require '../config.php';
|
||||
require '../include_header.php';
|
||||
|
||||
?>
|
||||
|
||||
<body>
|
||||
@@ -13,96 +12,87 @@
|
||||
<!-- 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-5 gap-3">
|
||||
<div class="">
|
||||
<div>
|
||||
<h1 class="fs-3 mb-1">Products</h1>
|
||||
<p class="mb-0">Manage your products</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary stats -->
|
||||
<div class="row g-5 mb-5">
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card">
|
||||
<div class="card-body p-6">
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<div class="icon-shape icon-lg bg-primary bg-opacity-10 text-primary rounded-2">
|
||||
<i class="ti ti-category fs-4 "></i>
|
||||
<i class="ti ti-category fs-4"></i>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
<span>Total Categories</span>
|
||||
<h2 class="fw-bold mb-0" id="total_category">6</h2>
|
||||
<h2 class="fw-bold mb-0" id="total_category">—</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card">
|
||||
<div class="card-body p-6">
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<div class="icon-shape icon-lg bg-info bg-opacity-10 text-info rounded-2">
|
||||
<i class="ti ti-check fs-4 "></i>
|
||||
<i class="ti ti-check fs-4"></i>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
<span>Active</span>
|
||||
<h2 class="fw-bold mb-0" id="active_category">5</h2>
|
||||
<h2 class="fw-bold mb-0" id="active_category">—</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card">
|
||||
<div class="card-body p-6">
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<div class="icon-shape icon-lg bg-warning bg-opacity-10 text-warning rounded-2">
|
||||
<i class="ti ti-x fs-4 "></i>
|
||||
<i class="ti ti-x fs-4"></i>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
<span>Inactive</span>
|
||||
<h2 class="fw-bold mb-0" id="inactive_category">1</h2>
|
||||
<h2 class="fw-bold mb-0" id="inactive_category">—</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card">
|
||||
<div class="card-body p-6">
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<div class="icon-shape icon-lg bg-warning bg-opacity-10 text-warning rounded-2">
|
||||
<i class="ti ti-box-seam fs-4 "></i>
|
||||
<i class="ti ti-box-seam fs-4"></i>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
<span>Total Products</span>
|
||||
<h2 class="fw-bold mb-0" id="total_product">356</h2>
|
||||
<h2 class="fw-bold mb-0" id="total_product">—</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-pills mb-3 bg-light p-2 rounded-2 d-inline-flex" id="pills-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="pills-categories-tab" data-bs-toggle="pill"
|
||||
@@ -115,6 +105,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Categories tab -->
|
||||
<div class="row g-5 mb-5 tab-pane fade show active" id="pills-categories" role="tabpanel"
|
||||
aria-labelledby="pills-categories-tab">
|
||||
<div class="col-12">
|
||||
@@ -129,20 +120,23 @@
|
||||
<div>
|
||||
<a href="<?php echo $server_url?>inventory/manage_category.php" class="btn btn-primary">
|
||||
<span class="me-1"><i class="ti ti-plus"></i></span>
|
||||
<span>Add Categories</span></a>
|
||||
<span>Add Categories</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search bar -->
|
||||
<div class="col-lg-12 mb-5">
|
||||
<div class="position-relative">
|
||||
<input type="search" class="form-control ps-9" placeholder="Search by product name or SKU">
|
||||
<input type="search" id="search_category" class="form-control ps-9"
|
||||
placeholder="Search by category, slug, or description…">
|
||||
<span class="position-absolute top-25 ms-4">
|
||||
<i class="ti ti-search text-muted"></i>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="table mb-0 text-nowrap table-hover table-centered" id="category">
|
||||
<thead class="table-primary border-light">
|
||||
@@ -154,26 +148,23 @@
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
<tfoot class="">
|
||||
|
||||
</tfoot>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
<tfoot></tfoot>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Products tab -->
|
||||
<div class="row g-5 mb-5 tab-pane fade" id="pills-products" role="tabpanel" aria-labelledby="pills-products-tab">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body p-5">
|
||||
|
||||
<div class="mb-6 d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h3 class="mb-1 fs-4">Products</h3>
|
||||
@@ -182,10 +173,23 @@
|
||||
<div>
|
||||
<a href="<?php echo $server_url?>inventory/manage_product.php" class="btn btn-secondary">
|
||||
<span class="me-1"><i class="ti ti-plus"></i></span>
|
||||
<span>Add Product</span></a>
|
||||
<span>Add Product</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search bar -->
|
||||
<div class="col-lg-12 mb-5">
|
||||
<div class="position-relative">
|
||||
<input type="search" id="search_product" class="form-control ps-9"
|
||||
placeholder="Search by product name or SKU…">
|
||||
<span class="position-absolute top-25 ms-4">
|
||||
<i class="ti ti-search text-muted"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 table-centered" id="product">
|
||||
<thead class="table-secondary border-light">
|
||||
@@ -201,24 +205,27 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
<tfoot class="">
|
||||
|
||||
</tfoot>
|
||||
<tfoot></tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php require "../include_ending.php";?>
|
||||
|
||||
<script>
|
||||
// ── Current search terms ─────────────────────────────────────────────
|
||||
var search_term_category = '';
|
||||
var search_term_product = '';
|
||||
|
||||
|
||||
// ── Stats ────────────────────────────────────────────────────────────
|
||||
function retrieve_stats() {
|
||||
|
||||
return ajax_request({
|
||||
@@ -228,20 +235,16 @@
|
||||
action: 'read',
|
||||
debugMode: 0,
|
||||
onSuccess: function(res) {
|
||||
|
||||
$("#total_category").text(res.total_category);
|
||||
$("#active_category").text(res.active_category);
|
||||
$("#inactive_category").text(res.inactive_category);
|
||||
$("#total_product").text(res.total_product);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ── Category: fetch + render ─────────────────────────────────────────
|
||||
function retrieve_category() {
|
||||
|
||||
return ajax_request({
|
||||
@@ -252,31 +255,129 @@
|
||||
debugMode: 0,
|
||||
onSuccess: function(res) {
|
||||
|
||||
// Register the array as a table name
|
||||
alasql('CREATE TABLE IF NOT EXISTS output');
|
||||
alasql.tables.output.data = res.output || [];
|
||||
alasql('CREATE TABLE IF NOT EXISTS category');
|
||||
alasql.tables.category.data = res.output || [];
|
||||
|
||||
var total_records = alasql.tables.output.data.length;
|
||||
|
||||
var table_id = 'category';
|
||||
|
||||
var page = generate_pagination(table_id,total_records);
|
||||
$(`table#${table_id} tfoot`).html(page);
|
||||
|
||||
// dynamic function name
|
||||
if (typeof window[`change_page_${table_id}`] === "function") {
|
||||
window[`change_page_${table_id}`](1);
|
||||
} else {
|
||||
console.error(`Function change_page_${table_id} does not exist.`);
|
||||
}
|
||||
change_page_category(1);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function change_page_category(page_num) {
|
||||
|
||||
function remove_category(id,element) {
|
||||
var offset = (page_num - 1) * prop_limit;
|
||||
var kw = search_term_category.toLowerCase();
|
||||
|
||||
var filtered = kw ?
|
||||
alasql(`SELECT * FROM category
|
||||
WHERE LOWER(category) LIKE '%${kw}%'
|
||||
OR LOWER(slug) LIKE '%${kw}%'
|
||||
OR LOWER(description) LIKE '%${kw}%'`) :
|
||||
alasql(`SELECT * FROM category`);
|
||||
|
||||
var page = generate_pagination('category', filtered.length);
|
||||
$(`table#category tfoot`).html(page);
|
||||
|
||||
if (filtered.length === 0) {
|
||||
$("table#category > tbody").html(
|
||||
'<tr><td colspan="6" class="text-center py-5 text-muted">No categories match your search.</td></tr>'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var page_data = filtered.slice(offset, offset + prop_limit);
|
||||
|
||||
var body = '';
|
||||
$.each(page_data, function(key, item) {
|
||||
body += `<tr>
|
||||
<td class="py-3">${item['category']}</td>
|
||||
<td class="py-3">${item['slug']}</td>
|
||||
<td class="py-3">${item['description']}</td>
|
||||
<td class="py-3" style="text-align: right;">${item['product_count']}</td>
|
||||
<td class="py-3">
|
||||
${(item['status']==1)?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>inventory/manage_category.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
|
||||
<a href="javascript:;" class="link-danger" onclick="remove_category(${item['id']},$(this))"><i class="ti ti-trash ms-2 fs-5"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
$("table#category > tbody").html(body);
|
||||
}
|
||||
|
||||
|
||||
// ── Product: fetch + render ──────────────────────────────────────────
|
||||
function retrieve_product() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/product.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
alasql('CREATE TABLE IF NOT EXISTS product');
|
||||
alasql.tables.product.data = res.output || [];
|
||||
|
||||
change_page_product(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function change_page_product(page_num) {
|
||||
|
||||
var offset = (page_num - 1) * prop_limit;
|
||||
var kw = search_term_product.toLowerCase();
|
||||
|
||||
var filtered = kw ?
|
||||
alasql(`SELECT * FROM product
|
||||
WHERE LOWER(product_name) LIKE '%${kw}%'
|
||||
OR LOWER(sku) LIKE '%${kw}%'`) :
|
||||
alasql(`SELECT * FROM product`);
|
||||
|
||||
var page = generate_pagination('product', filtered.length);
|
||||
$(`table#product tfoot`).html(page);
|
||||
|
||||
if (filtered.length === 0) {
|
||||
$("table#product > tbody").html(
|
||||
'<tr><td colspan="8" class="text-center py-5 text-muted">No products match your search.</td></tr>'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var page_data = filtered.slice(offset, offset + prop_limit);
|
||||
|
||||
var body = '';
|
||||
$.each(page_data, function(key, item) {
|
||||
body += `<tr height="50px">
|
||||
<td class="py-3">
|
||||
<img src="<?php echo $server_url?>uploads/product/${item['product_image']}" alt="${item['product_name']}" class="img-fluid" style="height: 25px;">
|
||||
</td>
|
||||
<td class="py-3">${item['product_name']}</td>
|
||||
<td class="py-3">${item['sku']}</td>
|
||||
<td class="py-3" style="text-align: right;">${format_number(item['product_balance'], 4)}</td>
|
||||
<td class="py-3" style="text-align: right;">${format_number(item['min_stock'], 2)}</td>
|
||||
<td class="py-3" style="text-align: right;">${format_number(item['reorder_point'], 2)}</td>
|
||||
<td class="py-3">
|
||||
${(item['status']==1)?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>inventory/manage_product.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
|
||||
<a href="javascript:;" class="link-danger" onclick="remove_product(${item['id']},$(this))"><i class="ti ti-trash ms-2 fs-5"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
$("table#product > tbody").html(body);
|
||||
}
|
||||
|
||||
|
||||
// ── Remove handlers ──────────────────────────────────────────────────
|
||||
function remove_category(id, element) {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/remove_category.php",
|
||||
@@ -288,17 +389,13 @@
|
||||
},
|
||||
action: 'delete',
|
||||
onSuccess: function(res) {
|
||||
|
||||
element.parent().parent().remove();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function remove_product(id,element) {
|
||||
function remove_product(id, element) {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/remove_product.php",
|
||||
@@ -310,109 +407,37 @@
|
||||
},
|
||||
action: 'delete',
|
||||
onSuccess: function(res) {
|
||||
|
||||
element.parent().parent().remove();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function change_page_category(page_num) {
|
||||
|
||||
var offset = (page_num - 1) * prop_limit;
|
||||
|
||||
var page_data = alasql(`SELECT * FROM output LIMIT ${prop_limit} OFFSET ${offset}`);
|
||||
|
||||
var body = ``;
|
||||
$.each(page_data, function(key, item) {
|
||||
body += `<tr>
|
||||
<td class="py-3">${item['category']}</td>
|
||||
<td class="py-3">${item['slug']}</td>
|
||||
<td class="py-3">${item['description']}</td>
|
||||
<td class="py-3" style="text-align: right;">${item['product_count']}</td>
|
||||
<td class="py-3">
|
||||
${(item['status']==1)?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>inventory/manage_category.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
|
||||
<a href="javascript:;" class="link-danger" onclick="remove_category(${item['id']},$(this))"><i class="ti ti-trash ms-2 fs-5"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
})
|
||||
|
||||
$("table#category > tbody").html(body);
|
||||
|
||||
// ── Search: debounce + re-render ─────────────────────────────────────
|
||||
function debounce(fn, ms) {
|
||||
var t;
|
||||
return function() {
|
||||
var args = arguments,
|
||||
ctx = this;
|
||||
clearTimeout(t);
|
||||
t = setTimeout(function() {
|
||||
fn.apply(ctx, args);
|
||||
}, ms);
|
||||
};
|
||||
}
|
||||
|
||||
$('#search_category').on('input', debounce(function() {
|
||||
search_term_category = $(this).val().trim();
|
||||
change_page_category(1);
|
||||
}, 200));
|
||||
|
||||
|
||||
function retrieve_product() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/product.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
// Register the array as a table name
|
||||
alasql('CREATE TABLE IF NOT EXISTS product');
|
||||
alasql.tables.product.data = res.output || [];
|
||||
|
||||
var total_records = alasql.tables.product.data.length;
|
||||
|
||||
var table_id = 'product';
|
||||
|
||||
var page = generate_pagination(table_id,total_records);
|
||||
$(`table#${table_id} tfoot`).html(page);
|
||||
|
||||
// dynamic function name
|
||||
if (typeof window[`change_page_${table_id}`] === "function") {
|
||||
window[`change_page_${table_id}`](1);
|
||||
} else {
|
||||
console.error(`Function change_page_${table_id} does not exist.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
$('#search_product').on('input', debounce(function() {
|
||||
search_term_product = $(this).val().trim();
|
||||
change_page_product(1);
|
||||
}, 200));
|
||||
|
||||
|
||||
|
||||
function change_page_product(page_num) {
|
||||
|
||||
var offset = (page_num - 1) * prop_limit;
|
||||
|
||||
var page_data = alasql(`SELECT * FROM product LIMIT ${prop_limit} OFFSET ${offset}`);
|
||||
|
||||
var body = ``;
|
||||
$.each(page_data, function(key, item) {
|
||||
body += `<tr height="50px">
|
||||
<td class="py-3">
|
||||
<img src="<?php echo $server_url?>uploads/product/${item['product_image']}" alt="${item['product_name']}" class="img-fluid" style="height: 25px;">
|
||||
</td>
|
||||
<td class="py-3">${item['product_name']}</td>
|
||||
<td class="py-3">${item['sku']}</td>
|
||||
<td class="py-3" style='text-align: right;'>${format_number(item['product_balance'],4)}</td>
|
||||
<td class="py-3" style='text-align: right;'>${format_number(item['min_stock'],2)}</td>
|
||||
<td class="py-3" style='text-align: right;'>${format_number(item['reorder_point'],2)}</td>
|
||||
<td class="py-3">
|
||||
${(item['status']==1)?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>inventory/manage_product.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
|
||||
<a href="javascript:;" class="link-danger" onclick="remove_product(${item['id']},$(this))"><i class="ti ti-trash ms-2 fs-5"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
})
|
||||
|
||||
$("table#product > tbody").html(body);
|
||||
|
||||
}
|
||||
|
||||
// ── Boot ─────────────────────────────────────────────────────────────
|
||||
$(async function() {
|
||||
try {
|
||||
await retrieve_stats();
|
||||
@@ -423,20 +448,19 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Remember last active tab across page reloads
|
||||
$(document).ready(function() {
|
||||
// switching to nav-link from last leave
|
||||
$('button.nav-link').on('click', function() {
|
||||
nav_id = $(this).attr('id');
|
||||
var nav_id = $(this).attr('id');
|
||||
sessionStorage.setItem("product_nav_id", nav_id);
|
||||
})
|
||||
let nav_id = sessionStorage.getItem("product_nav_id");
|
||||
if(nav_id){
|
||||
});
|
||||
var nav_id = sessionStorage.getItem("product_nav_id");
|
||||
if (nav_id) {
|
||||
$(`button#${nav_id}`).trigger('click');
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+171
-142
@@ -2,7 +2,6 @@
|
||||
session_start();
|
||||
require '../config.php';
|
||||
require '../include_header.php';
|
||||
|
||||
?>
|
||||
|
||||
<body>
|
||||
@@ -13,30 +12,33 @@
|
||||
<!-- MAIN CONTENT -->
|
||||
<main id="content" class="content py-15">
|
||||
<div class="container-fluid">
|
||||
<div class="row ">
|
||||
|
||||
<!-- Page header -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="mb-6 d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h1 class="fs-3 mb-1">Warehouse / Location Management
|
||||
</h1>
|
||||
<h1 class="fs-3 mb-1">Warehouse / Location Management</h1>
|
||||
<p class="mb-0">Manage your warehouses, storage locations, and capacity.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary stats -->
|
||||
<div class="row mb-5 g-5">
|
||||
<div class="col-12">
|
||||
<div class="card ">
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap ">
|
||||
<div class=" p-6 flex-grow-1 d-flex justify-content-between" id="total_warehouse">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap">
|
||||
|
||||
<div class="p-6 flex-grow-1 d-flex justify-content-between" id="total_warehouse">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fs-2 fw-bold mb-3 d-block">5</span>
|
||||
<span class="fs-2 fw-bold mb-3 d-block">—</span>
|
||||
<span class="fs-5">Total Warehouses</span>
|
||||
<small class="text-info">
|
||||
Across 5 locations</small>
|
||||
<small class="text-info">Across — locations</small>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
<span class="text-info">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"
|
||||
@@ -48,17 +50,15 @@
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class=" p-6 flex-grow-1 d-flex justify-content-between" id="total_capacity">
|
||||
|
||||
<div class="p-6 flex-grow-1 d-flex justify-content-between" id="total_capacity">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fs-2 fw-bold mb-3 d-block">41,000</span>
|
||||
<!-- Total Capacity card -->
|
||||
<span class="fs-2 fw-bold mb-3 d-block">—</span>
|
||||
<span class="fs-5">Total Capacity</span>
|
||||
<small class="text-warning">Rack positions available</small>
|
||||
<!-- was "Storage units available" -->
|
||||
<small class="text-warning">Rack positions available</small>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
<span class="text-warning">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"
|
||||
@@ -77,17 +77,15 @@
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class=" p-6 flex-grow-1 d-flex justify-content-between" id="total_space_used">
|
||||
|
||||
<div class="p-6 flex-grow-1 d-flex justify-content-between" id="total_space_used">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fs-2 fw-bold mb-3 d-block">25,500</span>
|
||||
<!-- Space Used card -->
|
||||
<span class="fs-2 fw-bold mb-3 d-block">—</span>
|
||||
<span class="fs-5">Space Used</span>
|
||||
<small class="text-success">Racks occupied</small>
|
||||
<!-- was "Storage units available" (which was wrong!) -->
|
||||
<small class="text-success">Racks occupied</small>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
<span class="text-success">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"
|
||||
@@ -101,16 +99,15 @@
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="-0 p-6 flex-grow-1 d-flex justify-content-between" id="total_product">
|
||||
|
||||
<div class="p-6 flex-grow-1 d-flex justify-content-between" id="total_product">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fs-2 fw-bold mb-3 d-block">730</span>
|
||||
<span class="fs-2 fw-bold mb-3 d-block">—</span>
|
||||
<span class="fs-5">Total Products</span>
|
||||
<small class="text-primary">
|
||||
Products stored</small>
|
||||
<small class="text-primary">Products stored</small>
|
||||
</div>
|
||||
<div class="">
|
||||
<div>
|
||||
<span class="text-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"
|
||||
@@ -121,19 +118,15 @@
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-pills mb-3 bg-light p-2 rounded-2 d-inline-flex" id="pills-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="pills-warehouse-tab" data-bs-toggle="pill"
|
||||
@@ -146,32 +139,37 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Warehouses tab -->
|
||||
<div class="row g-5 mb-5 tab-pane fade show active" id="pills-warehouse" role="tabpanel"
|
||||
aria-labelledby="pills-warehouse-tab">
|
||||
<div class="col-12">
|
||||
<div class="card ">
|
||||
<div class="card">
|
||||
<div class="card-body p-5 justify-content-between">
|
||||
|
||||
<div class="mb-6 d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h2 class="fs-4 mb-2">Warehouses</h2>
|
||||
<p>All warehouse facilities and their current status</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="<?php echo $server_url?>inventory/manage_warehouse.php" class="btn btn-primary">Add
|
||||
Warehouse</a>
|
||||
<a href="<?php echo $server_url?>inventory/manage_warehouse.php" class="btn btn-primary">
|
||||
Add Warehouse
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search bar -->
|
||||
<div class="col-lg-12 mb-5">
|
||||
<div class="position-relative">
|
||||
<input type="search" class="form-control ps-9" placeholder="Search by product name or SKU">
|
||||
<input type="search" id="search_warehouse" class="form-control ps-9"
|
||||
placeholder="Search by warehouse name, location, or manager…">
|
||||
<span class="position-absolute top-25 ms-4">
|
||||
<i class="ti ti-search text-muted"></i>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="table mb-0 text-nowrap table-hover table-centered" id="warehouse">
|
||||
<thead class="table-primary border-primary">
|
||||
@@ -182,46 +180,49 @@
|
||||
<th class="py-3">Capacity</th>
|
||||
<th class="py-3">Products</th>
|
||||
<th class="py-3">Status</th>
|
||||
|
||||
<th class="py-3">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
<tfoot class="">
|
||||
|
||||
</tfoot>
|
||||
<tfoot></tfoot>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Storage tab -->
|
||||
<div class="row g-5 mb-5 tab-pane fade" id="pills-storage" role="tabpanel" aria-labelledby="pills-storage-tab">
|
||||
<div class="col-12">
|
||||
<div class="card ">
|
||||
<div class="card">
|
||||
<div class="card-body p-5">
|
||||
|
||||
<div class="mb-6 d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h2 class="fs-4 mb-2">Storage Locations</h2>
|
||||
<p>Zones, aisles, and rack assignments</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="<?php echo $server_url?>inventory/manage_storage.php" class="btn btn-secondary">Add
|
||||
Storage</a>
|
||||
<a href="<?php echo $server_url?>inventory/manage_storage.php" class="btn btn-secondary">
|
||||
Add Storage
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search bar -->
|
||||
<div class="col-lg-12 mb-5">
|
||||
<div class="position-relative">
|
||||
<input type="search" class="form-control ps-9" placeholder="Search by product name or SKU">
|
||||
<input type="search" id="search_storage" class="form-control ps-9"
|
||||
placeholder="Search by warehouse or zone…">
|
||||
<span class="position-absolute top-25 ms-4">
|
||||
<i class="ti ti-search text-muted"></i>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="table mb-0 text-nowrap table-hover table-centered" id="storage">
|
||||
<thead class="table-secondary">
|
||||
@@ -231,17 +232,14 @@
|
||||
<th class="py-3">Aisle Range</th>
|
||||
<th class="py-3">Rack Range</th>
|
||||
<th class="py-3">Items Stored</th>
|
||||
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
<tfoot class="">
|
||||
|
||||
</tfoot>
|
||||
<tfoot></tfoot>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -253,7 +251,14 @@
|
||||
<?php require "../include_ending.php";?>
|
||||
|
||||
<script>
|
||||
// ── Current search terms (read by change_page_* when paginating) ─────
|
||||
var search_term_warehouse = '';
|
||||
var search_term_storage = '';
|
||||
|
||||
|
||||
// ── Stats card ───────────────────────────────────────────────────────
|
||||
function retrieve_stats() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/warehouse_stats.php",
|
||||
autoPrepare: true,
|
||||
@@ -264,16 +269,13 @@
|
||||
$("#total_warehouse span.fs-2").text(res.total_warehouse);
|
||||
$("#total_warehouse small.text-info").text(`Across ${res.total_location} locations`);
|
||||
|
||||
// Capacity = rack count (integer, no decimals)
|
||||
$("#total_capacity span.fs-2").text(format_number(res.total_capacity, 0));
|
||||
|
||||
// NEW: Space Used wiring
|
||||
$("#total_space_used span.fs-2").text(format_number(res.space_used, 0));
|
||||
|
||||
// Optional: show % utilization in the small text
|
||||
var pct = res.total_capacity > 0
|
||||
? Math.round(res.space_used / res.total_capacity * 100)
|
||||
: 0;
|
||||
|
||||
var pct = res.total_capacity > 0 ?
|
||||
Math.round(res.space_used / res.total_capacity * 100) :
|
||||
0;
|
||||
$("#total_space_used small").text(`${pct}% utilized`);
|
||||
|
||||
$("#total_product span.fs-2").text(res.total_product);
|
||||
@@ -282,8 +284,7 @@
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ── Warehouse: fetch + render ────────────────────────────────────────
|
||||
function retrieve_warehouse() {
|
||||
|
||||
return ajax_request({
|
||||
@@ -293,58 +294,65 @@
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
// Register the array as a table name
|
||||
alasql('CREATE TABLE IF NOT EXISTS output');
|
||||
alasql.tables.output.data = res.output || [];
|
||||
|
||||
var total_records = alasql.tables.output.data.length;
|
||||
|
||||
var table_id = 'warehouse';
|
||||
|
||||
var page = generate_pagination(table_id,total_records);
|
||||
$(`table#${table_id} tfoot`).html(page);
|
||||
|
||||
// dynamic function name
|
||||
if (typeof window[`change_page_${table_id}`] === "function") {
|
||||
window[`change_page_${table_id}`](1);
|
||||
} else {
|
||||
console.error(`Function change_page_${table_id} does not exist.`);
|
||||
}
|
||||
change_page_warehouse(1);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function change_page_warehouse(page_num) {
|
||||
|
||||
var offset = (page_num - 1) * prop_limit;
|
||||
var kw = search_term_warehouse.toLowerCase();
|
||||
|
||||
var page_data = alasql(`SELECT * FROM output LIMIT ${prop_limit} OFFSET ${offset}`);
|
||||
// Filter in-memory, then paginate
|
||||
var filtered = kw ?
|
||||
alasql(`SELECT * FROM output
|
||||
WHERE LOWER(warehouse_name) LIKE '%${kw}%'
|
||||
OR LOWER(location) LIKE '%${kw}%'
|
||||
OR LOWER(manager_name) LIKE '%${kw}%'
|
||||
OR LOWER(manager_surname) LIKE '%${kw}%'`) :
|
||||
alasql(`SELECT * FROM output`);
|
||||
|
||||
var body = ``;
|
||||
// Pagination reflects the filtered total
|
||||
var page = generate_pagination('warehouse', filtered.length);
|
||||
$(`table#warehouse tfoot`).html(page);
|
||||
|
||||
var page_data = filtered.slice(offset, offset + prop_limit);
|
||||
|
||||
if (filtered.length === 0) {
|
||||
$("table#warehouse > tbody").html(
|
||||
'<tr><td colspan="7" class="text-center py-5 text-muted">No warehouses match your search.</td></tr>'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var body = '';
|
||||
$.each(page_data, function(key, item) {
|
||||
body += `<tr>
|
||||
<td class="py-3">${item['warehouse_name']}</td>
|
||||
<td class="py-3">${item['location']}</td>
|
||||
<td class="py-3">${item['manager_name']} ${item['manager_surname']}</td>
|
||||
<td class="py-3" style='text-align: right;'>${format_number(item['capacity'],2)}</td>
|
||||
<td class="py-3">${item['unique_product']}</td>
|
||||
<td class="py-3">
|
||||
${(item['status']==1)?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>inventory/manage_warehouse.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
|
||||
<a href="javascript:;" class="link-danger" onclick="remove_warehouse(${item['id']},$(this))"><i class="ti ti-trash ms-2 fs-5"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
})
|
||||
<td class="py-3">${item['warehouse_name']}</td>
|
||||
<td class="py-3">${item['location']}</td>
|
||||
<td class="py-3">${item['manager_name']} ${item['manager_surname']}</td>
|
||||
<td class="py-3" style='text-align: right;'>${format_number(item['capacity'], 2)}</td>
|
||||
<td class="py-3">${item['unique_product']}</td>
|
||||
<td class="py-3">
|
||||
${(item['status']==1)?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>inventory/manage_warehouse.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
|
||||
<a href="javascript:;" class="link-danger" onclick="remove_warehouse(${item['id']},$(this))"><i class="ti ti-trash ms-2 fs-5"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
$("table#warehouse > tbody").html(body);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ── Storage: fetch + render ──────────────────────────────────────────
|
||||
function retrieve_storage() {
|
||||
|
||||
return ajax_request({
|
||||
@@ -354,56 +362,59 @@
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
// Register the array as a table name
|
||||
alasql('CREATE TABLE IF NOT EXISTS storage');
|
||||
alasql.tables.storage.data = res.output || [];
|
||||
|
||||
var total_records = alasql.tables.storage.data.length;
|
||||
|
||||
var table_id = 'storage';
|
||||
|
||||
var page = generate_pagination(table_id,total_records);
|
||||
$(`table#${table_id} tfoot`).html(page);
|
||||
|
||||
// dynamic function name
|
||||
if (typeof window[`change_page_${table_id}`] === "function") {
|
||||
window[`change_page_${table_id}`](1);
|
||||
} else {
|
||||
console.error(`Function change_page_${table_id} does not exist.`);
|
||||
}
|
||||
change_page_storage(1);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function change_page_storage(page_num) {
|
||||
|
||||
var offset = (page_num - 1) * prop_limit;
|
||||
var kw = search_term_storage.toLowerCase();
|
||||
|
||||
var page_data = alasql(`SELECT * FROM storage LIMIT ${prop_limit} OFFSET ${offset}`);
|
||||
var filtered = kw ?
|
||||
alasql(`SELECT * FROM storage
|
||||
WHERE LOWER(warehouse_name) LIKE '%${kw}%'
|
||||
OR LOWER(zone) LIKE '%${kw}%'`) :
|
||||
alasql(`SELECT * FROM storage`);
|
||||
|
||||
var body = ``;
|
||||
var page = generate_pagination('storage', filtered.length);
|
||||
$(`table#storage tfoot`).html(page);
|
||||
|
||||
var page_data = filtered.slice(offset, offset + prop_limit);
|
||||
|
||||
if (filtered.length === 0) {
|
||||
$("table#storage > tbody").html(
|
||||
'<tr><td colspan="6" class="text-center py-5 text-muted">No storage locations match your search.</td></tr>'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var body = '';
|
||||
$.each(page_data, function(key, item) {
|
||||
body += `<tr>
|
||||
<td class="py-3">${item['warehouse_name']}</td>
|
||||
<td class="py-3">${item['zone']}</td>
|
||||
<td class="py-3">${item['aisle_from']} - ${item["aisle_to"]}</td>
|
||||
<td class="py-3">${item['rack_from']} - ${item["rack_to"]}</td>
|
||||
<td class="py-3"></td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>inventory/manage_storage.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
|
||||
<a href="javascript:;" class="link-danger" onclick="remove_storage(${item['id']},$(this))"><i class="ti ti-trash ms-2 fs-5"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
})
|
||||
<td class="py-3">${item['warehouse_name']}</td>
|
||||
<td class="py-3">${item['zone']}</td>
|
||||
<td class="py-3">${item['aisle_from']} - ${item["aisle_to"]}</td>
|
||||
<td class="py-3">${item['rack_from']} - ${item["rack_to"]}</td>
|
||||
<td class="py-3"></td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>inventory/manage_storage.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
|
||||
<a href="javascript:;" class="link-danger" onclick="remove_storage(${item['id']},$(this))"><i class="ti ti-trash ms-2 fs-5"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
$("table#storage > tbody").html(body);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function remove_warehouse(id,element) {
|
||||
// ── Remove handlers ──────────────────────────────────────────────────
|
||||
function remove_warehouse(id, element) {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/remove_warehouse.php",
|
||||
@@ -415,16 +426,13 @@
|
||||
},
|
||||
action: 'delete',
|
||||
onSuccess: function(res) {
|
||||
|
||||
element.parent().parent().remove();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function remove_storage(id,element) {
|
||||
function remove_storage(id, element) {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/remove_storage.php",
|
||||
@@ -436,15 +444,37 @@
|
||||
},
|
||||
action: 'delete',
|
||||
onSuccess: function(res) {
|
||||
|
||||
element.parent().parent().remove();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ── Search: debounce + re-render ─────────────────────────────────────
|
||||
function debounce(fn, ms) {
|
||||
var t;
|
||||
return function() {
|
||||
var args = arguments,
|
||||
ctx = this;
|
||||
clearTimeout(t);
|
||||
t = setTimeout(function() {
|
||||
fn.apply(ctx, args);
|
||||
}, ms);
|
||||
};
|
||||
}
|
||||
|
||||
$('#search_warehouse').on('input', debounce(function() {
|
||||
search_term_warehouse = $(this).val().trim();
|
||||
change_page_warehouse(1); // always reset to page 1 on new search
|
||||
}, 200));
|
||||
|
||||
$('#search_storage').on('input', debounce(function() {
|
||||
search_term_storage = $(this).val().trim();
|
||||
change_page_storage(1);
|
||||
}, 200));
|
||||
|
||||
|
||||
// ── Boot ─────────────────────────────────────────────────────────────
|
||||
$(async function() {
|
||||
try {
|
||||
await retrieve_stats();
|
||||
@@ -455,20 +485,19 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Remember last active tab across page reloads
|
||||
$(document).ready(function() {
|
||||
// switching to nav-link from last leave
|
||||
$('button.nav-link').on('click', function() {
|
||||
nav_id = $(this).attr('id');
|
||||
var nav_id = $(this).attr('id');
|
||||
sessionStorage.setItem("warehouse_nav_id", nav_id);
|
||||
})
|
||||
let nav_id = sessionStorage.getItem("warehouse_nav_id");
|
||||
if(nav_id){
|
||||
});
|
||||
var nav_id = sessionStorage.getItem("warehouse_nav_id");
|
||||
if (nav_id) {
|
||||
$(`button#${nav_id}`).trigger('click');
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user