Files
soconnor 06bc91ac13 Redesign mobile time clock, add shortcuts, and improve account management.
Add iOS Shortcuts/Siri intents, local send-reminder notifications, stable
client picker with last-client defaults, account refresh/remove, and softer
session handling on unauthorized API responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 16:06:17 -04:00

65 lines
2.1 KiB
TypeScript

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 resolveEffectiveHourlyRate(
rateText: string,
clientDefaultRate?: number | null,
): number | null {
const parsed = rateText.trim() ? Number(rateText) : null;
if (parsed != null && !Number.isNaN(parsed) && parsed >= 0) return parsed;
if (clientDefaultRate != null && clientDefaultRate >= 0) return clientDefaultRate;
return null;
}
export function startedAtFromMinutesAgo(minutes: number): Date {
return new Date(Date.now() - minutes * 60_000);
}
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.";
}
}