`).join('');
};
/**
* Updates the summary statistics on the dashboard.
*/
const updateSummaries = () => {
let expiringCount = 0;
let expiredCount = 0;
licenses.forEach(license => {
const status = getLicenseStatus(license.expiryDate).text;
if (status === 'Expiring Soon') expiringCount++;
if (status === 'Expired') expiredCount++;
});
summaryTotal.textContent = licenses.length;
summaryExpiring.textContent = expiringCount;
summaryExpired.textContent = expiredCount;
};
// --- FORM HANDLING ---
/**
* Handles the form submission for adding or updating a license.
* @param {Event} e - The form submission event.
*/
const handleFormSubmit = (e) => {
e.preventDefault();
const id = licenseIdInput.value;
const licenseData = {
name: licenseNameInput.value.trim(),
authority: issuingAuthorityInput.value.trim(),
expiryDate: expiryDateInput.value,
cost: parseFloat(renewalCostInput.value) || 0
};
if (id) {
// Update existing license
const index = licenses.findIndex(l => l.id === id);
if (index > -1) {
licenses[index] = { ...licenses[index], ...licenseData };
}
} else {
// Add new license
licenses.push({ id: generateId(), ...licenseData });
}
// Sort licenses by expiry date, ascending
licenses.sort((a, b) => new Date(a.expiryDate) - new Date(b.expiryDate));
saveLicenses();
renderAll();
resetForm();
switchTab('dashboard'); // Switch to dashboard to see the result
};
/**
* Prepares the form for editing a license.
* @param {string} id - The ID of the license to edit.
*/
const editLicense = (id) => {
const license = licenses.find(l => l.id === id);
if (!license) return;
licenseIdInput.value = license.id;
licenseNameInput.value = license.name;
issuingAuthorityInput.value = license.authority;
expiryDateInput.value = license.expiryDate;
renewalCostInput.value = license.cost;
formTitle.textContent = 'Edit License';
formSubmitBtn.textContent = 'Update License';
cancelEditBtn.classList.remove('hidden');
};
/**
* Deletes a license after confirmation.
* @param {string} id - The ID of the license to delete.
*/
const deleteLicense = (id) => {
licenses = licenses.filter(l => l.id !== id);
saveLicenses();
renderAll();
resetForm(); // Reset form in case the deleted item was being edited
};
/**
* Resets the form to its initial state for adding a new license.
*/
const resetForm = () => {
licenseForm.reset();
licenseIdInput.value = '';
formTitle.textContent = 'Add a New License';
formSubmitBtn.textContent = 'Add License';
cancelEditBtn.classList.add('hidden');
};
// --- TAB & NAVIGATION LOGIC ---
/**
* Switches the view to the specified tab.
* @param {string} tabId - The ID of the tab to switch to ('dashboard' or 'data-config').
*/
const switchTab = (tabId) => {
currentTab = tabId;
Object.values(contentElements).forEach(el => el.classList.add('hidden'));
Object.values(tabElements).forEach(el => el.classList.remove('active'));
if (contentElements[tabId]) {
contentElements[tabId].classList.remove('hidden');
}
if (tabElements[tabId]) {
tabElements[tabId].classList.add('active');
}
updateNavButtons();
};
/**
* Handles clicks on the 'Previous' and 'Next' navigation buttons.
* @param {string} direction - 'prev' or 'next'.
*/
const navigateTabs = (direction) => {
const tabs = ['dashboard', 'data-config'];
const currentIndex = tabs.indexOf(currentTab);
let newIndex = currentIndex;
if (direction === 'next' && currentIndex < tabs.length - 1) {
newIndex++;
} else if (direction === 'prev' && currentIndex > 0) {
newIndex--;
}
switchTab(tabs[newIndex]);
};
/**
* Updates the visibility and disabled state of the 'Previous' and 'Next' buttons.
*/
const updateNavButtons = () => {
prevBtn.disabled = (currentTab === 'dashboard');
prevBtn.classList.toggle('opacity-50', prevBtn.disabled);
nextBtn.disabled = (currentTab === 'data-config');
nextBtn.classList.toggle('opacity-50', nextBtn.disabled);
};
/**
* A helper function to switch to the data config tab and reset the form for adding a new license.
*/
const switchToAddMode = () => {
switchTab('data-config');
resetForm();
licenseNameInput.focus();
};
// --- PDF GENERATION ---
/**
* Generates and downloads a PDF of the license dashboard.
*/
const generatePDF = () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFont('helvetica', 'bold');
doc.setFontSize(18);
doc.text('Business License Renewal Dashboard', 14, 22);
doc.setFont('helvetica', 'normal');
doc.setFontSize(11);
doc.setTextColor(100);
doc.text(`Report generated on: ${new Date().toLocaleDateString()}`, 14, 30);
const tableData = licenses.map(license => {
const status = getLicenseStatus(license.expiryDate);
return [
license.name,
license.authority,
formatDate(license.expiryDate),
`$${license.cost.toFixed(2)}`,
status.text
];
});
doc.autoTable({
startY: 40,
head: [['License Name', 'Issuing Authority', 'Expiry Date', 'Est. Cost', 'Status']],
body: tableData,
theme: 'striped',
headStyles: { fillColor: [37, 99, 235] }, // Blue header
didParseCell: function(data) {
// Color code the status cell based on its text content
if (data.column.dataKey === 4) { // 4 is the index of the 'Status' column
if (data.cell.raw === 'Expired') {
data.cell.styles.textColor = [153, 27, 27]; // Dark red
data.cell.styles.fontStyle = 'bold';
} else if (data.cell.raw === 'Expiring Soon') {
data.cell.styles.textColor = [133, 77, 14]; // Dark yellow/orange
data.cell.styles.fontStyle = 'bold';
}
}
}
});
doc.save('Business_License_Dashboard.pdf');
};
// --- EVENT LISTENERS & INITIALIZATION ---
licenseForm.addEventListener('submit', handleFormSubmit);
downloadPdfBtn.addEventListener('click', generatePDF);
// Make key functions globally accessible for inline onclick handlers, scoped to an app object.
// This is a clean way to meet specification IV.C.
window.app = {
editLicense,
deleteLicense
};
// Initialize the application
loadLicenses();
renderAll();
// Expose switchTab and other global functions needed by onclick attributes
window.switchTab = switchTab;
window.navigateTabs = navigateTabs;
window.resetForm = resetForm;
window.switchToAddMode = switchToAddMode;
});