Portfolio Project Tracker Generator

Portfolio Project Tracker

Current Projects

Project Summary

Full Project Report

Add New Project

No projects yet. Click 'Add New Project' to get started!

"; return; } projects.forEach(p => { const card = document.createElement("div"); card.className = "pptg-project-card"; card.innerHTML = `

${p.name}

Client: ${p.client || 'N/A'}

Status: ${p.status}

Due: ${p.dueDate || 'N/A'}

Budget: ${p.budget ? '$' + p.budget.toLocaleString() : 'N/A'}

`; projectList.appendChild(card); }); } /** * Renders the summary stats and report table. */ function pptg_renderReport() { const statsContainer = document.getElementById('pptg-summary-stats'); const tableContainer = document.getElementById('pptg-report-table'); if (!statsContainer || !tableContainer) return; // Guard clause const totalProjects = projects.length; const totalBudget = projects.reduce((sum, p) => sum + (p.budget || 0), 0); const completed = projects.filter(p => p.status === 'Completed').length; const inProgress = projects.filter(p => p.status === 'In Progress').length; statsContainer.innerHTML = `
Total Projects
${totalProjects}
Completed
${completed}
In Progress
${inProgress}
Total Earnings
$${totalBudget.toLocaleString()}
`; let tableHTML = `Project NameClientStatusDue DateBudget`; projects.forEach(p => { tableHTML += ` ${p.name} ${p.client || 'N/A'} ${p.status} ${p.dueDate || 'N/A'} ${p.budget ? '$' + p.budget.toLocaleString() : 'N/A'} `; }); tableHTML += ``; tableContainer.innerHTML = tableHTML; } /** * Handles form submission for adding or updating a project. * @param {Event} e The form submission event. */ function pptg_handleFormSubmit(e) { e.preventDefault(); const id = document.getElementById("pptg-project-id").value; const projectData = { name: document.getElementById("pptg-project-name").value, client: document.getElementById("pptg-client-name").value, status: document.getElementById("pptg-status").value, dueDate: document.getElementById("pptg-due-date").value, budget: parseFloat(document.getElementById("pptg-budget").value) || 0, notes: document.getElementById("pptg-notes").value, }; if (id) { // Update existing const index = projects.findIndex(p => p.id == id); if (index > -1) { projects[index] = { ...projects[index], ...projectData }; } } else { // Add new projectData.id = projects.length > 0 ? Math.max(...projects.map(p => p.id)) + 1 : 1; projects.push(projectData); } pptg_renderDashboard(); pptg_renderReport(); projectForm.reset(); formTitle.textContent = "Add New Project"; cancelBtn.style.display = "none"; document.querySelector('.pptg-tab-link[onclick*="pptg-tab-1"]').click(); // Go back to dashboard } /** * Handles clicks on Edit or Delete buttons on project cards. * @param {Event} e The click event. */ function pptg_handleCardActions(e) { if (!e.target.dataset.id) return; const id = parseInt(e.target.dataset.id, 10); const action = e.target.dataset.action; if (action === "edit") { const project = projects.find(p => p.id === id); if (!project) return; formTitle.textContent = "Edit Project"; document.getElementById("pptg-project-id").value = project.id; document.getElementById("pptg-project-name").value = project.name; document.getElementById("pptg-client-name").value = project.client; document.getElementById("pptg-status").value = project.status; document.getElementById("pptg-due-date").value = project.dueDate; document.getElementById("pptg-budget").value = project.budget; document.getElementById("pptg-notes").value = project.notes; cancelBtn.style.display = "inline-block"; document.querySelector('.pptg-tab-link[onclick*="pptg-tab-3"]').click(); // Switch to form tab } if (action === "delete") { projects = projects.filter(p => p.id !== id); pptg_renderDashboard(); pptg_renderReport(); } } /** * Resets the form to its "Add New" state. */ function pptg_resetForm() { projectForm.reset(); formTitle.textContent = "Add New Project"; document.getElementById("pptg-project-id").value = ""; cancelBtn.style.display = "none"; } /** * Generates and triggers a download for a PDF of the report. (Spec II.C) */ function pptg_downloadPdf() { if (typeof jspdf === 'undefined' || typeof jspdf.plugin.autotable === 'undefined') { alert('Error: PDF library is missing. Please refresh.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF(); const head = [['Project Name', 'Client', 'Status', 'Due Date', 'Budget ($)']]; const body = projects.map(p => [p.name, p.client, p.status, p.dueDate, p.budget.toLocaleString()]); doc.setFontSize(18); doc.text("Portfolio Project Report", 14, 22); doc.autoTable({ startY: 30, head: head, body: body, theme: 'striped', headStyles: { fillColor: '#3498db' } }); doc.save("Project-Report.pdf"); } // --- Event Listener Attachment --- projectForm.addEventListener("submit", pptg_handleFormSubmit); projectList.addEventListener("click", pptg_handleCardActions); cancelBtn.addEventListener("click", () => { pptg_resetForm(); document.querySelector('.pptg-tab-link[onclick*="pptg-tab-1"]').click(); }); addNewBtn.addEventListener("click", () => { pptg_resetForm(); document.querySelector('.pptg-tab-link[onclick*="pptg-tab-3"]').click(); }); document.getElementById("pptg-download-pdf").addEventListener("click", pptg_downloadPdf); // Initial Render pptg_renderDashboard(); pptg_renderReport(); }); // --- Global Functions for inline onclick (Spec IV.C) --- function pptg_openTab(evt, tabName) { const container = document.getElementById("pptg-container"); if (!container) return; const tabcontent = container.getElementsByClassName("pptg-tab-content"); for (let i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = "none"; tabcontent[i].classList.remove("pptg-active"); } const tablinks = container.getElementsByClassName("pptg-tab-link"); for (let i = 0; i < tablinks.length; i++) { tablinks[i].classList.remove("pptg-active"); } const activeTab = document.getElementById(tabName); if(activeTab) activeTab.style.display = "block"; if(activeTab) activeTab.classList.add("pptg-active"); if(evt.currentTarget) evt.currentTarget.classList.add("pptg-active"); } function pptg_navTab(direction) { const container = document.getElementById("pptg-container"); if (!container) return; const tabs = container.querySelectorAll(".pptg-tab-link"); let currentIndex = -1; for (let i = 0; i < tabs.length; i++) { if (tabs[i].classList.contains("pptg-active")) { currentIndex = i; break; } } if (currentIndex === -1) return; let newIndex = currentIndex + direction; if (newIndex >= 0 && newIndex < tabs.length) { tabs[newIndex].click(); } }
Scroll to Top