Drop the defaultOpen={i===0} on the first threshold_claim — when a
case has a lot of material already on screen (research background
+ chair positions + now precedents), auto-opening the first card
creates a wall of text on page load. All cards now start collapsed,
same as the issues list already did.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
236 lines
9.8 KiB
TypeScript
236 lines
9.8 KiB
TypeScript
"use client";
|
||
|
||
import { use } from "react";
|
||
import Link from "next/link";
|
||
import { AppShell } from "@/components/app-shell";
|
||
import { Card, CardContent } from "@/components/ui/card";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Skeleton } from "@/components/ui/skeleton";
|
||
import { SubsectionCard } from "@/components/compose/subsection-card";
|
||
import { PrecedentsSection } from "@/components/compose/precedents-section";
|
||
import { useCase } from "@/lib/api/cases";
|
||
import { useResearchAnalysis } from "@/lib/api/research";
|
||
import { useCasePrecedents } from "@/lib/api/precedents";
|
||
|
||
function ProseSection({ title, content }: { title: string; content?: string }) {
|
||
if (!content?.trim()) return null;
|
||
return (
|
||
<section className="space-y-2">
|
||
<h3 className="text-[0.78rem] uppercase tracking-[0.08em] text-gold-deep font-semibold">
|
||
{title}
|
||
</h3>
|
||
<p className="text-sm text-ink-soft leading-relaxed whitespace-pre-line prose">
|
||
{content.trim()}
|
||
</p>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
export default function ComposePage({
|
||
params,
|
||
}: {
|
||
params: Promise<{ caseNumber: string }>;
|
||
}) {
|
||
const { caseNumber } = use(params);
|
||
const caseQuery = useCase(caseNumber);
|
||
const analysis = useResearchAnalysis(caseNumber);
|
||
const precedentsQuery = useCasePrecedents(caseNumber);
|
||
|
||
/* Partition the flat list into scopes so each child renders its own slice
|
||
* without re-fetching. Done once at the page level. */
|
||
const allPrecedents = precedentsQuery.data ?? [];
|
||
const caseLevelPrecedents = allPrecedents.filter((p) => p.section_id === null);
|
||
const precedentsBySection = new Map<string, typeof allPrecedents>();
|
||
for (const p of allPrecedents) {
|
||
if (p.section_id) {
|
||
const existing = precedentsBySection.get(p.section_id) ?? [];
|
||
existing.push(p);
|
||
precedentsBySection.set(p.section_id, existing);
|
||
}
|
||
}
|
||
const practiceArea = caseQuery.data?.practice_area ?? null;
|
||
|
||
const isNotFound =
|
||
analysis.error instanceof Error &&
|
||
/404|לא נמצא|טרם בוצע/.test(analysis.error.message);
|
||
|
||
return (
|
||
<AppShell>
|
||
<section className="space-y-6">
|
||
{/* Header strip */}
|
||
<div className="flex items-center justify-between gap-4 flex-wrap">
|
||
<div>
|
||
<nav className="text-[0.78rem] text-ink-muted flex items-center gap-2 mb-1">
|
||
<Link href="/" className="hover:text-gold-deep">בית</Link>
|
||
<span aria-hidden>·</span>
|
||
<Link
|
||
href={`/cases/${caseNumber}`}
|
||
className="hover:text-gold-deep"
|
||
>
|
||
ערר {caseNumber}
|
||
</Link>
|
||
<span aria-hidden>·</span>
|
||
<span className="text-navy">עורך החלטה</span>
|
||
</nav>
|
||
<h1 className="text-navy mb-0">ניתוח משפטי וכתיבת עמדה</h1>
|
||
{caseQuery.data?.title && (
|
||
<p className="text-ink-muted text-sm mt-1 max-w-2xl">
|
||
{caseQuery.data.title}
|
||
</p>
|
||
)}
|
||
</div>
|
||
<Button asChild variant="outline">
|
||
<Link href={`/cases/${caseNumber}`}>חזרה לתיק</Link>
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />
|
||
|
||
{analysis.isPending ? (
|
||
<Card className="bg-surface border-rule shadow-sm">
|
||
<CardContent className="px-6 py-5 space-y-3">
|
||
<Skeleton className="h-6 w-48" />
|
||
<Skeleton className="h-4 w-96" />
|
||
<Skeleton className="h-4 w-80" />
|
||
<Skeleton className="h-32 w-full" />
|
||
</CardContent>
|
||
</Card>
|
||
) : isNotFound ? (
|
||
<Card className="bg-surface border-rule shadow-sm">
|
||
<CardContent className="px-6 py-12 text-center space-y-3">
|
||
<div className="text-gold text-3xl" aria-hidden>❦</div>
|
||
<h2 className="text-navy text-lg mb-0">
|
||
טרם בוצע ניתוח משפטי לתיק זה
|
||
</h2>
|
||
<p className="text-ink-muted text-sm max-w-md mx-auto">
|
||
לאחר שקובץ <code>analysis-and-research.md</code> ייווצר, תוכלי
|
||
לערוך כאן את עמדת הוועדה לכל טענת סף וסוגיה.
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
) : analysis.error ? (
|
||
<Card className="bg-danger-bg border-danger/40">
|
||
<CardContent className="px-6 py-5 text-center">
|
||
<p className="text-danger">{analysis.error.message}</p>
|
||
</CardContent>
|
||
</Card>
|
||
) : analysis.data ? (
|
||
<div className="grid gap-6 lg:grid-cols-[1fr_320px]">
|
||
{/* Main editable column */}
|
||
<div className="space-y-6">
|
||
{/* Case-level general precedents */}
|
||
<Card className="bg-surface border-rule shadow-sm">
|
||
<CardContent className="px-6 py-5">
|
||
<h2 className="text-navy text-xl mb-1">פסיקה כללית לדיון</h2>
|
||
<p className="text-[0.78rem] text-ink-muted mb-4">
|
||
ציטוטים התומכים בעמדה באופן רוחבי — ישולבו בפתיחת בלוק י (דיון).
|
||
</p>
|
||
<PrecedentsSection
|
||
caseNumber={caseNumber}
|
||
sectionId={null}
|
||
precedents={caseLevelPrecedents}
|
||
practiceArea={practiceArea}
|
||
emptyHelperText="עדיין לא צורפה פסיקה כללית לתיק"
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Threshold claims */}
|
||
{analysis.data.threshold_claims &&
|
||
analysis.data.threshold_claims.length > 0 && (
|
||
<div className="space-y-3">
|
||
<div className="flex items-center gap-2">
|
||
<h2 className="text-navy text-xl mb-0">טענות סף</h2>
|
||
<span className="text-[0.72rem] rounded-full bg-gold-wash text-gold-deep px-2 py-0.5 border border-gold/40 tabular-nums">
|
||
{analysis.data.threshold_claims.length}
|
||
</span>
|
||
</div>
|
||
<div className="space-y-3">
|
||
{analysis.data.threshold_claims.map((tc) => (
|
||
<SubsectionCard
|
||
key={tc.id}
|
||
caseNumber={caseNumber}
|
||
item={tc}
|
||
precedents={precedentsBySection.get(tc.id) ?? []}
|
||
practiceArea={practiceArea}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Issues */}
|
||
{analysis.data.issues && analysis.data.issues.length > 0 && (
|
||
<div className="space-y-3">
|
||
<div className="flex items-center gap-2">
|
||
<h2 className="text-navy text-xl mb-0">סוגיות להכרעה</h2>
|
||
<span className="text-[0.72rem] rounded-full bg-gold-wash text-gold-deep px-2 py-0.5 border border-gold/40 tabular-nums">
|
||
{analysis.data.issues.length}
|
||
</span>
|
||
</div>
|
||
<div className="space-y-3">
|
||
{analysis.data.issues.map((iss) => (
|
||
<SubsectionCard
|
||
key={iss.id}
|
||
caseNumber={caseNumber}
|
||
item={iss}
|
||
precedents={precedentsBySection.get(iss.id) ?? []}
|
||
practiceArea={practiceArea}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{(!analysis.data.threshold_claims?.length &&
|
||
!analysis.data.issues?.length) && (
|
||
<Card className="bg-surface border-rule">
|
||
<CardContent className="px-6 py-10 text-center text-ink-muted">
|
||
לא נמצאו טענות סף או סוגיות בניתוח זה.
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
</div>
|
||
|
||
{/* Side column: background prose + conclusions */}
|
||
<aside className="space-y-5">
|
||
<Card className="bg-surface border-rule shadow-sm">
|
||
<CardContent className="px-5 py-4 space-y-5">
|
||
<h2 className="text-navy text-base mb-0">רקע לניתוח</h2>
|
||
<ProseSection
|
||
title="צד מיוצג"
|
||
content={analysis.data.represented_party}
|
||
/>
|
||
<ProseSection
|
||
title="רקע דיוני"
|
||
content={analysis.data.procedural_background}
|
||
/>
|
||
<ProseSection
|
||
title="עובדות מוסכמות"
|
||
content={analysis.data.agreed_facts}
|
||
/>
|
||
<ProseSection
|
||
title="עובדות במחלוקת"
|
||
content={analysis.data.disputed_facts}
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{analysis.data.conclusions?.trim() && (
|
||
<Card className="bg-gold-wash border-gold/40 shadow-sm">
|
||
<CardContent className="px-5 py-4 space-y-2">
|
||
<h2 className="text-gold-deep text-base mb-0">מסקנות</h2>
|
||
<p className="text-sm text-ink leading-relaxed whitespace-pre-line prose">
|
||
{analysis.data.conclusions.trim()}
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
</aside>
|
||
</div>
|
||
) : null}
|
||
</section>
|
||
</AppShell>
|
||
);
|
||
}
|