From 78c7e16db0106c2e1f90446406465da7197308ab Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:38:26 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/forms/save-to-case-dialog.tsx | 191 +++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 src/components/forms/save-to-case-dialog.tsx diff --git a/src/components/forms/save-to-case-dialog.tsx b/src/components/forms/save-to-case-dialog.tsx new file mode 100644 index 0000000..a93e08d --- /dev/null +++ b/src/components/forms/save-to-case-dialog.tsx @@ -0,0 +1,191 @@ +import { useEffect, useMemo, 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 { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { fetchAccessibleCases, type CaseLite } from "@/lib/forms-shared"; +import { FileText, Loader2 } from "lucide-react"; + +export type SaveFormat = "pdf" | "docx" | "csv"; + +interface Props { + open: boolean; + onOpenChange: (v: boolean) => void; + /** Pre-selected case (e.g. when called from a case detail screen). */ + defaultCaseId?: string; + /** Allowed save formats. Defaults to ["pdf"]. */ + formats?: SaveFormat[]; + /** Suggested base filename (no extension). */ + defaultName: string; + /** Folder name within case-documents. Defaults to "Forms & Letters". */ + defaultFolder?: string; + /** Called when the user confirms. Should perform the actual upload. */ + onConfirm: (opts: { + caseId: string; + name: string; + format: SaveFormat; + folder: string; + }) => Promise | void; + title?: string; + description?: string; +} + +/** + * Reusable picker for saving a generated document directly into a case's + * Documents tab. Loads accessible cases on first open. + */ +export function SaveToCaseDialog({ + open, + onOpenChange, + defaultCaseId, + formats = ["pdf"], + defaultName, + defaultFolder = "Forms & Letters", + onConfirm, + title = "Save to case files", + description = "Pick a case and the file will be uploaded to its Documents tab.", +}: Props) { + const [cases, setCases] = useState([]); + const [loading, setLoading] = useState(false); + const [caseId, setCaseId] = useState(defaultCaseId ?? ""); + const [name, setName] = useState(defaultName); + const [folder, setFolder] = useState(defaultFolder); + const [format, setFormat] = useState(formats[0]); + const [search, setSearch] = useState(""); + const [saving, setSaving] = useState(false); + + // Load cases the first time the dialog opens. + useEffect(() => { + if (!open) return; + setName(defaultName); + setCaseId(defaultCaseId ?? ""); + if (cases.length === 0) { + setLoading(true); + fetchAccessibleCases() + .then(setCases) + .finally(() => setLoading(false)); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]); + + const filtered = useMemo(() => { + const q = search.trim().toLowerCase(); + if (!q) return cases.slice(0, 50); + return cases + .filter( + (c) => + c.case_number.toLowerCase().includes(q) || + c.title.toLowerCase().includes(q), + ) + .slice(0, 50); + }, [cases, search]); + + const handleConfirm = async () => { + if (!caseId || !name.trim()) return; + setSaving(true); + try { + await onConfirm({ caseId, name: name.trim(), format, folder: folder.trim() || "Forms & Letters" }); + onOpenChange(false); + } finally { + setSaving(false); + } + }; + + return ( + + + + {title} + {description} + + +
+
+ + setSearch(e.target.value)} + placeholder="Case number or title…" + /> +
+ {loading ? ( +
+ Loading cases… +
+ ) : filtered.length === 0 ? ( +
No cases match.
+ ) : ( + filtered.map((c) => ( + + )) + )} +
+
+ +
+
+ + setName(e.target.value)} /> +
+
+ + setFolder(e.target.value)} /> +
+
+ + {formats.length > 1 && ( +
+ + setFormat(v as SaveFormat)} + className="flex gap-4 mt-2" + > + {formats.map((f) => ( +
+ + +
+ ))} +
+
+ )} +
+ + + + + +
+
+ ); +}