Files

489 lines
18 KiB
PHP

<?php
require_once __DIR__ . '/../session.php';
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php';?>
<?php require '../include_master_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-5 gap-3">
<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>
</div>
<div>
<span>Total Categories</span>
<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>
</div>
<div>
<span>Active</span>
<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>
</div>
<div>
<span>Inactive</span>
<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>
</div>
<div>
<span>Total Products</span>
<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"
data-bs-target="#pills-categories" type="button" role="tab" aria-controls="pills-categories"
aria-selected="true">Categories</button>
</li>
<li class="nav-item nav-item-secondary" role="presentation">
<button class="nav-link" id="pills-products-tab" data-bs-toggle="pill" data-bs-target="#pills-products"
type="button" role="tab" aria-controls="pills-products" aria-selected="false">Products</button>
</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">
<div class="card overflow-hidden">
<div class="card-body p-6">
<div class="mb-6 d-flex align-items-center justify-content-between">
<div>
<h3 class="mb-1 fs-4">Product Categories</h3>
<p class="mb-0">List of all product categories</p>
</div>
<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>
</div>
</div>
<!-- Search bar -->
<div class="col-lg-12 mb-5">
<div class="position-relative">
<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">
<tr>
<th>Category</th>
<th>Slug</th>
<th>Description</th>
<th style="text-align: right;">Product Count</th>
<th>Status</th>
<th>Actions</th>
</tr>
</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>
<p class="mb-0">List of all products</p>
</div>
<div>
<a href="<?php echo $server_url?>inventory/manage_product.php" class="btn btn-secondary" data-min-role="admin">
<span class="me-1"><i class="ti ti-plus"></i></span>
<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">
<tr>
<th width="75px"></th>
<th>Product</th>
<th>SKU</th>
<th style="text-align: right;">Price / Cost</th>
<th style="text-align: right;">Margin</th>
<th>UOM</th>
<th style="text-align: right;">Current Stock</th>
<th style="text-align: right;">Min Stock</th>
<th style="text-align: right;">Reorder Point</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody></tbody>
<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({
url: "<?php echo $server_url?>inventory/api/engine/product_stats.php",
autoPrepare: true,
checkRequired: 0,
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({
url: "<?php echo $server_url?>inventory/api/engine/product_category.php",
autoPrepare: true,
checkRequired: 0,
action: 'read',
debugMode: 0,
onSuccess: function(res) {
alasql('CREATE TABLE IF NOT EXISTS category');
alasql.tables.category.data = res.output || [];
change_page_category(1);
}
});
}
function change_page_category(page_num) {
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="11" 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">
${item['product_image']
? `<img src="<?php echo $server_url?>uploads/product/${item['product_image']}" alt="${item['product_name']}" style="width:40px;height:40px;object-fit:cover;border-radius:4px;">`
: `<div style="width:40px;height:40px;display:flex;align-items:center;justify-content:center;" class="rounded-2 bg-light"><i class="ti ti-photo fs-5 text-muted"></i></div>`}
</td>
<td class="py-3">${item['product_name']}</td>
<td class="py-3">${item['sku']}</td>
<td class="py-3 text-end">
<div>${format_number(item['price'], 2)}</div>
<small class="text-muted">${format_number(item['cost_price'], 2)}</small>
</td>
<td class="py-3 text-end">
${(item['price'] > 0 && item['cost_price'] > 0)
? `<span class="${((item['price'] - item['cost_price']) / item['price'] * 100) >= 0 ? 'text-success' : 'text-danger'} fw-semibold">
${format_number((item['price'] - item['cost_price']) / item['price'] * 100, 1)}%
</span>`
: '<span class="text-muted">—</span>'}
</td>
<td class="py-3"><span class="badge bg-light text-secondary border">${item['uom'] || 'pcs'}</span></td>
<td class="py-3">
<div class="d-flex justify-content-end align-items-baseline gap-1">
<span>${format_number(item['product_balance'], 4)}</span>
<span class="text-muted small" style="min-width:28px;">${item['uom'] || 'pcs'}</span>
</div>
</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="" data-min-role="admin"><i class="ti ti-eye fs-5"></i></a>
<a href="javascript:;" class="link-danger" data-min-role="admin" 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",
autoPrepare: true,
checkRequired: 0,
debugMode: 0,
data: {
id: id
},
action: 'delete',
onSuccess: function(res) {
element.parent().parent().remove();
}
});
}
function remove_product(id, element) {
return ajax_request({
url: "<?php echo $server_url?>inventory/api/engine/remove_product.php",
autoPrepare: true,
checkRequired: 0,
debugMode: 0,
data: {
id: id
},
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_category').on('input', debounce(function() {
search_term_category = $(this).val().trim();
change_page_category(1);
}, 200));
$('#search_product').on('input', debounce(function() {
search_term_product = $(this).val().trim();
change_page_product(1);
}, 200));
// ── Boot ─────────────────────────────────────────────────────────────
$(async function() {
try {
await retrieve_stats();
await retrieve_category();
await retrieve_product();
} catch (e) {
console.log(e);
}
});
// Remember last active tab across page reloads
$(document).ready(function() {
$('button.nav-link').on('click', function() {
var nav_id = $(this).attr('id');
sessionStorage.setItem("product_nav_id", nav_id);
});
var nav_id = sessionStorage.getItem("product_nav_id");
if (nav_id) {
$(`button#${nav_id}`).trigger('click');
}
});
</script>
</body>
</html>