Personal Work-Hour Analyzer
Logged Entries for Date:
Select Period for Analysis
(Analysis uses date selected on "Daily Log" tab for context)
Analysis for Period
Total Hours Logged: 0 hours
Time Distribution by Category:
| Category | Hours | Percentage |
|---|
Detailed Log for Period:
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.
- 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.
- 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.
×
Add Work Entry
No categories defined.
'; return; } pwhaSettings.categories.forEach(cat => { const itemEl = document.createElement('div'); itemEl.className = 'pwha-list-item pwha-category-item'; itemEl.innerHTML = ` ${cat.name} `; pwhaCategoriesListEl.appendChild(itemEl); }); } window.pwhaEditCategoryName = function(catId, spanElement) { const cat = pwhaSettings.categories.find(c => c.id === catId); if (!cat) return; const newName = prompt("Edit category name:", cat.name); if (newName !== null && newName.trim() !== "") { if (!pwhaSettings.categories.find(c => c.name.toLowerCase() === newName.trim().toLowerCase() && c.id !== catId)) { cat.name = newName.trim(); spanElement.textContent = cat.name; pwhaSaveSettings(); pwhaPopulateCategorySelect(); // Update dropdowns } else { alert("Another category with this name already exists."); } } } window.pwhaDeleteCategory = function(catId) { if (confirm("Are you sure you want to delete this category? Logged entries using it will show 'Uncategorized'.")) { pwhaSettings.categories = pwhaSettings.categories.filter(c => c.id !== catId); pwhaSaveSettings(); pwhaRenderCategoriesList(); pwhaPopulateCategorySelect(); // No need to iterate through all past log entries to update categoryId for now, analysis will handle missing. } } function pwhaPopulateCategorySelect() { if (!pwhaEntryCategorySelectEl) return; const currentVal = pwhaEntryCategorySelectEl.value; pwhaEntryCategorySelectEl.innerHTML = ''; if (!pwhaSettings.categories.find(c => c.id === "")) { // Add a generic if not present const uncategorized = {id: "cat-uncategorized", name: "Uncategorized", color: "#cccccc"}; if (!pwhaSettings.categories.some(c => c.id === uncategorized.id)) { // pwhaSettings.categories.unshift(uncategorized); // Not saved, just for dropdown } } pwhaSettings.categories.forEach(cat => { const option = document.createElement('option'); option.value = cat.id; option.textContent = cat.name; pwhaEntryCategorySelectEl.appendChild(option); }); if(currentVal) pwhaEntryCategorySelectEl.value = currentVal; else if (pwhaSettings.categories.length > 0) { // Default to first if no val and list not empty // pwhaEntryCategorySelectEl.value = pwhaSettings.categories[0].id; } } // --- Daily Log Entry --- function pwhaHandleLogDateChange() { const selectedDateKey = pwhaSelectedLogDateInput.value; if (!selectedDateKey) { pwhaLogDateDisplay1El.textContent = "N/A"; pwhaLogDateDisplay2El.textContent = "N/A"; pwhaLoggedEntriesListEl.innerHTML = 'Please select a date.
'; return; } const displayDate = pwhaFormatDisplayDate(selectedDateKey); pwhaLogDateDisplay1El.textContent = displayDate; pwhaLogDateDisplay2El.textContent = displayDate; pwhaRenderLoggedEntries(selectedDateKey); } function pwhaRenderLoggedEntries(dateKey) { pwhaLoggedEntriesListEl.innerHTML = ''; const entries = pwhaWorkLogEntries[dateKey] || []; if (entries.length === 0) { pwhaLoggedEntriesListEl.innerHTML = 'No work entries logged for this day yet.
'; return; } entries.forEach(entry => { const cat = pwhaSettings.categories.find(c => c.id === entry.categoryId); const itemEl = document.createElement('div'); itemEl.className = 'pwha-list-item'; itemEl.innerHTML = `${entry.description}
Duration: ${entry.durationHours}h
${cat ? `| ${cat.name}` : '| Uncategorized'}
${entry.notes ? `
Notes: ${entry.notes}` : ''}
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 = '- ';
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 += '
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); } }); });