refactor(modal): extract order detail to shared.js

Move duplicated order detail modal logic from dashboard.js and logs.js
into a shared renderOrderDetailModal() function in shared.js. Move
modal HTML from dashboard.html and logs.html into base.html.

Shared functions: renderCodmatCell, orderStatusBadge, fmtCost, fmtNum,
computeDiscountSplit, renderReceipt. Both pages now call the shared
modal with page-specific quick map callbacks.

Net -152 lines. Logs page gains invoice info, TVA column, and receipt
footer that were previously dashboard-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-27 11:19:42 +00:00
parent c6d69ac0e0
commit c5757b8322
6 changed files with 384 additions and 536 deletions

View File

@@ -8,10 +8,6 @@ let ordersPage = 1;
let ordersSortColumn = 'order_date';
let ordersSortDirection = 'desc';
function fmtCost(v) {
return v > 0 ? Number(v).toFixed(2) : '';
}
function fmtDuration(startedAt, finishedAt) {
if (!startedAt || !finishedAt) return '-';
const diffMs = new Date(finishedAt) - new Date(startedAt);
@@ -30,17 +26,6 @@ function runStatusBadge(status) {
}
}
function orderStatusBadge(status) {
switch ((status || '').toUpperCase()) {
case 'IMPORTED': return '<span class="badge bg-success">Importat</span>';
case 'ALREADY_IMPORTED': return '<span class="badge bg-info">Deja importat</span>';
case 'SKIPPED': return '<span class="badge bg-warning">Omis</span>';
case 'ERROR': return '<span class="badge bg-danger">Eroare</span>';
case 'DELETED_IN_ROA': return '<span class="badge bg-dark">Sters din ROA</span>';
default: return `<span class="badge bg-secondary">${esc(status)}</span>`;
}
}
function logStatusText(status) {
switch ((status || '').toUpperCase()) {
case 'IMPORTED': return 'Importat';
@@ -296,125 +281,17 @@ async function fetchTextLog(runId) {
}
}
// ── Multi-CODMAT helper (D1) ─────────────────────
function renderCodmatCell(item) {
if (!item.codmat_details || item.codmat_details.length === 0) {
return `<code>${esc(item.codmat || '-')}</code>`;
}
if (item.codmat_details.length === 1) {
const d = item.codmat_details[0];
return `<code>${esc(d.codmat)}</code>`;
}
// Multi-CODMAT: compact list
return item.codmat_details.map(d =>
`<div class="small"><code>${esc(d.codmat)}</code> <span class="text-muted">\xd7${d.cantitate_roa}</span></div>`
).join('');
}
// ── Order Detail Modal (R9) ─────────────────────
async function openOrderDetail(orderNumber) {
document.getElementById('detailOrderNumber').textContent = '#' + orderNumber;
document.getElementById('detailCustomer').textContent = '...';
document.getElementById('detailDate').textContent = '';
document.getElementById('detailStatus').innerHTML = '';
document.getElementById('detailIdComanda').textContent = '-';
document.getElementById('detailIdPartener').textContent = '-';
document.getElementById('detailIdAdresaFact').textContent = '-';
document.getElementById('detailIdAdresaLivr').textContent = '-';
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="6" 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);
if (existing) { existing.show(); } else { new bootstrap.Modal(modalEl).show(); }
try {
const res = await fetch(`/api/sync/order/${encodeURIComponent(orderNumber)}`);
const data = await res.json();
if (data.error) {
document.getElementById('detailError').textContent = data.error;
document.getElementById('detailError').style.display = '';
return;
function openOrderDetail(orderNumber) {
_sharedModalQuickMapFn = function(sku, productName, orderNum, itemIdx) {
openLogsQuickMap(sku, productName, orderNum);
};
renderOrderDetailModal(orderNumber, {
onQuickMap: function(sku, productName, orderNum, itemIdx) {
openLogsQuickMap(sku, productName, orderNum);
}
const order = data.order || {};
document.getElementById('detailCustomer').textContent = order.customer_name || '-';
document.getElementById('detailDate').textContent = fmtDate(order.order_date);
document.getElementById('detailStatus').innerHTML = orderStatusBadge(order.status);
document.getElementById('detailIdComanda').textContent = order.id_comanda || '-';
document.getElementById('detailIdPartener').textContent = order.id_partener || '-';
document.getElementById('detailIdAdresaFact').textContent = order.id_adresa_facturare || '-';
document.getElementById('detailIdAdresaLivr').textContent = order.id_adresa_livrare || '-';
if (order.error_message) {
document.getElementById('detailError').textContent = order.error_message;
document.getElementById('detailError').style.display = '';
}
const dlvEl = document.getElementById('detailDeliveryCost');
if (dlvEl) dlvEl.textContent = order.delivery_cost > 0 ? Number(order.delivery_cost).toFixed(2) + ' lei' : '';
const dscEl = document.getElementById('detailDiscount');
if (dscEl) dscEl.textContent = order.discount_total > 0 ? '' + Number(order.discount_total).toFixed(2) + ' lei' : '';
const items = data.items || [];
if (items.length === 0) {
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="6" class="text-center text-muted">Niciun articol</td></tr>';
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 flat list
const mobileContainer = document.getElementById('detailItemsMobile');
if (mobileContainer) {
mobileContainer.innerHTML = '<div class="detail-item-flat">' + items.map((item, idx) => {
const codmatList = item.codmat_details?.length
? item.codmat_details.map(d => `<span class="dif-codmat-link" onclick="openLogsQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}')">${esc(d.codmat)}</span>`).join(' ')
: `<span class="dif-codmat-link" onclick="openLogsQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}')">${esc(item.codmat || '')}</span>`;
const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
return `<div class="dif-item">
<div class="dif-row">
<span class="dif-sku">${esc(item.sku)}</span>
${codmatList}
</div>
<div class="dif-row">
<span class="dif-name">${esc(item.product_name || '')}</span>
<span class="dif-qty">x${item.quantity || 0}</span>
<span class="dif-val">${valoare} lei</span>
</div>
</div>`;
}).join('') + '</div>';
}
document.getElementById('detailItemsBody').innerHTML = items.map(item => {
const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
const codmatCell = `<span class="codmat-link" onclick="openLogsQuickMap('${esc(item.sku)}', '${esc(item.product_name || '')}', '${esc(orderNumber)}')" title="Click pentru mapare">${renderCodmatCell(item)}</span>`;
return `<tr>
<td><code>${esc(item.sku)}</code></td>
<td>${esc(item.product_name || '-')}</td>
<td>${codmatCell}</td>
<td>${item.quantity || 0}</td>
<td>${item.price != null ? Number(item.price).toFixed(2) : '-'}</td>
<td class="text-end">${valoare}</td>
</tr>`;
}).join('');
} catch (err) {
document.getElementById('detailError').textContent = err.message;
document.getElementById('detailError').style.display = '';
}
});
}
// ── Quick Map Modal (uses shared openQuickMap) ───