diff --git a/bun.lockb b/bun.lockb index dab3146..050aece 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/src/components/clients/client-custom-fields-tab.tsx b/src/components/clients/client-custom-fields-tab.tsx new file mode 100644 index 0000000..82001af --- /dev/null +++ b/src/components/clients/client-custom-fields-tab.tsx @@ -0,0 +1,116 @@ +import { useEffect, useState } from "react"; +import { supabase } from "@/integrations/supabase/client"; +import { Card, CardContent } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Button } from "@/components/ui/button"; +import { Loader2, Save } from "lucide-react"; +import { toast } from "sonner"; + +interface FieldDef { + id: string; + key: string; + label: string; + field_type: string; + description: string | null; +} + +export function ClientCustomFieldsTab({ clientId }: { clientId: string }) { + const [defs, setDefs] = useState([]); + const [values, setValues] = useState>({}); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + + useEffect(() => { + (async () => { + setLoading(true); + const [{ data: d }, { data: v }] = await Promise.all([ + supabase + .from("custom_client_fields") + .select("id,key,label,field_type,description") + .eq("active", true) + .order("sort_order"), + supabase.from("client_field_values").select("field_id,value").eq("client_id", clientId), + ]); + const list = (d ?? []) as FieldDef[]; + setDefs(list); + const map: Record = {}; + (v ?? []).forEach((row: any) => { + map[row.field_id] = row.value ?? ""; + }); + setValues(map); + setLoading(false); + })(); + }, [clientId]); + + const save = async () => { + setSaving(true); + const rows = defs.map((d) => ({ + client_id: clientId, + field_id: d.id, + value: values[d.id] ?? "", + })); + for (const row of rows) { + const { error } = await supabase + .from("client_field_values") + .upsert(row, { onConflict: "client_id,field_id" }); + if (error) { + toast.error("Save failed", { description: error.message }); + setSaving(false); + return; + } + } + setSaving(false); + toast.success("Custom fields saved"); + }; + + if (loading) return

Loading…

; + + if (defs.length === 0) { + return ( + + +

+ No custom client fields are configured. An admin can add them in Settings → Custom client fields. +

+
+
+ ); + } + + return ( + + +
+ {defs.map((d) => ( +
+ + {d.field_type === "textarea" ? ( +