Spaces:
Running
Running
Update ui.js
Browse files
ui.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
import { appState } from './state.js';
|
| 4 |
import { attachAllListeners } from './events.js';
|
|
|
|
| 5 |
|
| 6 |
// Chart instances to prevent re-creation
|
| 7 |
let productionChart = null;
|
|
@@ -16,14 +17,25 @@ export function refreshUI() {
|
|
| 16 |
renderProductionLog();
|
| 17 |
renderAnalytics();
|
| 18 |
renderReorderList();
|
| 19 |
-
|
| 20 |
-
attachAllListeners();
|
| 21 |
}
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
// ---
|
| 27 |
|
| 28 |
function renderKpiCards() {
|
| 29 |
const kpiRow = document.getElementById('kpi-row');
|
|
@@ -157,12 +169,103 @@ function renderInventoryStatusChart() {
|
|
| 157 |
}
|
| 158 |
}
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
-
|
|
|
|
|
|
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
import { appState } from './state.js';
|
| 4 |
import { attachAllListeners } from './events.js';
|
| 5 |
+
import { getMonthlyProductionSummary, getMonthlyMaterialUsage } from './reportService.js';
|
| 6 |
|
| 7 |
// Chart instances to prevent re-creation
|
| 8 |
let productionChart = null;
|
|
|
|
| 17 |
renderProductionLog();
|
| 18 |
renderAnalytics();
|
| 19 |
renderReorderList();
|
| 20 |
+
renderModals();
|
| 21 |
+
attachAllListeners(); // Re-attach listeners to new/updated elements
|
| 22 |
}
|
| 23 |
|
| 24 |
+
export function showToast(message, type = 'info') {
|
| 25 |
+
const container = document.getElementById('toast-container');
|
| 26 |
+
const toast = document.createElement('div');
|
| 27 |
+
const icon = type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-times-circle' : 'fa-info-circle';
|
| 28 |
+
toast.className = `toast toast-${type}`;
|
| 29 |
+
toast.innerHTML = `<i class="fas ${icon}"></i><span>${message}</span>`;
|
| 30 |
+
container.appendChild(toast);
|
| 31 |
+
setTimeout(() => toast.classList.add('show'), 10);
|
| 32 |
+
setTimeout(() => {
|
| 33 |
+
toast.classList.remove('show');
|
| 34 |
+
toast.addEventListener('transitionend', () => toast.remove());
|
| 35 |
+
}, 3000);
|
| 36 |
+
}
|
| 37 |
|
| 38 |
+
// --- Dashboard Rendering Functions ---
|
| 39 |
|
| 40 |
function renderKpiCards() {
|
| 41 |
const kpiRow = document.getElementById('kpi-row');
|
|
|
|
| 169 |
}
|
| 170 |
}
|
| 171 |
|
| 172 |
+
function renderModals() {
|
| 173 |
+
const resetModalContainer = document.getElementById('reset-modal');
|
| 174 |
+
resetModalContainer.innerHTML = `<div class="bg-white rounded-lg p-6 max-w-sm w-full mx-4 shadow-xl"><h3 class="text-lg font-semibold mb-2">Confirm Reset</h3><p class="text-gray-600 mb-4">Are you sure you want to reset all inventory data? This action cannot be undone.</p><div class="flex justify-end space-x-2"><button id="cancel-reset-btn" class="px-4 py-2 text-sm bg-gray-200 rounded hover:bg-gray-300">Cancel</button><button id="confirm-reset-btn" class="px-4 py-2 text-sm bg-red-500 text-white rounded hover:bg-red-600">Reset Data</button></div></div>`;
|
| 175 |
+
|
| 176 |
+
const reportsModalContainer = document.getElementById('reports-modal');
|
| 177 |
+
reportsModalContainer.innerHTML = `<div class="bg-white rounded-lg p-6 max-w-2xl w-full mx-4 shadow-xl"><div class="flex justify-between items-center mb-4"><h3 class="text-lg font-semibold">Generate Report</h3><button id="close-reports-modal-btn" class="text-gray-500 hover:text-gray-800">×</button></div><div class="flex gap-4 mb-4"><button id="report-prod-summary" class="flex-1 px-4 py-2 text-sm bg-blue-500 text-white rounded hover:bg-blue-600">Monthly Production</button><button id="report-mat-usage" class="flex-1 px-4 py-2 text-sm bg-green-500 text-white rounded hover:bg-green-600">Monthly Material Usage</button></div><div id="report-content" class="p-4 border rounded bg-gray-50 min-h-[300px]">Select a report to view data.</div></div>`;
|
| 178 |
+
}
|
| 179 |
|
| 180 |
+
export function renderReport(type) {
|
| 181 |
+
const content = document.getElementById('report-content');
|
| 182 |
+
let data, title, headers, rows;
|
| 183 |
|
| 184 |
+
if (type === 'production') {
|
| 185 |
+
data = getMonthlyProductionSummary();
|
| 186 |
+
title = 'Monthly Production Summary (Last 30 Days)';
|
| 187 |
+
headers = ['Product/Assembly', 'Total Units Produced'];
|
| 188 |
+
rows = Object.entries(data).map(([name, qty]) => `<tr><td class="border px-4 py-2">${name}</td><td class="border px-4 py-2 text-right">${qty}</td></tr>`).join('');
|
| 189 |
+
} else {
|
| 190 |
+
data = getMonthlyMaterialUsage();
|
| 191 |
+
title = 'Monthly Material Usage (Last 30 Days)';
|
| 192 |
+
headers = ['Material', 'Total Quantity Consumed'];
|
| 193 |
+
rows = Object.entries(data).map(([name, qty]) => {
|
| 194 |
+
const material = appState.materials.find(m => m.name === name);
|
| 195 |
+
return `<tr><td class="border px-4 py-2">${name}</td><td class="border px-4 py-2 text-right">${qty} ${material.unit}</td></tr>`;
|
| 196 |
+
}).join('');
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
if (Object.keys(data).length === 0) {
|
| 200 |
+
content.innerHTML = `<p class="text-center text-gray-500 pt-12">No data available for this period.</p>`;
|
| 201 |
+
return;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
content.innerHTML = `<h4 class="font-bold mb-2">${title}</h4><table class="table-auto w-full text-sm"><thead><tr><th class="border px-4 py-2 text-left">${headers[0]}</th><th class="border px-4 py-2 text-right">${headers[1]}</th></tr></thead><tbody>${rows}</tbody></table>`;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
// --- Internal Rendering Functions ---
|
| 208 |
+
|
| 209 |
+
function renderProductInputs() {
|
| 210 |
+
const container = document.getElementById('product-cards');
|
| 211 |
+
container.innerHTML = '';
|
| 212 |
+
for (const productName in appState.productRecipes) {
|
| 213 |
+
const cardHTML = `<div class="product-card bg-white rounded-lg shadow p-4 border border-gray-100" data-product-name="${productName}"><h3 class="font-medium text-gray-700 mb-3">${productName}</h3><div class="flex items-center space-x-3"><input type="number" min="0" class="input-number w-20 px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500" value="0"><button class="update-btn px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">Produce</button></div></div>`;
|
| 214 |
+
container.insertAdjacentHTML('beforeend', cardHTML);
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
function renderInventory() {
|
| 219 |
+
const container = document.getElementById('material-cards');
|
| 220 |
+
container.innerHTML = '';
|
| 221 |
+
appState.materials.forEach(material => {
|
| 222 |
+
const stockPercentage = (material.currentStock / material.maxStock) * 100;
|
| 223 |
+
let statusClass = 'status-ok', progressClass = 'progress-ok';
|
| 224 |
+
if (stockPercentage <= 50 && stockPercentage > 20) { statusClass = 'status-warning'; progressClass = 'progress-warning'; }
|
| 225 |
+
else if (stockPercentage <= 20) { statusClass = 'status-critical'; progressClass = 'progress-critical'; }
|
| 226 |
+
const cardHTML = `<div class="material-card bg-white rounded-lg shadow p-4 border-l-4 ${statusClass}" data-material-name="${material.name}"><div class="flex justify-between items-start mb-1"><h3 class="font-medium text-gray-700">${material.name}</h3><div class="text-xs text-gray-500 flex items-center gap-3"><span>MAX:</span><span class="font-semibold max-stock-value">${material.maxStock}</span><i class="fas fa-plus-circle icon-btn restock-icon" title="Restock"></i><i class="fas fa-pencil-alt icon-btn edit-max-stock" title="Edit Max Stock"></i></div></div><div class="flex justify-between items-baseline mb-2"><div class="text-2xl font-bold current-stock">${material.currentStock}</div><span class="text-sm text-gray-500 unit">${material.unit}</span></div><div class="w-full bg-gray-200 rounded-full h-2.5"><div class="h-2.5 rounded-full ${progressClass} progress-bar" style="width: ${stockPercentage}%"></div></div><div class="restock-form mt-2 hidden"></div></div>`;
|
| 227 |
+
container.insertAdjacentHTML('beforeend', cardHTML);
|
| 228 |
+
});
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
function renderProductionLog() {
|
| 232 |
+
const list = document.getElementById('production-log-list');
|
| 233 |
+
list.innerHTML = '';
|
| 234 |
+
if (appState.productionLog.length === 0) { list.innerHTML = `<li class="text-gray-500 text-center pt-4">No production recorded yet.</li>`; return; }
|
| 235 |
+
[...appState.productionLog].reverse().forEach(entry => {
|
| 236 |
+
const date = new Date(entry.date);
|
| 237 |
+
const formattedDate = `${date.toLocaleDateString()} ${date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}`;
|
| 238 |
+
const logHTML = `<li class="p-2 border-b border-gray-100 flex justify-between items-center text-sm"><div><span class="font-semibold text-blue-600">${entry.quantity}x</span> <span class="text-gray-800">${entry.productName}</span></div><span class="text-xs text-gray-400">${formattedDate}</span></li>`;
|
| 239 |
+
list.insertAdjacentHTML('beforeend', logHTML);
|
| 240 |
+
});
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
function renderAnalytics() {
|
| 244 |
+
const container = document.getElementById('analytics-content');
|
| 245 |
+
container.innerHTML = '';
|
| 246 |
+
const totalValue = appState.materials.reduce((sum, mat) => sum + (mat.currentStock * (mat.costPerUnit || 0)), 0);
|
| 247 |
+
let productCostsHTML = '';
|
| 248 |
+
for (const productName in appState.productRecipes) {
|
| 249 |
+
const recipe = appState.productRecipes[productName];
|
| 250 |
+
const cost = Object.keys(recipe).reduce((sum, matName) => {
|
| 251 |
+
const material = appState.materials.find(m => m.name === matName);
|
| 252 |
+
const quantity = recipe[matName];
|
| 253 |
+
return sum + (quantity * (material.costPerUnit || 0));
|
| 254 |
+
}, 0);
|
| 255 |
+
productCostsHTML += `<div class="flex justify-between text-sm"><span class="text-gray-600">${productName}</span><span class="font-medium">$${cost.toFixed(2)} / unit</span></div>`;
|
| 256 |
+
}
|
| 257 |
+
const analyticsHTML = `<div class="p-3 bg-gray-50 rounded-lg"><div class="flex justify-between items-center"><span class="text-gray-600">Total Inventory Value</span><span class="text-lg font-bold text-green-600">$${totalValue.toFixed(2)}</span></div></div><div><h4 class="font-semibold text-sm mb-2 text-gray-700 mt-3">Material Cost Per Product</h4><div class="space-y-1">${productCostsHTML}</div></div>`;
|
| 258 |
+
container.innerHTML = analyticsHTML;
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
function renderReorderList() {
|
| 262 |
+
const list = document.getElementById('reorder-list');
|
| 263 |
+
list.innerHTML = '';
|
| 264 |
+
const itemsToReorder = appState.materials.filter(m => m.currentStock <= (m.reorderPoint || 0));
|
| 265 |
+
if (itemsToReorder.length === 0) { list.innerHTML = `<li class="text-gray-500 text-center pt-4">All stock levels are healthy.</li>`; return; }
|
| 266 |
+
itemsToReorder.forEach(item => {
|
| 267 |
+
const needed = item.maxStock - item.currentStock;
|
| 268 |
+
const itemHTML = `<li class="p-3 rounded-md bg-yellow-50 border border-yellow-200 text-sm" data-material-name="${item.name}"><div class="flex justify-between items-center"><div><span class="font-semibold text-yellow-800">${item.name}</span><span class="text-xs text-yellow-600 block">Stock: ${item.currentStock} / ${item.reorderPoint} (Need ${needed})</span></div><button class="restock-btn-reorder text-xs bg-yellow-400 text-yellow-900 font-bold py-1 px-3 rounded hover:bg-yellow-500">Restock</button></div><div class="restock-form mt-2 hidden"></div></li>`;
|
| 269 |
+
list.insertAdjacentHTML('beforeend', itemHTML);
|
| 270 |
+
});
|
| 271 |
+
}
|