Professional Development Budget Planner
Plan Setup
General Plan Information
Manage Activity Categories
Current Categories:
Manage Team Members (Optional - for team plans)
Team Members:
Plan Development Activities
Add New Activity
Planned Activities
| Activity Name | Category | Assigned To | Provider | Target Date | Priority | Budgeted Cost ($) | Actions |
|---|
Budget Summary & Report
Plan Name:
Budget Period:
Total Budget: $0.00
Total Budgeted for Activities: $0.00
Remaining Budget: $0.00
Budget by Category
| Category | Total Budgeted ($) | % of Total Activities Budget |
|---|
Budget by Team Member
| Team Member | Total Budgeted ($) | % of Total Activities Budget |
|---|
Budget Allocation by Category
Budget Allocation by Team Member
Chart library not loaded or canvas not found.
"; return null; // Return null if chart cannot be rendered } const ctx = chartCanvas.getContext('2d'); const chartColors = ['#6f42c1', '#007bff', '#28a745', '#ffc107', '#17a2b8', '#fd7e14', '#6c757d', '#dc3545']; if (chartInstance) chartInstance.destroy(); if (labels.length > 0 && data.some(d => d > 0)) { return new Chart(ctx, { // Return the new chart instance type: 'pie', data: { labels: labels, datasets: [{ label: chartLabel, data: data, backgroundColor: labels.map((_, i) => chartColors[i % chartColors.length]), borderColor: varGet('--pdbp-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('--pdbp-secondary-color'); ctx.font = "16px Arial"; ctx.fillText(`No data for "${chartLabel}" chart.`, ctx.canvas.width/2, ctx.canvas.height/2); return null; // Return null if no data } } function varGet(varName) { return getComputedStyle(document.documentElement).getPropertyValue(varName).trim(); } // --- PDF Download --- window.pdbpDownloadPDF = function() { if (typeof jsPDF === 'undefined' || typeof autoTable === 'undefined') { alert("PDF libraries not loaded."); return; } const { jsPDF: JSPDF } = window.jspdf; // Destructure with a different name const localAutoTable = window.jspdf.autoTable; // Access autoTable via window.jspdf const doc = new JSPDF(); pdbpUpdateBudgetSummary(); // Ensure data is current const planName = pdbpPlanSettings.planName; let periodString = pdbpPlanSettings.periodType; if (pdbpPlanSettings.periodType === 'Annually') periodString = `Annually (${pdbpPlanSettings.year})`; else if (pdbpPlanSettings.periodType === 'Quarterly') periodString = `${pdbpPlanSettings.quarter}, ${pdbpPlanSettings.year}`; else if (pdbpPlanSettings.periodType === 'Custom' && pdbpPlanSettings.periodStartDate && pdbpPlanSettings.periodEndDate) { periodString = `${pdbpPlanSettings.periodStartDate} to ${pdbpPlanSettings.periodEndDate}`; } 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('--pdbp-primary-color')); doc.text(planName, doc.internal.pageSize.getWidth() / 2, 15, { align: 'center' }); doc.setFontSize(11); doc.setTextColor(varGet('--pdbp-dark-text')); doc.text(`Budget Period: ${periodString}`, 14, 25); doc.text(`Report Generated: ${generationDate}`, doc.internal.pageSize.getWidth() - 14, 25, { align: 'right' }); let yPos = 35; doc.setFontSize(12); doc.setTextColor(varGet('--pdbp-primary-color')); doc.text("Budget Overview", 14, yPos); yPos += 7; doc.setFontSize(10); doc.setTextColor(varGet('--pdbp-dark-text')); doc.text(`Total Budget: $${pdbpPlanSettings.totalBudget.toFixed(2)}`, 14, yPos); yPos += 5; const totalActivitiesCost = pdbpDevelopmentActivities.reduce((sum, act) => sum + act.budgetedCost, 0); doc.text(`Total Budgeted for Activities: $${totalActivitiesCost.toFixed(2)}`, 14, yPos); yPos += 5; doc.text(`Remaining Budget: $${(pdbpPlanSettings.totalBudget - totalActivitiesCost).toFixed(2)}`, 14, yPos); yPos += 10; doc.setFontSize(12); doc.setTextColor(varGet('--pdbp-primary-color')); doc.text("Planned Activities", 14, yPos); yPos += 7; const activityHead = [['Activity', 'Category', ...(pdbpTeamMembers.length > 0 ? ['Assigned To'] : []), 'Provider', 'Target Date', 'Priority', 'Cost ($)']]; const activityBody = pdbpDevelopmentActivities.map(act => { let row = [act.name, act.category]; if (pdbpTeamMembers.length > 0) row.push(act.assignedTo || '-'); row.push(act.provider || '-', act.targetDate || '-', act.priority || '-', act.budgetedCost.toFixed(2)); return row; }); localAutoTable(doc, { head: activityHead, body: activityBody, startY: yPos, theme: 'grid', headStyles: { fillColor: varGet('--pdbp-primary-color'), textColor: varGet('--pdbp-light-text')}, styles: { fontSize: 8, cellPadding: 1.5, overflow: 'linebreak' }, columnStyles: { [activityHead[0].length-1]: {halign: 'right'} }, // last column (cost) right aligned didDrawPage: data => yPos = data.cursor.y }); yPos = doc.lastAutoTable.finalY + 10; // Add charts if visible and have data const chartsToPrint = []; if(document.getElementById('pdbpCategoryChart').offsetParent !== null) chartsToPrint.push({canvasId: 'pdbpCategoryChart', title: 'Budget by Category'}); if(pdbpTeamMembers.length > 0 && document.getElementById('pdbpTeamMemberChart').offsetParent !== null) chartsToPrint.push({canvasId: 'pdbpTeamMemberChart', title: 'Budget by Team Member'}); chartsToPrint.forEach(chartInfo => { const chartCanvas = document.getElementById(chartInfo.canvasId); // Check if chart has actual data by looking at its Chart.js instance dataset let chartInstance = (chartInfo.canvasId === 'pdbpCategoryChart') ? pdbpCategoryChartInstance : pdbpTeamMemberChartInstance; if (chartCanvas && chartInstance && chartInstance.data && chartInstance.data.datasets[0] && 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('--pdbp-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.7; 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; // Add some padding after the chart } catch(e) { console.error(`Error adding chart ${chartInfo.canvasId} to PDF:`, e); } } }); doc.save(`${planName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_dev_plan.pdf`); } // --- Initialization --- const today = new Date(); const todayISO = today.toISOString().split('T')[0]; document.getElementById('pdbpPeriodStartDate').value = todayISO; const endDate = new Date(today); endDate.setDate(today.getDate() + 30); // Default custom end date to 30 days from today document.getElementById('pdbpPeriodEndDate').value = endDate.toISOString().split('T')[0]; pdbpRenderCategories(); pdbpRenderTeamMembers(); // Also correctly sets display of "Assigned To" fields pdbpRenderActivitiesTable(); pdbpShowTab(0); // This will also call pdbpUpdateBudgetSummary indirectly if tab 2/3 active pdbpUpdateBudgetSummary(); // Initial call to populate summary fields });