`).join('');
// 2. Render Requirements (Tab 3)
acgRequirementsContent.innerHTML = acgData.requirements.map(req => `
`).join('');
// 3. Render Dashboard Checklist (Tab 1)
acgDashboardChecklist.innerHTML = acgData.checklist.map(item => `
${escapeHTML(item)}
`).join('');
// 4. Populate Config Tab (Tab 4)
// Pathways
if (acgData.pathways[0]) {
acgCfgPath1Title.value = acgData.pathways[0].title;
acgCfgPath1Desc.value = acgData.pathways[0].desc;
}
if (acgData.pathways[1]) {
acgCfgPath2Title.value = acgData.pathways[1].title;
acgCfgPath2Desc.value = acgData.pathways[1].desc;
}
// Requirements
if (acgData.requirements[0]) {
acgCfgReq1Title.value = acgData.requirements[0].title;
acgCfgReq1Desc.value = acgData.requirements[0].desc;
}
if (acgData.requirements[1]) {
acgCfgReq2Title.value = acgData.requirements[1].title;
acgCfgReq2Desc.value = acgData.requirements[1].desc;
}
if (acgData.requirements[2]) {
acgCfgReq3Title.value = acgData.requirements[2].title;
acgCfgReq3Desc.value = acgData.requirements[2].desc;
}
// 5. Populate Config Checklist (Tab 4)
acgCfgChecklistList.innerHTML = acgData.checklist.map((item, index) => `
${escapeHTML(req.title)}
-
${req.desc.split('\n').map(item => `
- ${escapeHTML(item.replace(/^\*/, ''))} `).join('')}
${escapeHTML(item)}
`).join('');
// Re-attach listeners for dynamically created 'Remove' buttons
acgContainer.querySelectorAll('.acg-cfg-checklist-remove').forEach(button => {
button.addEventListener('click', (e) => {
const indexToRemove = parseInt(e.target.dataset.index, 10);
acgData.checklist.splice(indexToRemove, 1);
renderAcgContent(); // Re-render all content
});
});
}
/**
* Sets up event listeners for the Data Configuration tab (Spec 4, 6)
*/
function setupConfigListeners() {
// Helper function to update data and re-render
const createInputListener = (element, dataPath, key) => {
if (element) {
element.addEventListener('input', () => {
dataPath[key] = element.value;
renderAcgContent(); // Re-render tabs with new data
});
}
};
// Pathways
createInputListener(acgCfgPath1Title, acgData.pathways[0], 'title');
createInputListener(acgCfgPath1Desc, acgData.pathways[0], 'desc');
createInputListener(acgCfgPath2Title, acgData.pathways[1], 'title');
createInputListener(acgCfgPath2Desc, acgData.pathways[1], 'desc');
// Requirements
createInputListener(acgCfgReq1Title, acgData.requirements[0], 'title');
createInputListener(acgCfgReq1Desc, acgData.requirements[0], 'desc');
createInputListener(acgCfgReq2Title, acgData.requirements[1], 'title');
createInputListener(acgCfgReq2Desc, acgData.requirements[1], 'desc');
createInputListener(acgCfgReq3Title, acgData.requirements[2], 'title');
createInputListener(acgCfgReq3Desc, acgData.requirements[2], 'desc');
// Checklist 'Add' button
acgCfgChecklistAdd.addEventListener('click', () => {
const newItem = acgCfgChecklistNew.value.trim();
if (newItem) {
acgData.checklist.push(newItem);
acgCfgChecklistNew.value = ''; // Clear input
renderAcgContent(); // Re-render
}
});
}
/**
* Handles the PDF Download functionality (Spec C, Spec 9)
*/
async function downloadAcgDashboard() {
// Check if libraries are loaded
if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') {
alert('PDF libraries are still loading. Please try again in a moment.');
return;
}
const { jsPDF } = jspdf;
const dashboardContent = acgContainer.querySelector('#acg-dashboard-content');
const container = acgContainer.querySelector('.acg-container');
// Add a class to the container to apply print-friendly styles (Spec 9)
container.classList.add('acg-pdf-view');
acgPdfDownloadBtn.style.display = 'none'; // Hide button itself
try {
// Use html2canvas to capture the styled element
const canvas = await html2canvas(dashboardContent, {
scale: 2, // Higher resolution
useCORS: true,
logging: false,
onclone: (doc) => {
// This runs on a clone of the document
// Convert selects to show the selected value clearly
const selects = doc.querySelectorAll('select');
selects.forEach(select => {
const selectedOption = select.options[select.selectedIndex];
if (selectedOption) {
const staticText = document.createElement('div');
staticText.textContent = selectedOption.text;
staticText.style.padding = '12px 0'; // Match input padding
staticText.style.fontSize = '16px';
staticText.style.fontWeight = 'normal';
staticText.style.color = '#000';
select.parentNode.replaceChild(staticText, select);
}
});
// Convert inputs to static text
const inputs = doc.querySelectorAll('input[type="text"], input[type="number"]');
inputs.forEach(input => {
const staticText = document.createElement('div');
staticText.textContent = input.value || 'N/A';
staticText.style.padding = '12px 0';
staticText.style.fontSize = '16px';
staticText.style.fontWeight = 'normal';
staticText.style.color = '#000';
input.parentNode.replaceChild(staticText, input);
});
}
});
// Create PDF
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
const pageMargin = 15;
const contentWidth = pdfWidth - (pageMargin * 2);
const contentHeight = (canvas.height * contentWidth) / canvas.width;
// Add title
pdf.setFont("helvetica", "bold");
pdf.setFontSize(18);
pdf.setTextColor(acgData.primaryColor); // Use root variable
pdf.text("Astronaut Career Dashboard", pageMargin, pageMargin + 5);
// Add image
pdf.addImage(imgData, 'PNG', pageMargin, pageMargin + 15, contentWidth, contentHeight);
pdf.save('Astronaut_Career_Dashboard.pdf');
} catch (error) {
console.error("ACG PDF Error:", error);
alert("An error occurred while generating the PDF.");
} finally {
// Clean up: remove the class and show the button again
container.classList.remove('acg-pdf-view');
acgPdfDownloadBtn.style.display = 'block';
}
}
/**
* Simple HTML escaper to prevent XSS from user-configured data
*/
function escapeHTML(str) {
if (typeof str !== 'string') return '';
return str.replace(/[&<>"']/g, function(m) {
return {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[m];
});
}
// --- 4. EVENT BINDING (Spec IV.C) ---
// Tab buttons
acgTabButtons.forEach((button, index) => {
button.addEventListener('click', () => {
showAcgTab(index);
});
});
// Navigation buttons
acgNavNext.addEventListener('click', () => {
showAcgTab(currentAcgTabIndex + 1);
});
acgNavPrev.addEventListener('click', () => {
showAcgTab(currentAcgTabIndex - 1);
});
// PDF Download Button
acgPdfDownloadBtn.addEventListener('click', downloadAcgDashboard);
// --- 5. INITIALIZATION ---
renderAcgContent(); // Populate the tool with default data
setupConfigListeners(); // Enable editing in Tab 4
showAcgTab(0); // Show the first tab
});
