import { getEntries, getSettings, formatDuration, formatDate } from '../storage.js' import type { TimeEntry } from '../types.js' import { generateInvoicePDF } from '../pdf.js' const els = { clientInput: document.getElementById('invoice-client') as HTMLInputElement, numberInput: document.getElementById('invoice-number') as HTMLInputElement, dateFrom: document.getElementById('date-from') as HTMLInputElement, dateTo: document.getElementById('date-to') as HTMLInputElement, entriesList: document.getElementById('matching-entries') as HTMLDivElement, invoiceTotal: document.getElementById('invoice-total') as HTMLDivElement, generateBtn: document.getElementById('generate-invoice') as HTMLButtonElement, } let currentEntries: TimeEntry[] = [] async function init(): Promise { // Set default date range to current month const now = new Date() const firstDay = new Date(now.getFullYear(), now.getMonth(), 1) els.dateFrom.value = toISODate(firstDay) els.dateTo.value = toISODate(now) const settings = await getSettings() els.clientInput.value = settings.defaultClient els.clientInput.addEventListener('input', debounce(updateMatching, 300)) els.dateFrom.addEventListener('change', updateMatching) els.dateTo.addEventListener('change', updateMatching) els.generateBtn.addEventListener('click', async () => { if (currentEntries.length === 0) { alert('No entries match the selected criteria.') return } const settings = await getSettings() const blob = generateInvoicePDF( currentEntries, settings, els.clientInput.value, els.numberInput.value || `INV-${Date.now().toString().slice(-6)}`, ) const fromDate = els.dateFrom.value || 'all' const toDate = els.dateTo.value || 'all' downloadBlob(blob, `invoice-${els.clientInput.value}-${fromDate}-to-${toDate}.pdf`) }) await updateMatching() } async function updateMatching(): Promise { const client = els.clientInput.value.trim().toLowerCase() const from = els.dateFrom.valueAsDate const to = els.dateTo.valueAsDate const allEntries = await getEntries() const settings = await getSettings() currentEntries = allEntries.filter((e) => { if (e.endTime === null) return false if (client && !e.client.toLowerCase().includes(client)) return false if (from && e.startTime < from.getTime()) return false if (to) { const endOfDay = new Date(to.getFullYear(), to.getMonth(), to.getDate(), 23, 59, 59).getTime() if (e.startTime > endOfDay) return false } return true }).sort((a, b) => a.startTime - b.startTime) if (currentEntries.length === 0) { els.entriesList.innerHTML = '

No entries match the selected criteria.

' els.invoiceTotal.textContent = '' return } const totalMs = currentEntries.reduce((sum, e) => sum + e.duration, 0) const totalHours = totalMs / 3600000 const totalAmount = totalHours * settings.hourlyRate els.entriesList.innerHTML = currentEntries.map((entry) => { const hours = entry.duration / 3600000 const amount = hours * settings.hourlyRate return `
${settings.currency}${amount.toFixed(2)}
` }).join('') els.invoiceTotal.textContent = `Total: ${settings.currency}${totalAmount.toFixed(2)} (${totalHours.toFixed(1)} hours @ ${settings.currency}${settings.hourlyRate}/hr)` } function toISODate(d: Date): string { return d.toISOString().slice(0, 10) } function escapeHtml(text: string): string { const div = document.createElement('div') div.textContent = text return div.innerHTML } function downloadBlob(blob: Blob, filename: string): void { const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = filename a.click() URL.revokeObjectURL(url) } function debounce void>(fn: T, ms: number): (...args: Parameters) => void { let timeout: ReturnType return (...args: Parameters) => { clearTimeout(timeout) timeout = setTimeout(() => fn(...args), ms) } } init().catch(console.error)