Time Blocking & Allocation Analysis Dashboard

Time Blocking & Allocation Analysis Dashboard

Plan your week, categorize tasks, and analyze your time allocation.

Time Allocation by Category

Hours Blocked per Day

Weekly Time Blocks

${deepWorkHours.toFixed(1)}

Total Activities

${totalBlocks}

`; }; const renderAllocationChart = () => { const ctx = document.getElementById('allocationChart').getContext('2d'); const categoryHours = {}; timeBlocks.forEach(block => { const duration = getDurationInHours(block.startTime, block.endTime); categoryHours[block.category] = (categoryHours[block.category] || 0) + duration; }); if (allocationChart) allocationChart.destroy(); allocationChart = new Chart(ctx, { type: 'pie', data: { labels: Object.keys(categoryHours), datasets: [{ data: Object.values(categoryHours), backgroundColor: Object.keys(categoryHours).map(cat => `bg-${categories[cat].color}-500`.replace('bg-', '#')), // Simplified }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); }; const renderDailyHoursChart = () => { const ctx = document.getElementById('dailyHoursChart').getContext('2d'); const dailyHours = daysOfWeek.map(day => timeBlocks.filter(b => b.day === day).reduce((sum, b) => sum + getDurationInHours(b.startTime, b.endTime), 0) ); if (dailyHoursChart) dailyHoursChart.destroy(); dailyHoursChart = new Chart(ctx, { type: 'bar', data: { labels: daysOfWeek.map(d => d.slice(0,3)), datasets: [{ label: 'Hours Blocked', data: dailyHours, backgroundColor: 'rgba(79, 70, 229, 0.6)', borderColor: 'rgba(79, 70, 229, 1)', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } } }); }; const renderCalendar = () => { const container = document.getElementById('calendar-container'); let tableHtml = ``; daysOfWeek.forEach(day => { tableHtml += ``; }); tableHtml += ``; for (let hour = 7; hour < 20; hour++) { tableHtml += ``; daysOfWeek.forEach(day => { tableHtml += ``; }); tableHtml += ``; } tableHtml += `
Time${day}
${hour}:00
`; container.innerHTML = tableHtml; // Now place the blocks timeBlocks.forEach(block => { const startH = parseInt(block.startTime.split(':')[0]); const startM = parseInt(block.startTime.split(':')[1]); const duration = getDurationInHours(block.startTime, block.endTime); const cell = container.querySelector(`[data-day="${block.day}"][data-hour="${startH}"]`); if (cell) { const blockEl = document.createElement('div'); const color = categories[block.category].color; blockEl.className = `task-block border-${color}-500 bg-${color}-100 text-${color}-800 rounded`; blockEl.style.top = `${(startM / 60) * 100}%`; blockEl.style.height = `${duration * 100}%`; blockEl.innerHTML = `${block.task}
${block.startTime} - ${block.endTime}`; cell.appendChild(blockEl); } }); }; const renderBlockListEditor = () => { const container = document.getElementById('block-list-editor').querySelector('.space-y-2'); container.innerHTML = timeBlocks.map(block => `

${block.task} (${block.category})

${block.day}, ${block.startTime} - ${block.endTime}

`).join(''); }; const populateSelects = () => { const daySelect = document.getElementById('block-day'); const categorySelect = document.getElementById('block-category'); daySelect.innerHTML = daysOfWeek.map(d => ``).join(''); categorySelect.innerHTML = Object.keys(categories).map(c => ``).join(''); }; // --- EVENT HANDLERS --- document.getElementById('block-form').addEventListener('submit', e => { e.preventDefault(); const id = document.getElementById('block-id').value; const newBlock = { task: document.getElementById('block-task').value, day: document.getElementById('block-day').value, category: document.getElementById('block-category').value, startTime: document.getElementById('block-start-time').value, endTime: document.getElementById('block-end-time').value, }; if (id) { const index = timeBlocks.findIndex(b => b.id == id); timeBlocks[index] = { ...timeBlocks[index], ...newBlock }; } else { newBlock.id = timeBlocks.length > 0 ? Math.max(...timeBlocks.map(b => b.id)) + 1 : 1; timeBlocks.push(newBlock); } resetForm(); renderAll(); }); document.getElementById('block-list-editor').addEventListener('click', e => { if (e.target.classList.contains('edit-btn')) { const id = e.target.dataset.id; const block = timeBlocks.find(b => b.id == id); if (block) populateFormForEdit(block); } if (e.target.classList.contains('delete-btn')) { const id = e.target.dataset.id; if (confirm('Are you sure you want to delete this time block?')) { timeBlocks = timeBlocks.filter(b => b.id != id); renderAll(); } } }); document.getElementById('cancel-edit-btn').addEventListener('click', resetForm); document.getElementById('download-pdf-btn').addEventListener('click', () => { const exportArea = document.getElementById('dashboard-export-area'); const mainTitleEl = document.querySelector('#time-block-tool-container h1'); if (!exportArea || !mainTitleEl) return; const btn = document.getElementById('download-pdf-btn'); const originalBtnText = btn.innerHTML; btn.disabled = true; btn.innerHTML = `Processing...`; html2canvas(exportArea, { scale: 2, useCORS: true, windowWidth: exportArea.scrollWidth, windowHeight: exportArea.scrollHeight }) .then(canvas => { const { jsPDF } = window.jspdf; const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' }); const pageMargin = 15; const pdfPageWidth = pdf.internal.pageSize.getWidth(); const pdfPageHeight = pdf.internal.pageSize.getHeight(); const pdfContentWidth = pdfPageWidth - (2 * pageMargin); const canvasAspectRatio = canvas.width / canvas.height; const pdfImgHeight = pdfContentWidth / canvasAspectRatio; let yPos = pageMargin; pdf.setFontSize(18).setFont('helvetica', 'bold'); pdf.text(mainTitleEl.innerText, pdfPageWidth / 2, yPos, { align: 'center' }); yPos += 12; let heightLeft = pdfImgHeight; let positionOnCanvas = 0; pdf.addImage(imgData, 'PNG', pageMargin, yPos, pdfContentWidth, pdfImgHeight); heightLeft -= (pdfPageHeight - yPos - pageMargin); while (heightLeft > 0) { positionOnCanvas -= (pdfPageHeight - (2 * pageMargin)); pdf.addPage(); pdf.addImage(imgData, 'PNG', pageMargin, positionOnCanvas + pageMargin, pdfContentWidth, pdfImgHeight); heightLeft -= (pdfPageHeight - (2 * pageMargin)); } pdf.save(`time-blocking-dashboard-${new Date().toISOString().slice(0,10)}.pdf`); }) .catch(err => { console.error("PDF generation failed:", err); alert("Error generating PDF."); }) .finally(() => { btn.disabled = false; btn.innerHTML = originalBtnText; }); }); // --- HELPER FUNCTIONS --- function populateFormForEdit(block) { document.getElementById('block-id').value = block.id; document.getElementById('block-task').value = block.task; document.getElementById('block-day').value = block.day; document.getElementById('block-category').value = block.category; document.getElementById('block-start-time').value = block.startTime; document.getElementById('block-end-time').value = block.endTime; document.getElementById('block-form').querySelector('button[type="submit"]').textContent = 'Update Block'; document.getElementById('cancel-edit-btn').classList.remove('hidden'); document.getElementById('block-form').scrollIntoView({ behavior: 'smooth' }); } function resetForm() { document.getElementById('block-form').reset(); document.getElementById('block-id').value = ''; document.getElementById('block-form').querySelector('button[type="submit"]').textContent = 'Add Block'; document.getElementById('cancel-edit-btn').classList.add('hidden'); } // --- INITIALIZATION --- const switchTab = (tabName) => { const tabDashboardBtn = document.getElementById('tab-dashboard-btn'); const tabConfigBtn = document.getElementById('tab-config-btn'); const tabDashboardContent = document.getElementById('tab-dashboard-content'); const tabConfigContent = document.getElementById('tab-config-content'); const prevTabBtn = document.getElementById('prev-tab-btn'); const nextTabBtn = document.getElementById('next-tab-btn'); if (tabName === 'dashboard') { tabDashboardBtn.classList.add('active'); tabConfigBtn.classList.remove('active'); tabDashboardContent.classList.remove('hidden'); tabConfigContent.classList.add('hidden'); prevTabBtn.disabled = true; nextTabBtn.disabled = false; } else { tabDashboardBtn.classList.remove('active'); tabConfigBtn.classList.add('active'); tabDashboardContent.classList.add('hidden'); tabConfigContent.classList.remove('hidden'); prevTabBtn.disabled = false; nextTabBtn.disabled = true; } }; document.getElementById('tab-dashboard-btn').addEventListener('click', () => switchTab('dashboard')); document.getElementById('tab-config-btn').addEventListener('click', () => switchTab('config')); document.getElementById('prev-tab-btn').addEventListener('click', () => switchTab('dashboard')); document.getElementById('next-tab-btn').addEventListener('click', () => switchTab('config')); switchTab('dashboard'); renderAll(); });
Scroll to Top