Donation & Fundraising Campaign Performance Dashboard

Donation & Fundraising Campaign Performance Dashboard

Total Donations Received

$0.00

Number of Campaigns

0

Average Donation Per Campaign

$0.00

Total Donors

0

Campaign Progress (Raised vs. Goal)

Campaign Overview

Campaign Name Goal ($) Raised ($) Donors Start Date End Date Status % to Goal

No campaign data to visualize.

`; return; } // Find the maximum goal to normalize bar widths const maxGoal = Math.max(...campaigns.map(c => c.goal)); campaigns.forEach(campaign => { const percentFilled = campaign.goal > 0 ? (campaign.raised / campaign.goal) * 100 : 0; const barItem = document.createElement('div'); barItem.classList.add('bar-item'); barItem.innerHTML = ` ${campaign.name}:
${percentFilled.toFixed(1)}%
`; campaignProgressChartEl.appendChild(barItem); }); } /** * Renders the campaign data into the data configuration table with edit/delete actions. */ function renderManageCampaignsTable() { if (!manageCampaignsTableBody) { console.error('Manage campaigns table body not found.'); return; } manageCampaignsTableBody.innerHTML = ''; // Clear existing rows if (campaigns.length === 0) { manageCampaignsTableBody.innerHTML = `No campaigns to manage.`; return; } campaigns.forEach(campaign => { const row = manageCampaignsTableBody.insertRow(); row.setAttribute('data-campaign-id', campaign.id); // Set data attribute for easy lookup row.classList.add('hover:bg-gray-50'); row.innerHTML = ` `; }); } /** * Adds a new campaign from the input fields. */ function addCampaign() { if (!newCampaignNameInput || !newCampaignGoalInput || !newCampaignRaisedInput || !newCampaignDonorsInput || !newStartDateInput || !newEndDateInput || !newCampaignStatusSelect) { console.error('New campaign input elements not found.'); return; } const name = newCampaignNameInput.value.trim(); const goal = parseFloat(newCampaignGoalInput.value); const raised = parseFloat(newCampaignRaisedInput.value); const donors = parseInt(newCampaignDonorsInput.value); const startDate = newStartDateInput.value; const endDate = newEndDateInput.value; const status = newCampaignStatusSelect.value; if (!name || isNaN(goal) || goal < 0 || isNaN(raised) || raised < 0 || isNaN(donors) || donors < 0 || !startDate || !endDate || !status) { alert('Please fill in all fields correctly: Campaign Name, non-negative Goal, non-negative Raised, non-negative Donors, Start Date, End Date, and Status.'); return; } if (raised > goal) { alert('Raised amount cannot be greater than the Goal amount.'); return; } if (new Date(startDate) > new Date(endDate)) { alert('Start Date cannot be after End Date.'); return; } campaigns.push({ id: generateUniqueId(), name: name, goal: goal, raised: raised, donors: donors, startDate: startDate, endDate: endDate, status: status }); // Clear input fields newCampaignNameInput.value = ''; newCampaignGoalInput.value = ''; newCampaignRaisedInput.value = ''; newCampaignDonorsInput.value = ''; newStartDateInput.value = ''; newEndDateInput.value = ''; newCampaignStatusSelect.value = 'Planned'; // Reset to default // Re-render tables and update metrics updateAllViews(); } /** * Updates an existing campaign based on changes in the manage table. * This function is exposed globally for `onclick` attributes. * @param {string} campaignId - The ID of the campaign to update. * @param {HTMLElement} buttonElement - The button element that triggered the update. */ window.updateCampaign = function(campaignId, buttonElement) { const row = buttonElement.closest('tr'); if (!row) { console.error('Could not find row for campaignId:', campaignId); return; } const nameInput = row.querySelector('[data-field="name"]'); const goalInput = row.querySelector('[data-field="goal"]'); const raisedInput = row.querySelector('[data-field="raised"]'); const donorsInput = row.querySelector('[data-field="donors"]'); const startDateInput = row.querySelector('[data-field="startDate"]'); const endDateInput = row.querySelector('[data-field="endDate"]'); const statusSelect = row.querySelector('[data-field="status"]'); if (!nameInput || !goalInput || !raisedInput || !donorsInput || !startDateInput || !endDateInput || !statusSelect) { console.error('Input fields not found in row for campaignId:', campaignId); return; } const updatedName = nameInput.value.trim(); const updatedGoal = parseFloat(goalInput.value); const updatedRaised = parseFloat(raisedInput.value); const updatedDonors = parseInt(donorsInput.value); const updatedStartDate = startDateInput.value; const updatedEndDate = endDateInput.value; const updatedStatus = statusSelect.value; if (!updatedName || isNaN(updatedGoal) || updatedGoal < 0 || isNaN(updatedRaised) || updatedRaised < 0 || isNaN(updatedDonors) || updatedDonors < 0 || !updatedStartDate || !updatedEndDate || !updatedStatus) { alert('Please ensure all fields are valid before saving.'); return; } if (updatedRaised > updatedGoal) { alert('Raised amount cannot be greater than the Goal amount.'); return; } if (new Date(updatedStartDate) > new Date(updatedEndDate)) { alert('Start Date cannot be after End Date.'); return; } const campaignIndex = campaigns.findIndex(c => c.id === campaignId); if (campaignIndex !== -1) { campaigns[campaignIndex] = { ...campaigns[campaignIndex], name: updatedName, goal: updatedGoal, raised: updatedRaised, donors: updatedDonors, startDate: updatedStartDate, endDate: updatedEndDate, status: updatedStatus }; updateAllViews(); } else { console.warn('Campaign not found for update:', campaignId); } }; /** * Deletes a campaign from the list. * This function is exposed globally for `onclick` attributes. * @param {string} campaignId - The ID of the campaign to delete. */ window.deleteCampaign = function(campaignId) { const initialLength = campaigns.length; campaigns = campaigns.filter(campaign => campaign.id !== campaignId); if (campaigns.length < initialLength) { updateAllViews(); } else { console.warn('Campaign not found for deletion:', campaignId); } }; /** * Updates all relevant views (metrics, dashboard table, manage table, bar chart). */ function updateAllViews() { updateDashboardMetrics(); renderCampaignsTable(); renderCampaignProgressChart(); renderManageCampaignsTable(); } /** * Generates and downloads a PDF of the dashboard content. */ function downloadDashboardPdf() { // Ensure jsPDF is available if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { console.error("jsPDF library not loaded. Cannot generate PDF."); alert("PDF generation library not loaded. Please try again later."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('landscape'); // Use landscape for wider tables // Set font and color for consistency doc.setFont('helvetica'); doc.setTextColor('#1f2937'); // Dark gray // Title doc.setFontSize(24); doc.text('Donation & Fundraising Campaign Performance Dashboard', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' }); // Add a small separator doc.setDrawColor('#d1d5db'); doc.line(20, 25, doc.internal.pageSize.getWidth() - 20, 25); // Dashboard Metrics doc.setFontSize(14); let yPos = 40; const metrics = [ { label: 'Total Donations Received:', value: totalDonationsReceivedEl ? totalDonationsReceivedEl.textContent : 'N/A' }, { label: 'Number of Campaigns:', value: numCampaignsEl ? numCampaignsEl.textContent : 'N/A' }, { label: 'Average Donation Per Campaign:', value: avgDonationPerCampaignEl ? avgDonationPerCampaignEl.textContent : 'N/A' }, { label: 'Total Donors:', value: totalDonorsEl ? totalDonorsEl.textContent : 'N/A' } ]; metrics.forEach(metric => { doc.text(`${metric.label} ${metric.value}`, 20, yPos); yPos += 10; }); yPos += 15; // Extra space before table // Campaign Progress (as a table in PDF) doc.setFontSize(16); doc.text('Campaign Progress (Raised vs. Goal)', 20, yPos); yPos += 10; const progressTableColumn = ["Campaign Name", "Goal ($)", "Raised ($)", "% to Goal"]; const progressTableRows = []; campaigns.forEach(campaign => { const percentToGoal = campaign.goal > 0 ? ((campaign.raised / campaign.goal) * 100).toFixed(2) : 0; progressTableRows.push([campaign.name, formatCurrency(campaign.goal), formatCurrency(campaign.raised), `${percentToGoal}%`]); }); doc.autoTable({ head: [progressTableColumn], body: progressTableRows, startY: yPos, theme: 'grid', styles: { fontSize: 10, cellPadding: 3, textColor: '#1f2937', lineColor: '#e5e7eb', lineWidth: 0.1 }, headStyles: { fillColor: '#f3f4f6', textColor: '#4b5563', fontStyle: 'bold' }, alternateRowStyles: { fillColor: '#ffffff' }, margin: { left: 20, right: 20 }, didDrawPage: function(data) { let str = "Page " + doc.internal.getNumberOfPages(); doc.setFontSize(10); doc.setTextColor('#6b7280'); doc.text(str, doc.internal.pageSize.getWidth() - 20, doc.internal.pageSize.getHeight() - 10, { align: 'right' }); } }); yPos = doc.autoTable.previous.finalY + 15; // Continue from where the last table ended // Campaign Overview Table doc.setFontSize(16); doc.text('Campaign Overview', 20, yPos); yPos += 10; const overviewTableColumn = ["Campaign Name", "Goal ($)", "Raised ($)", "Donors", "Start Date", "End Date", "Status", "% to Goal"]; const overviewTableRows = []; campaigns.forEach(campaign => { const percentToGoal = campaign.goal > 0 ? ((campaign.raised / campaign.goal) * 100).toFixed(2) : 0; overviewTableRows.push([ campaign.name, formatCurrency(campaign.goal), formatCurrency(campaign.raised), formatNumber(campaign.donors), campaign.startDate, campaign.endDate, campaign.status, `${percentToGoal}%` ]); }); doc.autoTable({ head: [overviewTableColumn], body: overviewTableRows, startY: yPos, theme: 'grid', styles: { fontSize: 6, // Further reduced font size for more columns in landscape cellPadding: 1, textColor: '#1f2937', lineColor: '#e5e7eb', lineWidth: 0.1 }, headStyles: { fillColor: '#f3f4f6', textColor: '#4b5563', fontStyle: 'bold' }, alternateRowStyles: { fillColor: '#ffffff' }, margin: { left: 10, right: 10 }, // Adjusted margins to fit more content didDrawPage: function(data) { let str = "Page " + doc.internal.getNumberOfPages(); doc.setFontSize(10); doc.setTextColor('#6b7280'); doc.text(str, doc.internal.pageSize.getWidth() - 20, doc.internal.pageSize.getHeight() - 10, { align: 'right' }); } }); // Save the PDF doc.save('donation_fundraising_dashboard.pdf'); } // --- Event Listeners --- // Tab button clicks if (dashboardTabButton) { dashboardTabButton.addEventListener('click', function() { switchTab(0); }); } if (dataConfigTabButton) { dataConfigTabButton.addEventListener('click', function() { switchTab(1); }); } // Navigation button clicks if (prevTabButton) { prevTabButton.addEventListener('click', function() { if (currentTabIndex > 0) { switchTab(currentTabIndex - 1); } }); } if (nextTabButton) { nextTabButton.addEventListener('click', function() { if (currentTabIndex < tabs.length - 1) { switchTab(currentTabIndex + 1); } }); } // Add Campaign button click if (addCampaignButton) { addCampaignButton.addEventListener('click', addCampaign); } // PDF Download button click if (downloadPdfButton) { downloadPdfButton.addEventListener('click', downloadDashboardPdf); } // --- Initial Render --- // Set initial tab to Dashboard switchTab(0); // Populate data and update views updateAllViews(); });
Scroll to Top