Work Hour Tracking for Freelancers

Work Hour Tracking for Freelancers

Settings

Add New Project

Log Work Hours

Time Log

Generate Report

Report Period: ${start.toLocaleDateString()} to ${end.toLocaleDateString()}

`; for (const projectName in reportByProject) { const data = reportByProject[projectName]; totalHours += data.hours; totalEarnings += data.earnings; reportHTML += `

${projectName}

${data.entries.map(e => ` `).join('')}
${new Date(e.date).toLocaleDateString()} ${e.description} ${e.duration.toFixed(2)} hrs
Project Total ${data.hours.toFixed(2)} hrs ($${data.earnings.toFixed(2)})
`; } reportHTML += `

Grand Total Hours: ${totalHours.toFixed(2)}

Grand Total Earnings: $${totalEarnings.toFixed(2)}

`; reportPreviewOutput.innerHTML = reportHTML; } reportPreviewContainer.style.display = 'block'; downloadPdfBtn.disabled = false; downloadPdfBtn.classList.remove('bg-gray-600', 'hover:bg-gray-700'); downloadPdfBtn.classList.add('bg-green-600', 'hover:bg-green-700'); reportPreviewContainer.scrollIntoView({ behavior: 'smooth' }); }; const generatePDF = () => { if (downloadPdfBtn.disabled) return; const { jsPDF } = jspdf; const doc = new jsPDF(); const startDate = new Date(reportStartInput.value); const endDate = new Date(reportEndInput.value); const filteredEntries = state.timeEntries.filter(entry => { const entryDate = new Date(entry.date); return entryDate >= startDate && entryDate <= endDate; }); const reportByProject = {}; filteredEntries.forEach(entry => { const project = state.projects.find(p => p.id === entry.projectId); const projectName = project ? project.name : 'Uncategorized'; if (!reportByProject[projectName]) { reportByProject[projectName] = { hours: 0, earnings: 0, entries: [] }; } reportByProject[projectName].hours += entry.duration; reportByProject[projectName].earnings += entry.earnings; reportByProject[projectName].entries.push(entry); }); let totalHours = 0; let totalEarnings = 0; doc.setFontSize(18); doc.text('Work Hours Report', 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Period: ${startDate.toLocaleDateString()} to ${endDate.toLocaleDateString()}`, 14, 30); let lastY = 35; for (const projectName in reportByProject) { const data = reportByProject[projectName]; totalHours += data.hours; totalEarnings += data.earnings; doc.setFontSize(14); doc.setTextColor(40); doc.text(projectName, 14, lastY + 10); lastY += 10; const tableBody = data.entries.map(e => [ new Date(e.date).toLocaleDateString(), e.description, `${e.duration.toFixed(2)} hrs` ]); tableBody.push([{ content: `Project Total: ${data.hours.toFixed(2)} hrs ($${data.earnings.toFixed(2)})`, colSpan: 3, styles: { halign: 'right', fontStyle: 'bold' } }]); doc.autoTable({ startY: lastY + 2, head: [['Date', 'Description', 'Duration']], body: tableBody, theme: 'striped' }); lastY = doc.autoTable.previous.finalY; } doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.text(`Grand Total Hours: ${totalHours.toFixed(2)}`, 14, lastY + 15); doc.text(`Grand Total Earnings: $${totalEarnings.toFixed(2)}`, 14, lastY + 22); doc.save(`Work-Report-${reportStartInput.value}-to-${reportEndInput.value}.pdf`); }; // --- Event Listeners --- defaultRateInput.addEventListener('change', () => { state.defaultRate = parseFloat(defaultRateInput.value) || 50; }); addProjectBtn.addEventListener('click', addProject); addEntryBtn.addEventListener('click', addTimeEntry); timeLogContainer.addEventListener('click', (e) => { if (e.target.classList.contains('remove-entry-btn')) { const id = parseInt(e.target.dataset.id); removeTimeEntry(id); } }); generateReportBtn.addEventListener('click', generateReport); downloadPdfBtn.addEventListener('click', generatePDF); // --- Initial Setup --- const setupInitialData = () => { const today = new Date(); const yesterday = new Date(today); yesterday.setDate(today.getDate() - 1); entryDateInput.value = today.toISOString().split('T')[0]; reportEndInput.value = today.toISOString().split('T')[0]; const lastWeek = new Date(today); lastWeek.setDate(today.getDate() - 7); reportStartInput.value = lastWeek.toISOString().split('T')[0]; state.projects = [ { id: 1, name: 'Client A - Marketing Site', rate: 65 }, { id: 2, name: 'Client B - App Backend', rate: null } ]; state.timeEntries = [ { id: 1, projectId: 1, date: yesterday.toISOString().split('T')[0], start: '09:00', end: '11:30', description: 'Homepage design mockups', duration: 2.5, earnings: 2.5 * 65 }, { id: 2, projectId: 2, date: yesterday.toISOString().split('T')[0], start: '13:00', end: '16:00', description: 'API endpoint development', duration: 3, earnings: 3 * 50 }, { id: 3, projectId: 1, date: today.toISOString().split('T')[0], start: '10:00', end: '12:00', description: 'Contact form implementation', duration: 2, earnings: 2 * 65 } ]; renderProjects(); renderTimeLog(); }; setupInitialData(); });
Scroll to Top