Productivity Score Analyzer

Productivity Score Analyzer

Add / Edit Task

Task List

Productivity Analysis

Scoring Settings

`; taskListUL.appendChild(li); }); document.querySelectorAll('.edit-task-btn').forEach(btn => btn.addEventListener('click', (e) => populateTaskFormForEdit(e.target.dataset.id))); document.querySelectorAll('.delete-task-btn').forEach(btn => btn.addEventListener('click', (e) => deleteTask(e.target.dataset.id))); document.querySelectorAll('.toggle-status-btn').forEach(btn => btn.addEventListener('click', (e) => toggleTaskStatus(e.target.dataset.id))); } // --- Analysis Logic --- analysisPeriodSelect.addEventListener('change', () => { customDateRangeDiv.style.display = analysisPeriodSelect.value === 'custom' ? 'flex' : 'none'; }); analyzeBtn.addEventListener('click', () => { let startDate, endDate; const today = new Date(); const period = analysisPeriodSelect.value; switch(period) { case 'today': startDate = new Date(today.setHours(0,0,0,0)); endDate = new Date(today.setHours(23,59,59,999)); break; case 'past7days': endDate = new Date(today.setHours(23,59,59,999)); startDate = new Date(today); startDate.setDate(today.getDate() - 6); // Covers today + 6 previous days startDate.setHours(0,0,0,0); break; case 'past30days': endDate = new Date(today.setHours(23,59,59,999)); startDate = new Date(today); startDate.setDate(today.getDate() - 29); startDate.setHours(0,0,0,0); break; case 'custom': if (!customStartDateInput.value || !customEndDateInput.value) { alert('Please select a valid custom date range.'); return; } startDate = new Date(customStartDateInput.value + "T00:00:00"); endDate = new Date(customEndDateInput.value + "T23:59:59"); if (startDate > endDate) { alert('Start date cannot be after end date.'); return; } break; default: return; } performAnalysis(startDate, endDate); }); function performAnalysis(startDate, endDate) { let totalPlannedValue = 0; let totalAchievedValue = 0; let tasksCompleted = 0; let tasksOnTime = 0; let tasksLate = 0; const periodPlannedTasks = []; const periodCompletedTasks = []; tasks.forEach(task => { const taskDueDate = task.dueDate ? new Date(task.dueDate + "T00:00:00") : null; const taskCreatedAt = new Date(task.createdAt + "T00:00:00"); // Ensure createdAt is treated as date // Determine if task was planned for the period let isPlannedForPeriod = false; if (taskDueDate && taskDueDate >= startDate && taskDueDate <= endDate) { isPlannedForPeriod = true; } else if (!taskDueDate && taskCreatedAt >= startDate && taskCreatedAt <= endDate) { // If no due date, it's planned if created within period isPlannedForPeriod = true; } if (isPlannedForPeriod) { totalPlannedValue += task.value; periodPlannedTasks.push(task); } // Determine if task was completed in the period if (task.status === 'done' && task.completionDate) { const completionDate = new Date(task.completionDate + "T00:00:00"); if (completionDate >= startDate && completionDate <= endDate) { tasksCompleted++; let taskAchievedValue = task.value; let timelinessStatus = 'N/A'; if (taskDueDate) { if (completionDate <= taskDueDate) { tasksOnTime++; taskAchievedValue += task.value * (settings.onTimeBonusPercent / 100); timelinessStatus = 'On Time'; } else { tasksLate++; timelinessStatus = 'Late'; } } totalAchievedValue += taskAchievedValue; periodCompletedTasks.push({...task, timelinessStatus, achievedValueWithBonus: taskAchievedValue}); // If a task was completed in period but not "planned" (e.g. due earlier but done now) // it still contributes to achieved value but might not have been in planned value if we are strict // Current logic for planned value relies on due date or creation date. // For simplicity, let's ensure its original value is added to planned if it wasn't already. if (!isPlannedForPeriod) { // This case can be debated: if a very old task is completed, should it inflate "planned"? // For now, let's stick to the definition of planned above. // The score might exceed 100% if old valuable tasks are cleared. } } } }); const productivityScore = totalPlannedValue > 0 ? Math.min(100, (totalAchievedValue / totalPlannedValue) * 100) : 0; productivityScoreDiv.textContent = `${productivityScore.toFixed(0)}%`; totalValuePlannedDiv.textContent = totalPlannedValue.toFixed(0); totalValueAchievedDiv.textContent = totalAchievedValue.toFixed(1); // Show decimal for bonus tasksCompletedCountDiv.textContent = tasksCompleted; tasksOnTimeCountDiv.textContent = tasksOnTime; tasksLateCountDiv.textContent = tasksLate; const progressPercent = totalPlannedValue > 0 ? (totalAchievedValue / totalPlannedValue) * 100 : 0; achievedProgressBar.style.width = `${Math.min(100, progressPercent)}%`; // Cap at 100% for visual achievedProgressBarText.textContent = `${progressPercent.toFixed(0)}% Achieved`; renderAnalysisTaskLists(periodPlannedTasks, periodCompletedTasks); analysisResultsDiv.style.display = 'block'; } function renderAnalysisTaskLists(planned, completed) { plannedTasksListUL.innerHTML = ''; completedTasksListUL.innerHTML = ''; if(planned.length === 0) plannedTasksListUL.innerHTML = "
  • No tasks were planned for this period.
  • "; planned.forEach(task => { const li = document.createElement('li'); li.className = 'task-item'; li.innerHTML = `

    ${task.description}

    Value: ${task.value} | Due: ${task.dueDate ? formatDate(task.dueDate) : 'N/A'}

    `; plannedTasksListUL.appendChild(li); }); if(completed.length === 0) completedTasksListUL.innerHTML = "
  • No tasks were completed in this period.
  • "; completed.forEach(task => { const li = document.createElement('li'); li.className = 'task-item status-done'; // All completed tasks are 'done' let timelinessInfo = task.timelinessStatus && task.timelinessStatus !== 'N/A' ? ` | Status: ${task.timelinessStatus}` : ''; li.innerHTML = `

    ${task.description}

    Achieved Value: ${task.achievedValueWithBonus.toFixed(1)} (Base: ${task.value}) | Completed: ${formatDate(task.completionDate)}${timelinessInfo}

    `; completedTasksListUL.appendChild(li); }); } // --- PDF Download --- downloadReportPdfBtn.addEventListener('click', () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); const periodText = analysisPeriodSelect.options[analysisPeriodSelect.selectedIndex].text; let dateRangeText = periodText; if (periodText === 'Custom Range') { dateRangeText = `${formatDate(customStartDateInput.value)} to ${formatDate(customEndDateInput.value)}`; } doc.setFontSize(18); doc.setTextColor(parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(1,3),16), parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(3,5),16), parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(5,7),16)); doc.text('Productivity Analysis Report', 14, 22); doc.setFontSize(12); doc.setTextColor(100); doc.text(`Period: ${dateRangeText}`, 14, 30); doc.text(`Generated on: ${new Date().toLocaleString()}`, 14, 36); doc.setFontSize(14); doc.text(`Overall Productivity Score: ${productivityScoreDiv.textContent}`, 14, 46); doc.setFontSize(11); let yPos = 54; doc.text(`Value Planned for Period: ${totalValuePlannedDiv.textContent} points`, 14, yPos); yPos +=6; doc.text(`Value Achieved (incl. bonus): ${totalValueAchievedDiv.textContent} points`, 14, yPos); yPos +=6; doc.text(`Tasks Completed: ${tasksCompletedCountDiv.textContent}`, 14, yPos); yPos +=6; doc.text(`Tasks On Time: ${tasksOnTimeCountDiv.textContent}`, 14, yPos); yPos +=6; doc.text(`Tasks Late: ${tasksLateCountDiv.textContent}`, 14, yPos); yPos +=10; doc.setFontSize(12); doc.text('Tasks Considered Planned:', 14, yPos); yPos +=6; const plannedData = Array.from(plannedTasksListUL.children).map(li => { const desc = li.querySelector('.task-description').textContent; const meta = li.querySelector('.task-meta').textContent; const value = meta.match(/Value: (\d+)/) ? meta.match(/Value: (\d+)/)[1] : 'N/A'; const dueDate = meta.match(/Due: (.*?)$/) ? meta.match(/Due: (.*?)$/)[1].trim() : 'N/A'; return [desc, value, dueDate]; }); if (plannedData.length > 0) { doc.autoTable({ startY: yPos, head: [['Description', 'Value', 'Due Date']], body: plannedData, theme: 'grid' }); yPos = doc.lastAutoTable.finalY + 10; } else { doc.text('No tasks were planned for this period.', 16, yPos); yPos += 7; } doc.setFontSize(12); doc.text('Tasks Completed in Period:', 14, yPos); yPos +=6; const completedData = Array.from(completedTasksListUL.children).map(li => { const desc = li.querySelector('.task-description').textContent; const meta = li.querySelector('.task-meta').textContent; // "Achieved Value: X (Base: Y) | Completed: Z | Status: W" const achieved = meta.match(/Achieved Value: ([\d\.]+)/) ? meta.match(/Achieved Value: ([\d\.]+)/)[1] : 'N/A'; const completed = meta.match(/Completed: ([\d\-]+)/) ? meta.match(/Completed: ([\d\-]+)/)[1] : 'N/A'; const status = meta.match(/Status: (.*?)$/) ? meta.match(/Status: (.*?)$/)[1].trim() : 'N/A'; return [desc, achieved, completed, status]; }); if (completedData.length > 0) { doc.autoTable({ startY: yPos, head: [['Description', 'Achieved Value', 'Completed Date', 'Timeliness']], body: completedData, theme: 'grid' }); } else { doc.text('No tasks were completed in this period.', 16, yPos); } doc.save(`Productivity_Report_${dateRangeText.replace(/[\s/:]/g, '_')}.pdf`); }); // --- Initializations --- setDateInputConstraints(); loadSettings(); renderTaskList(); // Set default custom range for analysis tab const today = new Date(); const todayStr = formatDate(today); const sevenDaysAgo = new Date(today); sevenDaysAgo.setDate(today.getDate() - 6); customStartDateInput.value = formatDate(sevenDaysAgo); customEndDateInput.value = todayStr; })(); // End IIFE
    Scroll to Top