refactor: consolidate export detail into drawing detail page
Remove the duplicate export detail page and route exports list directly to drawing detail. When navigating from exports, the specific export's BOM items are shown via eid param; from drawings, items are deduplicated to the latest revision. Add Rev column, PDF download, and All DXFs download to drawing detail. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ const pages = {
|
||||
setPage('Exports', `${data.items.length} exports`);
|
||||
|
||||
const rows = data.items.map((e, i) => `
|
||||
<tr class="clickable" onclick="router.go('export-detail', {id: ${e.id}})" style="animation: fadeSlideIn 0.2s ease ${0.02 * Math.min(i, 25)}s forwards; opacity: 0">
|
||||
<tr class="clickable" onclick="router.go('drawing-detail', {id: '${encodeURIComponent(e.drawingNumber)}', eid: '${e.id}'})" style="animation: fadeSlideIn 0.2s ease ${0.02 * Math.min(i, 25)}s forwards; opacity: 0">
|
||||
<td style="font-family:var(--font-mono);color:var(--text-dim);font-size:13px">${e.id}</td>
|
||||
<td><strong>${esc(e.drawingNumber) || '<span style="color:var(--text-dim)">\u2014</span>'}</strong></td>
|
||||
<td style="color:var(--text-secondary);font-size:13px">${esc(e.title) || ''}</td>
|
||||
@@ -62,85 +62,6 @@ const pages = {
|
||||
}
|
||||
},
|
||||
|
||||
async exportDetail(id) {
|
||||
const actions = document.getElementById('topbar-actions');
|
||||
const content = document.getElementById('page-content');
|
||||
setPage('Loading...');
|
||||
actions.innerHTML = '';
|
||||
content.innerHTML = `<div class="loading">Loading export</div>`;
|
||||
|
||||
try {
|
||||
const exp = await api.get(`/api/exports/${id}`);
|
||||
setPage(exp.drawingNumber || `Export #${exp.id}`, 'export detail');
|
||||
|
||||
const dxfCount = (exp.bomItems || []).filter(b => b.cutTemplate?.contentHash).length;
|
||||
|
||||
const bomRows = (exp.bomItems || []).map((b, i) => {
|
||||
const hasDetails = b.cutTemplate || b.formProgram;
|
||||
const toggleId = `bom-${b.id}`;
|
||||
return `
|
||||
<tr class="${hasDetails ? 'clickable' : ''}" ${hasDetails ? `onclick="toggleBomRow('${toggleId}')"` : ''} style="animation: fadeSlideIn 0.25s ease ${0.03 * i}s forwards; opacity: 0">
|
||||
<td style="width:32px">${hasDetails ? `<span class="chevron-toggle" id="${toggleId}-icon">${icons.chevron}</span>` : ''}</td>
|
||||
<td style="font-family:var(--font-mono);font-weight:600;color:var(--cyan)">${esc(b.itemNo)}</td>
|
||||
<td><strong>${esc(b.partName)}</strong></td>
|
||||
<td style="color:var(--text-secondary)">${esc(b.description)}</td>
|
||||
<td><span style="font-family:var(--font-mono);font-size:13px">${esc(b.material)}</span></td>
|
||||
<td style="font-family:var(--font-mono);text-align:center">${b.qty ?? ''}</td>
|
||||
<td style="font-family:var(--font-mono);text-align:center">${b.totalQty ?? ''}</td>
|
||||
<td>
|
||||
${b.cutTemplate ? `<span class="badge badge-cyan">${icons.laser} DXF</span>` : ''}
|
||||
${b.formProgram ? `<span class="badge badge-amber">${icons.bend} Form</span>` : ''}
|
||||
</td>
|
||||
</tr>
|
||||
${hasDetails ? `<tr class="bom-expand-row" id="${toggleId}" style="display:none"><td colspan="8">${renderBomDetails(b)}</td></tr>` : ''}`;
|
||||
}).join('');
|
||||
|
||||
content.innerHTML = `
|
||||
<a class="back-link" onclick="router.go('exports')">${icons.back} Back to exports</a>
|
||||
|
||||
<div class="card animate-in" style="margin-bottom:20px">
|
||||
<div class="card-header">Export Information</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-grid">
|
||||
<div class="detail-field"><label>Drawing Number</label><div class="value">${esc(exp.drawingNumber) || '\u2014'}</div></div>
|
||||
${exp.title ? `<div class="detail-field"><label>Title</label><div class="value">${esc(exp.title)}</div></div>` : ''}
|
||||
<div class="detail-field"><label>Exported By</label><div class="value">${esc(exp.exportedBy)}</div></div>
|
||||
<div class="detail-field"><label>Date</label><div class="value mono">${fmtDate(exp.exportedAt)}</div></div>
|
||||
<div class="detail-field"><label>Source File</label><div class="value mono">${esc(exp.sourceFilePath)}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card animate-in">
|
||||
<div class="card-header">
|
||||
BOM Items
|
||||
<span class="badge badge-count">${exp.bomItems?.length || 0} items</span>
|
||||
<span style="margin-left:auto;display:flex;gap:6px">
|
||||
${exp.pdfContentHash ? `<a class="btn btn-amber btn-sm" href="/api/filebrowser/download?hash=${encodeURIComponent(exp.pdfContentHash)}&ext=pdf&name=${encodeURIComponent((exp.drawingNumber || 'drawing') + '.pdf')}">${icons.download} PDF</a>` : ''}
|
||||
${dxfCount > 0 ? `<a class="btn btn-cyan btn-sm" href="/api/exports/${exp.id}/download-dxfs">${icons.download} All DXFs</a>` : ''}
|
||||
<button class="btn btn-red btn-sm" onclick="deleteExport(${exp.id})">${icons.trash} Delete</button>
|
||||
</span>
|
||||
</div>
|
||||
${exp.bomItems?.length ? `
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th style="width:32px"></th>
|
||||
<th style="width:60px">Item</th>
|
||||
<th>Part Name</th>
|
||||
<th>Description</th>
|
||||
<th>Material</th>
|
||||
<th style="width:50px;text-align:center">Qty</th>
|
||||
<th style="width:55px;text-align:center">Total</th>
|
||||
<th style="width:120px">Data</th>
|
||||
</tr></thead>
|
||||
<tbody>${bomRows}</tbody>
|
||||
</table>` : '<div class="empty">No BOM items for this export.</div>'}
|
||||
</div>`;
|
||||
} catch (err) {
|
||||
content.innerHTML = `<div class="empty">Error: ${esc(err.message)}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
async drawings(params) {
|
||||
const actions = document.getElementById('topbar-actions');
|
||||
const content = document.getElementById('page-content');
|
||||
@@ -255,8 +176,9 @@ const pages = {
|
||||
}
|
||||
},
|
||||
|
||||
async drawingDetail(drawingEncoded) {
|
||||
async drawingDetail(drawingEncoded, params) {
|
||||
const drawingNumber = decodeURIComponent(drawingEncoded);
|
||||
const exportId = params?.eid ? parseInt(params.eid) : null;
|
||||
const actions = document.getElementById('topbar-actions');
|
||||
const content = document.getElementById('page-content');
|
||||
setPage(drawingNumber, 'drawing');
|
||||
@@ -273,12 +195,23 @@ const pages = {
|
||||
return;
|
||||
}
|
||||
|
||||
const allBom = [];
|
||||
exports.forEach(exp => {
|
||||
(exp.bomItems || []).forEach(b => {
|
||||
allBom.push({ ...b, exportId: exp.id, exportedAt: exp.exportedAt });
|
||||
let allBom;
|
||||
const singleExport = exportId ? exports.find(e => e.id === exportId) : null;
|
||||
if (singleExport) {
|
||||
// Viewing a specific export - show only its BOM items
|
||||
allBom = (singleExport.bomItems || []).map(b => ({ ...b, exportId: singleExport.id, exportedAt: singleExport.exportedAt }));
|
||||
} else {
|
||||
// Viewing drawing overview - deduplicate by itemNo, keeping latest revision (exports are newest-first)
|
||||
const bomByItem = new Map();
|
||||
exports.forEach(exp => {
|
||||
(exp.bomItems || []).forEach(b => {
|
||||
if (!bomByItem.has(b.itemNo)) {
|
||||
bomByItem.set(b.itemNo, { ...b, exportId: exp.id, exportedAt: exp.exportedAt });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
allBom = [...bomByItem.values()];
|
||||
}
|
||||
|
||||
const bomRows = allBom.map((b, i) => {
|
||||
const hasDetails = b.cutTemplate || b.formProgram;
|
||||
@@ -288,6 +221,7 @@ const pages = {
|
||||
<td style="width:32px">${hasDetails ? `<span class="chevron-toggle" id="${toggleId}-icon">${icons.chevron}</span>` : ''}</td>
|
||||
<td style="font-family:var(--font-mono);font-weight:600;color:var(--cyan)">${esc(b.itemNo)}</td>
|
||||
<td><strong>${esc(b.partName)}</strong></td>
|
||||
<td style="font-family:var(--font-mono);text-align:center;font-size:13px">${b.cutTemplate?.revision ?? ''}</td>
|
||||
<td style="color:var(--text-secondary)">${esc(b.description)}</td>
|
||||
<td><span style="font-family:var(--font-mono);font-size:13px">${esc(b.material)}</span></td>
|
||||
<td style="font-family:var(--font-mono);text-align:center">${b.qty ?? ''}</td>
|
||||
@@ -297,22 +231,43 @@ const pages = {
|
||||
${b.formProgram ? `<span class="badge badge-amber">${icons.bend} Form</span>` : ''}
|
||||
</td>
|
||||
</tr>
|
||||
${hasDetails ? `<tr class="bom-expand-row" id="${toggleId}" style="display:none"><td colspan="8">${renderBomDetails(b)}</td></tr>` : ''}`;
|
||||
${hasDetails ? `<tr class="bom-expand-row" id="${toggleId}" style="display:none"><td colspan="9">${renderBomDetails(b)}</td></tr>` : ''}`;
|
||||
}).join('');
|
||||
|
||||
content.innerHTML = `
|
||||
<a class="back-link" onclick="router.go('drawings')">${icons.back} Back to drawings</a>
|
||||
const backLink = singleExport
|
||||
? `<a class="back-link" onclick="router.go('exports')">${icons.back} Back to exports</a>`
|
||||
: `<a class="back-link" onclick="router.go('drawings')">${icons.back} Back to drawings</a>`;
|
||||
|
||||
<div class="stats-grid">
|
||||
const statsHtml = singleExport
|
||||
? `<div class="stats-grid">
|
||||
<div class="stat-card animate-in"><div class="stat-label">Exported By</div><div class="stat-value stat-sm">${esc(singleExport.exportedBy)}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">BOM Items</div><div class="stat-value">${allBom.length}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">Exported</div><div class="stat-value stat-sm">${fmtDate(singleExport.exportedAt)}</div></div>
|
||||
</div>`
|
||||
: `<div class="stats-grid">
|
||||
<div class="stat-card animate-in"><div class="stat-label">Exports</div><div class="stat-value">${exports.length}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">BOM Items</div><div class="stat-value">${allBom.length}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">Latest Export</div><div class="stat-value stat-sm">${fmtDate(exports[0].exportedAt)}</div></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const bomHeader = singleExport ? 'BOM Items' : 'All BOM Items';
|
||||
const activeExport = singleExport || exports[0];
|
||||
const dxfCount = allBom.filter(b => b.cutTemplate?.contentHash).length;
|
||||
const pdfHash = activeExport.pdfContentHash;
|
||||
const pdfName = encodeURIComponent((drawingNumber || 'drawing') + '.pdf');
|
||||
|
||||
content.innerHTML = `
|
||||
${backLink}
|
||||
${statsHtml}
|
||||
|
||||
<div class="card animate-in">
|
||||
<div class="card-header">
|
||||
All BOM Items
|
||||
${bomHeader}
|
||||
<span class="badge badge-count">${allBom.length} items</span>
|
||||
<span style="margin-left:auto;display:flex;gap:6px">
|
||||
${pdfHash ? `<a class="btn btn-amber btn-sm" href="/api/filebrowser/download?hash=${encodeURIComponent(pdfHash)}&ext=pdf&name=${pdfName}">${icons.download} PDF</a>` : ''}
|
||||
${dxfCount > 0 ? `<a class="btn btn-cyan btn-sm" href="/api/exports/${activeExport.id}/download-dxfs">${icons.download} All DXFs</a>` : ''}
|
||||
</span>
|
||||
</div>
|
||||
${allBom.length ? `
|
||||
<table>
|
||||
@@ -320,6 +275,7 @@ const pages = {
|
||||
<th style="width:32px"></th>
|
||||
<th style="width:60px">Item</th>
|
||||
<th>Part Name</th>
|
||||
<th style="width:45px;text-align:center">Rev</th>
|
||||
<th>Description</th>
|
||||
<th>Material</th>
|
||||
<th style="width:50px;text-align:center">Qty</th>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
const router = {
|
||||
go(page, params = {}) {
|
||||
const hash = page + (params.id ? '/' + params.id : '') + (params.q ? '?q=' + encodeURIComponent(params.q) : '');
|
||||
const qParts = [];
|
||||
if (params.q) qParts.push('q=' + encodeURIComponent(params.q));
|
||||
if (params.eid) qParts.push('eid=' + encodeURIComponent(params.eid));
|
||||
const hash = page + (params.id ? '/' + params.id : '') + (qParts.length ? '?' + qParts.join('&') : '');
|
||||
location.hash = hash;
|
||||
},
|
||||
parse() {
|
||||
@@ -20,12 +23,10 @@ const router = {
|
||||
document.querySelectorAll('.nav-item').forEach(el => {
|
||||
el.classList.toggle('active',
|
||||
el.dataset.page === page ||
|
||||
(page === 'export-detail' && el.dataset.page === 'exports') ||
|
||||
(page === 'drawing-detail' && el.dataset.page === 'drawings'));
|
||||
});
|
||||
switch(page) {
|
||||
case 'exports': pages.exports(params); break;
|
||||
case 'export-detail': pages.exportDetail(id); break;
|
||||
case 'drawings': pages.drawings(params); break;
|
||||
case 'drawing-detail': pages.drawingDetail(id, params); break;
|
||||
case 'files': pages.files(params); break;
|
||||
|
||||
Reference in New Issue
Block a user