From 07fe4df9b397d803c4e98a97b4b20c714bf1348d Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 15:44:38 +0000 Subject: [PATCH 1/2] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/timer/header-timer.tsx | 38 +++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/components/timer/header-timer.tsx b/src/components/timer/header-timer.tsx index 6e8e2ca..fd29065 100644 --- a/src/components/timer/header-timer.tsx +++ b/src/components/timer/header-timer.tsx @@ -34,6 +34,13 @@ interface CaseOption { default_hourly_rate: number | null; } +interface FeeItem { + id: string; + name: string; + description: string | null; + amount: number; +} + export function HeaderTimer() { const { user } = useAuth(); const { timer, elapsedMs, isRunning, hasStarted, setClient, setCase, setDescription, start, pause, resume, reset, consume } = useTimer(); @@ -44,22 +51,33 @@ export function HeaderTimer() { const [profileRate, setProfileRate] = useState(null); const [saving, setSaving] = useState(false); const [billable, setBillable] = useState(true); + const [feeItems, setFeeItems] = useState([]); + const [feeItemId, setFeeItemId] = useState(""); // Load clients + cases the user can access (RLS handles filtering) useEffect(() => { if (!user?.id) return; (async () => { - const [{ data: cs }, { data: ks }, { data: prof }] = await Promise.all([ + const [{ data: cs }, { data: ks }, { data: prof }, { data: fees }] = await Promise.all([ supabase.from("clients").select("id, name").order("name"), supabase .from("cases") .select("id, case_number, title, client_id, default_hourly_rate") .order("case_number", { ascending: false }), supabase.from("profiles").select("hourly_rate").eq("id", user.id).maybeSingle(), + supabase + .from("fee_schedule_items") + .select("id, name, description, amount") + .eq("active", true) + .eq("billable", true) + .eq("category", "time") + .order("sort_order") + .order("name"), ]); setClients(cs ?? []); setCases(ks ?? []); setProfileRate((prof as any)?.hourly_rate ?? null); + setFeeItems((fees ?? []) as FeeItem[]); })(); }, [user?.id]); @@ -73,7 +91,21 @@ export function HeaderTimer() { [cases, timer.caseId], ); - const effectiveRate = activeCase?.default_hourly_rate ?? profileRate ?? 0; + const selectedFee = useMemo( + () => feeItems.find((f) => f.id === feeItemId) ?? null, + [feeItems, feeItemId], + ); + + const effectiveRate = + selectedFee?.amount ?? activeCase?.default_hourly_rate ?? profileRate ?? 0; + + const handleSelectFee = (id: string) => { + setFeeItemId(id); + const fee = feeItems.find((f) => f.id === id); + if (fee && !timer.description.trim()) { + setDescription(fee.description?.trim() || fee.name); + } + }; const handleSave = async () => { if (!user?.id) return toast.error("Not signed in"); @@ -100,6 +132,7 @@ export function HeaderTimer() { description: `${formatCurrency(hours * effectiveRate)} at ${formatCurrency(effectiveRate)}/hr`, }); setBillable(true); + setFeeItemId(""); setOpen(false); }; @@ -107,6 +140,7 @@ export function HeaderTimer() { if (hasStarted && !confirm("Discard the running timer?")) return; reset(); setBillable(true); + setFeeItemId(""); }; const dotClass = isRunning From c8521f74558ff1645d9a3ba69ecab8cb760698ca Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 15:45:02 +0000 Subject: [PATCH 2/2] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/timer/header-timer.tsx | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/components/timer/header-timer.tsx b/src/components/timer/header-timer.tsx index fd29065..558837d 100644 --- a/src/components/timer/header-timer.tsx +++ b/src/components/timer/header-timer.tsx @@ -232,6 +232,29 @@ export function HeaderTimer() { + {feeItems.length > 0 && ( +
+ + +
+ )} +