Personal Work-Hour Analyzer

Personal Work-Hour Analyzer


Logged Entries for Date:

Select Period for Analysis

(Analysis uses date selected on "Daily Log" tab for context)

Manage Work Categories

Using the Work-Hour Analyzer

1. Define Your Categories (Categories Tab)
Start by setting up categories that represent the different types of work you do or projects you're involved in (e.g., "Client Project A," "Administrative Tasks," "Meetings," "Learning & Development"). Assign a color to each for easier visual identification in reports.
2. Log Your Work Daily (Daily Log Tab)
Select the date for which you want to log activities. Click "Add Work Entry." For each significant activity or task:
  • Enter a clear **Description**.
  • Select the appropriate **Category**.
  • Input the **Duration** in hours (e.g., 1.5 for 1 hour 30 minutes).
  • Add any **Notes** if needed.
Try to log entries as you complete tasks or at the end of your workday for better accuracy.
3. Analyze Your Time (Analysis & Reports Tab)
Select the period you want to analyze:
  • Selected Day: Shows data for the date currently chosen in the "Daily Log" tab.
  • Selected Week: Analyzes the full week (Monday to Sunday) that includes the date selected in the "Daily Log" tab.
  • Selected Month: Analyzes all entries for the full calendar month of the date selected in the "Daily Log" tab.
Click "Run Analysis." The tool will show you:
  • Total hours logged for the period.
  • A bar chart and table breaking down your time by category.
  • A detailed list of all activities logged during that period.
4. Gain Insights
Use the analysis to understand:
  • Where is most of your time going?
  • Are you spending enough time on high-priority categories/projects?
  • Are there any time sinks (categories where you spend a lot of time with little perceived output)?
  • How does your time distribution change day-to-day, week-to-week, or month-to-month?
5. Export for Records or Sharing
You can download a PDF of the analysis for the selected period to keep for your records or share with your team/manager if applicable.

Duration: ${entry.durationHours}h ${cat ? `| ${cat.name}` : '| Uncategorized'} ${entry.notes ? `
Notes: ${entry.notes}` : ''}

`; pwhaLoggedEntriesListEl.appendChild(itemEl); }); } window.pwhaOpenLogEntryModal = function(entryId = null) { const selectedDateKey = pwhaSelectedLogDateInput.value; if (!selectedDateKey) { alert("Please select a date first."); return; } pwhaLogEntryForm.reset(); pwhaLogEntryIdInput.value = ''; pwhaLogEntryDateKeyInput.value = selectedDateKey; // Store date context for save pwhaPopulateCategorySelect(); // Ensure categories are fresh if (entryId) { const entries = pwhaWorkLogEntries[selectedDateKey] || []; const entry = entries.find(e => e.id === entryId); if (entry) { pwhaLogEntryModalTitleEl.textContent = 'Edit Work Entry for ' + pwhaFormatDisplayDate(selectedDateKey); pwhaLogEntryIdInput.value = entry.id; pwhaEntryDescriptionInput.value = entry.description; pwhaEntryCategorySelectEl.value = entry.categoryId || ""; pwhaEntryDurationInput.value = entry.durationHours; pwhaEntryNotesInput.value = entry.notes || ""; } } else { pwhaLogEntryModalTitleEl.textContent = 'Add Work Entry for ' + pwhaFormatDisplayDate(selectedDateKey); } pwhaLogEntryModalEl.style.display = 'block'; } window.pwhaCloseModal = (modalId) => { const modal = document.getElementById(modalId); if(modal) modal.style.display = 'none'; } if(pwhaLogEntryForm) pwhaLogEntryForm.addEventListener('submit', function(e) { e.preventDefault(); const entryId = pwhaLogEntryIdInput.value; const dateKey = pwhaLogEntryDateKeyInput.value; if (!dateKey) { alert("Error: Date context lost for entry."); return; } const entryData = { description: pwhaEntryDescriptionInput.value.trim(), categoryId: pwhaEntryCategorySelectEl.value, durationHours: parseFloat(pwhaEntryDurationInput.value), notes: pwhaEntryNotesInput.value.trim() }; if (!entryData.description || isNaN(entryData.durationHours) || entryData.durationHours <= 0) { alert("Description and a valid positive duration are required."); return; } if (!entryData.categoryId) { // Ensure a category is selected, or default alert("Please select a category."); return; } if (!pwhaWorkLogEntries[dateKey]) pwhaWorkLogEntries[dateKey] = []; if (entryId) { // Editing const index = pwhaWorkLogEntries[dateKey].findIndex(e => e.id === entryId); if (index > -1) { pwhaWorkLogEntries[dateKey][index] = { ...pwhaWorkLogEntries[dateKey][index], ...entryData }; } } else { // Adding new const newEntry = { id: 'entry-' + Date.now(), ...entryData }; pwhaWorkLogEntries[dateKey].push(newEntry); } pwhaSaveLogEntries(); pwhaRenderLoggedEntries(dateKey); pwhaCloseModal('pwhaLogEntryModal'); }); window.pwhaDeleteLogEntry = function(entryId) { const selectedDateKey = pwhaSelectedLogDateInput.value; if (!selectedDateKey || !pwhaWorkLogEntries[selectedDateKey]) return; if (confirm('Are you sure you want to delete this work entry?')) { pwhaWorkLogEntries[selectedDateKey] = pwhaWorkLogEntries[selectedDateKey].filter(e => e.id !== entryId); if(pwhaWorkLogEntries[selectedDateKey].length === 0) { delete pwhaWorkLogEntries[selectedDateKey]; // Clean up if day becomes empty } pwhaSaveLogEntries(); pwhaRenderLoggedEntries(selectedDateKey); } } // --- Analysis & Reports --- window.pwhaRunAnalysis = function() { const periodType = pwhaAnalysisPeriodSelectEl.value; const selectedDateForContextKey = pwhaSelectedLogDateInput.value; // Date from Daily Log tab if (!selectedDateForContextKey) { alert("Please select a date in the 'Daily Log' tab to provide context for the analysis period."); pwhaAnalysisResultsEl.style.display = 'none'; return; } const contextDate = new Date(selectedDateForContextKey + "T00:00:00Z"); // Use UTC to avoid timezone issues from YYYY-MM-DD let startDate, endDate; let periodDisplayStr = ""; if (periodType === "selected_day") { startDate = new Date(contextDate); endDate = new Date(contextDate); periodDisplayStr = pwhaFormatDisplayDate(selectedDateForContextKey); } else if (periodType === "current_week") { startDate = new Date(contextDate); const dayOfWeek = startDate.getUTCDay(); // 0 (Sun) - 6 (Sat) const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; // Adjust to get Monday startDate.setUTCDate(startDate.getUTCDate() + diffToMonday); endDate = new Date(startDate); endDate.setUTCDate(startDate.getUTCDate() + 6); periodDisplayStr = `Week of ${pwhaFormatDisplayDate(pwhaFormatDateKey(startDate))} - ${pwhaFormatDisplayDate(pwhaFormatDateKey(endDate))}`; } else if (periodType === "current_month") { startDate = new Date(Date.UTC(contextDate.getUTCFullYear(), contextDate.getUTCMonth(), 1)); endDate = new Date(Date.UTC(contextDate.getUTCFullYear(), contextDate.getUTCMonth() + 1, 0)); // Last day of month periodDisplayStr = contextDate.toLocaleDateString(undefined, { month: 'long', year: 'numeric' }); } pwhaAnalysisPeriodDisplayEl.textContent = periodDisplayStr; const analysisData = { totalHours: 0, byCategory: {}, detailedLog: [] }; let currentDateIter = new Date(startDate); while(currentDateIter <= endDate) { const dateKey = pwhaFormatDateKey(currentDateIter); if (pwhaWorkLogEntries[dateKey]) { pwhaWorkLogEntries[dateKey].forEach(entry => { analysisData.totalHours += entry.durationHours; const cat = pwhaSettings.categories.find(c => c.id === entry.categoryId) || {name: "Uncategorized", color:"#cccccc"}; if (!analysisData.byCategory[cat.name]) { analysisData.byCategory[cat.name] = { hours: 0, color: cat.color, entries:0 }; } analysisData.byCategory[cat.name].hours += entry.durationHours; analysisData.byCategory[cat.name].entries++; analysisData.detailedLog.push({...entry, date: dateKey, categoryName: cat.name}); }); } currentDateIter.setUTCDate(currentDateIter.getUTCDate() + 1); } // Display summary text pwhaTotalHoursAnalyzedEl.textContent = analysisData.totalHours.toFixed(1); // Render Bar Chart (HTML/CSS based) pwhaCategoryChartContainerEl.innerHTML = '
Time by Category:
'; const maxCategoryHours = Math.max(...Object.values(analysisData.byCategory).map(c => c.hours), 0); for (const catName in analysisData.byCategory) { const catData = analysisData.byCategory[catName]; const percentageOfMax = maxCategoryHours > 0 ? (catData.hours / maxCategoryHours) * 100 : 0; const barContainer = document.createElement('div'); barContainer.className = 'pwha-chart-bar-container'; barContainer.innerHTML = `
${catName}
${catData.hours.toFixed(1)}h
${analysisData.totalHours > 0 ? ((catData.hours / analysisData.totalHours) * 100).toFixed(1) : 0}%
`; pwhaCategoryChartContainerEl.appendChild(barContainer); } if(Object.keys(analysisData.byCategory).length === 0 && analysisData.totalHours === 0){ pwhaCategoryChartContainerEl.innerHTML = '

No data to display in chart for this period.

'; } // Render Category Summary Table pwhaCategorySummaryTableBodyEl.innerHTML = ''; if(Object.keys(analysisData.byCategory).length > 0) { for (const catName in analysisData.byCategory) { const catData = analysisData.byCategory[catName]; const percentage = analysisData.totalHours > 0 ? ((catData.hours / analysisData.totalHours) * 100).toFixed(1) : 0; const row = pwhaCategorySummaryTableBodyEl.insertRow(); row.insertCell().textContent = catName; row.insertCell().textContent = catData.hours.toFixed(1); row.insertCell().textContent = percentage + "%"; } } else { pwhaCategorySummaryTableBodyEl.innerHTML = 'No category data for this period.'; } // Render Detailed Log pwhaDetailedLogForPeriodEl.innerHTML = ''; if (analysisData.detailedLog.length > 0) { let logHtml = '
    '; analysisData.detailedLog.sort((a,b) => new Date(a.date + "T00:00:00Z") - new Date(b.date + "T00:00:00Z") ); // Sort by date analysisData.detailedLog.forEach(entry => { logHtml += `
  • ${pwhaFormatDisplayDate(entry.date)} - ${entry.description} (${entry.durationHours}h) [${entry.categoryName}] ${entry.notes ? ' - ' + entry.notes : ''}
  • `; }); logHtml += '
'; pwhaDetailedLogForPeriodEl.innerHTML = logHtml; } else { pwhaDetailedLogForPeriodEl.innerHTML = '

No detailed activities logged for this period.

'; } pwhaAnalysisResultsEl.style.display = 'block'; pwhaDownloadPdfBtn.style.display = 'block'; } // --- PDF Export --- function handlePdfDownload() { const periodType = pwhaAnalysisPeriodSelectEl.value; const selectedDateForContextKey = pwhaSelectedLogDateInput.value; if (!selectedDateForContextKey) { alert("Please select a date for context."); return; } const contextDate = new Date(selectedDateForContextKey + "T00:00:00Z"); let startDate, endDate; let periodDisplayStrFull = ""; if (periodType === "selected_day") { /* ... same logic as pwhaRunAnalysis ... */ startDate = new Date(contextDate); endDate = new Date(contextDate); periodDisplayStrFull = `Day: ${pwhaFormatDisplayDate(selectedDateForContextKey)}`; } else if (periodType === "current_week") { startDate = new Date(contextDate); const dayOfWeek = startDate.getUTCDay(); const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; startDate.setUTCDate(startDate.getUTCDate() + diffToMonday); endDate = new Date(startDate); endDate.setUTCDate(startDate.getUTCDate() + 6); periodDisplayStrFull = `Week: ${pwhaFormatDisplayDate(pwhaFormatDateKey(startDate))} to ${pwhaFormatDisplayDate(pwhaFormatDateKey(endDate))}`; } else if (periodType === "current_month") { startDate = new Date(Date.UTC(contextDate.getUTCFullYear(), contextDate.getUTCMonth(), 1)); endDate = new Date(Date.UTC(contextDate.getUTCFullYear(), contextDate.getUTCMonth() + 1, 0)); periodDisplayStrFull = `Month: ${contextDate.toLocaleDateString(undefined, { month: 'long', year: 'numeric' })}`; } const analysisData = { totalHours: 0, byCategory: {}, detailedLog: [] }; let currentDateIter = new Date(startDate); while(currentDateIter <= endDate) { /* ... same data aggregation as pwhaRunAnalysis ... */ const dateKey = pwhaFormatDateKey(currentDateIter); if (pwhaWorkLogEntries[dateKey]) { pwhaWorkLogEntries[dateKey].forEach(entry => { analysisData.totalHours += entry.durationHours; const cat = pwhaSettings.categories.find(c => c.id === entry.categoryId) || {name: "Uncategorized", color:"#cccccc"}; if (!analysisData.byCategory[cat.name]) { analysisData.byCategory[cat.name] = { hours: 0, color: cat.color }; } analysisData.byCategory[cat.name].hours += entry.durationHours; analysisData.detailedLog.push({...entry, date: dateKey, categoryName: cat.name}); }); } currentDateIter.setUTCDate(currentDateIter.getUTCDate() + 1); } if(analysisData.totalHours === 0 && analysisData.detailedLog.length === 0) { alert("No data to export for the selected period."); return; } const { jsPDF } = window.jspdf; // Local instance const doc = new jsPDF(); doc.setFontSize(18); doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--pwha-primary-color').trim()); doc.text("Personal Work-Hour Analysis", 14, 22); doc.setFontSize(12); doc.setTextColor(100); doc.text(`Period: ${periodDisplayStrFull}`, 14, 30); doc.text(`Total Hours Logged: ${analysisData.totalHours.toFixed(1)} hours`, 14, 38); let yPos = 50; doc.setFontSize(14); doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--pwha-primary-color').trim()); doc.text("Time Distribution by Category:", 14, yPos); yPos += 8; const tableCategoryColumn = ["Category", "Hours Spent", "% of Total"]; const tableCategoryRows = []; for (const catName in analysisData.byCategory) { const catData = analysisData.byCategory[catName]; const percentage = analysisData.totalHours > 0 ? ((catData.hours / analysisData.totalHours) * 100).toFixed(1) : 0; tableCategoryRows.push([catName, catData.hours.toFixed(1), percentage + "%"]); } tableCategoryRows.sort((a,b) => parseFloat(b[1]) - parseFloat(a[1])); // Sort by hours desc doc.autoTable({ head: [tableCategoryColumn], body: tableCategoryRows, startY: yPos, theme: 'striped', headStyles: { fillColor: getComputedStyle(document.documentElement).getPropertyValue('--pwha-secondary-color').trim() } }); yPos = doc.lastAutoTable.finalY + 15; if (analysisData.detailedLog.length > 0) { if (yPos > 250) { doc.addPage(); yPos = 20; } doc.setFontSize(14); doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--pwha-primary-color').trim()); doc.text("Detailed Activities:", 14, yPos); yPos += 8; const tableDetailColumn = ["Date", "Activity", "Category", "Duration (h)", "Notes"]; const tableDetailRows = []; analysisData.detailedLog.sort((a,b) => new Date(a.date + "T00:00:00Z") - new Date(b.date + "T00:00:00Z") ); analysisData.detailedLog.forEach(entry => { tableDetailRows.push([ pwhaFormatDisplayDate(entry.date), entry.description, entry.categoryName, entry.durationHours.toFixed(1), entry.notes || "" ]); }); doc.autoTable({ head: [tableDetailColumn], body: tableDetailRows, startY: yPos, theme: 'grid', headStyles: { fillColor: getComputedStyle(document.documentElement).getPropertyValue('--pwha-secondary-color').trim() }, columnStyles: { 1: { cellWidth: 60 }, 4: { cellWidth: 'auto'} } // Activity desc, Notes auto }); } doc.save(`WorkHourAnalysis_${periodDisplayStrFull.replace(/[^a-zA-Z0-9]/g, '_')}.pdf`); } // --- Initialization --- document.addEventListener('DOMContentLoaded', () => { // Assign DOM elements pwhaSelectedLogDateInput = document.getElementById('pwhaSelectedLogDate'); pwhaLogDateDisplay1El = document.getElementById('pwhaLogDateDisplay1'); pwhaLogDateDisplay2El = document.getElementById('pwhaLogDateDisplay2'); pwhaLoggedEntriesListEl = document.getElementById('pwhaLoggedEntriesList'); pwhaAddCategoryForm = document.getElementById('pwhaAddCategoryForm'); pwhaCategoryNameInput = document.getElementById('pwhaCategoryNameInput'); pwhaCategoryColorInput = document.getElementById('pwhaCategoryColorInput'); pwhaCategoriesListEl = document.getElementById('pwhaCategoriesList'); pwhaLogEntryModalEl = document.getElementById('pwhaLogEntryModal'); pwhaLogEntryModalTitleEl = document.getElementById('pwhaLogEntryModalTitle'); pwhaLogEntryForm = document.getElementById('pwhaLogEntryForm'); pwhaLogEntryIdInput = document.getElementById('pwhaLogEntryId'); pwhaLogEntryDateKeyInput = document.getElementById('pwhaLogEntryDateKey'); pwhaEntryDescriptionInput = document.getElementById('pwhaEntryDescription'); pwhaEntryCategorySelectEl = document.getElementById('pwhaEntryCategory'); pwhaEntryDurationInput = document.getElementById('pwhaEntryDuration'); pwhaEntryNotesInput = document.getElementById('pwhaEntryNotes'); pwhaAnalysisPeriodSelectEl = document.getElementById('pwhaAnalysisPeriodSelect'); pwhaAnalysisResultsEl = document.getElementById('pwhaAnalysisResults'); pwhaAnalysisPeriodDisplayEl = document.getElementById('pwhaAnalysisPeriodDisplay'); pwhaTotalHoursAnalyzedEl = document.getElementById('pwhaTotalHoursAnalyzed'); pwhaCategoryChartContainerEl = document.getElementById('pwhaCategoryChartContainer'); pwhaCategorySummaryTableBodyEl = document.querySelector('#pwhaCategorySummaryTable tbody'); pwhaDetailedLogForPeriodEl = document.getElementById('pwhaDetailedLogForPeriod'); pwhaDownloadPdfBtn = document.getElementById('pwhaDownloadPdfButton'); // Attach event listeners if(pwhaAddCategoryForm) pwhaAddCategoryForm.addEventListener('submit', handleAddCategoryFormSubmit); if(pwhaLogEntryForm) pwhaLogEntryForm.addEventListener('submit', pwhaLogEntryForm.onsubmit); // Check this, should be the function name directly if(pwhaSelectedLogDateInput) pwhaSelectedLogDateInput.addEventListener('change', pwhaHandleLogDateChange); // Analysis button is handled by its onclick in HTML directly, or add listener here if preferred. if(pwhaDownloadPdfBtn) pwhaDownloadPdfBtn.addEventListener('click', handlePdfDownload); // Set default date for log entry and load data const today = new Date(2025, 4, 14); // May 14, 2025 if(pwhaSelectedLogDateInput) pwhaSelectedLogDateInput.value = pwhaFormatDateKey(today); pwhaLoadData(); const firstTabButton = document.querySelector('#personalWorkHourAnalyzerTool .pwha-tab-button'); if (firstTabButton) { firstTabButton.click(); } window.addEventListener('click', function(event) { if (event.target.classList.contains('pwha-modal')) { pwhaCloseModal(event.target.id); } }); });
Scroll to Top