`;
for (const projectName in reportByProject) {
const data = reportByProject[projectName];
totalHours += data.hours;
totalEarnings += data.earnings;
reportHTML += `
${data.entries.map(e => `
`;
}
reportHTML += `
`;
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();
});
${projectName}
| ${new Date(e.date).toLocaleDateString()} | ${e.description} | ${e.duration.toFixed(2)} hrs |
| Project Total | ${data.hours.toFixed(2)} hrs ($${data.earnings.toFixed(2)}) | |
Grand Total Hours: ${totalHours.toFixed(2)}
Grand Total Earnings: $${totalEarnings.toFixed(2)}
