Real-Time Work Hour Logging Tool

Real-Time Work Hour Logger

Live Timer

Quick Start Recent Tasks:

Log time for projects to see them here for quick starting.


Daily Log for Date

Manage Projects/Common Tasks

Generate Time Report

Real-Time Work Hour Logging Guide

1. Define Your Projects (Projects Tab)
Set up a list of common projects, clients, or general task types you work on. Assigning a color can help in visual identification.
2. Logging Time (Timer & Daily Log Tab)
Using the Live Timer:
  • Select a project from the dropdown or choose "Log Ad-hoc Task" and enter a specific task name.
  • Click "Start New Timer". The timer will run, showing elapsed time for the active task. The browser tab title will also show the timer.
  • Use "Pause" and "Resume" as needed. The timer accounts for paused duration.
  • When finished, click "Stop & Log". A modal will appear allowing you to confirm/edit the auto-filled start/end times, task description (if ad-hoc or needs refinement), select a project if not already, and add notes.
  • "Discard Timer" will stop the timer without creating a log entry.
Manual Entry:
  • Select the date for which you want to log time using the "View/Manage Log for Date" picker.
  • Click "Add Manual Entry".
  • Fill in the task description, select a project (or leave for ad-hoc), set Start Date/Time, and End Date/Time. The duration will be auto-calculated. Add optional notes.
3. Recovering Interrupted Timers
If you accidentally close your browser or refresh the page while a timer is running, the tool will try to detect this when you next open it. You'll be prompted to log the potentially elapsed time, adjust it, or discard it.
4. Viewing Reports (Reports Tab)
Select a period (Today, This Week, This Month, or a Custom Date Range) and click "Generate Report". You'll see:
  • Total time logged for the period.
  • A summary of time spent per project/task.
  • A detailed chronological list of all individual time entries within that period.
5. Exporting Your Data
Use the "Download Report as PDF" button to save your time analysis for records, billing, or sharing.
Tips for Effective Time Logging:
  • Log in Real-Time: Start the timer when you begin a task for maximum accuracy.
  • Be Consistent: The more consistently you log, the more valuable your data.
  • Use Projects/Categories: This helps in analyzing where your time is spent.
  • Add Notes: Brief notes can provide context to your time entries later.
  • Review Reports: Periodically check your reports to understand your work patterns and identify areas for improvement or better estimation.
×

Log Time Entry

${projectTagHTML}${entry.taskDescription}

${rtwlFormatTimeForDisplay(entry.startTimeISO)} - ${rtwlFormatTimeForDisplay(entry.endTimeISO)} (Duration: ${rtwlFormatDuration(entry.durationMinutes)})

${entry.notes ? `

${entry.notes.replace(/\n/g, '
')}

` : ''}
`; rtwlDailyLogEntriesListEl.appendChild(itemEl); }); } function rtwlUpdateQuickStartButtons() { if(!rtwlQuickStartButtonsEl) return; rtwlQuickStartButtonsEl.innerHTML = ''; const recentTaskMap = new Map(); let count = 0; for (let i = 0; i < rtwlTimeEntries.length && count < 4; i++) { const entry = rtwlTimeEntries[i]; const key = entry.projectId ? entry.projectId : `adhoc-${entry.taskDescription}`; // Use task desc for adhoc key if (!recentTaskMap.has(key)) { let displayName = entry.taskDescription; if(entry.projectId){ const proj = rtwlProjects.find(p=>p.id===entry.projectId); if(proj) displayName = proj.name; // Prefer project name if available } recentTaskMap.set(key, { projectId: entry.projectId, taskDescription: entry.taskDescription, // Store original adhoc desc if needed display: displayName }); count++; } } if (recentTaskMap.size === 0) { rtwlQuickStartButtonsEl.innerHTML = '

Log time for projects to see them here for quick starting.

'; return; } recentTaskMap.forEach((taskInfo) => { const btn = document.createElement('button'); btn.className = 'rtwl-button rtwl-button-secondary'; btn.textContent = `Start: ${taskInfo.display.substring(0,25)}${taskInfo.display.length > 25 ? '...' : '' }`; btn.title = `Start timer for ${taskInfo.display}`; btn.onclick = () => { if (taskInfo.projectId) { rtwlTaskSelectTimerEl.value = taskInfo.projectId; if(rtwlAdhocTaskNameGroupEl) rtwlAdhocTaskNameGroupEl.style.display = 'none'; } else { rtwlTaskSelectTimerEl.value = 'adhoc'; if(rtwlAdhocTaskNameTimerEl) rtwlAdhocTaskNameTimerEl.value = taskInfo.taskDescription; // Use original taskDescription for adhoc if(rtwlAdhocTaskNameGroupEl) rtwlAdhocTaskNameGroupEl.style.display = 'block'; } rtwlStartNewTimer(); }; rtwlQuickStartButtonsEl.appendChild(btn); }); } // --- Reports --- window.rtwlGenerateReport = function() { /* ... same as stl, with rtwl prefixes ... */ if (!rtwlReportOutputAreaEl || !rtwlReportPeriodSelectEl || !rtwlReportPeriodDisplayEl || !rtwlReportSummaryEl || !rtwlReportDetailsTableBodyEl) return; const period = rtwlReportPeriodSelectEl.value; let startDateObj, endDateObj; // Use actual Date objects for filtering const todayForReport = new Date(2025, 4, 14); todayForReport.setHours(0,0,0,0); let periodDisplayString = ""; switch(period) { case 'today': startDateObj = new Date(todayForReport); endDateObj = new Date(todayForReport); endDateObj.setHours(23,59,59,999); periodDisplayString = "Today (" + todayForReport.toLocaleDateString() + ")"; break; case 'this_week': startDateObj = new Date(todayForReport); const dayOfWeek = startDateObj.getDay(); const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; startDateObj.setDate(startDateObj.getDate() + diffToMonday); endDateObj = new Date(startDateObj); endDateObj.setDate(startDateObj.getDate() + 6); endDateObj.setHours(23,59,59,999); periodDisplayString = `This Week (${startDateObj.toLocaleDateString()} - ${endDateObj.toLocaleDateString()})`; break; case 'this_month': startDateObj = new Date(todayForReport.getFullYear(), todayForReport.getMonth(), 1); endDateObj = new Date(todayForReport.getFullYear(), todayForReport.getMonth() + 1, 0); endDateObj.setHours(23,59,59,999); periodDisplayString = todayForReport.toLocaleDateString(undefined, {month: 'long', year: 'numeric'}); break; case 'custom_range': if (!rtwlReportStartDateInputEl.value || !rtwlReportEndDateInputEl.value) { alert("Please select a start and end date for custom range."); return; } startDateObj = new Date(rtwlReportStartDateInputEl.value + "T00:00:00"); endDateObj = new Date(rtwlReportEndDateInputEl.value + "T23:59:59"); if (startDateObj > endDateObj) { alert("End date must be after start date."); return; } periodDisplayString = `Custom (${startDateObj.toLocaleDateString()} - ${endDateObj.toLocaleDateString()})`; break; } if(rtwlReportPeriodDisplayEl) rtwlReportPeriodDisplayEl.textContent = periodDisplayString; const entriesInPeriod = rtwlTimeEntries.filter(entry => { const entryStart = new Date(entry.startTimeISO); return entryStart >= startDateObj && entryStart <= endDateObj; }); let totalLoggedMinutes = 0; const projectTime = {}; if(rtwlReportDetailsTableBodyEl) rtwlReportDetailsTableBodyEl.innerHTML = ''; entriesInPeriod.sort((a,b) => new Date(a.startTimeISO) - new Date(b.startTimeISO)).forEach(entry => { totalLoggedMinutes += entry.durationMinutes; const project = entry.projectId ? rtwlProjects.find(p => p.id === entry.projectId) : null; // For summary, group by project name if available, otherwise use taskDescription as key const summaryKey = project ? project.name : (entry.taskDescription || "Ad-hoc"); projectTime[summaryKey] = (projectTime[summaryKey] || 0) + entry.durationMinutes; if(rtwlReportDetailsTableBodyEl){ const row = rtwlReportDetailsTableBodyEl.insertRow(); row.insertCell().textContent = new Date(entry.startTimeISO).toLocaleDateString(); row.insertCell().textContent = project ? project.name : "Ad-hoc"; row.insertCell().textContent = entry.taskDescription; row.insertCell().textContent = rtwlFormatTimeForDisplay(entry.startTimeISO); row.insertCell().textContent = rtwlFormatTimeForDisplay(entry.endTimeISO); row.insertCell().textContent = rtwlFormatDuration(entry.durationMinutes); row.insertCell().textContent = entry.notes || ""; } }); let summaryHTML = `

Total Time Logged: ${rtwlFormatDuration(totalLoggedMinutes)}

Time by Project/Task:

    `; Object.keys(projectTime).sort((a,b) => projectTime[b] - projectTime[a]).forEach(projName => { summaryHTML += `
  • ${projName}: ${rtwlFormatDuration(projectTime[projName])}
  • `; }); summaryHTML += `
`; if(rtwlReportSummaryEl) rtwlReportSummaryEl.innerHTML = summaryHTML; if(rtwlReportOutputAreaEl) rtwlReportOutputAreaEl.style.display = 'block'; if(rtwlDownloadPdfBtn) rtwlDownloadPdfBtn.style.display = entriesInPeriod.length > 0 ? 'block' : 'none'; } function handlePdfDownload() { /* ... same as stlHandlePdfDownload, with rtwl prefix ... */ const periodText = rtwlReportPeriodDisplayEl.textContent; const totalTimeText = rtwlReportSummaryEl.querySelector('p strong')?.parentElement.textContent || "Total Time Logged: N/A"; const projectSummaryData = []; const projectLis = rtwlReportSummaryEl.querySelectorAll('ul li'); projectLis.forEach(li => projectSummaryData.push(li.textContent)); const detailTableRowsData = []; const detailTableHtmlRows = rtwlReportDetailsTableBodyEl.querySelectorAll('tr'); detailTableHtmlRows.forEach(row => { const cells = Array.from(row.cells).map(cell => cell.textContent); detailTableRowsData.push(cells); }); if(detailTableRowsData.length === 0 && totalTimeText.includes("N/A") && projectSummaryData.length === 0) { alert("No data to export for the selected report period."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF(); doc.setFontSize(18); doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--rtwl-primary-color').trim()); doc.text("Real-Time Work Log Report", 14, 22); doc.setFontSize(12); doc.setTextColor(100); doc.text(`Period: ${periodText}`, 14, 30); doc.text(totalTimeText, 14, 38); let yPos = 46; if (projectSummaryData.length > 0) { doc.setFontSize(11); doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--rtwl-text-color').trim()); doc.text("Summary by Project/Task:", 14, yPos); yPos += 7; doc.setFontSize(9); projectSummaryData.forEach(item => { if (yPos > 270) { doc.addPage(); yPos = 20; } doc.text(`\u2022 ${item}`, 16, yPos); yPos += 5; }); yPos += 5; } if (detailTableRowsData.length > 0) { if (yPos > 250) { doc.addPage(); yPos = 20; } doc.setFontSize(11); doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--rtwl-text-color').trim()); doc.text("Detailed Log:", 14, yPos); yPos += 7; doc.autoTable({ head: [['Date', 'Project', 'Task Description', 'Start', 'End', 'Duration', 'Notes']], body: detailTableRowsData, startY: yPos, theme: 'striped', headStyles: { fillColor: getComputedStyle(document.documentElement).getPropertyValue('--rtwl-primary-color').trim() }, styles: { fontSize: 8, cellPadding: 1.5, overflow: 'linebreak' }, columnStyles: { 1: { cellWidth: 30 }, 2: { cellWidth: 50 }, 6: { cellWidth: 'auto'} } }); } doc.save(`TimeLogReport_${periodText.replace(/[^a-zA-Z0-9]/g, '_')}.pdf`); } window.rtwlCloseModal = (modalId) => { /* ... */ const modal = document.getElementById(modalId); if(modal) modal.style.display = 'none'; // Reset timer-related modal fields if active timer was cleared during modal interaction. if (!rtwlActiveTimer && modalId === 'rtwlTimeEntryModal' && rtwlTimeEntryModalTitleEl.textContent.includes("Confirm & Log Timed Entry")) { if(rtwlTaskSelectTimerEl) rtwlTaskSelectTimerEl.value = ""; if(rtwlAdhocTaskNameTimerEl) rtwlAdhocTaskNameTimerEl.value = ""; if(rtwlAdhocTaskNameGroupEl) rtwlAdhocTaskNameGroupEl.style.display = "none"; } } // --- DOMContentLoaded --- document.addEventListener('DOMContentLoaded', () => { // Assign DOM elements rtwlTaskSelectTimerEl = document.getElementById('rtwlTaskSelectTimer'); rtwlAdhocTaskNameGroupEl = document.getElementById('rtwlAdhocTaskNameGroup'); rtwlAdhocTaskNameTimerEl = document.getElementById('rtwlAdhocTaskNameTimer'); rtwlStartNewTimerButtonEl = document.getElementById('rtwlStartNewTimerButton'); rtwlActiveTimerAreaEl = document.getElementById('rtwlActiveTimerArea'); rtwlActiveTaskNameDisplayEl = document.getElementById('rtwlActiveTaskNameDisplay'); rtwlActiveTimerDisplayEl = document.getElementById('rtwlActiveTimerDisplay'); rtwlPauseResumeTimerButtonEl = document.getElementById('rtwlPauseResumeTimerButton'); rtwlStopTimerButtonEl = document.getElementById('rtwlStopTimerButton'); rtwlDiscardTimerButtonEl = document.getElementById('rtwlDiscardTimerButton'); rtwlCurrentDateDisplayEl = document.getElementById('rtwlCurrentDateDisplay'); rtwlLogViewDateEl = document.getElementById('rtwlLogViewDate'); rtwlDailyLogEntriesListEl = document.getElementById('rtwlDailyLogEntriesList'); rtwlQuickStartButtonsEl = document.getElementById('rtwlQuickStartButtons'); rtwlAddProjectForm = document.getElementById('rtwlAddProjectForm'); rtwlProjectNameInputEl = document.getElementById('rtwlProjectNameInput'); rtwlProjectColorInputEl = document.getElementById('rtwlProjectColorInput'); rtwlProjectsListEl = document.getElementById('rtwlProjectsList'); rtwlReportPeriodSelectEl = document.getElementById('rtwlReportPeriodSelect'); rtwlCustomDateRangePickerEl = document.getElementById('rtwlCustomDateRangePicker'); rtwlReportStartDateInputEl = document.getElementById('rtwlReportStartDate'); rtwlReportEndDateInputEl = document.getElementById('rtwlReportEndDate'); rtwlReportOutputAreaEl = document.getElementById('rtwlReportOutputArea'); rtwlReportPeriodDisplayEl = document.getElementById('rtwlReportPeriodDisplay'); rtwlReportSummaryEl = document.getElementById('rtwlReportSummary'); rtwlReportDetailsTableBodyEl = document.querySelector('#rtwlReportDetailsTable tbody'); // Corrected selector rtwlTimeEntryModalEl = document.getElementById('rtwlTimeEntryModal'); rtwlTimeEntryModalTitleEl = document.getElementById('rtwlTimeEntryModalTitle'); rtwlTimeEntryForm = document.getElementById('rtwlTimeEntryForm'); rtwlEntryIdInput = document.getElementById('rtwlEntryId'); rtwlEntryTaskDescriptionInput = document.getElementById('rtwlEntryTaskDescription'); rtwlEntryProjectIdSelectEl = document.getElementById('rtwlEntryProjectId'); rtwlEntryStartDateInput = document.getElementById('rtwlEntryStartDate'); rtwlEntryStartTimeInput = document.getElementById('rtwlEntryStartTime'); rtwlEntryEndDateInput = document.getElementById('rtwlEntryEndDate'); rtwlEntryEndTimeInput = document.getElementById('rtwlEntryEndTime'); rtwlEntryDurationMinutesInput = document.getElementById('rtwlEntryDurationMinutes'); rtwlEntryNotesInput = document.getElementById('rtwlEntryNotes'); rtwlDownloadPdfBtn = document.getElementById('rtwlDownloadPdfButton'); // Check if all critical elements are found const criticalElements = [rtwlTaskSelectTimerEl, rtwlAddProjectForm, rtwlTimeEntryForm, rtwlLogViewDateEl, rtwlReportPeriodSelectEl]; if (criticalElements.some(el => !el)) { console.error("RealTimeWorkHourLoggingTool: Not all critical DOM elements were found. Tool may not function correctly."); alert("Error initializing the Time Logger: Some UI elements are missing. Please ensure the HTML code is complete."); return; // Stop further initialization if critical parts are missing } // Attach event listeners if(rtwlAddProjectForm) rtwlAddProjectForm.addEventListener('submit', handleAddProjectFormSubmit); if(rtwlTimeEntryForm) rtwlTimeEntryForm.addEventListener('submit', handleTimeEntryFormSubmit); if(rtwlTaskSelectTimerEl) rtwlTaskSelectTimerEl.addEventListener('change', () => { if(rtwlAdhocTaskNameGroupEl) rtwlAdhocTaskNameGroupEl.style.display = rtwlTaskSelectTimerEl.value === 'adhoc' ? 'block' : 'none'; }); if(rtwlStartNewTimerButtonEl) rtwlStartNewTimerButtonEl.addEventListener('click', rtwlStartNewTimer); if(rtwlPauseResumeTimerButtonEl) rtwlPauseResumeTimerButtonEl.addEventListener('click', rtwlTogglePauseResumeTimer); if(rtwlStopTimerButtonEl) rtwlStopTimerButtonEl.addEventListener('click', rtwlStopAndLogTimer); if(rtwlDiscardTimerButtonEl) rtwlDiscardTimerButtonEl.addEventListener('click', rtwlDiscardActiveTimer); if(rtwlLogViewDateEl) rtwlLogViewDateEl.addEventListener('change', rtwlRenderDailyLogEntries); if(rtwlReportPeriodSelectEl) rtwlReportPeriodSelectEl.addEventListener('change', () => { if(rtwlCustomDateRangePickerEl) rtwlCustomDateRangePickerEl.style.display = rtwlReportPeriodSelectEl.value === 'custom_range' ? 'grid' : 'none'; }); if(rtwlDownloadPdfBtn) rtwlDownloadPdfBtn.addEventListener('click', handlePdfDownload); [rtwlEntryStartDateInput, rtwlEntryStartTimeInput, rtwlEntryEndDateInput, rtwlEntryEndTimeInput].forEach(el => { if(el) el.addEventListener('change', rtwlCalculateDurationManual); }); // Removed rtwlAdjustEndTimeManual listener from duration input as it's now read-only in modal const today = new Date(2025, 4, 14); // May 14, 2025 if(rtwlLogViewDateEl) rtwlLogViewDateEl.value = rtwlFormatDateKey(today); if(rtwlCurrentDateDisplayEl) rtwlCurrentDateDisplayEl.textContent = today.toLocaleDateString(); rtwlLoadData(); const firstTabButton = document.querySelector('#realTimeWorkHourLoggingTool .rtwl-tab-button'); if (firstTabButton) firstTabButton.click(); window.addEventListener('click', function(event) { if (event.target.classList.contains('rtwl-modal')) { rtwlCloseModal(event.target.id); } }); });
Scroll to Top