Renamed Documents to Pleadings
X-Lovable-Edit-ID: edt-42eea1a3-f143-4e37-ace0-2cb73795fe4c Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -27,7 +27,7 @@ const NAV: NavItem[] = [
|
||||
{ to: "/inbox", label: "Inbox", icon: InboxIcon },
|
||||
{ to: "/status", label: "Status Updates", icon: Activity },
|
||||
{ to: "/collections", label: "Collections", icon: DollarSign },
|
||||
{ to: "/documents", label: "Documents", icon: FolderOpen },
|
||||
{ to: "/documents", label: "Pleadings", icon: FolderOpen },
|
||||
{ to: "/files", label: "Files", icon: FilesIcon },
|
||||
{ to: "/forms", label: "Forms & Letters", icon: ClipboardList },
|
||||
{ to: "/invoices", label: "Invoices", icon: Receipt },
|
||||
|
||||
@@ -4,40 +4,38 @@ import { ProtectedLayout } from "@/components/protected-layout";
|
||||
import { PageContainer, PageHeader } from "@/components/app-shell";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { formatDate } from "@/lib/format";
|
||||
import { FilePlus2, FileText, Gavel, LayoutTemplate, Plus, Download, Trash2 } from "lucide-react";
|
||||
import { FileText, Gavel, Download, Trash2, FilePlus2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const Route = createFileRoute("/documents/")({
|
||||
component: DocumentsIndex,
|
||||
component: PleadingsIndex,
|
||||
});
|
||||
|
||||
function DocumentsIndex() {
|
||||
const [generated, setGenerated] = useState<any[]>([]);
|
||||
const [templates, setTemplates] = useState<any[]>([]);
|
||||
function PleadingsIndex() {
|
||||
const [pleadings, setPleadings] = useState<any[]>([]);
|
||||
|
||||
const load = async () => {
|
||||
const [g, t] = await Promise.all([
|
||||
supabase.from("generated_documents").select("*").order("created_at", { ascending: false }).limit(20),
|
||||
supabase.from("document_templates").select("*").order("name"),
|
||||
]);
|
||||
setGenerated(g.data ?? []);
|
||||
setTemplates(t.data ?? []);
|
||||
const { data } = await supabase
|
||||
.from("generated_documents")
|
||||
.select("*")
|
||||
.eq("kind", "pleading")
|
||||
.order("created_at", { ascending: false });
|
||||
setPleadings(data ?? []);
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
const removeGenerated = async (row: any) => {
|
||||
const removePleading = async (row: any) => {
|
||||
if (!confirm(`Delete ${row.name}?`)) return;
|
||||
if (row.storage_path) await supabase.storage.from("generated-documents").remove([row.storage_path]);
|
||||
const { error } = await supabase.from("generated_documents").delete().eq("id", row.id);
|
||||
if (error) toast.error(error.message); else { toast.success("Deleted"); load(); }
|
||||
};
|
||||
|
||||
const downloadGenerated = async (row: any) => {
|
||||
if (!row.storage_path) { toast.error("No file stored for this document"); return; }
|
||||
const downloadPleading = async (row: any) => {
|
||||
if (!row.storage_path) { toast.error("No file stored for this pleading"); return; }
|
||||
const { data, error } = await supabase.storage.from("generated-documents").createSignedUrl(row.storage_path, 60);
|
||||
if (error) { toast.error(error.message); return; }
|
||||
window.open(data.signedUrl, "_blank");
|
||||
@@ -47,25 +45,18 @@ function DocumentsIndex() {
|
||||
<ProtectedLayout>
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Documents"
|
||||
description="Create pleadings and reusable document templates."
|
||||
title="Pleadings"
|
||||
description="Create Florida pleadings and reuse saved pleadings as templates."
|
||||
actions={
|
||||
<>
|
||||
<Button asChild variant="outline">
|
||||
<Link to="/documents/templates">
|
||||
<LayoutTemplate className="h-4 w-4 mr-2" /> Templates
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild>
|
||||
<Link to="/documents/pleading/new">
|
||||
<Gavel className="h-4 w-4 mr-2" /> New Pleading
|
||||
</Link>
|
||||
</Button>
|
||||
</>
|
||||
<Button asChild>
|
||||
<Link to="/documents/pleading/new">
|
||||
<Gavel className="h-4 w-4 mr-2" /> New Pleading
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-4 mb-8">
|
||||
<div className="grid md:grid-cols-2 gap-4 mb-8">
|
||||
<Link to="/documents/pleading/new">
|
||||
<Card className="hover:border-primary/40 transition-colors cursor-pointer h-full">
|
||||
<CardContent className="p-5 flex items-start gap-3">
|
||||
@@ -77,58 +68,51 @@ function DocumentsIndex() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
<Link to="/documents/templates/new">
|
||||
<Card className="hover:border-primary/40 transition-colors cursor-pointer h-full">
|
||||
<CardContent className="p-5 flex items-start gap-3">
|
||||
<div className="h-10 w-10 rounded-md bg-primary/10 text-primary flex items-center justify-center"><FilePlus2 className="h-5 w-5" /></div>
|
||||
<div>
|
||||
<div className="font-medium">New template</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">Define custom merge fields and reuse them.</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
<Link to="/documents/templates">
|
||||
<Card className="hover:border-primary/40 transition-colors cursor-pointer h-full">
|
||||
<CardContent className="p-5 flex items-start gap-3">
|
||||
<div className="h-10 w-10 rounded-md bg-primary/10 text-primary flex items-center justify-center"><LayoutTemplate className="h-5 w-5" /></div>
|
||||
<div>
|
||||
<div className="font-medium">Template library</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">{templates.length} template{templates.length === 1 ? "" : "s"} available.</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
<Card className="h-full">
|
||||
<CardContent className="p-5 flex items-start gap-3">
|
||||
<div className="h-10 w-10 rounded-md bg-primary/10 text-primary flex items-center justify-center"><FilePlus2 className="h-5 w-5" /></div>
|
||||
<div>
|
||||
<div className="font-medium">Saved pleadings</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">{pleadings.length} saved · open any below as a new template.</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle className="text-base">Recent documents</CardTitle>
|
||||
<CardTitle className="text-base">Saved pleadings</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
{generated.length === 0 ? (
|
||||
<div className="text-center py-12 text-muted-foreground text-sm">No documents yet. Create a pleading or use a template.</div>
|
||||
{pleadings.length === 0 ? (
|
||||
<div className="text-center py-12 text-muted-foreground text-sm">No pleadings yet. Create one to save it as a reusable template.</div>
|
||||
) : (
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/40 text-xs uppercase tracking-wider text-muted-foreground">
|
||||
<tr>
|
||||
<th className="text-left px-4 py-3 font-medium">Name</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Type</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Created</th>
|
||||
<th className="text-right px-4 py-3 font-medium">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{generated.map((d) => (
|
||||
{pleadings.map((d) => (
|
||||
<tr key={d.id} className="border-t hover:bg-muted/30">
|
||||
<td className="px-4 py-3"><div className="flex items-center gap-2"><FileText className="h-4 w-4 text-muted-foreground" /><span className="font-medium">{d.name}</span></div></td>
|
||||
<td className="px-4 py-3"><Badge variant="outline" className="capitalize">{d.kind}</Badge></td>
|
||||
<td className="px-4 py-3">
|
||||
<Link to="/documents/pleading/new" search={{ from: d.id }} className="flex items-center gap-2 hover:text-primary">
|
||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="font-medium">{d.name}</span>
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{formatDate(d.created_at)}</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<Link to="/documents/pleading/new" search={{ from: d.id }}>Open as template</Link>
|
||||
</Button>
|
||||
{d.storage_path && (
|
||||
<Button variant="ghost" size="icon" onClick={() => downloadGenerated(d)}><Download className="h-4 w-4" /></Button>
|
||||
<Button variant="ghost" size="icon" onClick={() => downloadPleading(d)}><Download className="h-4 w-4" /></Button>
|
||||
)}
|
||||
<Button variant="ghost" size="icon" onClick={() => removeGenerated(d)}><Trash2 className="h-4 w-4 text-destructive" /></Button>
|
||||
<Button variant="ghost" size="icon" onClick={() => removePleading(d)}><Trash2 className="h-4 w-4 text-destructive" /></Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { ProtectedLayout } from "@/components/protected-layout";
|
||||
import { PageContainer, PageHeader } from "@/components/app-shell";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
@@ -22,11 +22,15 @@ import { Download, FileText, Save, Loader2, Plus, Trash2 } from "lucide-react";
|
||||
|
||||
export const Route = createFileRoute("/documents/pleading/new")({
|
||||
component: PleadingNewPage,
|
||||
validateSearch: (search: Record<string, unknown>) => ({
|
||||
from: typeof search.from === "string" ? search.from : undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
function PleadingNewPage() {
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const { from } = Route.useSearch();
|
||||
const [courtType, setCourtType] = useState<"CIRCUIT" | "COUNTY">("CIRCUIT");
|
||||
const [circuit, setCircuit] = useState<string>("ELEVENTH");
|
||||
const [county, setCounty] = useState<string>("Miami-Dade");
|
||||
@@ -42,6 +46,33 @@ function PleadingNewPage() {
|
||||
const [docName, setDocName] = useState("Pleading");
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!from) return;
|
||||
(async () => {
|
||||
const { data, error } = await supabase
|
||||
.from("generated_documents")
|
||||
.select("name, payload")
|
||||
.eq("id", from)
|
||||
.maybeSingle();
|
||||
if (error || !data) return;
|
||||
const p = (data.payload || {}) as any;
|
||||
if (p.courtType) setCourtType(p.courtType);
|
||||
if (p.circuit) setCircuit(p.circuit);
|
||||
if (p.county) setCounty(p.county);
|
||||
if (typeof p.plaintiffs === "string") setPlaintiffs(p.plaintiffs);
|
||||
if (typeof p.defendants === "string") setDefendants(p.defendants);
|
||||
if (typeof p.caseNumber === "string") setCaseNumber(p.caseNumber);
|
||||
if (typeof p.title === "string") setTitle(p.title);
|
||||
if (typeof p.bodyHtml === "string") setBodyHtml(p.bodyHtml);
|
||||
if (Array.isArray(p.footnotes)) setFootnotes(p.footnotes);
|
||||
if (typeof p.footerLeft === "string") setFooterLeft(p.footerLeft);
|
||||
if (typeof p.footerCenter === "string") setFooterCenter(p.footerCenter);
|
||||
if (typeof p.footerRight === "string") setFooterRight(p.footerRight);
|
||||
setDocName(`${data.name} (copy)`);
|
||||
toast.success("Loaded saved pleading");
|
||||
})();
|
||||
}, [from]);
|
||||
|
||||
const counties = useMemo(() => {
|
||||
const c = FL_CIRCUITS.find((x) => x.value === circuit);
|
||||
return c?.counties ?? [];
|
||||
|
||||
Reference in New Issue
Block a user