No maintenance items added yet. Go to the 'Data Configuration' tab.
`;
return;
}
// Group items by frequency
const groupedItems = FREQUENCY_OPTIONS.reduce((acc, freq) => {
acc[freq] = [];
return acc;
}, {});
pms_data.items.forEach(item => {
if(groupedItems[item.frequency]) {
groupedItems[item.frequency].push(item);
} else {
// Fallback for unexpected frequency
if (!groupedItems['Other']) groupedItems['Other'] = [];
groupedItems['Other'].push(item);
}
});
// Render each frequency group
FREQUENCY_OPTIONS.forEach(freq => {
if (groupedItems[freq].length > 0) {
const sectionDiv = document.createElement('div');
const h3Class = "text-xl font-semibold text-blue-800 border-b border-gray-300 pb-2 mb-3";
const tableClass = "w-full text-left border-collapse";
const thClass = "p-3 bg-gray-100 text-sm font-semibold text-gray-600 border-b border-gray-300";
const tdClass = "p-3 border-b border-gray-200 text-sm";
let itemsHTML = groupedItems[freq].map(item => `
| ${pms_escapeHTML(item.equipment)} |
${pms_escapeHTML(item.task)} |
`).join('');
sectionDiv.innerHTML = `
${freq} Tasks
| Equipment / Asset |
Task |
${itemsHTML}
`;
targetDiv.appendChild(sectionDiv);
}
});
// Handle 'Other' if it exists (shouldn't with current setup but good practice)
if (groupedItems['Other'] && groupedItems['Other'].length > 0) {
// Similar rendering logic as above for 'Other' category
}
}
/**
* Renders a clone for PDF generation
*/
function pms_renderPdfClone() {
pms_pdfRenderClone.innerHTML = `
Preventive Maintenance Schedule
`;
// Render data into the clone's content area
const contentArea = pms_pdfRenderClone.querySelector('.space-y-6');
pms_renderDashboard(contentArea, true);
}
/**
* Generates and downloads a PDF of the schedule
*/
async function pms_downloadPDF() {
if (pms_data.items.length === 0) {
alert("Please add some maintenance items before downloading.");
return;
}
if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') {
console.error("PMS Tool Error: jsPDF or html2canvas library not loaded.");
alert("Error: PDF libraries failed to load. Please check console.");
return;
}
pms_renderPdfClone(); // Create and populate the clone
const { jsPDF } = window.jspdf;
try {
// Target the entire clone div
const canvas = await html2canvas(pms_pdfRenderClone, {
scale: 1.5, // Increase resolution slightly
useCORS: true,
windowWidth: pms_pdfRenderClone.scrollWidth,
windowHeight: pms_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('Preventive_Maintenance_Schedule.pdf');
} catch (error) {
console.error("PMS Tool Error: PDF generation failed.", error);
alert("An error occurred while generating the PDF. Please try again.");
}
}
// --- EVENT LISTENERS ---
// Tab link clicks
pms_tabLinks.forEach((link, index) => {
link.addEventListener('click', () => pms_switchTab(index));
});
// Next/Prev button clicks
if (pms_prevButton) {
pms_prevButton.addEventListener('click', () => {
if (pms_currentTab > 0) pms_switchTab(pms_currentTab - 1);
});
}
if (pms_nextButton) {
pms_nextButton.addEventListener('click', () => {
// If on the last tab (Config tab, index 1)
if (pms_currentTab === pms_tabLinks.length - 1) {
// Act as Submit: Save data and switch to Dashboard (index 0)
pms_updateDataFromConfig();
pms_switchTab(0);
} else {
// Otherwise, just go to the next tab
if (pms_currentTab < pms_tabLinks.length - 1) pms_switchTab(pms_currentTab + 1);
}
});
}
// PDF download
if (pms_downloadPdfButton) {
pms_downloadPdfButton.addEventListener('click', pms_downloadPDF);
}
// --- Config Tab Listeners ---
if (pms_addItemButton) {
pms_addItemButton.addEventListener('click', () => {
pms_itemsContainer.appendChild(pms_createItemInput());
});
}
if (pms_configTab) {
// Handle remove
pms_configTab.addEventListener('click', (e) => {
const removeButton = e.target.closest('.pms-remove-item');
if (removeButton) {
removeButton.closest('.border[data-id]').remove();
// Prevent having zero rows if applicable
if(pms_itemsContainer.children.length === 0){
pms_itemsContainer.appendChild(pms_createItemInput());
}
}
});
// Update data on change (no need for keyup here unless desired for live preview)
pms_configTab.addEventListener('change', () => {
pms_updateDataFromConfig();
// Optionally update dashboard live if on dashboard tab
// if (pms_currentTab === 0) { pms_renderDashboard(); }
});
}
// --- INITIALIZATION ---
pms_initSampleData();
pms_renderConfig(); // Populate config tab on load
pms_renderDashboard(); // Show initial schedule on dashboard
// Set initial tab state
pms_tabPanes.forEach((pane, index) => {
pane.classList.toggle('hidden', index !== 0);
pane.classList.toggle('pms-active', index === 0);
});
pms_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));
}
});
});