Business Subscription & SaaS Cost Tracker

General Settings

Manage Categories

No categories added. Examples: Marketing, Productivity, Development.

Manage Departments

No departments added. Examples: Sales, Engineering, Operations.

Subscription Details

No subscriptions added yet. Click "Add Subscription" to begin.

Cost Dashboard

Enter subscription data in Tab 2 to see the dashboard.

Total Monthly Recurring Cost: ${currency}${totalMonthly.toFixed(2)}

`; html += `

Total Annual Recurring Cost: ${currency}${totalAnnual.toFixed(2)}

`; html += '

All Subscriptions

'; html += ''; subscriptions.forEach(s => { html += ``; }); if (subscriptions.length === 0) { html += ''; } html += '
ServicePlanCycleCycle CostCategoryDept.MonthlyAnnual
${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)}
No subscriptions entered.
'; // Cost Breakdown by Category (Table & Simple Bar Chart) html += '

Cost Breakdown by Category (Annual)

'; if (Object.keys(costsByCategory).length > 0) { html += ''; const chartCategoryData = []; for (const cat in costsByCategory) { const percentage = totalAnnual > 0 ? (costsByCategory[cat] / totalAnnual * 100) : 0; html += ``; chartCategoryData.push({label: cat, value: costsByCategory[cat], percent: percentage }); } html += '
CategoryAnnual Cost% of Total
${cat}${currency}${costsByCategory[cat].toFixed(2)}${percentage.toFixed(1)}%
'; 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 += ''; const chartDeptData = []; for (const dept in costsByDepartment) { const percentage = totalAnnual > 0 ? (costsByDepartment[dept] / totalAnnual * 100) : 0; html += ``; chartDeptData.push({label: dept, value: costsByDepartment[dept], percent: percentage }); } html += '
DepartmentAnnual Cost% of Total
${dept}${currency}${costsByDepartment[dept].toFixed(2)}${percentage.toFixed(1)}%
'; 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 });
Scroll to Top