No investigation steps defined yet. Go to the 'Data Configuration' tab.
`;
return;
}
// Render each step sequentially (could group by category later if needed)
pl_data.steps.forEach((step, index) => {
const stepDiv = document.createElement('div');
stepDiv.className = `p-4 border rounded-lg bg-white shadow-sm ${isPDF ? '' : 'flex flex-col md:flex-row md:items-start md:space-x-4'}`;
stepDiv.setAttribute('data-id', step.id);
const statusSlug = step.status.toLowerCase().replace(/ /g, '-').replace(/[^a-z-]/g,''); // create slug
const statusBadge = `
${pl_escapeHTML(step.status)}`;
let contentHTML = `
${pl_escapeHTML(step.category)}
${index + 1}. ${pl_escapeHTML(step.stepName)}
${pl_escapeHTML(step.description)}
`;
if (isPDF) {
// PDF View: Show static status and notes
contentHTML += `
${statusBadge}
${step.notes ? `
Notes: ${pl_escapeHTML(step.notes)}
` : ''}
`;
stepDiv.classList.add('flex', 'justify-between', 'items-start'); // Flex layout for PDF rows
} else {
// Dashboard View: Interactive elements
const statusOptions = STATUS_OPTIONS.map(opt =>
`
`
).join('');
contentHTML += `
`;
}
stepDiv.innerHTML = contentHTML;
targetDiv.appendChild(stepDiv);
});
// Add event listeners only if not rendering for PDF
if (!isPDF) {
pl_addDashboardListeners();
}
}
/**
* Adds event listeners to dashboard interactive elements
*/
function pl_addDashboardListeners() {
pl_dashboardOutput.querySelectorAll('.pl-dashboard-status').forEach(select => {
select.addEventListener('change', (e) => {
const stepDiv = e.target.closest('[data-id]');
const id = parseInt(stepDiv.getAttribute('data-id'), 10);
const newStatus = e.target.value;
pl_updateStepData(id, 'status', newStatus);
});
});
pl_dashboardOutput.querySelectorAll('.pl-dashboard-notes').forEach(textarea => {
textarea.addEventListener('input', (e) => { // Use 'input' for live updates
const stepDiv = e.target.closest('[data-id]');
const id = parseInt(stepDiv.getAttribute('data-id'), 10);
const newNotes = e.target.value;
pl_updateStepData(id, 'notes', newNotes);
});
});
}
/**
* Updates the status or notes for a specific step in the pl_data object
*/
function pl_updateStepData(id, field, value) {
const stepIndex = pl_data.steps.findIndex(s => s.id === id);
if (stepIndex !== -1) {
pl_data.steps[stepIndex][field] = value;
// Re-render dashboard ONLY if status changed, to update badge
if(field === 'status') pl_renderDashboard();
}
}
/**
* Renders a clone for PDF generation
*/
function pl_renderPdfClone() {
pl_pdfRenderClone.innerHTML = `
Product Liability Investigation Plan
`;
// Render data into the clone's content area
const contentArea = pl_pdfRenderClone.querySelector('.space-y-4');
pl_renderDashboard(contentArea, true);
}
/**
* Generates and downloads a PDF of the plan
*/
async function pl_downloadPDF() {
if (pl_data.steps.length === 0) {
alert("Please add some investigation steps before downloading.");
return;
}
if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') {
console.error("PL Tool Error: jsPDF or html2canvas library not loaded.");
alert("Error: PDF libraries failed to load. Please check console.");
return;
}
pl_renderPdfClone(); // Create and populate the clone
const { jsPDF } = window.jspdf;
try {
// Target the entire clone div
const canvas = await html2canvas(pl_pdfRenderClone, {
scale: 1.5, // Increase resolution slightly
useCORS: true,
windowWidth: pl_pdfRenderClone.scrollWidth,
windowHeight: pl_pdfRenderClone.scrollHeight // Capture full height
});
const imgData = canvas.toDataURL('image/png');
const imgWidth = canvas.width;
const imgHeight = canvas.height;
const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
// Scale image height to fit pdf width, handle multiple pages
const margin = 40;
const contentWidth = pdfWidth - (margin * 2);
const contentHeight = (contentWidth * imgHeight) / imgWidth;
let heightLeft = contentHeight;
let position = 0; // y-position of the image slice on the page
// Add the first page
pdf.addImage(imgData, 'PNG', margin, position + margin, contentWidth, contentHeight);
heightLeft -= (pdfHeight - margin * 2);
// Add subsequent pages if needed
while (heightLeft > 0) {
position -= (pdfHeight - margin * 2); // Move the image's y-position up
pdf.addPage();
pdf.addImage(imgData, 'PNG', margin, position + margin, contentWidth, contentHeight);
heightLeft -= (pdfHeight - margin * 2);
}
pdf.save('Product_Liability_Investigation_Plan.pdf');
} catch (error) {
console.error("PL Tool Error: PDF generation failed.", error);
alert("An error occurred while generating the PDF. Please try again.");
}
}
// --- EVENT LISTENERS ---
// Tab link clicks
pl_tabLinks.forEach((link, index) => {
link.addEventListener('click', () => pl_switchTab(index));
});
// Next/Prev button clicks
if (pl_prevButton) {
pl_prevButton.addEventListener('click', () => {
if (pl_currentTab > 0) pl_switchTab(pl_currentTab - 1);
});
}
if (pl_nextButton) {
pl_nextButton.addEventListener('click', () => {
// If on the last tab (Config tab, index 1)
if (pl_currentTab === pl_tabLinks.length - 1) {
// Act as Submit: Save data and switch to Dashboard (index 0)
pl_updateDataFromConfig();
pl_switchTab(0);
} else {
// Otherwise, just go to the next tab
if (pl_currentTab < pl_tabLinks.length - 1) pl_switchTab(pl_currentTab + 1);
}
});
}
// PDF download
if (pl_downloadPdfButton) {
pl_downloadPdfButton.addEventListener('click', pl_downloadPDF);
}
// --- Config Tab Listeners ---
if (pl_addStepButton) {
pl_addStepButton.addEventListener('click', () => {
pl_stepsContainer.appendChild(pl_createStepInput());
});
}
if (pl_configTab) {
// Handle remove
pl_configTab.addEventListener('click', (e) => {
const removeButton = e.target.closest('.pl-remove-item');
if (removeButton) {
const entryDiv = removeButton.closest('.border[data-id]');
const idToRemove = parseInt(entryDiv.getAttribute('data-id'));
pl_data.steps = pl_data.steps.filter(s => s.id !== idToRemove); // Remove from data
entryDiv.remove(); // Remove from UI
// Prevent having zero rows if applicable
if(pl_stepsContainer.children.length === 0){
pl_stepsContainer.appendChild(pl_createStepInput());
}
}
});
// Update data on input/change (but only save fully on tab switch/next button)
// No immediate live update needed here unless desired
}
// --- INITIALIZATION ---
pl_initSampleData();
pl_renderConfig(); // Populate config tab on load
pl_renderDashboard(); // Show initial state on dashboard
// Set initial tab state
pl_tabPanes.forEach((pane, index) => {
pane.classList.toggle('hidden', index !== 0);
pane.classList.toggle('pl-active', index === 0);
});
pl_tabLinks.forEach((link, index) => {
TAB_CLASSES.active.forEach(cls => link.classList.remove(cls));
TAB_CLASSES.inactive.forEach(cls => link.classList.remove(cls));
if (index === 0) {
TAB_CLASSES.active.forEach(cls => link.classList.add(cls));
} else {
TAB_CLASSES.inactive.forEach(cls => link.classList.add(cls));
}
});
});