Spaces:
Running
Running
Update ui.js
Browse files
ui.js
CHANGED
|
@@ -4,8 +4,8 @@ import { appState } from './state.js';
|
|
| 4 |
import { attachAllListeners } from './events.js';
|
| 5 |
import { getMonthlyProductionSummary, getMonthlyMaterialUsage } from './reportService.js';
|
| 6 |
|
| 7 |
-
// ... (keep all functions from
|
| 8 |
-
//
|
| 9 |
|
| 10 |
let productionChart = null;
|
| 11 |
let inventoryChart = null;
|
|
@@ -19,9 +19,9 @@ export function refreshUI() {
|
|
| 19 |
renderProductInputs();
|
| 20 |
renderInventory();
|
| 21 |
renderProductionLog();
|
| 22 |
-
renderModals();
|
| 23 |
renderReorderList();
|
| 24 |
-
attachAllListeners();
|
| 25 |
}
|
| 26 |
|
| 27 |
export function showToast(message, type = 'info') {
|
|
@@ -131,7 +131,7 @@ function renderProductionHistoryChart() {
|
|
| 131 |
},
|
| 132 |
options: {
|
| 133 |
responsive: true,
|
| 134 |
-
maintainAspectRatio: false,
|
| 135 |
plugins: { legend: { display: false } },
|
| 136 |
scales: {
|
| 137 |
y: { beginAtZero: true, grid: { color: 'hsl(220, 13%, 30%)' } },
|
|
@@ -149,7 +149,7 @@ function renderInventoryStatusChart() {
|
|
| 149 |
let okCount = 0, warningCount = 0, criticalCount = 0;
|
| 150 |
appState.materials.forEach(m => {
|
| 151 |
if (m.currentStock <= m.reorderPoint) criticalCount++;
|
| 152 |
-
else if (m.currentStock <= m.reorderPoint * 1.5) warningCount++;
|
| 153 |
else okCount++;
|
| 154 |
});
|
| 155 |
|
|
@@ -172,7 +172,7 @@ function renderInventoryStatusChart() {
|
|
| 172 |
data: data,
|
| 173 |
options: {
|
| 174 |
responsive: true,
|
| 175 |
-
maintainAspectRatio: false,
|
| 176 |
plugins: { legend: { position: 'top', labels: { color: 'hsl(210, 14%, 66%)' } } }
|
| 177 |
}
|
| 178 |
});
|
|
@@ -229,7 +229,6 @@ function renderInventory() {
|
|
| 229 |
if (!container) return;
|
| 230 |
container.innerHTML = '';
|
| 231 |
appState.materials.forEach(material => {
|
| 232 |
-
// The bar is "full" at 2x the reorder point. Capped at 100%.
|
| 233 |
const safeStockLevel = material.reorderPoint * 2;
|
| 234 |
const stockPercentage = Math.min((material.currentStock / safeStockLevel) * 100, 100);
|
| 235 |
|
|
@@ -278,22 +277,62 @@ function renderReorderList() {
|
|
| 278 |
if (!list || !header) return;
|
| 279 |
|
| 280 |
list.innerHTML = '';
|
| 281 |
-
header.querySelector('#
|
| 282 |
|
| 283 |
-
const itemsToReorder = appState.materials.filter(m => m.currentStock <= m.reorderPoint);
|
| 284 |
|
| 285 |
if (itemsToReorder.length === 0) {
|
| 286 |
list.innerHTML = `<li class="text-secondary text-center pt-4">All stock levels are healthy.</li>`;
|
| 287 |
return;
|
| 288 |
}
|
| 289 |
|
| 290 |
-
|
| 291 |
-
const poButton = `<button id="generate-full-po-btn" class="btn btn-secondary text-sm">Generate Full Reorder Report</button>`;
|
| 292 |
header.insertAdjacentHTML('beforeend', poButton);
|
| 293 |
|
| 294 |
itemsToReorder.forEach(item => {
|
| 295 |
-
const needed = (item.reorderPoint * 2) - item.currentStock;
|
| 296 |
-
const itemHTML = `<li class="p-3 rounded-md text-sm flex justify-between items-center" style="background-color: #f6e05e20;"><div class="flex-grow"><span class="font-semibold">${item.name}</span><span class="text-xs text-secondary block">Stock: ${item.currentStock} / Reorder at: ${item.reorderPoint}</span></div><div class="text-right"><span class="font-bold">
|
| 297 |
list.insertAdjacentHTML('beforeend', itemHTML);
|
| 298 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
}
|
|
|
|
| 4 |
import { attachAllListeners } from './events.js';
|
| 5 |
import { getMonthlyProductionSummary, getMonthlyMaterialUsage } from './reportService.js';
|
| 6 |
|
| 7 |
+
// ... (keep chart variables and all functions from refreshUI down to renderProductionLog) ...
|
| 8 |
+
// We will add one new function and modify renderReorderList.
|
| 9 |
|
| 10 |
let productionChart = null;
|
| 11 |
let inventoryChart = null;
|
|
|
|
| 19 |
renderProductInputs();
|
| 20 |
renderInventory();
|
| 21 |
renderProductionLog();
|
| 22 |
+
renderModals();
|
| 23 |
renderReorderList();
|
| 24 |
+
attachAllListeners();
|
| 25 |
}
|
| 26 |
|
| 27 |
export function showToast(message, type = 'info') {
|
|
|
|
| 131 |
},
|
| 132 |
options: {
|
| 133 |
responsive: true,
|
| 134 |
+
maintainAspectRatio: false,
|
| 135 |
plugins: { legend: { display: false } },
|
| 136 |
scales: {
|
| 137 |
y: { beginAtZero: true, grid: { color: 'hsl(220, 13%, 30%)' } },
|
|
|
|
| 149 |
let okCount = 0, warningCount = 0, criticalCount = 0;
|
| 150 |
appState.materials.forEach(m => {
|
| 151 |
if (m.currentStock <= m.reorderPoint) criticalCount++;
|
| 152 |
+
else if (m.currentStock <= m.reorderPoint * 1.5) warningCount++;
|
| 153 |
else okCount++;
|
| 154 |
});
|
| 155 |
|
|
|
|
| 172 |
data: data,
|
| 173 |
options: {
|
| 174 |
responsive: true,
|
| 175 |
+
maintainAspectRatio: false,
|
| 176 |
plugins: { legend: { position: 'top', labels: { color: 'hsl(210, 14%, 66%)' } } }
|
| 177 |
}
|
| 178 |
});
|
|
|
|
| 229 |
if (!container) return;
|
| 230 |
container.innerHTML = '';
|
| 231 |
appState.materials.forEach(material => {
|
|
|
|
| 232 |
const safeStockLevel = material.reorderPoint * 2;
|
| 233 |
const stockPercentage = Math.min((material.currentStock / safeStockLevel) * 100, 100);
|
| 234 |
|
|
|
|
| 277 |
if (!list || !header) return;
|
| 278 |
|
| 279 |
list.innerHTML = '';
|
| 280 |
+
header.querySelector('#open-po-modal-btn')?.remove();
|
| 281 |
|
| 282 |
+
const itemsToReorder = appState.materials.filter(m => m.currentStock <= m.reorderPoint * 1.5);
|
| 283 |
|
| 284 |
if (itemsToReorder.length === 0) {
|
| 285 |
list.innerHTML = `<li class="text-secondary text-center pt-4">All stock levels are healthy.</li>`;
|
| 286 |
return;
|
| 287 |
}
|
| 288 |
|
| 289 |
+
const poButton = `<button id="open-po-modal-btn" class="btn btn-secondary text-sm">Create Purchase Order</button>`;
|
|
|
|
| 290 |
header.insertAdjacentHTML('beforeend', poButton);
|
| 291 |
|
| 292 |
itemsToReorder.forEach(item => {
|
| 293 |
+
const needed = Math.max(1, (item.reorderPoint * 2) - item.currentStock);
|
| 294 |
+
const itemHTML = `<li class="p-3 rounded-md text-sm flex justify-between items-center" style="background-color: ${item.currentStock <= item.reorderPoint ? '#f5656520' : '#f6e05e20'};"><div class="flex-grow"><span class="font-semibold">${item.name}</span><span class="text-xs text-secondary block">Stock: ${item.currentStock} / Reorder at: ${item.reorderPoint}</span></div><div class="text-right"><span class="font-bold">Suggests ${needed}</span><span class="text-xs text-secondary ml-1">${item.unit}</span></div></li>`;
|
| 295 |
list.insertAdjacentHTML('beforeend', itemHTML);
|
| 296 |
});
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
export function renderCustomPOModal(materials) {
|
| 300 |
+
const modal = document.getElementById('custom-po-modal');
|
| 301 |
+
if (!modal) return;
|
| 302 |
+
|
| 303 |
+
const materialRows = materials.map(material => {
|
| 304 |
+
const suggestedQty = Math.max(1, (material.reorderPoint * 2) - material.currentStock);
|
| 305 |
+
return `
|
| 306 |
+
<div class="po-item-row" data-material-name="${material.name}">
|
| 307 |
+
<input type="checkbox" class="po-item-select form-checkbox h-5 w-5 rounded bg-dark border-border-color text-accent-blue focus:ring-accent-blue" checked>
|
| 308 |
+
<div>
|
| 309 |
+
<span class="font-semibold">${material.name}</span>
|
| 310 |
+
<span class="text-xs text-secondary block">Stock: ${material.currentStock} | Reorder: ${material.reorderPoint}</span>
|
| 311 |
+
</div>
|
| 312 |
+
<input type="number" class="po-item-qty input-field w-24 text-right" value="${suggestedQty}" min="0">
|
| 313 |
+
</div>
|
| 314 |
+
`;
|
| 315 |
+
}).join('');
|
| 316 |
+
|
| 317 |
+
modal.innerHTML = `
|
| 318 |
+
<div class="modal-content dashboard-card p-6 max-w-3xl w-full mx-4 flex flex-col">
|
| 319 |
+
<div class="flex justify-between items-center mb-4">
|
| 320 |
+
<h3 class="text-lg font-semibold">Create Custom Purchase Order</h3>
|
| 321 |
+
<button id="cancel-po-btn" class="text-secondary hover:text-primary text-2xl">×</button>
|
| 322 |
+
</div>
|
| 323 |
+
<div class="mb-4">
|
| 324 |
+
<label for="supplier-name" class="block text-sm font-medium text-secondary mb-1">Supplier Name (Optional)</label>
|
| 325 |
+
<input type="text" id="supplier-name" class="input-field w-full" placeholder="Enter supplier name...">
|
| 326 |
+
</div>
|
| 327 |
+
<div id="po-item-list" class="flex-grow overflow-y-auto pr-2" style="max-height: 40vh;">
|
| 328 |
+
${materialRows}
|
| 329 |
+
</div>
|
| 330 |
+
<div class="flex justify-end space-x-2 mt-6">
|
| 331 |
+
<button id="cancel-po-btn-footer" class="btn btn-secondary">Cancel</button>
|
| 332 |
+
<button id="confirm-po-generation-btn" class="btn btn-primary">Generate PO PDF</button>
|
| 333 |
+
</div>
|
| 334 |
+
</div>
|
| 335 |
+
`;
|
| 336 |
+
|
| 337 |
+
modal.classList.remove('hidden');
|
| 338 |
}
|