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:
@@ -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 },
|
||||
|
||||
@@ -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).";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user