export type ClockOutOutcome = | "linked_to_invoice" | "saved_no_invoice" | "saved_no_client" | "zero_hours"; export const DEFAULT_CLOCK_DESCRIPTION = "Clock In"; export function resolveClockDescription(description: string | null | undefined): string { const trimmed = description?.trim(); return trimmed || DEFAULT_CLOCK_DESCRIPTION; } export function formatElapsedSeconds(seconds: number): string { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; return [h, m, s].map((v) => String(v).padStart(2, "0")).join(":"); } /** Hours and minutes only — for Live Activity / compact displays. */ export function formatElapsedHoursMinutes(seconds: number): string { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); return `${h}:${String(m).padStart(2, "0")}`; } export function describeClockOutOutcome(input: { outcome: ClockOutOutcome; hours: number; rate: number; invoice?: { invoicePrefix: string; invoiceNumber: string } | null; }): string { const amount = input.hours * input.rate; switch (input.outcome) { case "linked_to_invoice": if (input.invoice) { const label = `${input.invoice.invoicePrefix}${input.invoice.invoiceNumber}`; return `Added ${input.hours}h @ $${input.rate}/hr ($${amount.toFixed(2)}) to ${label}`; } return `Added ${input.hours}h to invoice`; case "saved_no_invoice": return `Saved ${input.hours}h — no open invoice for this client.`; case "saved_no_client": return `Saved ${input.hours}h — pick a client and invoice to bill.`; case "zero_hours": return "Timer stopped."; } }