No technical parameters added yet.
";
return;
}
parameters.forEach(p => {
const itemEl = document.createElement("div");
itemEl.className = "ear-config-list-item";
itemEl.dataset.id = p.id;
itemEl.innerHTML = `
${escapeHTML(p.parameter)} (${escapeHTML(p.value)}) - Ref: ${escapeHTML(p.ref)}
`;
configParameterList.appendChild(itemEl);
});
}
// --- Dashboard Management ---
function renderDashboard(isInitial = true) {
// Get config values
const itemName = configItemName.value;
const description = configDescription.value;
const eccnInitial = configEccnInitial.value;
const reason = configReason.value;
// --- Build Dashboard HTML ---
dashboardOutput.innerHTML = `
`;
// Populate Table Body
const tbody = dashboardOutput.querySelector('#ear-dash-tbody');
if (parameters.length === 0) {
tbody.innerHTML = `
|
No technical parameters logged yet.
|
`;
} else {
parameters.forEach((p, index) => {
const tr = document.createElement('tr');
tr.dataset.id = p.id;
tr.innerHTML = `
${index + 1} |
|
|
|
|
`;
tbody.appendChild(tr);
});
}
setupDashboardListeners();
if (!isInitial) showTab('ear-tab-dashboard');
}
/**
* Attaches listeners to the dashboard table elements
*/
function setupDashboardListeners() {
const output = dashboardOutput;
if (!output) return;
// Event delegation for removal
output.addEventListener('click', (e) => {
if (e.target.dataset.action === 'remove-dash-item') {
const tr = e.target.closest('tr');
parameters.splice(parameters.findIndex(p => p.id === tr.dataset.id), 1);
// Rerender entirely to renumber and update state/config
renderDashboard(false);
updateConfigListDisplay();
}
});
// Event delegation for input/change updates
output.addEventListener('input', handleDashboardUpdate);
output.addEventListener('change', handleDashboardUpdate);
}
/**
* Handles updates made directly to the dashboard inputs/textareas
*/
function handleDashboardUpdate(e) {
const target = e.target;
const value = target.value;
// 1. Update Config header fields from Dashboard
if (target.id === 'ear-dash-name') configItemName.value = value;
else if (target.id === 'ear-dash-description') configDescription.value = value;
else if (target.id === 'ear-dash-eccn') configEccnInitial.value = value;
else if (target.id === 'ear-dash-reason') configReason.value = value;
// 2. Update Parameter State
const tr = target.closest('tr');
if (tr) {
const pId = tr.dataset.id;
const param = parameters.find(p => p.id === pId);
if (param) {
const field = target.dataset.field;
if (field) {
param[field] = value;
// Rerender config list immediately
updateConfigListDisplay();
}
}
}
}
/**
* Generates a PDF report from the dashboard data
*/
function downloadPDF() {
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF.autoTable === 'undefined') {
alert("Error: PDF libraries could not be loaded. Please try again.");
return;
}
// Get final data from dashboard inputs
const itemName = configItemName.value || "Untitled Item";
const description = configDescription.value || "N/A";
const finalEccn = configEccnInitial.value || "EAR99";
const reason = configReason.value || "N/A";
const date = new Date().toLocaleDateString('en-US');
if (!itemName.trim()) {
alert("Please generate the worksheet first.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF("p", "pt", "a4");
const margin = 40;
let yPos = margin;
const lineHeight = 16;
const usableWidth = doc.internal.pageSize.getWidth() - margin * 2;
// Function to add a structured text block
function addBlock(title, text, isMainTitle = false) {
const titleHeight = isMainTitle ? 25 : 20;
const textLines = doc.splitTextToSize(text, usableWidth);
const textHeight = textLines.length * lineHeight;
if (yPos + titleHeight + textHeight > doc.internal.pageSize.getHeight() - margin) {
doc.addPage();
yPos = margin;
}
doc.setFontSize(isMainTitle ? 16 : 12);
doc.setFont(undefined, 'bold');
doc.text(title, margin, yPos);
yPos += lineHeight * 1.5;
if (!isMainTitle) {
doc.setFontSize(10);
doc.setFont(undefined, 'normal');
doc.text(textLines, margin, yPos);
yPos += textHeight + lineHeight * 0.5;
} else {
yPos += 5; // Extra space after main title
}
}
// --- Document Title & Item Header ---
doc.setFontSize(20);
doc.setFont(undefined, 'bold');
doc.text("EAR CLASSIFICATION WORKSHEET", doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += lineHeight * 2;
addBlock(`Item Name: ${itemName}`, `Date: ${date}`, true);
// --- 1. Item Description ---
addBlock("1. General Description", description);
// --- 2. Final ECCN Determination ---
const eccnText = `FINAL ECCN: ${finalEccn}\nREASON(S) FOR CONTROL: ${reason}`;
const eccnLines = doc.splitTextToSize(eccnText, usableWidth);
if (yPos > doc.internal.pageSize.getHeight() - 150) {
doc.addPage();
yPos = margin;
}
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text("2. Classification Determination", margin, yPos);
yPos += 20;
doc.setFontSize(11);
doc.setFont(undefined, 'bold');
doc.text(eccnLines, margin, yPos);
yPos += eccnLines.length * 14 + 15;
// --- 3. Technical Parameters Table ---
if (yPos > doc.internal.pageSize.getHeight() - 100) {
doc.addPage();
yPos = margin;
}
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.text("3. Technical Parameters and Justification", margin, yPos);
yPos += 20;
const tableHead = [["#", "Parameter / Specification", "Measured Value", "CCL Reference"]];
const tableBody = parameters.map((p, index) => [
index + 1,
p.parameter,
p.value,
p.ref
]);
doc.autoTable({
startY: yPos,
head: tableHead,
body: tableBody,
theme: 'grid',
headStyles: { fillColor: [0, 115, 230], textColor: [255, 255, 255], fontSize: 9 },
styles: { fontSize: 8, cellPadding: 4, overflow: 'linebreak' },
columnStyles: {
0: { cellWidth: 20, halign: 'center' },
1: { cellWidth: 150 },
2: { cellWidth: 120 },
3: { cellWidth: 'auto' }
},
margin: { left: margin, right: margin }
});
doc.save(`${itemName.replace(/ /g,"_")}_EAR_Worksheet.pdf`);
}
/**
* Helper to escape HTML
*/
function escapeHTML(str) {
if (!str) return "";
return str
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// --- 4. INITIALIZATION & EVENT LISTENERS ---
// Tab Listeners
tabButtons.forEach((btn) => {
btn.addEventListener("click", () => showTab(btn.dataset.target));
});
navButtons.forEach((btn) => {
btn.addEventListener("click", () => showTab(btn.dataset.target));
});
// Config Tab Listeners
if (addParameterForm) {
addParameterForm.addEventListener("submit", handleAddParameter);
}
if (configParameterList) {
configParameterList.addEventListener("click", handleRemoveConfigItem);
}
if (generateBtn) {
generateBtn.addEventListener("click", () => renderDashboard(false));
}
// PDF Button
if (pdfBtn) {
pdfBtn.addEventListener("click", downloadPDF);
}
// Dashboard Listeners
if (dashboardOutput) {
dashboardOutput.addEventListener('input', handleDashboardUpdate);
dashboardOutput.addEventListener('change', handleDashboardUpdate);
dashboardOutput.addEventListener('click', (e) => {
if (e.target.dataset.action === 'remove-dash-item') {
const tr = e.target.closest('tr');
parameters.splice(parameters.findIndex(p => p.id === tr.dataset.id), 1);
renderDashboard(false);
updateConfigListDisplay();
}
});
}
// Initial config list display
updateConfigListDisplay();
// Initial State: Generate dashboard with samples
renderDashboard();
showTab("ear-tab-dashboard");
});