Lot, Serial, ExpiryDate
This commit is contained in:
+199
-357
@@ -2,183 +2,151 @@
|
||||
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">
|
||||
<div class="row ">
|
||||
<div class="col-12">
|
||||
<div class="mb-6">
|
||||
<h1 class="fs-3 mb-0" style="float: left;">Stock Overview</h1>
|
||||
<div class="d-flex gap-2" style="float: right;">
|
||||
<div class="mb-3 col-lg-6">
|
||||
<input class="form-control" value='Warehouse' disabled>
|
||||
</div>
|
||||
<div class="mb-3 col-lg-6">
|
||||
<select name="warehouse" id="warehouse" class="form-select" required></select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- 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">Stock Overview</h1>
|
||||
<p class="mb-0">Warehouse performance and stock summary</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>
|
||||
<div class="row g-5 mb-5">
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card" id="capacity">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<span class="mb-4 d-block">Total Capacity</span>
|
||||
<h3 class="fw-bold mb-0 text-info">0</h3>
|
||||
<p class="mb-0 small text-secondary">Across all warehouses</p>
|
||||
</div>
|
||||
<div class="">
|
||||
|
||||
<!-- Main content -->
|
||||
<div id="overview_content">
|
||||
|
||||
<!-- Stat cards -->
|
||||
<div class="row g-5 mb-5">
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card" style="position:relative;">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<span class="mb-4 d-block">Total Capacity</span>
|
||||
<h3 class="fw-bold mb-0 text-info" id="stat_capacity">—</h3>
|
||||
<p class="mb-0 small text-secondary" id="stat_capacity_sub">Racks in warehouse</p>
|
||||
</div>
|
||||
<i class="ti ti-box-seam fs-3 text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- overlay -->
|
||||
<div class="card-loader-overlay">
|
||||
<div class="card-loader-overlay" id="overlay_capacity">
|
||||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card " id="total_stock_in">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<span class="mb-4 d-block">Total Stock In</span>
|
||||
<h3 class="fw-bold mb-0 text-success">+456</h3>
|
||||
<p class="mb-0 small text-secondary">Unit added</p>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card" style="position:relative;">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<span class="mb-4 d-block">Total Stock In</span>
|
||||
<h3 class="fw-bold mb-0 text-success" id="stat_stock_in">—</h3>
|
||||
<p class="mb-0 small text-secondary">Units added</p>
|
||||
</div>
|
||||
<i class="ti ti-trending-up fs-3 text-success"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- overlay -->
|
||||
<div class="card-loader-overlay">
|
||||
<div class="card-loader-overlay" id="overlay_stock_in">
|
||||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card " id="total_stock_out">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<span class="mb-4 d-block">Total Stock Out</span>
|
||||
<h3 class="fw-bold mb-0 text-danger">-289</h3>
|
||||
<p class="mb-0 small text-secondary">Unit removed</p>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card" style="position:relative;">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<span class="mb-4 d-block">Total Stock Out</span>
|
||||
<h3 class="fw-bold mb-0 text-danger" id="stat_stock_out">—</h3>
|
||||
<p class="mb-0 small text-secondary">Units removed</p>
|
||||
</div>
|
||||
<i class="ti ti-trending-down fs-3 text-danger"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- overlay -->
|
||||
<div class="card-loader-overlay">
|
||||
<div class="card-loader-overlay" id="overlay_stock_out">
|
||||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card " id="low_stock">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<span class="mb-4 d-block">Low Stock Alerts</span>
|
||||
<h3 class="fw-bold mb-0 text-warning">12</h3>
|
||||
<p class="mb-0 small text-secondary">Items need attention</p>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="card" style="position:relative;">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div>
|
||||
<span class="mb-4 d-block">Low Stock Alerts</span>
|
||||
<h3 class="fw-bold mb-0 text-warning" id="stat_low_stock">—</h3>
|
||||
<p class="mb-0 small text-secondary">Items need attention</p>
|
||||
</div>
|
||||
<i class="ti ti-alert-triangle fs-3 text-warning"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- overlay -->
|
||||
<div class="card-loader-overlay">
|
||||
<div class="card-loader-overlay" id="overlay_low_stock">
|
||||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-5 mb-5">
|
||||
<div class="col-lg-6 col-12">
|
||||
|
||||
<div class="card" id="stock_movement">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
|
||||
<!-- Charts -->
|
||||
<div class="row g-5 mb-5">
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="card" style="position:relative;">
|
||||
<div class="card-body p-5">
|
||||
<h5 class="mb-0">Stock Movement</h5>
|
||||
|
||||
<div id="ics_stockMovementChart"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div id="stockMovementChart"></div>
|
||||
</div>
|
||||
<!-- overlay -->
|
||||
<div class="card-loader-overlay">
|
||||
<div class="card-loader-overlay" id="overlay_movement">
|
||||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-lg-6 col-12">
|
||||
|
||||
<div class="card" id="stock_trend">
|
||||
<div class="card-body p-5">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="card" style="position:relative;">
|
||||
<div class="card-body p-5">
|
||||
<h5 class="mb-0">Stock Level Trend</h5>
|
||||
|
||||
<div id="ics_stockLevelTrendChart"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div id="stockLevelTrendChart"></div>
|
||||
</div>
|
||||
<!-- overlay -->
|
||||
<div class="card-loader-overlay">
|
||||
<div class="card-loader-overlay" id="overlay_trend">
|
||||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card" id="stock_activity">
|
||||
<div class="card-body p-5">
|
||||
<h3 class="fs-5">Recent Stock Activity</h3>
|
||||
|
||||
<!-- overlay -->
|
||||
<div class="card-loader-overlay">
|
||||
<!-- Recent activity -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card" style="position:relative;">
|
||||
<div class="card-body p-5">
|
||||
<h3 class="fs-5 mb-4">Recent Stock Activity</h3>
|
||||
<div id="activity_list"></div>
|
||||
</div>
|
||||
<div class="card-loader-overlay" id="overlay_activity">
|
||||
<div class="spinner-border spinner-border-sm text-primary"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /overview_content -->
|
||||
|
||||
</div>
|
||||
</main>
|
||||
@@ -186,233 +154,120 @@
|
||||
<?php require "../include_ending.php";?>
|
||||
|
||||
<script>
|
||||
var stockMovementChart = null;
|
||||
var stockLevelTrendChart = null;
|
||||
|
||||
|
||||
function retrieve_warehouse_stats() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>ics/api/engine_report/warehouse_stats.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
queueLock: false, // Allow this request to be sent even if another request is processing
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
$("#capacity h3").text(format_number(res.total_capacity, 2));
|
||||
$("#capacity p").text("Accross " + format_number(res.total_warehouse) + " warehouses");
|
||||
$("#capacity .card-loader-overlay").hide();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// ── Show all overlays before fetching ────────────────────────────────
|
||||
function show_overlays() {
|
||||
$('#overlay_capacity, #overlay_stock_in, #overlay_stock_out, #overlay_low_stock, #overlay_movement, #overlay_trend, #overlay_activity').show();
|
||||
}
|
||||
|
||||
|
||||
function retrieve_warehouse_balance() {
|
||||
// ── Load all overview data in a single call ───────────────────────────
|
||||
function retrieve_overview() {
|
||||
|
||||
var warehouse_id = $('#warehouse').val();
|
||||
if (!warehouse_id) return;
|
||||
|
||||
show_overlays();
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>ics/api/engine_report/warehouse_balance.php",
|
||||
url: "<?php echo $server_url?>ics/api/engine_report/warehouse_overview.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
debugMode: 0,
|
||||
queueLock: false, // Allow this request to be sent even if another request is processing
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
$("#total_stock_in h3").text(format_number(res.output.total_in, 2));
|
||||
$("#total_stock_in p").text("Accross " + format_number(res.output.total_warehouse) + " warehouses");
|
||||
$("#total_stock_in .card-loader-overlay").hide();
|
||||
|
||||
$("#total_stock_out h3").text(format_number(res.output.total_out, 2));
|
||||
$("#total_stock_out p").text("Accross " + format_number(res.output.total_warehouse) + " warehouses");
|
||||
$("#total_stock_out .card-loader-overlay").hide();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function retrieve_product_stats() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>ics/api/engine_report/product_stats.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
debugMode: 0,
|
||||
queueLock: false, // Allow this request to be sent even if another request is processing
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
$("#low_stock h3").text(format_number(res.low_stock, 2));
|
||||
$("#low_stock .card-loader-overlay").hide();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function retrieve_stock_movement() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>ics/api/engine_report/stock_movement.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
debugMode: 0,
|
||||
queueLock: false, // Allow this request to be sent even if another request is processing
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
onSuccess: async function(res) {
|
||||
|
||||
var d = res.output;
|
||||
|
||||
// Clear previous chart safely
|
||||
if (stockMovementChart && typeof stockMovementChart.destroy === 'function') {
|
||||
stockMovementChart.destroy();
|
||||
// ── Stats ──────────────────────────────────────────────────────
|
||||
$('#stat_capacity').text(format_number(d.total_capacity));
|
||||
$('#stat_capacity_sub').text(format_number(d.space_used) + ' occupied');
|
||||
$('#stat_stock_in').text(format_number(d.total_in, 2));
|
||||
$('#stat_stock_out').text(format_number(d.total_out, 2));
|
||||
$('#stat_low_stock').text(format_number(d.low_stock));
|
||||
|
||||
$('#overlay_capacity, #overlay_stock_in, #overlay_stock_out, #overlay_low_stock').hide();
|
||||
|
||||
// ── Stock Movement chart ───────────────────────────────────────
|
||||
var mv = d.stock_movement;
|
||||
if (stockMovementChart) {
|
||||
await stockMovementChart.updateOptions({ xaxis: { categories: mv.labels || [] } }, false, false);
|
||||
await stockMovementChart.updateSeries([
|
||||
{ name: 'Stock In', data: mv.stock_in || [] },
|
||||
{ name: 'Stock Out', data: mv.stock_out || [] }
|
||||
]);
|
||||
} else {
|
||||
// First load: clear any chart rendered by main.js
|
||||
$("#stockMovementChart").empty();
|
||||
// Empty container to destroy any instance pre-rendered by main.js
|
||||
$('#ics_stockMovementChart').empty();
|
||||
stockMovementChart = new ApexCharts(document.querySelector('#ics_stockMovementChart'), {
|
||||
series: [
|
||||
{ name: 'Stock In', data: mv.stock_in || [] },
|
||||
{ name: 'Stock Out', data: mv.stock_out || [] }
|
||||
],
|
||||
chart: { type: 'bar', height: 350, toolbar: { show: false } },
|
||||
plotOptions: { bar: { horizontal: false, columnWidth: '55%', borderRadius: 5, borderRadiusApplication: 'end' } },
|
||||
colors: ['#00C951', '#E66239'],
|
||||
dataLabels: { enabled: false },
|
||||
stroke: { show: true, width: 2, colors: ['transparent'] },
|
||||
xaxis: { categories: mv.labels || [] },
|
||||
yaxis: { title: { text: 'Quantity' } },
|
||||
fill: { opacity: 1 },
|
||||
tooltip: { y: { formatter: function(a) { return Number(a).toLocaleString() + ' units'; } } }
|
||||
});
|
||||
await stockMovementChart.render();
|
||||
}
|
||||
$('#overlay_movement').hide();
|
||||
|
||||
var n = {
|
||||
series: [
|
||||
{ name: "Stock In", data: d.stock_in || [] },
|
||||
{ name: "Stock Out", data: d.stock_out || [] }
|
||||
],
|
||||
chart: { type: "bar", height: 350, toolbar: { show: !1 } },
|
||||
plotOptions: { bar: { horizontal: !1, columnWidth: "55%", borderRadius: 5, borderRadiusApplication: "end" } },
|
||||
colors: ["#E66239", "#00C951"],
|
||||
dataLabels: { enabled: !1 },
|
||||
stroke: { show: !0, width: 2, colors: ["transparent"] },
|
||||
xaxis: { categories: d.labels || [] },
|
||||
yaxis: { title: { text: "Quantity" } },
|
||||
fill: { opacity: 1 },
|
||||
tooltip: { y: { formatter: function(a) { return Number(a).toLocaleString() + " units" } } }
|
||||
};
|
||||
|
||||
stockMovementChart = new ApexCharts(document.querySelector("#stockMovementChart"), n);
|
||||
stockMovementChart.render();
|
||||
|
||||
$("#stockMovementChart").closest(".card").find(".card-loader-overlay").hide();
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function retrieve_stock_trend() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>ics/api/engine_report/stock_trend.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
debugMode: 0,
|
||||
queueLock: false, // Allow this request to be sent even if another request is processing
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
var d = res.output;
|
||||
|
||||
// Clear previous chart safely
|
||||
if (stockLevelTrendChart && typeof stockLevelTrendChart.destroy === 'function') {
|
||||
stockLevelTrendChart.destroy();
|
||||
// ── Stock Trend chart ──────────────────────────────────────────
|
||||
var tr = d.stock_trend;
|
||||
if (stockLevelTrendChart) {
|
||||
await stockLevelTrendChart.updateOptions({ xaxis: { categories: tr.labels || [] } }, false, false);
|
||||
await stockLevelTrendChart.updateSeries([
|
||||
{ name: 'Stock Balance', data: tr.balance || [] }
|
||||
]);
|
||||
} else {
|
||||
$("#stockLevelTrendChart").empty();
|
||||
// Empty container to destroy any instance pre-rendered by main.js
|
||||
$('#ics_stockLevelTrendChart').empty();
|
||||
stockLevelTrendChart = new ApexCharts(document.querySelector('#ics_stockLevelTrendChart'), {
|
||||
series: [{ name: 'Stock Balance', data: tr.balance || [] }],
|
||||
chart: { height: 350, type: 'area', toolbar: { show: false } },
|
||||
colors: ['#E66239'],
|
||||
dataLabels: { enabled: false },
|
||||
stroke: { curve: 'smooth', width: 3 },
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: { shadeIntensity: 1, inverseColors: false, opacityFrom: 0.45, opacityTo: 0.05, stops: [20, 100] }
|
||||
},
|
||||
xaxis: { categories: tr.labels || [], axisBorder: { show: false }, axisTicks: { show: false } },
|
||||
yaxis: { title: { text: 'Quantity' }, labels: { formatter: function(a) { return Number(a).toLocaleString(); } } },
|
||||
tooltip: { y: { formatter: function(a) { return Number(a).toLocaleString() + ' units'; } } },
|
||||
grid: { show: true, borderColor: '#e2e8f0' }
|
||||
});
|
||||
await stockLevelTrendChart.render();
|
||||
}
|
||||
|
||||
var n = {
|
||||
series: [
|
||||
{ name: "Stock Balance", data: d.balance || [] }
|
||||
],
|
||||
chart: { height: 350, type: "area", toolbar: { show: !1 } },
|
||||
colors: ["#E66239"],
|
||||
dataLabels: { enabled: !1 },
|
||||
stroke: { curve: "smooth", width: 3 },
|
||||
fill: {
|
||||
type: "gradient",
|
||||
gradient: {
|
||||
shadeIntensity: 1,
|
||||
inverseColors: !1,
|
||||
opacityFrom: 0.45,
|
||||
opacityTo: 0.05,
|
||||
stops: [20, 100]
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: d.labels || [],
|
||||
axisBorder: { show: !1 },
|
||||
axisTicks: { show: !1 }
|
||||
},
|
||||
yaxis: {
|
||||
title: { text: "Quantity" },
|
||||
labels: {
|
||||
formatter: function(a) {
|
||||
return Number(a).toLocaleString();
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(a) {
|
||||
return Number(a).toLocaleString() + " units";
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: !0,
|
||||
borderColor: "#e2e8f0"
|
||||
}
|
||||
};
|
||||
|
||||
stockLevelTrendChart = new ApexCharts(document.querySelector("#stockLevelTrendChart"), n);
|
||||
stockLevelTrendChart.render();
|
||||
|
||||
$("#stockLevelTrendChart").closest(".card").find(".card-loader-overlay").hide();
|
||||
$('#overlay_trend').hide();
|
||||
|
||||
}
|
||||
});
|
||||
// ── Recent activity ────────────────────────────────────────────
|
||||
var $list = $('#activity_list');
|
||||
$list.empty();
|
||||
|
||||
}
|
||||
|
||||
|
||||
function retrieve_stock_activity() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>ics/api/engine_report/stock_activity.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
debugMode: 0,
|
||||
queueLock: false, // Allow this request to be sent even if another request is processing
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
|
||||
var d = res.output;
|
||||
var $container = $("#stock_activity .card-body");
|
||||
|
||||
// Keep the title, remove old activity rows
|
||||
$container.find(".stock-activity-item").remove();
|
||||
$container.find(".stock-activity-empty").remove();
|
||||
|
||||
if (!d.activities || d.activities.length === 0) {
|
||||
$container.append('<div class="stock-activity-empty text-center text-muted py-4">No recent activity</div>');
|
||||
$("#stock_activity .card-loader-overlay").hide();
|
||||
$list.html('<div class="text-center text-muted py-4">No recent activity</div>');
|
||||
$('#overlay_activity').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var html = '';
|
||||
|
||||
d.activities.forEach(function(item) {
|
||||
var isIn = item.direction === 'in';
|
||||
var colorCls = isIn ? 'success' : 'danger';
|
||||
var icon = isIn ? 'ti-trending-up' : 'ti-trending-down';
|
||||
var sign = isIn ? '+' : '-';
|
||||
var label = isIn ? 'Stock In' : 'Stock Out';
|
||||
var qty = Number(item.qty).toLocaleString();
|
||||
var timeAgo = time_ago(item.date);
|
||||
|
||||
html += '<div class="border-bottom d-flex justify-content-between align-items-center mb-4 pb-3 stock-activity-item">'
|
||||
|
||||
html += '<div class="border-bottom d-flex justify-content-between align-items-center mb-4 pb-3">'
|
||||
+ '<div class="d-flex gap-3 align-items-center py-3">'
|
||||
+ '<div class="icon-shape icon-md bg-' + colorCls + ' bg-opacity-10 text-' + colorCls + ' rounded-2">'
|
||||
+ '<i class="ti ' + icon + '"></i>'
|
||||
@@ -423,87 +278,74 @@
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="d-flex flex-column align-items-end">'
|
||||
+ '<span class="fs-5 text-' + colorCls + '">' + sign + qty + '</span>'
|
||||
+ '<span class="small text-muted">' + timeAgo + '</span>'
|
||||
+ '<span class="fs-5 text-' + colorCls + '">' + sign + Number(item.qty).toLocaleString() + '</span>'
|
||||
+ '<span class="small text-muted">' + time_ago(item.date) + '</span>'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
});
|
||||
|
||||
$container.append(html);
|
||||
|
||||
$("#stock_activity .card-loader-overlay").hide();
|
||||
|
||||
$list.html(html);
|
||||
$('#overlay_activity').hide();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ── Load warehouse dropdown ───────────────────────────────────────────
|
||||
function retrieve_warehouse() {
|
||||
|
||||
return ajax_request({
|
||||
url: "<?php echo $server_url?>inventory/api/engine/warehouse.php",
|
||||
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
var option = ``;
|
||||
var option = '';
|
||||
$.each(res.output, function(key, item) {
|
||||
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
|
||||
})
|
||||
$(`select#warehouse`).html(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_overview();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ── Helper: escape HTML ────────────────────────────────────────────
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
|
||||
// ── Helper: time ago from datetime string ─────────────────────────
|
||||
function time_ago(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
var now = new Date();
|
||||
var past = new Date(dateStr.replace(/-/g, '/')); // cross-browser parse
|
||||
var diff = Math.floor((now - past) / 1000); // seconds
|
||||
|
||||
if (diff < 60) return 'Just now';
|
||||
if (diff < 3600) return Math.floor(diff / 60) + ' min ago';
|
||||
if (diff < 86400) return Math.floor(diff / 3600) + ' hour ago';
|
||||
if (diff < 2592000) return Math.floor(diff / 86400) + ' day ago';
|
||||
if (diff < 31536000) return Math.floor(diff / 2592000) + ' month ago';
|
||||
var diff = Math.floor((new Date() - new Date(dateStr.replace(/-/g,'/'))) / 1000);
|
||||
if (diff < 60) return 'Just now';
|
||||
if (diff < 3600) return Math.floor(diff / 60) + ' min ago';
|
||||
if (diff < 86400) return Math.floor(diff / 3600) + ' hour ago';
|
||||
if (diff < 2592000) return Math.floor(diff / 86400) + ' day ago';
|
||||
if (diff < 31536000) return Math.floor(diff / 2592000) + ' month ago';
|
||||
return Math.floor(diff / 31536000) + ' year ago';
|
||||
}
|
||||
|
||||
|
||||
$(function() {
|
||||
|
||||
retrieve_warehouse().done(function() {
|
||||
retrieve_stock_movement();
|
||||
retrieve_stock_trend();
|
||||
retrieve_stock_activity();
|
||||
});
|
||||
|
||||
retrieve_warehouse_stats();
|
||||
retrieve_warehouse_balance();
|
||||
retrieve_product_stats();
|
||||
|
||||
$(`#warehouse`).change(function() {
|
||||
retrieve_stock_movement();
|
||||
retrieve_stock_trend();
|
||||
retrieve_stock_activity();
|
||||
});
|
||||
// ── Boot ─────────────────────────────────────────────────────────────
|
||||
$(async function() {
|
||||
try {
|
||||
await retrieve_warehouse();
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
$('#warehouse').on('change', function() {
|
||||
retrieve_overview();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user