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 exportDetail(id) { const actions = document.getElementById('topbar-actions'); const content = document.getElementById('page-content'); setPage('Loading...'); actions.innerHTML = ''; content.innerHTML = `
Loading export
`; 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 ` ${hasDetails ? `${icons.chevron}` : ''} ${esc(b.itemNo)} ${esc(b.partName)} ${esc(b.description)} ${esc(b.material)} ${b.qty ?? ''} ${b.totalQty ?? ''} ${b.cutTemplate ? `${icons.laser} DXF` : ''} ${b.formProgram ? `${icons.bend} Form` : ''} ${hasDetails ? `${renderBomDetails(b)}` : ''}`; }).join(''); content.innerHTML = ` ${icons.back} Back to exports
Export Information
${esc(exp.drawingNumber) || '\u2014'}
${exp.title ? `
${esc(exp.title)}
` : ''}
${esc(exp.exportedBy)}
${fmtDate(exp.exportedAt)}
${esc(exp.sourceFilePath)}
BOM Items ${exp.bomItems?.length || 0} items ${exp.pdfContentHash ? `${icons.download} PDF` : ''} ${dxfCount > 0 ? `${icons.download} All DXFs` : ''}
${exp.bomItems?.length ? ` ${bomRows}
Item Part Name Description Material Qty Total Data
` : '
No BOM items for this export.
'}
`; } 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) { const drawingNumber = decodeURIComponent(drawingEncoded); 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; } const allBom = []; exports.forEach(exp => { (exp.bomItems || []).forEach(b => { allBom.push({ ...b, exportId: exp.id, exportedAt: exp.exportedAt }); }); }); 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)} ${esc(b.description)} ${esc(b.material)} ${b.qty ?? ''} ${b.totalQty ?? ''} ${b.cutTemplate ? `${icons.laser} DXF` : ''} ${b.formProgram ? `${icons.bend} Form` : ''} ${hasDetails ? `${renderBomDetails(b)}` : ''}`; }).join(''); content.innerHTML = ` ${icons.back} Back to drawings
Exports
${exports.length}
BOM Items
${allBom.length}
Latest Export
${fmtDate(exports[0].exportedAt)}
All BOM Items ${allBom.length} items
${allBom.length ? ` ${bomRows}
Item Part Name 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)}
`; } } };