Training & Certification Cost Estimator
Plan Setup
Manage Activity Types
Define the types of training or certification activities you will be budgeting for.
Current Activity Types:
Add Training & Certification Items
New Item Details
Estimated Costs for this Item ($)
Estimated Cost Items
| Activity Name | Type | Provider | Tuition ($) | Exam ($) | Materials ($) | Travel ($) | Software ($) | Other ($) | Total ($) | Actions |
|---|
Cost Estimation Summary
Plan Name:
Estimator:
Total Estimated Cost for All Items: $0.00
Estimated Costs by Activity Type
| Activity Type | Total Estimated Cost ($) | % of Total |
|---|
Cost Distribution by Activity Type
Chart library not loaded or canvas not found.
"; return; } const ctx = chartCanvas.getContext('2d'); const chartColors = ['#17a2b8', '#007bff', '#28a745', '#ffc107', '#6f42c1', '#fd7e14', '#6c757d', '#dc3545']; if (tcceActivityTypeChartInstance) { tcceActivityTypeChartInstance.destroy(); } if (labels.length > 0 && data.some(d => d > 0)) { tcceActivityTypeChartInstance = new Chart(ctx, { type: 'doughnut', data: { labels: labels, datasets: [{ label: 'Cost by Activity Type', data: data, backgroundColor: labels.map((_, i) => chartColors[i % chartColors.length]), borderColor: varGet('--tcce-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('--tcce-secondary-color'); ctx.font = "16px Arial"; ctx.fillText("No cost data by activity type to display.", ctx.canvas.width/2, ctx.canvas.height/2); } } function varGet(varName) { return getComputedStyle(document.documentElement).getPropertyValue(varName).trim(); } // --- PDF Download --- window.tcceDownloadPDF = 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(); tcceUpdateCostSummary(); // Ensure data is current const planName = tccePlanDetails.planName; const estimatorName = tccePlanDetails.estimatorName; 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('--tcce-primary-color')); doc.text(planName, doc.internal.pageSize.getWidth() / 2, 15, { align: 'center' }); doc.setFontSize(11); doc.setTextColor(varGet('--tcce-dark-text')); if(estimatorName) doc.text(`Estimator: ${estimatorName}`, 14, 25); doc.text(`Report Generated: ${generationDate}`, doc.internal.pageSize.getWidth() - 14, 25, { align: 'right' }); let yPos = 35; const grandTotalCost = tcceCostItems.reduce((sum, item) => sum + item.totalItemCost, 0); doc.setFontSize(12); doc.setTextColor(varGet('--tcce-primary-color')); doc.text("Overall Estimated Cost", 14, yPos); yPos += 7; doc.setFontSize(10); doc.setTextColor(varGet('--tcce-dark-text')); doc.text(`Total Estimated Cost for All Items: $${grandTotalCost.toFixed(2)}`, 14, yPos); yPos += 10; doc.setFontSize(12); doc.setTextColor(varGet('--tcce-primary-color')); doc.text("Itemized Cost Estimates", 14, yPos); yPos += 7; const itemHead = [['Activity', 'Type', 'Provider', 'Tuition ($)', 'Exam ($)', 'Materials ($)', 'Travel ($)', 'Software ($)', 'Other ($)', 'Total ($)']]; const itemBody = tcceCostItems.map(item => [ item.name, item.type, item.provider || '-', item.costs.tuition.toFixed(2), item.costs.examFee.toFixed(2), item.costs.materials.toFixed(2), item.costs.travel.toFixed(2), item.costs.software.toFixed(2), item.costs.other.toFixed(2), item.totalItemCost.toFixed(2) ]); localAutoTable(doc, { head: itemHead, body: itemBody, startY: yPos, theme: 'grid', headStyles: { fillColor: varGet('--tcce-primary-color'), textColor: varGet('--tcce-light-text')}, styles: { fontSize: 7, cellPadding: 1, overflow: 'linebreak' }, columnStyles: { 3: {halign:'right'}, 4:{halign:'right'}, 5:{halign:'right'}, 6:{halign:'right'}, 7:{halign:'right'}, 8:{halign:'right'}, 9:{halign:'right', fontStyle:'bold'} }, didDrawPage: data => yPos = data.cursor.y }); yPos = doc.lastAutoTable.finalY + 10; // Chart const chartCanvas = document.getElementById('tcceActivityTypeChart'); if (chartCanvas && tcceActivityTypeChartInstance && tcceActivityTypeChartInstance.data.datasets[0].data.length > 0) { if (yPos > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); yPos = 20;} doc.setFontSize(12); doc.setTextColor(varGet('--tcce-primary-color')); doc.text("Cost Distribution by Activity Type", 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("Cost Distribution by Activity Type (cont.)", doc.internal.pageSize.getWidth() / 2, yPos, {align: 'center'}); yPos += 7; } doc.addImage(chartImage, 'PNG', xOffset, yPos, pdfChartWidth, chartHeight); } catch(e) { console.error("Error adding chart to PDF:", e); } } doc.save(`${planName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_cost_estimate.pdf`); } // --- Initialization --- tcceRenderActivityTypes(); tcceRenderCostItemsTable(); tcceUpdateCostSummary(); tcceShowTab(0); });