SaaS Cost Analyzer for Businesses

Setup

Manage SaaS Categories/Departments

Define categories (e.g., by function or department) to classify your SaaS tools.

Current Categories:

Add & Manage SaaS Subscriptions

Add New SaaS Subscription

Current SaaS Subscriptions

Product Name Category/Dept. Billing Cycle Cost/Cycle ($) Licenses Renewal Date Annualized Cost ($) Actions

SaaS Cost Analysis

Analysis Name:

Total Annual SaaS Spend: $0.00

Total Monthly SaaS Spend: $0.00

Annual Spend by Category/Department

Category/Dept.Total Annual Spend ($)% of Total Spend

Annual Spend Distribution by Category/Department

Annual Spend Distribution by Billing Cycle

Chart library not loaded or canvas not found.

"; return; } const ctx = chartCanvas.getContext('2d'); const chartColors = ['#6f42c1', '#007bff', '#28a745', '#ffc107', '#17a2b8', '#fd7e14', '#6c757d', '#dc3545']; let chartInstance = window[chartInstanceVarName]; // Access global chart instance variable if (chartInstance) chartInstance.destroy(); if (labels.length > 0 && data.some(d => d > 0)) { window[chartInstanceVarName] = new Chart(ctx, { // Assign to global chart instance variable type: 'doughnut', data: { labels: labels, datasets: [{ label: chartLabel, data: data, backgroundColor: labels.map((_, i) => chartColors[i % chartColors.length]), borderColor: varGet('--sca-light-text'), borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' }, tooltip: { callbacks: { label: c => `${c.label}: $${c.parsed.toFixed(2)}` } } } } }); } else { ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); ctx.textAlign='center'; ctx.fillStyle = varGet('--sca-secondary-color'); ctx.font = "16px Arial"; ctx.fillText(`No data for "${chartLabel}" chart.`, ctx.canvas.width/2, ctx.canvas.height/2); window[chartInstanceVarName] = null; // Clear instance if no data } } function varGet(varName) { return getComputedStyle(document.documentElement).getPropertyValue(varName).trim(); } // --- PDF Download --- window.scaDownloadPDF = function() { if (typeof jsPDF === 'undefined' || typeof autoTable === 'undefined') { alert("PDF libraries not loaded."); return; } const { jsPDF: JSPDF } = window.jspdf; const localAutoTable = window.jspdf.autoTable; const doc = new JSPDF(); scaUpdateAnalysisReport(); // Ensure data is current const analysisName = scaAnalysisSettings.analysisName; const generationDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) + " (Reference Time: Friday, May 16, 2025)"; doc.setFontSize(18); doc.setTextColor(varGet('--sca-primary-color')); doc.text(analysisName, doc.internal.pageSize.getWidth() / 2, 15, { align: 'center' }); doc.setFontSize(10); doc.setTextColor(varGet('--sca-dark-text')); doc.text(`Report Generated: ${generationDate}`, doc.internal.pageSize.getWidth() - 14, 25, { align: 'right' }); let yPos = 30; const totalAnnualSpend = scaSubscriptions.reduce((sum, sub) => sum + sub.annualizedCost, 0); const totalMonthlySpend = totalAnnualSpend / 12; doc.setFontSize(12); doc.setTextColor(varGet('--sca-primary-color')); doc.text("Overall SaaS Spend Summary", 14, yPos); yPos += 7; doc.setFontSize(10); doc.setTextColor(varGet('--sca-dark-text')); doc.text(`Total Annual SaaS Spend: $${totalAnnualSpend.toFixed(2)}`, 14, yPos); yPos += 5; doc.text(`Equivalent Monthly SaaS Spend: $${totalMonthlySpend.toFixed(2)}`, 14, yPos); yPos += 5; if (scaAnalysisSettings.totalCompanyEmployees > 0) { const avgCost = totalAnnualSpend / scaAnalysisSettings.totalCompanyEmployees; doc.text(`Average Annual SaaS Cost per Employee: $${avgCost.toFixed(2)} (for ${scaAnalysisSettings.totalCompanyEmployees} employees)`, 14, yPos); yPos += 5; } yPos += 5; doc.setFontSize(12); doc.setTextColor(varGet('--sca-primary-color')); doc.text("Itemized SaaS Subscriptions", 14, yPos); yPos += 7; const subHead = [['Product', 'Category', 'Billing Cycle', 'Cost/Cycle ($)', '#Licenses', 'Renewal', 'Annual Cost ($)', 'Notes']]; const subBody = scaSubscriptions.map(s => [ s.productName, s.category, s.billingCycle, s.costPerCycle.toFixed(2), s.numLicenses > 0 ? s.numLicenses : '-', s.renewalDate || '-', s.annualizedCost.toFixed(2), s.notes || '-' ]); localAutoTable(doc, { head: subHead, body: subBody, startY: yPos, theme: 'grid', headStyles: { fillColor: varGet('--sca-primary-color'), textColor: varGet('--sca-light-text')}, styles: { fontSize: 7, cellPadding: 1.5, overflow: 'linebreak' }, columnStyles: { 3:{halign:'right'}, 4:{halign:'right'}, 6:{halign:'right', fontStyle:'bold'} }, didDrawPage: data => yPos = data.cursor.y }); yPos = doc.lastAutoTable.finalY + 10; // Charts const chartsToPrint = [ {canvasId: 'scaCategorySpendChart', title: 'Annual Spend by Category/Department', instanceVar: window.scaCategorySpendChartInstance}, {canvasId: 'scaBillingCycleChart', title: 'Annual Spend by Billing Cycle', instanceVar: window.scaBillingCycleChartInstance} ]; chartsToPrint.forEach(chartInfo => { const chartCanvas = document.getElementById(chartInfo.canvasId); const chartInstance = chartInfo.instanceVar; if (chartCanvas && chartInstance && chartInstance.data.datasets[0].data.length > 0 && chartInstance.data.datasets[0].data.some(d => d > 0)) { if (yPos > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); yPos = 20;} doc.setFontSize(12); doc.setTextColor(varGet('--sca-primary-color')); doc.text(chartInfo.title, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 7; try { const chartImage = chartCanvas.toDataURL('image/png', 1.0); const imgProps = doc.getImageProperties(chartImage); const pdfChartWidth = doc.internal.pageSize.getWidth() * 0.6; const chartHeight = (imgProps.height * pdfChartWidth) / imgProps.width; const xOffset = (doc.internal.pageSize.getWidth() - pdfChartWidth) / 2; if (yPos + chartHeight > doc.internal.pageSize.getHeight() -10 ) { doc.addPage(); yPos = 20; doc.text(chartInfo.title + " (cont.)", doc.internal.pageSize.getWidth() / 2, yPos, {align: 'center'}); yPos += 7; } doc.addImage(chartImage, 'PNG', xOffset, yPos, pdfChartWidth, chartHeight); yPos += chartHeight + 5; } catch(e) { console.error(`Error adding chart ${chartInfo.canvasId} to PDF:`, e); } } }); doc.save(`${analysisName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_saas_analysis.pdf`); } // --- Initialization --- scaRenderCategories(); scaRenderSubscriptionsTable(); scaShowTab(0); // This will call scaUpdateAnalysisReport if tab 2/3 shown via tab click scaUpdateAnalysisReport(); // For initial load of summary tab content if needed });
Scroll to Top