Multiple Project Dashboard

Multiple Project Dashboard

Track budgets, timelines, and progress across all your projects.

Portfolio Overview

${completedTasks} / ${allTasks.length}

Overall Progress

${overallProgress.toFixed(1)}%

`; }; const renderProjectSelector = () => { projectSelector.innerHTML = ''; state.projects.forEach(p => { const option = document.createElement('option'); option.value = p.id; option.textContent = p.name; if (p.id === state.selectedProjectId) { option.selected = true; } projectSelector.appendChild(option); }); }; const renderProjectDetails = (projectId) => { const project = state.projects.find(p => p.id === projectId); if (!project) { projectDetailsContainer.innerHTML = '

Please select a project.

'; return; } const completedTasks = project.tasks.filter(t => t.status === 'Completed').length; const progress = project.tasks.length > 0 ? (completedTasks / project.tasks.length) * 100 : 0; let taskRows = ''; project.tasks.forEach(task => { const statusColors = { "Not Started": "bg-red-100 text-red-800", "In Progress": "bg-yellow-100 text-yellow-800", "Completed": "bg-green-100 text-green-800" }; taskRows += ` ${task.name} ${task.start} ${task.end} ${task.status} `; }); projectDetailsContainer.innerHTML = `

${project.name}

Budget

${formatCurrency(project.budget)}

Tasks

${project.tasks.length}

Progress

${progress.toFixed(1)}%

Task List

${taskRows}
Task Start Date End Date Status

Project Timeline

`; renderGanttChart(project); }; const renderGanttChart = (project) => { const container = document.getElementById(`gantt-chart-${project.id}`); if (!container || project.tasks.length === 0) return; const allDates = project.tasks.flatMap(t => [parseDate(t.start), parseDate(t.end)]); const minDate = new Date(Math.min.apply(null, allDates)); const maxDate = new Date(Math.max.apply(null, allDates)); minDate.setDate(minDate.getDate() - 2); // Add padding maxDate.setDate(maxDate.getDate() + 2); // Add padding const totalDays = dayDiff(minDate, maxDate); container.innerHTML = ''; // Clear previous chart // Render Header const header = document.createElement('div'); header.className = 'gantt-header-days'; header.style.gridTemplateColumns = `repeat(${totalDays}, minmax(0, 1fr))`; for (let i = 0; i < totalDays; i++) { const day = new Date(minDate); day.setDate(minDate.getDate() + i); const dayEl = document.createElement('div'); dayEl.className = 'text-center'; dayEl.textContent = `${day.getMonth()+1}/${day.getDate()}`; header.appendChild(dayEl); } container.appendChild(header); // Render Task Rows and Bars const chartBody = document.createElement('div'); chartBody.style.height = `${project.tasks.length * 40}px`; // 40px per task chartBody.className = 'relative'; project.tasks.forEach((task, index) => { const taskRow = document.createElement('div'); taskRow.className = 'gantt-row h-10 flex items-center pl-2 text-sm'; taskRow.textContent = task.name; container.appendChild(taskRow); const startDate = parseDate(task.start); const endDate = parseDate(task.end); const startOffset = dayDiff(minDate, startDate); const duration = dayDiff(startDate, endDate) + 1; const bar = document.createElement('div'); const statusColors = { "Completed": "bg-green-600", "In Progress": "bg-blue-600", "Not Started": "bg-gray-400" }; bar.className = `gantt-bar ${statusColors[task.status]}`; bar.style.top = `${index * 40 + 5}px`; bar.style.left = `calc(${(startOffset / totalDays) * 100}% + 2px)`; bar.style.width = `calc(${(duration / totalDays) * 100}% - 4px)`; bar.textContent = task.name; bar.title = `${task.name} (${task.status})`; chartBody.appendChild(bar); }); container.appendChild(chartBody); }; const renderConfigurator = () => { configProjectsContainer.innerHTML = ''; state.projects.forEach(p => { let taskInputs = ''; p.tasks.forEach((t, taskIndex) => { taskInputs += `
`; }); const projectBlock = `

Tasks

${taskInputs}
`; configProjectsContainer.innerHTML += projectBlock; }); addConfigEventListeners(); }; // --- EVENT HANDLERS --- const handleProjectSelect = (e) => { state.selectedProjectId = parseInt(e.target.value); renderProjectDetails(state.selectedProjectId); }; const handleConfigChange = (e) => { const projectId = parseInt(e.target.dataset.projectId); const field = e.target.dataset.field; const project = state.projects.find(p => p.id === projectId); if (!project) return; if (e.target.classList.contains('config-project-input')) { project[field] = field === 'name' ? e.target.value : parseFloat(e.target.value) || 0; } else if (e.target.classList.contains('config-task-input')) { const taskId = parseInt(e.target.dataset.taskId); const task = project.tasks.find(t => t.id === taskId); if (task) { task[field] = e.target.value; } } renderAll(); }; const handleAddProject = () => { const newId = state.projects.length > 0 ? Math.max(...state.projects.map(p => p.id)) + 1 : 1; state.projects.push({ id: newId, name: "New Project", budget: 0, tasks: [] }); renderAll(); }; const handleRemoveProject = (e) => { const projectId = parseInt(e.target.dataset.projectId); state.projects = state.projects.filter(p => p.id !== projectId); if (state.selectedProjectId === projectId && state.projects.length > 0) { state.selectedProjectId = state.projects[0].id; } else if (state.projects.length === 0) { state.selectedProjectId = null; } renderAll(); }; const handleAddTask = (e) => { const projectId = parseInt(e.target.dataset.projectId); const project = state.projects.find(p => p.id === projectId); if (project) { const newTaskId = project.tasks.length > 0 ? Math.max(...project.tasks.map(t => t.id)) + 1 : projectId * 100 + 1; const today = new Date().toISOString().split('T')[0]; project.tasks.push({ id: newTaskId, name: "New Task", start: today, end: today, status: "Not Started" }); } renderAll(); }; const handleRemoveTask = (e) => { const projectId = parseInt(e.target.dataset.projectId); const taskId = parseInt(e.target.dataset.taskId); const project = state.projects.find(p => p.id === projectId); if (project) { project.tasks = project.tasks.filter(t => t.id !== taskId); } renderAll(); }; const addConfigEventListeners = () => { document.querySelectorAll('.config-project-input, .config-task-input').forEach(input => input.addEventListener('change', handleConfigChange)); document.querySelectorAll('.remove-project-btn').forEach(btn => btn.addEventListener('click', handleRemoveProject)); document.querySelectorAll('.add-task-btn').forEach(btn => btn.addEventListener('click', handleAddTask)); document.querySelectorAll('.remove-task-btn').forEach(btn => btn.addEventListener('click', handleRemoveTask)); }; const handleDownloadPdf = () => { const { jsPDF } = window.jspdf; const pdfContent = document.getElementById('pdf-content'); // Temporarily show all project details for PDF const originalContent = projectDetailsContainer.innerHTML; projectDetailsContainer.innerHTML = ''; state.projects.forEach(p => { const projectDiv = document.createElement('div'); projectDetailsContainer.appendChild(projectDiv); renderProjectDetails(p.id); projectDiv.innerHTML = projectDetailsContainer.innerHTML; // Add a page break element for styling in PDF const pageBreak = document.createElement('div'); pageBreak.style.pageBreakAfter = 'always'; projectDetailsContainer.appendChild(pageBreak); }); // Hide non-PDF elements document.querySelectorAll('.no-print').forEach(el => el.style.display = 'none'); html2canvas(pdfContent, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }).then(canvas => { // Restore original view projectDetailsContainer.innerHTML = originalContent; renderProjectDetails(state.selectedProjectId); document.querySelectorAll('.no-print').forEach(el => el.style.display = ''); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const canvasWidth = canvas.width; const canvasHeight = canvas.height; const ratio = canvasWidth / canvasHeight; let imgWidth = pdfWidth - 20; let imgHeight = imgWidth / ratio; let heightLeft = imgHeight; let position = 10; pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight); heightLeft -= (pdfHeight - 20); while (heightLeft > 0) { position = -heightLeft - 10; pdf.addPage(); pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight); heightLeft -= (pdfHeight - 20); } pdf.save('Multiple-Project-Dashboard.pdf'); }); }; // --- TABBING LOGIC --- let currentTabIndex = 0; const updateTabButtons = () => { prevTabBtn.disabled = currentTabIndex === 0; nextTabBtn.disabled = currentTabIndex === tabContents.length - 1; }; const switchTab = (index) => { tabButtons.forEach(btn => btn.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); tabButtons[index].classList.add('active'); tabContents[index].classList.add('active'); currentTabIndex = index; updateTabButtons(); }; tabButtons.forEach((button, index) => button.addEventListener('click', () => switchTab(index))); prevTabBtn.addEventListener('click', () => { if (currentTabIndex > 0) switchTab(currentTabIndex - 1); }); nextTabBtn.addEventListener('click', () => { if (currentTabIndex < tabContents.length - 1) switchTab(currentTabIndex + 1); }); // --- INITIALIZATION --- if (projectSelector && addProjectBtn && downloadPdfBtn) { projectSelector.addEventListener('change', handleProjectSelect); addProjectBtn.addEventListener('click', handleAddProject); downloadPdfBtn.addEventListener('click', handleDownloadPdf); renderAll(); updateTabButtons(); } else { console.error("Essential dashboard elements could not be found."); } });
Scroll to Top