From 2feb02175628cd99094fb42241cb7fb1d8268ddc 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:40 +0000 Subject: [PATCH 1/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/integrations/supabase/types.ts | 3 +++ ...260419154537_4a5c5615-6f4e-4ae3-bd8e-561943247c3b.sql | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 supabase/migrations/20260419154537_4a5c5615-6f4e-4ae3-bd8e-561943247c3b.sql diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 7246c52..321fb7b 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -1490,6 +1490,7 @@ export type Database = { description: string | null id: string name: string + pricing_type: string sort_order: number updated_at: string } @@ -1503,6 +1504,7 @@ export type Database = { description?: string | null id?: string name: string + pricing_type?: string sort_order?: number updated_at?: string } @@ -1516,6 +1518,7 @@ export type Database = { description?: string | null id?: string name?: string + pricing_type?: string sort_order?: number updated_at?: string } diff --git a/supabase/migrations/20260419154537_4a5c5615-6f4e-4ae3-bd8e-561943247c3b.sql b/supabase/migrations/20260419154537_4a5c5615-6f4e-4ae3-bd8e-561943247c3b.sql new file mode 100644 index 0000000..7bc0700 --- /dev/null +++ b/supabase/migrations/20260419154537_4a5c5615-6f4e-4ae3-bd8e-561943247c3b.sql @@ -0,0 +1,9 @@ +ALTER TABLE public.fee_schedule_items + ADD COLUMN IF NOT EXISTS pricing_type text NOT NULL DEFAULT 'hourly'; + +ALTER TABLE public.fee_schedule_items + DROP CONSTRAINT IF EXISTS fee_schedule_items_pricing_type_check; + +ALTER TABLE public.fee_schedule_items + ADD CONSTRAINT fee_schedule_items_pricing_type_check + CHECK (pricing_type IN ('hourly', 'flat')); \ No newline at end of file From f778f7fbdb6c7d4d1a7005b24eb00ce3441793b0 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:54 +0000 Subject: [PATCH 2/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.fees.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/settings.fees.tsx b/src/routes/settings.fees.tsx index abb5560..5eacd8a 100644 --- a/src/routes/settings.fees.tsx +++ b/src/routes/settings.fees.tsx @@ -45,6 +45,7 @@ interface FeeItem { name: string; description: string | null; category: "time" | "expense"; + pricing_type: "hourly" | "flat"; amount: number; billable: boolean; active: boolean; From 4d8a6a3d820ef7391f50753ba5ff07142f516db0 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:46:01 +0000 Subject: [PATCH 3/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.fees.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/settings.fees.tsx b/src/routes/settings.fees.tsx index 5eacd8a..e0247aa 100644 --- a/src/routes/settings.fees.tsx +++ b/src/routes/settings.fees.tsx @@ -99,7 +99,7 @@ function FeeSchedulePage() { logging time; expense fees auto-fill the amount when adding an expense. The billable flag becomes the default for new entries.

- From 8cfebbec69011889cc560a7ba151cfc25bef98e6 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:46:14 +0000 Subject: [PATCH 4/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.fees.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/routes/settings.fees.tsx b/src/routes/settings.fees.tsx index e0247aa..3c8267a 100644 --- a/src/routes/settings.fees.tsx +++ b/src/routes/settings.fees.tsx @@ -203,11 +203,13 @@ function FeeSection({ {formatCurrency(Number(i.amount))} - {unit && ( - - {unit} - - )} + {(() => { + const isHourly = i.category === "time" && i.pricing_type !== "flat"; + const u = isHourly ? "/ hr" : i.category === "time" ? " flat" : unit; + return u ? ( + {u} + ) : null; + })()} {i.billable ? ( From 4c6646c19f35d85c03d56c3747a420fa597a0fbd 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:46:23 +0000 Subject: [PATCH 5/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.fees.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/settings.fees.tsx b/src/routes/settings.fees.tsx index 3c8267a..7c0526e 100644 --- a/src/routes/settings.fees.tsx +++ b/src/routes/settings.fees.tsx @@ -282,6 +282,8 @@ function FeeEditDialog({ name: form.name.trim(), description: form.description?.trim() || null, category: form.category, + pricing_type: + form.category === "expense" ? "flat" : form.pricing_type ?? "hourly", amount: Number(form.amount) || 0, billable: form.billable ?? true, active: form.active ?? true, From fc9dd410a61833787f1a022dcdb8be0b20d0cca2 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:46:36 +0000 Subject: [PATCH 6/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.fees.tsx | 39 +++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/routes/settings.fees.tsx b/src/routes/settings.fees.tsx index 7c0526e..b31aee8 100644 --- a/src/routes/settings.fees.tsx +++ b/src/routes/settings.fees.tsx @@ -321,27 +321,56 @@ function FeeEditDialog({
-
+
+
+
+ +
Date: Sun, 19 Apr 2026 15:47:00 +0000 Subject: [PATCH 7/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/timer/header-timer.tsx | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/components/timer/header-timer.tsx b/src/components/timer/header-timer.tsx index 558837d..8b93ed5 100644 --- a/src/components/timer/header-timer.tsx +++ b/src/components/timer/header-timer.tsx @@ -39,6 +39,7 @@ interface FeeItem { name: string; description: string | null; amount: number; + pricing_type: "hourly" | "flat"; } export function HeaderTimer() { @@ -67,7 +68,7 @@ export function HeaderTimer() { supabase.from("profiles").select("hourly_rate").eq("id", user.id).maybeSingle(), supabase .from("fee_schedule_items") - .select("id, name, description, amount") + .select("id, name, description, amount, pricing_type") .eq("active", true) .eq("billable", true) .eq("category", "time") @@ -96,6 +97,8 @@ export function HeaderTimer() { [feeItems, feeItemId], ); + const isFlatFee = selectedFee?.pricing_type === "flat"; + const effectiveRate = selectedFee?.amount ?? activeCase?.default_hourly_rate ?? profileRate ?? 0; @@ -111,15 +114,18 @@ export function HeaderTimer() { if (!user?.id) return toast.error("Not signed in"); if (!timer.caseId) return toast.error("Select a case before saving"); if (!timer.description.trim()) return toast.error("Description required"); - if (elapsedMs < 1000) return toast.error("Timer hasn't recorded any time yet"); + if (elapsedMs < 1000 && !isFlatFee) return toast.error("Timer hasn't recorded any time yet"); setSaving(true); - const { hours } = consume(); + const { hours: tracked } = consume(); + // For a flat-fee item, store 1 hour at the flat amount so the total = flat amount. + const hours = isFlatFee ? 1 : tracked; + const rate = isFlatFee ? selectedFee!.amount : effectiveRate; const { error } = await supabase.from("time_entries").insert({ case_id: timer.caseId, user_id: user.id, work_date: new Date().toISOString().slice(0, 10), hours, - hourly_rate: effectiveRate, + hourly_rate: rate, description: timer.description.trim(), billable, }); @@ -128,9 +134,14 @@ export function HeaderTimer() { toast.error(error.message); return; } - toast.success(`Saved ${hours.toFixed(1)} hr`, { - description: `${formatCurrency(hours * effectiveRate)} at ${formatCurrency(effectiveRate)}/hr`, - }); + toast.success( + isFlatFee ? `Saved flat fee` : `Saved ${hours.toFixed(1)} hr`, + { + description: isFlatFee + ? formatCurrency(rate) + : `${formatCurrency(hours * rate)} at ${formatCurrency(rate)}/hr`, + }, + ); setBillable(true); setFeeItemId(""); setOpen(false); From b37c52750cd299e38fd709bab0853afc879531ab 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:47:17 +0000 Subject: [PATCH 8/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/timer/header-timer.tsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/timer/header-timer.tsx b/src/components/timer/header-timer.tsx index 8b93ed5..f789e0b 100644 --- a/src/components/timer/header-timer.tsx +++ b/src/components/timer/header-timer.tsx @@ -279,9 +279,15 @@ export function HeaderTimer() {
-
Rate
+
+ {isFlatFee ? "Flat fee" : "Rate"} +
- {effectiveRate ? `${formatCurrency(effectiveRate)}/hr` : "—"} + {effectiveRate + ? isFlatFee + ? formatCurrency(effectiveRate) + : `${formatCurrency(effectiveRate)}/hr` + : "—"}
{selectedFee @@ -300,17 +306,24 @@ export function HeaderTimer() {

- Time saves rounded up to the nearest 1/10 hour (6 minutes). + {isFlatFee + ? "Flat fee items save at the fixed amount regardless of elapsed time." + : "Time saves rounded up to the nearest 1/10 hour (6 minutes)."}

From dbc02db9591fcad06f44e26b07aa748714cb5f20 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:47:29 +0000 Subject: [PATCH 9/9] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/timer/header-timer.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/timer/header-timer.tsx b/src/components/timer/header-timer.tsx index f789e0b..35c80d5 100644 --- a/src/components/timer/header-timer.tsx +++ b/src/components/timer/header-timer.tsx @@ -258,7 +258,10 @@ export function HeaderTimer() { {feeItems.map((f) => ( {f.name} - {formatCurrency(f.amount)}/hr + + {formatCurrency(f.amount)} + {f.pricing_type === "flat" ? " flat" : "/hr"} + ))}