Files
legal-ai/web-ui/src/components/compose/precedent-card.tsx
Chaim 6b8f002596 Precedent attachment UI in the compose screen
Surface the new POST/GET/DELETE /api/cases/{n}/precedents endpoints
in the compose screen as two insertion points:

1. A new case-level card "פסיקה כללית לדיון" at the top of the
   main column, for precedents that support the discussion intro
   rather than a specific threshold_claim / issue.
2. An inline "פסיקה תומכת" section inside each SubsectionCard,
   below the ChairEditor.

Both insertion points render a `<PrecedentsSection>` which shows a
list of `<PrecedentCard>` (citation + blockquote + optional chair
note + 📄 chip if a PDF was archived) followed by a `<PrecedentAttacher>`
popover trigger.

The Attacher is a Popover with cross-case typeahead: typing 2+
characters into the citation field hits /api/precedents/search and
shows distinct library matches; picking one prefills quote + chair
note but leaves them editable so customizing the quote for this
case doesn't mutate the library. An optional PDF/DOCX/DOC file can
be attached — it uploads first via POST .../upload-pdf and the
returned document_id is passed into the precedent create call.

The parent compose page issues a single useCasePrecedents query
and partitions the result by section_id into a Map so each
SubsectionCard renders its own slice without re-fetching.

shadcn Popover installed as a new primitive. sonner toasts wired
for success/error in both attach and delete flows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 19:20:45 +00:00

78 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { Trash2, FileText } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { useDeletePrecedent, type CasePrecedent } from "@/lib/api/precedents";
/*
* Read-only display of a single attached precedent. Layout is:
*
* ┌───────────────────────────────────────────┐
* │ citation (gold semibold) [🗑] │
* │ ┌──┐ │
* │ │╎ │ "quote text…" │
* │ └──┘ │
* │ chair_note (muted) │
* │ 📄 קובץ מצורף │
* └───────────────────────────────────────────┘
*/
export function PrecedentCard({
caseNumber,
precedent,
}: {
caseNumber: string;
precedent: CasePrecedent;
}) {
const del = useDeletePrecedent(caseNumber);
const onDelete = async () => {
if (!window.confirm("להסיר פסיקה זו מהתיק?")) return;
try {
await del.mutateAsync(precedent.id);
toast.success("הפסיקה הוסרה");
} catch (e) {
toast.error(e instanceof Error ? e.message : "שגיאה בהסרה");
}
};
return (
<article className="rounded-lg border border-rule bg-parchment/40 px-4 py-3 space-y-2">
<div className="flex items-start gap-3">
<p className="flex-1 text-[0.82rem] text-gold-deep font-semibold leading-snug">
{precedent.citation}
</p>
<Button
type="button"
variant="ghost"
size="sm"
onClick={onDelete}
disabled={del.isPending}
aria-label="הסר פסיקה זו"
className="text-danger hover:text-danger hover:bg-danger-bg shrink-0 -mt-1 -me-2"
>
<Trash2 className="w-4 h-4" aria-hidden="true" />
</Button>
</div>
<blockquote className="border-e-2 border-gold pe-3 text-sm text-ink leading-relaxed whitespace-pre-line">
{precedent.quote}
</blockquote>
{precedent.chair_note && (
<p className="text-[0.78rem] text-ink-muted italic">
{precedent.chair_note}
</p>
)}
{precedent.pdf_document_id && (
<div className="flex items-center gap-1.5 text-[0.72rem] text-ink-muted">
<FileText className="w-3 h-3" aria-hidden="true" />
<span>קובץ מצורף</span>
</div>
)}
</article>
);
}