`;
html += '
All Subscriptions
';
html += '
| Service | Plan | Cycle | Cycle Cost | Category | Dept. | Monthly | Annual |
';
subscriptions.forEach(s => {
html += `
| ${s.name || 'N/A'} |
${s.plan || '-'} |
${s.cycle} |
${currency}${s.cycleCost.toFixed(2)} |
${s.category || '-'} |
${s.department || '-'} |
${currency}${s.monthly.toFixed(2)} |
${currency}${s.annual.toFixed(2)} |
`;
});
if (subscriptions.length === 0) {
html += '| No subscriptions entered. |
';
}
html += '
';
// Cost Breakdown by Category (Table & Simple Bar Chart)
html += '
Cost Breakdown by Category (Annual)
';
if (Object.keys(costsByCategory).length > 0) {
html += '
| Category | Annual Cost | % of Total |
';
const chartCategoryData = [];
for (const cat in costsByCategory) {
const percentage = totalAnnual > 0 ? (costsByCategory[cat] / totalAnnual * 100) : 0;
html += `| ${cat} | ${currency}${costsByCategory[cat].toFixed(2)} | ${percentage.toFixed(1)}% |
`;
chartCategoryData.push({label: cat, value: costsByCategory[cat], percent: percentage });
}
html += '
';
html += '
';
chartCategoryData.sort((a,b) => b.value - a.value).forEach(d => { // Sort by value desc
const barWidth = totalAnnual > 0 ? (d.value / totalAnnual * 100) : 0;
html += `
${d.label}
${currency}${d.value.toFixed(2)}
`;
});
html += '
';
} else { html += '
No costs assigned to categories.
'; }
// Cost Breakdown by Department (Table & Simple Bar Chart)
html += '
Cost Breakdown by Department (Annual)
';
if (Object.keys(costsByDepartment).length > 0) {
html += '
| Department | Annual Cost | % of Total |
';
const chartDeptData = [];
for (const dept in costsByDepartment) {
const percentage = totalAnnual > 0 ? (costsByDepartment[dept] / totalAnnual * 100) : 0;
html += `| ${dept} | ${currency}${costsByDepartment[dept].toFixed(2)} | ${percentage.toFixed(1)}% |
`;
chartDeptData.push({label: dept, value: costsByDepartment[dept], percent: percentage });
}
html += '
';
html += '
';
chartDeptData.sort((a,b) => b.value - a.value).forEach(d => { // Sort by value desc
const barWidth = totalAnnual > 0 ? (d.value / totalAnnual * 100) : 0;
html += `
${d.label}
${currency}${d.value.toFixed(2)}
`;
});
html += '
';
} else { html += '
No costs assigned to departments.
'; }
dashboardOutput.innerHTML = html;
downloadPdfButton.disabled = subscriptions.length === 0;
}
// PDF Download
downloadPdfButton.addEventListener('click', function () {
if (typeof jspdf === 'undefined' || typeof jspdf.jsPDF === 'undefined') {
alert('Error: jsPDF library is not loaded.'); return;
}
const { jsPDF } = jspdf;
const doc = new jsPDF('p');
const currency = currencySymbolInput.value || '$';
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--bsst-primary-color').trim();
const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--bsst-secondary-color').trim();
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--bsst-text-color').trim();
const buttonTextColor = getComputedStyle(document.documentElement).getPropertyValue('--bsst-button-text-color').trim();
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text('Subscription & SaaS Cost Summary', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' });
doc.setFontSize(12);
doc.setTextColor(textColor);
doc.text(`Business Name: ${document.getElementById('bsst-business-name').value || 'N/A'}`, 14, 30);
doc.text(`Currency: ${currency}`, 14, 37);
let lastY = 45;
const subscriptionsData = [];
let totalMonthlyPdf = 0;
let totalAnnualPdf = 0;
const costsByCategoryPdf = {};
const costsByDepartmentPdf = {};
document.querySelectorAll('.bsst-subscription-item').forEach(item => {
const cycle = item.querySelector('.bsst-sub-cycle').value;
const cycleCost = getNum(item.querySelector('.bsst-sub-cost').value);
const { monthly, annual } = calculateCosts(cycle, cycleCost);
totalMonthlyPdf += monthly;
totalAnnualPdf += annual;
const category = item.querySelector('.bsst-sub-category').value;
const department = item.querySelector('.bsst-sub-department').value;
if (category) costsByCategoryPdf[category] = (costsByCategoryPdf[category] || 0) + annual;
if (department) costsByDepartmentPdf[department] = (costsByDepartmentPdf[department] || 0) + annual;
subscriptionsData.push([
item.querySelector('.bsst-sub-name').value || 'N/A',
item.querySelector('.bsst-sub-plan').value || '-',
cycle,
`${currency}${cycleCost.toFixed(2)}`,
category || '-',
department || '-',
`${currency}${monthly.toFixed(2)}`,
`${currency}${annual.toFixed(2)}`
]);
});
doc.setFontSize(10);
doc.text(`Total Monthly Recurring Cost: ${currency}${totalMonthlyPdf.toFixed(2)}`, 14, lastY); lastY += 7;
doc.text(`Total Annual Recurring Cost: ${currency}${totalAnnualPdf.toFixed(2)}`, 14, lastY); lastY += 10;
doc.setFontSize(12);
doc.setTextColor(primaryColor);
doc.text('All Subscriptions:', 14, lastY); lastY += 7;
doc.autoTable({
startY: lastY,
head: [['Service', 'Plan', 'Cycle', 'Cycle Cost', 'Category', 'Dept.', 'Monthly', 'Annual']],
body: subscriptionsData,
theme: 'grid',
headStyles: { fillColor: secondaryColor, textColor: buttonTextColor, fontSize: 8 },
styles: { fontSize: 7, cellPadding: 1.5 },
columnStyles: { 3: {halign: 'right'}, 6: {halign: 'right'}, 7: {halign: 'right'} }
});
lastY = doc.lastAutoTable.finalY + 10;
if (Object.keys(costsByCategoryPdf).length > 0) {
if (lastY > 250) { doc.addPage(); lastY = 20; }
doc.setFontSize(12); doc.setTextColor(primaryColor);
doc.text('Cost Breakdown by Category (Annual):', 14, lastY); lastY += 7;
const catBody = [];
for (const cat in costsByCategoryPdf) {
catBody.push([cat, `${currency}${costsByCategoryPdf[cat].toFixed(2)}`, `${totalAnnualPdf > 0 ? (costsByCategoryPdf[cat] / totalAnnualPdf * 100).toFixed(1) : 0}%`]);
}
doc.autoTable({ startY: lastY, head: [['Category', 'Annual Cost', '% of Total']], body: catBody, headStyles: {fillColor: secondaryColor, textColor:buttonTextColor, fontSize:9}, styles:{fontSize:8, cellPadding:1.5}, columnStyles:{1:{halign:'right'}, 2:{halign:'right'}}});
lastY = doc.lastAutoTable.finalY + 10;
}
if (Object.keys(costsByDepartmentPdf).length > 0) {
if (lastY > 250) { doc.addPage(); lastY = 20; }
doc.setFontSize(12); doc.setTextColor(primaryColor);
doc.text('Cost Breakdown by Department (Annual):', 14, lastY); lastY += 7;
const deptBody = [];
for (const dept in costsByDepartmentPdf) {
deptBody.push([dept, `${currency}${costsByDepartmentPdf[dept].toFixed(2)}`, `${totalAnnualPdf > 0 ? (costsByDepartmentPdf[dept] / totalAnnualPdf * 100).toFixed(1) : 0}%`]);
}
doc.autoTable({ startY: lastY, head: [['Department', 'Annual Cost', '% of Total']], body: deptBody, headStyles: {fillColor: secondaryColor, textColor:buttonTextColor, fontSize:9}, styles:{fontSize:8, cellPadding:1.5}, columnStyles:{1:{halign:'right'}, 2:{halign:'right'}}});
}
doc.save(`SaaS_Subscription_Costs_${document.getElementById('bsst-business-name').value.replace(/\s+/g, '_') || 'Report'}.pdf`);
});
// Initial setup
checkEmptyState('#bsst-categories-container', 'bsst-no-items-text');
checkEmptyState('#bsst-departments-container', 'bsst-no-items-text');
checkEmptyState('#bsst-subscriptions-list-container', 'bsst-no-items-text');
showTab(0);
updateAllCurrencyPrefixes(); // Set initial currency symbols for any static prefixes if added later
});