feat(ui): order totals, decimals, mobile modal cards, set editing

- Dashboard/Logs: Total column with 2 decimals (order_total)
- Order detail modal: totals summary row (items total + order total)
- Order detail modal mobile: compact article cards (d-md-none)
- Mappings: openEditModal loads all CODMATs for SKU, saveMapping
  replaces entire set via delete-all + batch POST
- Add project-specific team agents: ui-templates, ui-js, ui-verify,
  backend-api
- CLAUDE.md: mandatory preview approval before implementation,
  fix-loop after verification, server must start via start.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-15 21:55:58 +00:00
parent ac8a01eb3e
commit 137c4a8b0b
11 changed files with 438 additions and 41 deletions

View File

@@ -295,7 +295,6 @@ async function loadDashOrders() {
// Invoice info
let invoiceBadge = '';
let invoiceTotal = '';
if (o.status !== 'IMPORTED' && o.status !== 'ALREADY_IMPORTED') {
invoiceBadge = '<span class="text-muted">-</span>';
} else if (o.invoice && o.invoice.facturat) {
@@ -303,11 +302,12 @@ async function loadDashOrders() {
if (o.invoice.serie_act || o.invoice.numar_act) {
invoiceBadge += `<br><small>${esc(o.invoice.serie_act || '')} ${esc(String(o.invoice.numar_act || ''))}</small>`;
}
invoiceTotal = o.invoice.total_cu_tva ? Number(o.invoice.total_cu_tva).toFixed(2) : '-';
} else {
invoiceBadge = `<span style="color:#dc2626">Nefacturat</span>`;
}
const orderTotal = o.order_total != null ? Number(o.order_total).toFixed(2) : '-';
return `<tr style="cursor:pointer" onclick="openDashOrderDetail('${esc(o.order_number)}')">
<td><code>${esc(o.order_number)}</code></td>
<td>${dateStr}</td>
@@ -316,7 +316,7 @@ async function loadDashOrders() {
<td class="text-nowrap">${statusDot(o.status)} ${statusLabelText(o.status)}</td>
<td>${o.id_comanda || '-'}</td>
<td>${invoiceBadge}</td>
<td>${invoiceTotal}</td>
<td>${orderTotal}</td>
</tr>`;
}).join('');
}
@@ -335,7 +335,7 @@ async function loadDashOrders() {
if (d.length >= 16) dateFmt += ' ' + d.slice(11, 16);
}
const name = o.shipping_name || o.customer_name || o.billing_name || '\u2014';
const totalStr = o.order_total ? Math.round(o.order_total) : '';
const totalStr = o.order_total ? Number(o.order_total).toFixed(2) : '';
return `<div class="flat-row" onclick="openDashOrderDetail('${esc(o.order_number)}')" style="font-size:0.875rem">
${statusDot(o.status)}
<span style="color:#6b7280" class="text-nowrap">${dateFmt}</span>
@@ -475,6 +475,12 @@ async function openDashOrderDetail(orderNumber) {
document.getElementById('detailIdAdresaLivr').textContent = '-';
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="8" class="text-center">Se incarca...</td></tr>';
document.getElementById('detailError').style.display = 'none';
const detailItemsTotal = document.getElementById('detailItemsTotal');
if (detailItemsTotal) detailItemsTotal.textContent = '-';
const detailOrderTotal = document.getElementById('detailOrderTotal');
if (detailOrderTotal) detailOrderTotal.textContent = '-';
const mobileContainer = document.getElementById('detailItemsMobile');
if (mobileContainer) mobileContainer.innerHTML = '';
const modalEl = document.getElementById('orderDetailModal');
const existing = bootstrap.Modal.getInstance(modalEl);
@@ -510,6 +516,36 @@ async function openDashOrderDetail(orderNumber) {
return;
}
// Update totals row
const itemsTotal = items.reduce((sum, item) => sum + (Number(item.price || 0) * Number(item.quantity || 0)), 0);
document.getElementById('detailItemsTotal').textContent = itemsTotal.toFixed(2) + ' lei';
document.getElementById('detailOrderTotal').textContent = order.order_total != null ? Number(order.order_total).toFixed(2) + ' lei' : '-';
// Mobile article cards
const mobileContainer = document.getElementById('detailItemsMobile');
if (mobileContainer) {
mobileContainer.innerHTML = items.map(item => {
let statusLabel = '';
switch (item.mapping_status) {
case 'mapped': statusLabel = '<span class="badge bg-success">Mapat</span>'; break;
case 'direct': statusLabel = '<span class="badge bg-info">Direct</span>'; break;
case 'missing': statusLabel = '<span class="badge bg-warning">Lipsa</span>'; break;
default: statusLabel = '<span class="badge bg-secondary">?</span>';
}
const codmat = item.codmat || '-';
return `<div class="detail-item-card">
<div class="card-sku">${esc(item.sku)}</div>
<div class="card-name">${esc(item.product_name || '-')}</div>
<div class="card-details">
<span>x${item.quantity || 0}</span>
<span>${item.price != null ? Number(item.price).toFixed(2) : '-'} lei</span>
<span><code>${esc(codmat)}</code></span>
<span>${statusLabel}</span>
</div>
</div>`;
}).join('');
}
document.getElementById('detailItemsBody').innerHTML = items.map(item => {
let statusBadge;
switch (item.mapping_status) {