Future Outlook Dashboard

Configuration

Add a New Goal


Existing Goals

Log Progress


Progress History

${metrics.daysRemaining.toLocaleString()}

PROJECTED

${metrics.projectedDate}

CURRENT

${state.unitPrefix}${metrics.currentValue.toLocaleString()}

`; dashboardGrid.appendChild(card); createGoalChart(document.getElementById(`chart-${goal.id}`), goal, metrics); }); } }; const createGoalChart = (canvas, goal, metrics) => { const { sortedProgress, projectedDate } = metrics; const today = new Date(); const targetDate = new Date(goal.targetDate); const progressData = sortedProgress.map(p => ({ x: new Date(p.date), y: p.value })); const targetData = [{ x: progressData.length > 0 ? progressData[0].x : today, y: goal.targetValue }, { x: targetDate, y: goal.targetValue }]; const projectionData = []; if (metrics.currentRate > 0 && new Date() < targetDate) { const lastProgressPoint = progressData[progressData.length - 1] || { x: today, y: goal.startValue }; projectionData.push({x: lastProgressPoint.x, y: lastProgressPoint.y}); // Start from last known point if (projectedDate !== 'N/A' && projectedDate !== 'Achieved!') { projectionData.push({ x: new Date(projectedDate), y: goal.targetValue }); } } charts[goal.id] = new Chart(canvas.getContext('2d'), { type: 'line', data: { datasets: [{ label: 'Progress', data: progressData, borderColor: '#007ace', backgroundColor: '#007ace', fill: false, tension: 0.1 }, { label: 'Target', data: targetData, borderColor: '#28a745', borderDash: [5, 5], fill: false, pointRadius: 0 }, { label: 'Projection', data: projectionData, borderColor: '#ff9f40', borderDash: [10, 5], fill: false, }] }, options: { scales: { x: { type: 'time', time: { unit: 'month' } }, y: { ticks: { callback: value => state.unitPrefix + value } } } } }); }; const renderManagementView = () => { // Populate goals table goalsBody.innerHTML = ''; state.goals.forEach(goal => { goalsBody.innerHTML += ` ${goal.name} `; }); // Populate progress dropdown progressGoalSelect.innerHTML = state.goals.map(g => ``).join(''); renderProgressHistory(); }; const renderProgressHistory = () => { const selectedGoalId = progressGoalSelect.value; progressBody.innerHTML = ''; const goal = state.goals.find(g => g.id == selectedGoalId); if (goal) { const { sortedProgress } = calculateOutlook(goal); sortedProgress.reverse().forEach(p => { progressBody.innerHTML += `${new Date(p.date).toLocaleDateString()}${state.unitPrefix}${p.value.toLocaleString()}`; }); } }; // --- Event Handlers --- unitPrefixInput.addEventListener('input', () => { state.unitPrefix = unitPrefixInput.value; render(); }); goalForm.addEventListener('submit', (e) => { e.preventDefault(); const goalData = { id: goalIdInput.value ? Number(goalIdInput.value) : Date.now(), name: goalNameInput.value, startValue: parseFloat(startValueInput.value), targetValue: parseFloat(targetValueInput.value), targetDate: targetDateInput.value, progress: [] }; const existingIndex = state.goals.findIndex(g => g.id === goalData.id); if(existingIndex > -1) { // Editing goalData.progress = state.goals[existingIndex].progress; // Keep existing progress state.goals[existingIndex] = goalData; } else { // Adding goalData.progress.push({ date: new Date().toISOString().slice(0,10), value: goalData.startValue }); state.goals.push(goalData); } goalForm.reset(); goalIdInput.value = ''; goalFormTitle.textContent = 'Add a New Goal'; render(); }); clearGoalFormBtn.addEventListener('click', () => { goalForm.reset(); goalIdInput.value = ''; goalFormTitle.textContent = 'Add a New Goal'; }); window.handleEditGoal = (id) => { const goal = state.goals.find(g => g.id == id); if (goal) { goalIdInput.value = goal.id; goalNameInput.value = goal.name; startValueInput.value = goal.startValue; targetValueInput.value = goal.targetValue; targetDateInput.value = goal.targetDate; goalFormTitle.textContent = 'Edit Goal'; goalNameInput.focus(); } }; window.handleDeleteGoal = (id) => { if (confirm('Are you sure you want to delete this goal and all its progress?')) { state.goals = state.goals.filter(g => g.id != id); render(); } }; progressGoalSelect.addEventListener('change', renderProgressHistory); progressForm.addEventListener('submit', e => { e.preventDefault(); const goal = state.goals.find(g => g.id == progressGoalSelect.value); if (goal) { goal.progress.push({ date: progressDateInput.value, value: parseFloat(progressValueInput.value) }); progressForm.reset(); render(); } }); // --- PDF Generation --- downloadPdfBtn.addEventListener('click', async () => { const { jsPDF } = jspdf; const doc = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); let yPos = 20; doc.setFontSize(20); doc.text("Future Outlook Report", doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 15; for (const goal of state.goals) { if (yPos > 240) { // Check for page break doc.addPage(); yPos = 20; } const metrics = calculateOutlook(goal); doc.setFontSize(16); doc.text(goal.name, 14, yPos); yPos += 8; // Add chart const chartCanvas = document.getElementById(`chart-${goal.id}`); doc.addImage(chartCanvas.toDataURL('image/png'), 'PNG', 14, yPos, 180, 90); yPos += 100; // Add metrics table const tableData = [ ['Status', 'Value'], ['Percent Complete', `${metrics.percentComplete.toFixed(1)}%`], ['Current Value', `${state.unitPrefix}${metrics.currentValue.toLocaleString()}`], ['Days Remaining', metrics.daysRemaining.toLocaleString()], ['Projected Completion', metrics.projectedDate] ]; jspdf.autoTable(doc, { head: [['Metric', 'Value']], body: tableData.slice(1), startY: yPos, theme: 'striped', headStyles: { fillColor: [0, 122, 206] } }); yPos = doc.lastAutoTable.finalY + 15; } doc.save(`Future-Outlook-Report-${new Date().toISOString().slice(0,10)}.pdf`); }); // --- Initial Load --- loadState(); render(); progressDateInput.valueAsDate = new Date(); });
Scroll to Top