Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-17 00:04:14 +00:00
co-authored by renee-png
parent 82f943c308
commit ece6b645af
+260
View File
@@ -0,0 +1,260 @@
import { useState } from "react";
import { supabase } from "@/integrations/supabase/client";
import { Button } from "@/components/ui/button";
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 { Separator } from "@/components/ui/separator";
import { Pencil, Save, X, Calendar, Gavel, Scale, FileSearch, Users, AlertCircle } from "lucide-react";
import { toast } from "sonner";
import { formatCurrency, formatDate } from "@/lib/format";
interface Props {
caseRecord: any;
canManage: boolean;
onSaved: () => void;
}
const FIELDS: Array<{ key: string; label: string; type?: string; full?: boolean }> = [
{ key: "case_caption", label: "Case caption", full: true },
{ key: "court_case_number", label: "Court case #" },
{ key: "litigation_stage", label: "Litigation stage" },
{ key: "court", label: "Court" },
{ key: "jurisdiction", label: "Jurisdiction" },
{ key: "judge", label: "Judge" },
{ key: "filing_date", label: "Filing date", type: "date" },
{ key: "next_hearing_date", label: "Next hearing", type: "date" },
{ key: "statute_of_limitations", label: "Statute of limitations", type: "date" },
{ key: "claim_amount", label: "Claim amount", type: "number" },
{ key: "settlement_amount", label: "Settlement amount", type: "number" },
];
const OPPOSING: Array<{ key: string; label: string }> = [
{ key: "opposing_party", label: "Opposing party" },
{ key: "opposing_counsel", label: "Opposing counsel" },
{ key: "opposing_counsel_firm", label: "Counsel firm" },
{ key: "opposing_counsel_email", label: "Counsel email" },
{ key: "opposing_counsel_phone", label: "Counsel phone" },
];
export function CaseLitigationTab({ caseRecord, canManage, onSaved }: Props) {
const [editing, setEditing] = useState(false);
const [saving, setSaving] = useState(false);
const [form, setForm] = useState<any>(() => ({ ...caseRecord }));
const [keyDates, setKeyDates] = useState<Array<{ date: string; label: string }>>(
Array.isArray(caseRecord.key_dates) ? caseRecord.key_dates : [],
);
const startEdit = () => {
setForm({ ...caseRecord });
setKeyDates(Array.isArray(caseRecord.key_dates) ? caseRecord.key_dates : []);
setEditing(true);
};
const cancel = () => {
setEditing(false);
};
const save = async () => {
setSaving(true);
const payload: any = { key_dates: keyDates.filter((k) => k.date || k.label) };
[...FIELDS, ...OPPOSING, { key: "case_summary", label: "", type: "text" }, { key: "next_hearing_notes", label: "", type: "text" }].forEach((f) => {
const v = form[f.key];
if (f.type === "number") payload[f.key] = v === "" || v == null ? null : Number(v);
else payload[f.key] = v === "" ? null : v ?? null;
});
const { error } = await supabase.from("cases").update(payload).eq("id", caseRecord.id);
setSaving(false);
if (error) {
toast.error("Could not save", { description: error.message });
return;
}
toast.success("Litigation details updated");
setEditing(false);
onSaved();
};
const renderValue = (key: string, type?: string) => {
const v = caseRecord[key];
if (v == null || v === "") return <span className="text-muted-foreground">—</span>;
if (type === "date") return formatDate(v);
if (type === "number") return formatCurrency(Number(v));
return String(v);
};
const updateKeyDate = (i: number, patch: Partial<{ date: string; label: string }>) => {
setKeyDates((prev) => prev.map((k, idx) => (idx === i ? { ...k, ...patch } : k)));
};
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h2 className="font-serif text-xl">Litigation overview</h2>
<p className="text-xs text-muted-foreground">Court details, opposing parties, and key dates.</p>
</div>
{canManage && !editing && (
<Button variant="outline" size="sm" onClick={startEdit}>
<Pencil className="h-3.5 w-3.5 mr-1.5" /> Edit
</Button>
)}
{editing && (
<div className="flex gap-2">
<Button variant="ghost" size="sm" onClick={cancel} disabled={saving}>
<X className="h-3.5 w-3.5 mr-1.5" /> Cancel
</Button>
<Button size="sm" onClick={save} disabled={saving}>
<Save className="h-3.5 w-3.5 mr-1.5" /> {saving ? "Saving…" : "Save"}
</Button>
</div>
)}
</div>
<Card className="border-border/60">
<CardContent className="p-5 space-y-5">
<Section icon={<FileSearch className="h-4 w-4" />} title="Case summary">
{editing ? (
<Textarea
rows={5}
value={form.case_summary ?? ""}
onChange={(e) => setForm({ ...form, case_summary: e.target.value })}
placeholder="Facts, claims, theory of the case, posture…"
/>
) : caseRecord.case_summary ? (
<p className="text-sm whitespace-pre-wrap">{caseRecord.case_summary}</p>
) : (
<p className="text-sm text-muted-foreground">No summary yet.</p>
)}
</Section>
<Separator />
<Section icon={<Gavel className="h-4 w-4" />} title="Court & filing">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{FIELDS.map((f) => (
<Field key={f.key} field={f} editing={editing} form={form} setForm={setForm} renderValue={renderValue} />
))}
</div>
</Section>
<Separator />
<Section icon={<Users className="h-4 w-4" />} title="Opposing party & counsel">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{OPPOSING.map((f) => (
<Field key={f.key} field={f} editing={editing} form={form} setForm={setForm} renderValue={renderValue} />
))}
</div>
</Section>
<Separator />
<Section icon={<AlertCircle className="h-4 w-4" />} title="Next hearing notes">
{editing ? (
<Textarea
rows={3}
value={form.next_hearing_notes ?? ""}
onChange={(e) => setForm({ ...form, next_hearing_notes: e.target.value })}
placeholder="What's on calendar, prep needs, location…"
/>
) : caseRecord.next_hearing_notes ? (
<p className="text-sm whitespace-pre-wrap">{caseRecord.next_hearing_notes}</p>
) : (
<p className="text-sm text-muted-foreground">—</p>
)}
</Section>
<Separator />
<Section icon={<Calendar className="h-4 w-4" />} title="Key dates timeline">
{editing ? (
<div className="space-y-2">
{keyDates.map((k, i) => (
<div key={i} className="flex gap-2 items-center">
<Input
type="date"
value={k.date ?? ""}
onChange={(e) => updateKeyDate(i, { date: e.target.value })}
className="w-[170px]"
/>
<Input
placeholder="Event description"
value={k.label ?? ""}
onChange={(e) => updateKeyDate(i, { label: e.target.value })}
/>
<Button variant="ghost" size="icon" onClick={() => setKeyDates((p) => p.filter((_, idx) => idx !== i))}>
<X className="h-4 w-4" />
</Button>
</div>
))}
<Button variant="outline" size="sm" onClick={() => setKeyDates((p) => [...p, { date: "", label: "" }])}>
Add date
</Button>
</div>
) : keyDates.length === 0 ? (
<p className="text-sm text-muted-foreground">No key dates recorded.</p>
) : (
<ul className="space-y-1.5">
{[...keyDates]
.sort((a, b) => (a.date || "").localeCompare(b.date || ""))
.map((k, i) => (
<li key={i} className="flex gap-3 text-sm">
<Scale className="h-3.5 w-3.5 mt-0.5 text-muted-foreground" />
<span className="font-mono text-xs text-muted-foreground w-24 shrink-0">
{k.date ? formatDate(k.date) : "—"}
</span>
<span>{k.label || "—"}</span>
</li>
))}
</ul>
)}
</Section>
</CardContent>
</Card>
</div>
);
}
function Section({ icon, title, children }: { icon: React.ReactNode; title: string; children: React.ReactNode }) {
return (
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs uppercase tracking-wider text-muted-foreground">
{icon}
{title}
</div>
{children}
</div>
);
}
function Field({
field,
editing,
form,
setForm,
renderValue,
}: {
field: { key: string; label: string; type?: string };
editing: boolean;
form: any;
setForm: (v: any) => void;
renderValue: (key: string, type?: string) => React.ReactNode;
}) {
return (
<div>
<Label className="text-xs text-muted-foreground">{field.label}</Label>
{editing ? (
<Input
type={field.type ?? "text"}
step={field.type === "number" ? "0.01" : undefined}
value={form[field.key] ?? ""}
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
className="mt-1"
/>
) : (
<div className="text-sm mt-1">{renderValue(field.key, field.type)}</div>
)}
</div>
);
}