From f5231046a99050eb0f56c5fb0bdcb2304d977033 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 16:02:00 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/fees/new-fee-item-dialog.tsx | 176 ++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/components/fees/new-fee-item-dialog.tsx diff --git a/src/components/fees/new-fee-item-dialog.tsx b/src/components/fees/new-fee-item-dialog.tsx new file mode 100644 index 0000000..aa98435 --- /dev/null +++ b/src/components/fees/new-fee-item-dialog.tsx @@ -0,0 +1,176 @@ +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { supabase } from "@/integrations/supabase/client"; +import { toast } from "sonner"; +import { Loader2 } from "lucide-react"; + +export interface NewFeeItem { + id: string; + name: string; + description: string | null; + amount: number; + pricing_type: "hourly" | "flat"; + billable: boolean; +} + +interface Props { + open: boolean; + onOpenChange: (open: boolean) => void; + defaultName?: string; + defaultDescription?: string; + defaultAmount?: number; + defaultPricingType?: "hourly" | "flat"; + defaultBillable?: boolean; + onCreated?: (item: NewFeeItem) => void; +} + +export function NewFeeItemDialog({ + open, + onOpenChange, + defaultName = "", + defaultDescription = "", + defaultAmount, + defaultPricingType = "hourly", + defaultBillable = true, + onCreated, +}: Props) { + const [name, setName] = useState(defaultName); + const [description, setDescription] = useState(defaultDescription); + const [amount, setAmount] = useState( + defaultAmount != null ? String(defaultAmount) : "", + ); + const [pricingType, setPricingType] = useState<"hourly" | "flat">(defaultPricingType); + const [billable, setBillable] = useState(defaultBillable); + const [saving, setSaving] = useState(false); + + // Reset state whenever the dialog opens with new defaults + const handleOpenChange = (next: boolean) => { + if (next) { + setName(defaultName); + setDescription(defaultDescription); + setAmount(defaultAmount != null ? String(defaultAmount) : ""); + setPricingType(defaultPricingType); + setBillable(defaultBillable); + } + onOpenChange(next); + }; + + const save = async () => { + if (!name.trim()) return toast.error("Name required"); + const amt = parseFloat(amount || "0"); + if (Number.isNaN(amt) || amt < 0) return toast.error("Amount must be 0 or more"); + setSaving(true); + const { data, error } = await supabase + .from("fee_schedule_items") + .insert({ + category: "time", + name: name.trim(), + description: description.trim() || null, + amount: amt, + pricing_type: pricingType, + billable, + active: true, + }) + .select("id, name, description, amount, pricing_type, billable") + .single(); + setSaving(false); + if (error) { + toast.error(error.message); + return; + } + toast.success("Fee item saved"); + onCreated?.(data as NewFeeItem); + onOpenChange(false); + }; + + return ( + + + + Add new item + + Save this work item to your fee schedule for reuse on future entries. + + + +
+
+ + setName(e.target.value)} autoFocus /> +
+
+ +