const pages = { async exports(params) { const actions = document.getElementById('topbar-actions'); const content = document.getElementById('page-content'); setPage('Exports'); const searchVal = params.q || ''; actions.innerHTML = ` `; content.innerHTML = `
Loading exports
`; const searchInput = document.getElementById('export-search'); let debounce; searchInput.addEventListener('input', () => { clearTimeout(debounce); debounce = setTimeout(() => router.go('exports', { q: searchInput.value }), 400); }); try { const searchQ = searchVal ? `&search=${encodeURIComponent(searchVal)}` : ''; const data = await api.get(`/api/exports?take=500${searchQ}`); if (data.items.length === 0) { content.innerHTML = `
No exports found.
`; return; } setPage('Exports', `${data.items.length} exports`); const rows = data.items.map((e, i) => ` ${e.id} ${esc(e.drawingNumber) || '\u2014'} ${esc(e.title) || ''} ${e.bomItemCount} ${esc(e.exportedBy)} ${fmtDate(e.exportedAt)} `).join(''); content.innerHTML = `
${rows}
# Drawing Title Items Exported By Date
`; } catch (err) { content.innerHTML = `
Error: ${esc(err.message)}
`; } }, async drawings(params) { const actions = document.getElementById('topbar-actions'); const content = document.getElementById('page-content'); setPage('Drawings'); const searchVal = (params && params.q) || ''; actions.innerHTML = ` `; content.innerHTML = `
Loading drawings
`; const searchInput = document.getElementById('drawing-search'); let debounce; searchInput.addEventListener('input', () => { clearTimeout(debounce); debounce = setTimeout(() => router.go('drawings', { q: searchInput.value }), 400); }); try { const searchQ = searchVal ? `&search=${encodeURIComponent(searchVal)}` : ''; const data = await api.get(`/api/exports?take=500${searchQ}`); if (data.items.length === 0) { content.innerHTML = `
No drawings found.
`; return; } // Deduplicate: keep only the latest export per drawing number const seen = new Set(); const unique = data.items.filter(e => { const dn = e.drawingNumber || ''; if (seen.has(dn)) return false; seen.add(dn); return true; }); // Group by equipment number (first token of drawing number) const groups = new Map(); unique.forEach(e => { const dn = e.drawingNumber || ''; const spaceIdx = dn.indexOf(' '); const equip = spaceIdx > 0 ? dn.substring(0, spaceIdx) : (dn || 'Other'); if (!groups.has(equip)) groups.set(equip, []); groups.get(equip).push(e); }); // Sort equipment groups by number descending (most recent equipment first) const sortedGroups = [...groups.entries()].sort((a, b) => { const numA = parseInt(a[0]) || 0; const numB = parseInt(b[0]) || 0; return numB - numA; }); const uniqueEquip = sortedGroups.length; const uniqueDrawings = unique.length; setPage('Drawings', `${uniqueDrawings} drawings / ${uniqueEquip} equipment`); const groupsHtml = sortedGroups.map(([equip, items], gi) => { const totalBom = items.reduce((s, e) => s + e.bomItemCount, 0); const rows = items.map((e, i) => { const dn = e.drawingNumber || ''; const spaceIdx = dn.indexOf(' '); const drawingPart = spaceIdx > 0 ? dn.substring(spaceIdx + 1) : dn; return ` ${esc(drawingPart) || '\u2014'} ${esc(e.title) || ''} ${e.bomItemCount} ${esc(e.exportedBy)} ${fmtDate(e.exportedAt)} `; }).join(''); return `
${icons.chevron} ${esc(equip)}
${items.length} drawings ${totalBom} items
${rows}
Drawing Title Items Exported By Latest Export
`; }).join(''); content.innerHTML = `
Drawings
${uniqueDrawings}
Equipment
${uniqueEquip}
${groupsHtml}`; } catch (err) { content.innerHTML = `
Error: ${esc(err.message)}
`; } }, 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'); actions.innerHTML = ''; content.innerHTML = `
Loading drawing
`; try { const exports = await api.get(`/api/exports/by-drawing?drawingNumber=${encodeURIComponent(drawingNumber)}`); if (exports.length === 0) { content.innerHTML = ` ${icons.back} Back to drawings
No exports found for this drawing.
`; return; } 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; const toggleId = `dbom-${b.id}`; return ` ${hasDetails ? `${icons.chevron}` : ''} ${esc(b.itemNo)} ${esc(b.partName)} ${b.cutTemplate?.revision ?? ''} ${esc(b.description)} ${esc(b.material)} ${b.qty ?? ''} ${b.totalQty ?? ''} ${b.cutTemplate ? `${icons.laser} DXF` : ''} ${b.formProgram ? `${icons.bend} Form` : ''} ${hasDetails ? `${renderBomDetails(b)}` : ''}`; }).join(''); const backLink = singleExport ? `${icons.back} Back to exports` : `${icons.back} Back to drawings`; const statsHtml = singleExport ? `
Exported By
${esc(singleExport.exportedBy)}
BOM Items
${allBom.length}
Exported
${fmtDate(singleExport.exportedAt)}
` : `
Exports
${exports.length}
BOM Items
${allBom.length}
Latest Export
${fmtDate(exports[0].exportedAt)}
`; 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}
${bomHeader} ${allBom.length} items ${pdfHash ? `${icons.download} PDF` : ''} ${dxfCount > 0 ? `${icons.download} All DXFs` : ''}
${allBom.length ? ` ${bomRows}
Item Part Name Rev Description Material Qty Total Data
` : '
No BOM items.
'}
`; } catch (err) { content.innerHTML = `
Error: ${esc(err.message)}
`; } }, async files(params) { const actions = document.getElementById('topbar-actions'); const content = document.getElementById('page-content'); setPage('Files'); const searchVal = params.q || ''; actions.innerHTML = `
`; content.innerHTML = `
Loading files
`; const searchInput = document.getElementById('file-search'); const typeFilter = document.getElementById('file-type-filter'); let debounce; const refresh = () => { clearTimeout(debounce); debounce = setTimeout(() => router.go('files', { q: searchInput.value + (typeFilter.value ? '&type=' + typeFilter.value : '') }), 400); }; searchInput.addEventListener('input', refresh); typeFilter.addEventListener('change', refresh); // Parse search and type from combined param let searchQ = searchVal; let typeQ = ''; if (searchVal.includes('&type=')) { const parts = searchVal.split('&type='); searchQ = parts[0]; typeQ = parts[1] || ''; searchInput.value = searchQ; typeFilter.value = typeQ; } try { let url = '/api/filebrowser/files?'; if (searchQ) url += `search=${encodeURIComponent(searchQ)}&`; if (typeQ) url += `type=${encodeURIComponent(typeQ)}&`; const data = await api.get(url); setPage('Files', `${data.total} files`); if (data.files.length === 0) { content.innerHTML = `
No files found.
`; return; } const rows = data.files.map((f, i) => { const ext = f.fileType || f.fileName.split('.').pop().toLowerCase(); const hashShort = f.contentHash ? f.contentHash.substring(0, 12) : ''; return `
${ext === 'pdf' ? icons.filePdf : icons.fileDxf}${esc(f.fileName)}
${ext.toUpperCase()} ${esc(f.drawingNumber)} ${f.thickness != null ? f.thickness.toFixed(4) + '"' : '\u2014'} ${fmtDate(f.createdAt)} ${esc(hashShort)} ${icons.download} `; }).join(''); content.innerHTML = `
${rows}
Name Type Drawing Thickness Date Hash Actions
`; } catch (err) { content.innerHTML = `
Error: ${esc(err.message)}
`; } } };