Membership Site Statistics

Membership Site Statistics

Membership Overview

All Members

Member ID Member Name Join Date Status Membership Type Annual Revenue ($)

Configure Your Membership Data

Manually input or edit your membership data below. All changes will automatically update the dashboard.

Member ID Member Name Join Date Status Membership Type Annual Revenue ($) Actions

${stats.activeMembers.toLocaleString()}

New Members (Last 30 Days)

${stats.newMembersThisMonth.toLocaleString()}

Churn Rate

${stats.churnRate.toFixed(2)}%

Avg. Membership Duration

${stats.averageMembershipDurationYears.toFixed(1)} Years

Total Annual Revenue

${formatCurrency(stats.totalAnnualRevenue)}

`; } /** * Renders the all members table in the Dashboard tab. */ function renderAllMembersTable() { // Null check for the all members table body if (!allMembersTableBody) { console.error("All members table body not found."); return; } allMembersTableBody.innerHTML = ''; // Clear existing rows membershipData.forEach(member => { const row = document.createElement('tr'); row.innerHTML = ` ${member.id} ${member.memberName} ${member.joinDate} ${member.status} ${member.membershipType} ${formatCurrency(member.annualRevenue)} `; allMembersTableBody.appendChild(row); }); } /** * Renders the data input table in the Data Configuration tab. */ function renderDataInputTable() { // Null check for the data input table body if (!dataInputTableBody) { console.error("Data input table body not found."); return; } dataInputTableBody.innerHTML = ''; // Clear existing rows membershipData.forEach(member => { const row = document.createElement('tr'); row.innerHTML = ` ${member.id} `; dataInputTableBody.appendChild(row); }); // Attach event listeners to newly created input fields and delete buttons document.querySelectorAll('.member-input').forEach(input => { input.addEventListener('change', handleInputChange); }); document.querySelectorAll('.delete-row-btn').forEach(button => { button.addEventListener('click', handleDeleteRow); }); } /** * Updates all dashboard and data configuration views. */ function updateViews() { renderMembershipSummary(); renderAllMembersTable(); renderDataInputTable(); } // --- Event Handlers --- /** * Handles tab button clicks to switch between tabs. * @param {Event} event - The click event. */ function handleTabClick(event) { // Null check for event target if (!event.target) { console.error("Event target is null."); return; } const targetTab = event.target.dataset.tab; // Remove 'active' class from all tab buttons and content tabButtons.forEach(button => button.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); // Add 'active' class to the clicked tab button and corresponding content event.target.classList.add('active'); const activeTabContent = document.getElementById(`${targetTab}-tab`); if (activeTabContent) { // Null check for activeTabContent activeTabContent.classList.add('active'); } else { console.error(`Tab content for ${targetTab} not found.`); } updateViews(); // Update views whenever tab changes } /** * Handles changes in data input fields. * @param {Event} event - The change event. */ function handleInputChange(event) { // Null check for event target if (!event.target) { console.error("Event target is null."); return; } const input = event.target; const id = parseInt(input.dataset.id); const field = input.dataset.field; let value = input.value; // Convert numeric fields to numbers if (['annualRevenue'].includes(field)) { value = parseFloat(value) || 0; // Default to 0 if parsing fails if (value < 0) value = 0; // Ensure non-negative values input.value = value; // Update input field with sanitized value } const memberIndex = membershipData.findIndex(d => d.id === id); if (memberIndex !== -1) { membershipData[memberIndex][field] = value; updateViews(); // Re-render dashboard and data input tables } else { console.error(`Member with ID ${id} not found.`); } } /** * Adds a new empty row to the data input table. */ function handleAddMember() { const newMember = { id: nextId++, memberName: 'New Member', joinDate: new Date().toISOString().slice(0, 10), // Default to today's date status: 'Active', membershipType: 'Basic', annualRevenue: 0 }; membershipData.push(newMember); updateViews(); // Re-render data input table } /** * Deletes a row from the data input table. * @param {Event} event - The click event. */ function handleDeleteRow(event) { // Null check for event target if (!event.target) { console.error("Event target is null."); return; } const idToDelete = parseInt(event.target.dataset.id); membershipData = membershipData.filter(d => d.id !== idToDelete); updateViews(); // Re-render data input table } /** * Handles navigation to the previous tab. */ function handlePrevTab() { const currentActiveTab = document.querySelector('.tab-button.active'); if (!currentActiveTab) { console.error("No active tab found."); return; } const currentIndex = Array.from(tabButtons).indexOf(currentActiveTab); if (currentIndex > 0) { tabButtons[currentIndex - 1].click(); // Simulate click on previous tab } } /** * Handles navigation to the next tab. */ function handleNextTab() { const currentActiveTab = document.querySelector('.tab-button.active'); if (!currentActiveTab) { console.error("No active tab found."); return; } const currentIndex = Array.from(tabButtons).indexOf(currentActiveTab); if (currentIndex < tabButtons.length - 1) { tabButtons[currentIndex + 1].click(); // Simulate click on next tab } } /** * Handles the PDF download functionality. * It uses html2canvas to capture the dashboard content and jsPDF to generate the PDF. */ async function handleDownloadPdf() { const dashboardContent = document.querySelector('.dashboard-content-for-pdf'); if (!dashboardContent) { console.error("Dashboard content for PDF not found."); return; } // Add a temporary title for the PDF const tempTitle = document.createElement('h1'); tempTitle.textContent = "Membership Site Statistics"; tempTitle.style.textAlign = 'center'; tempTitle.style.marginBottom = '20px'; tempTitle.style.fontSize = '2em'; tempTitle.style.fontWeight = 'bold'; tempTitle.style.color = '#333'; dashboardContent.prepend(tempTitle); // Prepend to capture // Temporarily hide tab navigation and buttons for PDF capture const elementsToHide = document.querySelectorAll('.tab-nav, .tab-navigation-buttons, .add-row-btn, .delete-row-btn, .pdf-download-section .btn-primary'); elementsToHide.forEach(el => el.style.display = 'none'); // Ensure the dashboard tab is active before capturing for PDF const dashboardTabButton = document.querySelector('.tab-button[data-tab="dashboard"]'); if (dashboardTabButton && !dashboardTabButton.classList.contains('active')) { dashboardTabButton.click(); // Activate dashboard tab if not already active // A small delay might be needed here if content takes time to render after tab switch await new Promise(resolve => setTimeout(resolve, 100)); } try { const canvas = await html2canvas(dashboardContent, { scale: 2, // Increase scale for better resolution useCORS: true, // Required if images are from different origin (though we don't use external images) windowWidth: dashboardContent.scrollWidth, windowHeight: dashboardContent.scrollHeight }); const imgData = canvas.toDataURL('image/png'); const { jsPDF } = window.jspdf; const pdf = new jsPDF({ orientation: 'portrait', unit: 'px', format: 'a4' }); const imgWidth = pdf.internal.pageSize.getWidth(); const pageHeight = pdf.internal.pageSize.getHeight(); const imgHeight = canvas.height * imgWidth / canvas.width; let heightLeft = imgHeight; let position = 0; pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; while (heightLeft >= 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; } pdf.save('membership_site_statistics.pdf'); } catch (error) { console.error("Error generating PDF:", error); // Using alert as a fallback for critical error feedback, as per standard specification alert("Failed to generate PDF. Please try again."); } finally { // Restore hidden elements and remove temporary title elementsToHide.forEach(el => el.style.display = ''); if (tempTitle.parentNode) { tempTitle.parentNode.removeChild(tempTitle); } } } // --- Event Listener Attachments --- tabButtons.forEach(button => { // Null check for button if (button) { button.addEventListener('click', handleTabClick); } }); // Null checks before attaching listeners if (addMemberBtn) { addMemberBtn.addEventListener('click', handleAddMember); } else { console.error("Add Member button not found."); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', handleDownloadPdf); } else { console.error("Download PDF button not found."); } if (prevTabBtn) { prevTabBtn.addEventListener('click', handlePrevTab); } else { console.error("Previous Tab button not found."); } if (nextTabBtn) { nextTabBtn.addEventListener('click', handleNextTab); } else { console.error("Next Tab button not found."); } // Initial render of the dashboard and data input table updateViews(); });
Scroll to Top