Unify time tracking on server-backed entries with updateRunning.

Consolidates dashboard and invoice timers onto shared time-entry APIs so clock-in state, invoice linking, and clock-out flow stay consistent across surfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 22:36:39 -04:00
co-authored by Cursor
parent 81a0ce33a4
commit 5c28b33e9f
15 changed files with 730 additions and 373 deletions
+2
View File
@@ -8,6 +8,7 @@ import {
BarChart2,
Shield,
RefreshCw,
Clock,
} from "lucide-react";
export interface NavLink {
@@ -26,6 +27,7 @@ export const navigationConfig: NavSection[] = [
title: "Main",
links: [
{ name: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
{ name: "Time clock", href: "/dashboard/time-clock", icon: Clock },
{ name: "Clients", href: "/dashboard/clients", icon: Users },
{ name: "Businesses", href: "/dashboard/businesses", icon: Building },
{ name: "Invoices", href: "/dashboard/invoices", icon: FileText },
+41
View File
@@ -0,0 +1,41 @@
export type ClockOutOutcome =
| "linked_to_invoice"
| "saved_no_invoice"
| "saved_no_client"
| "zero_hours";
export function computeTrackedHours(startedAt: Date, endedAt: Date): number {
const seconds = Math.floor((endedAt.getTime() - startedAt.getTime()) / 1000);
return Math.max(0.25, Math.ceil(seconds / 900) * 0.25);
}
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(":");
}
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 invoice ${label}`;
}
return `Added ${input.hours}h to invoice`;
case "saved_no_invoice":
return `Saved ${input.hours}h — no open invoice found for this client. Pick an invoice on the time clock or create one.`;
case "saved_no_client":
return `Saved ${input.hours}h — assign a client and invoice to bill this time.`;
case "zero_hours":
return "Timer stopped (less than minimum billable increment).";
}
}