Files
legal-ai/web-ui/src/components/cases/case-header.tsx
Chaim 130f7cff71
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
feat(case-ui): full-width tabs + parties popover + subject on title line
Chair-requested refinements to the case banner:
- Tabs now span the full band width via justify-between — wide gaps with the
  current 6 tabs, tightening as more are added (gap-2 no-overlap floor).
- Parties moved out of the always-on header line into a "צדדים" Popover, so the
  band is shorter and the tab content sits higher on the page.
- Case subject (title) moved up onto the case-number line in a smaller font,
  instead of its own line below.

tsc --noEmit + eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 19:03:05 +00:00

159 lines
6.3 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.
import type { ReactNode } from "react";
import Link from "next/link";
import { Badge } from "@/components/ui/badge";
import { StatusBadge } from "@/components/cases/status-badge";
import { StatusRail } from "@/components/cases/status-rail";
import { CaseArchiveAction } from "@/components/cases/case-archive-action";
import { CreateRepoButton } from "@/components/cases/create-repo-button";
import {
PRACTICE_AREA_LABELS,
APPEAL_SUBTYPE_LABELS,
isBlamSubtype,
} from "@/lib/practice-area";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Users } from "lucide-react";
import type { CaseDetail } from "@/lib/api/cases";
/**
* Case header — parchment band (IA-redesign mockup 17): full-bleed band with
* the case title + status/type chips inline, a parties line, the case actions
* (edit / archive / repo / sync), and a metadata strip. The `tabs` slot renders
* the tab strip inside the band, anchored to its bottom edge.
*/
export function CaseHeader({
caseNumber,
data,
actions,
tabs,
}: {
caseNumber: string;
data?: CaseDetail;
actions?: ReactNode;
tabs?: ReactNode;
}) {
const appellants = data?.appellants?.filter(Boolean) ?? [];
const respondents = data?.respondents?.filter(Boolean) ?? [];
const hasParties = appellants.length > 0 || respondents.length > 0;
const isBlam =
data?.proceeding_type === 'בל"מ' || isBlamSubtype(data?.appeal_subtype);
return (
<div className="-mx-10 -mt-10 mb-2 bg-parchment border-b border-rule px-10 pt-6">
<nav className="text-[0.78rem] text-ink-muted mb-3 flex items-center gap-2">
<Link href="/" className="hover:text-gold-deep">בית</Link>
<span aria-hidden>·</span>
<span>תיקי ערר</span>
<span aria-hidden>·</span>
<span className="text-navy tabular-nums">{data?.case_number ?? "…"}</span>
</nav>
{/* title block + metadata on one row — no wrap, so the metadata dl stays
parallel to the H1 and a long parties line can't push it below. */}
<div className="flex items-start justify-between gap-6">
<div className="min-w-0 flex-1">
{/* title row — H1 + status/type/blam chips inline (mockup .band h1) */}
<h1 className="text-navy text-[1.7rem] font-bold leading-tight flex items-center gap-3 flex-wrap mb-0">
<span className="tabular-nums">
{data?.proceeding_type ?? "ערר"} {data?.case_number ?? "—"}
</span>
{/* case subject on the same line, smaller font (chair request) */}
{data?.title && (
<span className="text-navy/75 text-lg font-semibold leading-snug">
{data.title}
</span>
)}
{data?.status && <StatusBadge status={data.status} />}
{data?.archived_at && (
<Badge
variant="outline"
className="rounded-full px-3 py-0.5 text-[0.75rem] font-semibold bg-ink-muted/10 text-ink-muted border-ink-muted/30"
>
בארכיון
</Badge>
)}
{data?.practice_area && (
<Badge
variant="outline"
className="rounded-full px-3 py-0.5 text-[0.75rem] font-semibold bg-gold-wash text-gold-deep border-rule"
>
{PRACTICE_AREA_LABELS[data.practice_area]}
{data.appeal_subtype && data.appeal_subtype !== "unknown" && (
<> · {APPEAL_SUBTYPE_LABELS[data.appeal_subtype]}</>
)}
</Badge>
)}
{isBlam && (
<Badge
variant="outline"
className="rounded-full px-3 py-0.5 text-[0.75rem] font-bold bg-warn/10 text-warn-deep border-warn/40"
title="בקשה להארכת מועד להגשת ערר"
>
בל&quot;מ
</Badge>
)}
</h1>
{/* case actions — parties now live in a popover (chair request) so
they don't push the tab content down the page. */}
<div className="flex items-center gap-2 flex-wrap mt-3">
{hasParties && (
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className="inline-flex items-center gap-1.5 rounded-md border border-rule bg-surface px-3 py-1.5 text-[0.82rem] font-semibold text-ink-soft hover:bg-parchment transition-colors"
>
<Users className="w-3.5 h-3.5" />
צדדים
</button>
</PopoverTrigger>
<PopoverContent align="start" className="w-72 p-3 space-y-2.5 text-sm">
{appellants.length > 0 && (
<div>
<div className="text-[0.7rem] font-bold text-gold-deep uppercase tracking-wider mb-1">
עוררים
</div>
<ul className="space-y-0.5 text-ink-soft">
{appellants.map((a, i) => <li key={`a${i}`}>{a}</li>)}
</ul>
</div>
)}
{respondents.length > 0 && (
<div>
<div className="text-[0.7rem] font-bold text-gold-deep uppercase tracking-wider mb-1">
משיבים
</div>
<ul className="space-y-0.5 text-ink-soft">
{respondents.map((r, i) => <li key={`r${i}`}>{r}</li>)}
</ul>
</div>
)}
</PopoverContent>
</Popover>
)}
{data?.case_number && (
<CaseArchiveAction
caseNumber={data.case_number}
archivedAt={data.archived_at}
/>
)}
<CreateRepoButton data={data} />
{actions}
</div>
</div>
</div>
{/* ★ integrated status rail — visible on every tab (banner A, X17) */}
<StatusRail caseNumber={caseNumber} data={data} />
{/* tab strip anchored to band bottom */}
{tabs ? <div className="mt-4">{tabs}</div> : null}
</div>
);
}