No procedures added yet.
" : procedures.map(p => `
${procedures.indexOf(p) + 1}. ${escapeHTML(p.text)}
`).join('');
// --- Responsibilities List ---
configResponsibilityList.innerHTML = responsibilities.length === 0 ? "
No responsibilities added yet.
" : responsibilities.map(r => `
${escapeHTML(r.role)}: ${escapeHTML(r.duty)}
`).join('');
// --- Resources List ---
configResourceList.innerHTML = resources.length === 0 ? "
No resources added yet.
" : resources.map(r => `
${escapeHTML(r.item)} (${escapeHTML(r.location)})
`).join('');
}
// --- Dashboard Management ---
function renderDashboard() {
// Transfer basic details
dashHazardTitle.value = configHazardTitle.value;
dashSchoolName.value = configSchoolName.value;
const purpose = configPurpose.value;
dashboardOutput.innerHTML = `
`;
// Populate Procedures (editable list)
const procListEl = dashboardOutput.querySelector('#eop-dash-procedures');
procedures.forEach(p => {
const li = document.createElement('li');
li.dataset.id = p.id;
li.innerHTML = `
`;
li.style.display = 'flex';
li.style.gap = '10px';
li.style.alignItems = 'center';
procListEl.appendChild(li);
});
// Populate Responsibilities (editable table)
const respBodyEl = dashboardOutput.querySelector('#eop-dash-responsibilities tbody');
responsibilities.forEach(r => {
const tr = document.createElement('tr');
tr.dataset.id = r.id;
tr.innerHTML = `
|
|
|
`;
respBodyEl.appendChild(tr);
});
// Populate Resources (editable table)
const resBodyEl = dashboardOutput.querySelector('#eop-dash-resources tbody');
resources.forEach(r => {
const tr = document.createElement('tr');
tr.dataset.id = r.id;
tr.innerHTML = `
|
|
|
`;
resBodyEl.appendChild(tr);
});
setupDashboardListeners();
showTab('eop-tab-dashboard');
}
/**
* Attaches listeners to the newly rendered dashboard elements for editing/removal
*/
function setupDashboardListeners() {
// Event delegation for removal
dashboardOutput.addEventListener('click', (e) => {
if (e.target.dataset.action === 'remove-proc') {
const li = e.target.closest('li');
procedures.splice(procedures.findIndex(p => p.id === li.dataset.id), 1);
} else if (e.target.dataset.action === 'remove-resp') {
const tr = e.target.closest('tr');
responsibilities.splice(responsibilities.findIndex(r => r.id === tr.dataset.id), 1);
} else if (e.target.dataset.action === 'remove-res') {
const tr = e.target.closest('tr');
resources.splice(resources.findIndex(r => r.id === tr.dataset.id), 1);
}
// Rerender on removal to ensure cleanup/update
if (e.target.dataset.action && e.target.dataset.action.startsWith('remove-')) {
renderDashboard();
}
});
// Event delegation for input/change updates
dashboardOutput.addEventListener('input', handleDashboardUpdate);
dashboardOutput.addEventListener('change', handleDashboardUpdate);
}
/**
* Handles updates made directly to the dashboard inputs/textareas
*/
function handleDashboardUpdate(e) {
const target = e.target;
const field = target.dataset.field;
if (!field) return;
const parent = target.closest('li, tr, .eop-input-group');
if (!parent) return;
if (parent.id === 'eop-dash-purpose') { // Simple textarea update
configPurpose.value = target.value;
return;
}
const itemId = parent.dataset.id;
const value = target.value;
if (parent.tagName === 'LI') { // Procedure list item
const item = procedures.find(p => p.id === itemId);
if (item) item[field] = value;
} else if (parent.tagName === 'TR' && parent.closest('#eop-dash-responsibilities')) { // Responsibility table row
const item = responsibilities.find(r => r.id === itemId);
if (item) item[field] = value;
} else if (parent.tagName === 'TR' && parent.closest('#eop-dash-resources')) { // Resource table row
const item = resources.find(r => r.id === itemId);
if (item) item[field] = value;
}
}
/**
* Generates a PDF report from the dashboard data
*/
function downloadPDF() {
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF.autoTable === 'undefined') {
alert("Error: PDF library could not be loaded. Please try again.");
return;
}
try {
const { jsPDF } = window.jspdf;
const doc = new jsPDF("p", "pt", "a4");
const margin = 40;
let yPos = margin;
// Get final edited data from the dashboard inputs/textareas
const schoolName = eopContainer.querySelector("#eop-dash-school-name")?.value || "School";
const hazardTitle = (eopContainer.querySelector("#eop-dash-hazard-title")?.value || "Annex").toUpperCase();
const purpose = eopContainer.querySelector("#eop-dash-purpose")?.value || "Purpose statement not defined.";
// --- Procedures Data ---
const finalProcedures = Array.from(eopContainer.querySelectorAll('#eop-dash-procedures input'))
.map((input, i) => `${i + 1}. ${input.value || '[No text provided]'}`)
.filter(p => p.length > 3); // Filter out empty strings/placeholders
// --- Responsibilities Data ---
const respData = Array.from(eopContainer.querySelectorAll('#eop-dash-responsibilities tbody tr')).map(tr => [
tr.querySelector('[data-field="role"]')?.value || '',
tr.querySelector('[data-field="duty"]')?.value || ''
]);
// --- Resources Data ---
const resData = Array.from(eopContainer.querySelectorAll('#eop-dash-resources tbody tr')).map(tr => [
tr.querySelector('[data-field="item"]')?.value || '',
tr.querySelector('[data-field="location"]')?.value || ''
]);
// Check if core data is missing entirely
if (!purpose.trim() && finalProcedures.length === 0 && respData.length === 0) {
alert("The Annex is empty. Please generate content before downloading.");
return;
}
// --- PDF Title & Header ---
doc.setFontSize(18);
doc.setFont(undefined, 'bold');
doc.text("School Emergency Operations Plan (EOP)", doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 20;
doc.setFontSize(14);
doc.text(`Functional Annex: ${hazardTitle}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 15;
doc.setFontSize(10);
doc.setFont(undefined, 'normal');
doc.text(`School: ${schoolName}`, margin, yPos);
yPos += 30;
const tableOptions = {
startY: yPos,
theme: 'grid',
headStyles: { fillColor: [0, 115, 230], textColor: [255, 255, 255], fontSize: 10 },
styles: { fontSize: 10, cellPadding: 5, overflow: 'linebreak' },
margin: { left: margin, right: margin }
};
// Function to add a section with title and text/list/table
function addSection(title, content, contentType) {
// Check for page break
if (yPos > doc.internal.pageSize.getHeight() - 80) {
doc.addPage();
yPos = margin;
}
doc.setFontSize(12);
doc.setFont(undefined, 'bold');
doc.text(title, margin, yPos);
yPos += 15;
if (contentType === 'text') {
doc.setFontSize(10);
doc.setFont(undefined, 'normal');
const textLines = doc.splitTextToSize(content, doc.internal.pageSize.getWidth() - margin * 2);
doc.text(textLines, margin, yPos);
yPos += textLines.length * 12 + 20;
} else if (contentType === 'list') {
doc.setFontSize(10);
doc.setFont(undefined, 'normal');
content.forEach((item, index) => {
const itemLines = doc.splitTextToSize(item, doc.internal.pageSize.getWidth() - margin * 2 - 20);
doc.text(itemLines, margin + 15, yPos);
yPos += itemLines.length * 12;
});
yPos += 15;
} else if (contentType === 'table') {
tableOptions.startY = yPos;
tableOptions.body = content.data;
tableOptions.head = [content.head];
doc.autoTable(tableOptions);
yPos = doc.autoTable.previous.finalY + 20;
}
}
// 1. Purpose
addSection('1. Purpose', purpose, 'text');
// 2. Procedures
addSection('2. Procedures', finalProcedures, 'list');
// 3. Responsibilities
addSection('3. Responsibilities', {
head: ['Role/Position', 'Specific Duty'],
data: respData
}, 'table');
// 4. Resources
addSection('4. Required Resources', {
head: ['Resource/Equipment', 'Location/Owner'],
data: resData
}, 'table');
doc.save(`${schoolName.replace(/ /g,"_")}_EOP_Annex_${hazardTitle}.pdf`);
} catch (error) {
console.error("PDF Generation Error:", error);
alert("An error occurred during PDF creation. Please ensure all data fields are filled correctly.");
}
}
/**
* 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 - Setup Dynamic Forms
addDynamicItem(addProcedureForm, procedures, procedureTemplate, configProcedureList, 'remove-procedure');
addDynamicItem(addResponsibilityForm, responsibilities, responsibilityTemplate, configResponsibilityList, 'remove-responsibility');
addDynamicItem(addResourceForm, resources, resourceTemplate, configResourceList, 'remove-resource');
// Initial config list display
updateConfigLists();
// Generate Button
if (generateBtn) {
generateBtn.addEventListener("click", renderDashboard);
}
// PDF Button
if (pdfBtn) {
pdfBtn.addEventListener("click", downloadPDF);
}
// Initial State: Generate dashboard with samples
renderDashboard();
showTab("eop-tab-dashboard");
});