14c880123c
Expo app with dashboard, time clock, invoices, and settings — native tabs, glass UI, theme-aware components, and iOS Live Activities. Co-authored-by: Cursor <cursoragent@cursor.com>
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
export type ClockOutOutcome =
|
|
| "linked_to_invoice"
|
|
| "saved_no_invoice"
|
|
| "saved_no_client"
|
|
| "zero_hours";
|
|
|
|
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.";
|
|
}
|
|
}
|