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 = `
${icons.search}
`;
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 = `
| # |
Drawing |
Title |
Items |
Exported By |
Date |
|
${rows}
`;
} 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
${esc(exp.drawingNumber) || '\u2014'}
${exp.title ? `
` : ''}
${esc(exp.exportedBy)}
${fmtDate(exp.exportedAt)}
${esc(exp.sourceFilePath)}
${exp.bomItems?.length ? `
|
Item |
Part Name |
Description |
Material |
Qty |
Total |
Data |
${bomRows}
` : '
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 = `
${icons.search}
`;
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 `
| Drawing |
Title |
Items |
Exported By |
Latest Export |
${rows}
`;
}).join('');
content.innerHTML = `
Drawings
${uniqueDrawings}
${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
BOM Items
${allBom.length}
Latest Export
${fmtDate(exports[0].exportedAt)}
${allBom.length ? `
|
Item |
Part Name |
Description |
Material |
Qty |
Total |
Data |
${bomRows}
` : '
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.toUpperCase()} |
${esc(f.drawingNumber)} |
${f.thickness != null ? f.thickness.toFixed(4) + '"' : '\u2014'} |
${fmtDate(f.createdAt)} |
${esc(hashShort)} |
${icons.download}
|
`;
}).join('');
content.innerHTML = `
| Name |
Type |
Drawing |
Thickness |
Date |
Hash |
Actions |
${rows}
`;
} catch (err) {
content.innerHTML = `Error: ${esc(err.message)}
`;
}
}
};