Smart Time Logger

Smart Time Logger

Live Timer

Quick Start Recent Tasks:

Log time for recent projects to see them here.


Daily Log for Date

Manage Projects/Common Tasks

Generate Time Report

Smart Time 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.
  • Use "Pause" and "Resume" as needed. The timer accounts for paused duration.
  • When finished with the task or segment, 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 blank for general tasks), set Start Date/Time, and End Date/Time. The duration will be auto-calculated. You can also adjust duration which will then adjust the end time. Add optional notes.
3. Interrupted Timer Recovery
If you accidentally close your browser or refresh the page while a timer is running, the tool will attempt to detect this when you next open it. You'll be prompted to log the potentially elapsed time, adjust it, or discard it, so you don't lose track of your work.
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 (available after a report is generated) to save your time analysis for records, billing, or sharing.
Tips for Effective Time Logging:
  • Log Consistently: The more consistently you log your time, the more accurate and useful your analysis will be.
  • Be Specific (Enough): Use descriptive names for tasks or detailed notes so you can recall what you worked on.
  • Categorize with Projects: Using projects helps group related work for better analysis.
  • Review Your Reports: Periodically check your time reports to understand how you're spending your time. This can reveal patterns, inefficiencies, or confirm that you're focusing on priorities.
×

Log Time Entry

'; if(stlCurrentDateDisplayEl) stlCurrentDateDisplayEl.textContent = 'N/A'; return; } if(stlCurrentDateDisplayEl) stlCurrentDateDisplayEl.textContent = new Date(selectedDateKey + "T00:00:00").toLocaleDateString(); const entriesForDay = stlTimeEntries.filter(entry => stlFormatDateKey(new Date(entry.startTimeISO)) === selectedDateKey ).sort((a,b) => new Date(a.startTimeISO).getTime() - new Date(b.startTimeISO).getTime()); stlDailyLogEntriesListEl.innerHTML = ''; if (entriesForDay.length === 0) { stlDailyLogEntriesListEl.innerHTML = '

No time logged for this day.

'; return; } entriesForDay.forEach(entry => { const itemEl = document.createElement('div'); itemEl.className = 'stl-entry-item'; const project = entry.projectId ? stlProjects.find(p => p.id === entry.projectId) : null; const projectTagHTML = project ? `${project.name}` : ''; itemEl.innerHTML = `

${projectTagHTML}${entry.taskDescription}

${stlFormatTimeForDisplay(entry.startTimeISO)} - ${stlFormatTimeForDisplay(entry.endTimeISO)} (Duration: ${stlFormatDuration(entry.durationMinutes)})

${entry.notes ? `

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

` : ''}
`; stlDailyLogEntriesListEl.appendChild(itemEl); }); } // --- Reports --- window.stlGenerateReport = function() { /* ... similar to Personal Work Hour Analyzer ... */ if (!stlReportOutputAreaEl || !stlReportPeriodSelectEl || !stlReportPeriodDisplayEl || !stlReportSummaryEl || !stlReportDetailsTableBodyEl) return; const period = stlReportPeriodSelectEl.value; let startDate, endDate; const todayForReport = new Date(2025, 4, 14); // May 14, 2025 for consistency in reporting periods todayForReport.setHours(0,0,0,0); switch(period) { case 'today': startDate = new Date(todayForReport); endDate = new Date(todayForReport); endDate.setHours(23,59,59,999); stlReportPeriodDisplayEl.textContent = "Today (" + todayForReport.toLocaleDateString() + ")"; break; case 'this_week': startDate = new Date(todayForReport); const dayOfWeek = startDate.getDay(); const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; startDate.setDate(startDate.getDate() + diffToMonday); endDate = new Date(startDate); endDate.setDate(startDate.getDate() + 6); endDate.setHours(23,59,59,999); stlReportPeriodDisplayEl.textContent = `This Week (${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()})`; break; case 'this_month': startDate = new Date(todayForReport.getFullYear(), todayForReport.getMonth(), 1); endDate = new Date(todayForReport.getFullYear(), todayForReport.getMonth() + 1, 0); endDate.setHours(23,59,59,999); stlReportPeriodDisplayEl.textContent = todayForReport.toLocaleDateString(undefined, {month: 'long', year: 'numeric'}); break; case 'custom_range': if (!stlReportStartDateInputEl.value || !stlReportEndDateInputEl.value) { alert("Please select a start and end date for custom range."); return; } startDate = new Date(stlReportStartDateInputEl.value + "T00:00:00"); endDate = new Date(stlReportEndDateInputEl.value + "T23:59:59"); if (startDate > endDate) { alert("End date must be after start date."); return; } stlReportPeriodDisplayEl.textContent = `Custom (${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()})`; break; } const entriesInPeriod = stlTimeEntries.filter(entry => { const entryStart = new Date(entry.startTimeISO); return entryStart >= startDate && entryStart <= endDate; }); let totalLoggedMinutes = 0; const projectTime = {}; stlReportDetailsTableBodyEl.innerHTML = ''; entriesInPeriod.sort((a,b) => new Date(a.startTimeISO) - new Date(b.startTimeISO)).forEach(entry => { totalLoggedMinutes += entry.durationMinutes; const project = entry.projectId ? stlProjects.find(p => p.id === entry.projectId) : null; const projectNameForReport = project ? project.name : (entry.taskDescription.length > 30 ? entry.taskDescription.substring(0,27)+"..." : entry.taskDescription); // Fallback to task desc if no project if (projectNameForReport) { projectTime[projectNameForReport] = (projectTime[projectNameForReport] || 0) + entry.durationMinutes; } const row = stlReportDetailsTableBodyEl.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 = stlFormatTimeForDisplay(entry.startTimeISO); row.insertCell().textContent = stlFormatTimeForDisplay(entry.endTimeISO); row.insertCell().textContent = stlFormatDuration(entry.durationMinutes); row.insertCell().textContent = entry.notes || ""; }); let summaryHTML = `

Total Time Logged: ${stlFormatDuration(totalLoggedMinutes)}

Time by Project/Task:

    `; Object.keys(projectTime).sort((a,b) => projectTime[b] - projectTime[a]).forEach(projName => { summaryHTML += `
  • ${projName}: ${stlFormatDuration(projectTime[projName])}
  • `; }); summaryHTML += `
`; stlReportSummaryEl.innerHTML = summaryHTML; stlReportOutputAreaEl.style.display = 'block'; if(stlDownloadPdfBtn) stlDownloadPdfBtn.style.display = entriesInPeriod.length > 0 ? 'block' : 'none'; } // --- PDF Export --- function handlePdfDownload() { /* ... similar to Work Hour Analyzer PDF ... */ const periodText = stlReportPeriodDisplayEl.textContent; const totalTimeText = stlReportSummaryEl.querySelector('p strong')?.parentElement.textContent || "Total Time Logged: N/A"; const projectSummaryData = []; const projectLis = stlReportSummaryEl.querySelectorAll('ul li'); projectLis.forEach(li => projectSummaryData.push(li.textContent)); const detailTableRowsData = []; const detailTableHtmlRows = stlReportDetailsTableBodyEl.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")) { alert("No detailed data to export for the selected report period."); } const { jsPDF } = window.jspdf; const doc = new jsPDF(); doc.setFontSize(18); doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--stl-primary-color').trim()); doc.text("Smart Time 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('--stl-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('--stl-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('--stl-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`); } // Add placeholders for modal closing for safety, will be assigned if modals exist window.stlCloseModal = (modalId) => { const modal = document.getElementById(modalId); if(modal) modal.style.display = 'none'; }
Scroll to Top