School Emergency Operations Plan Functional Annex Template

School EOP Functional Annex Template

Annex:

School:

Go to the "Scenario Configuration" tab to define the Annex contents and generate the document.

Annex Basics

Procedures (Steps)

Current Procedures

Team Responsibilities

Current Responsibilities

Required Resources

Current Resources

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 = `
    Role/PositionSpecific DutyAction
    Resource/EquipmentLocation/OwnerAction
    `; // 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"); });
    Scroll to Top