Files
legal-ai/web-ui/src/app/page.tsx
Chaim b0efa700da
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 7s
feat(ui): IA redesign → production · /home pending-approvals card + Claude Design gate (2/17)
- /home: כרטיס "מה ממתין להכרעתך" ב-aside (מצביע INV-IA1 ל-/approvals; usePendingApprovals,
  ללא מונה-מתחרה) — תואם מוקאפ 04-home המאושר.
- web-ui/AGENTS.md: §"שער-עיצוב חובה — Claude Design קודם" — כל יצירת/שינוי עמוד-UI
  עוברת קודם דרך פרויקט Claude Design "עוזר משפטי — IA Redesign (X17)"
  (7a85b323-d880-4b6d-bac5-d4aa396fe93c) לאישור, ורק אז מוטמע (הנחיית חיים).

בדיקה: npx tsc --noEmit ✓.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 22:10:56 +00:00

158 lines
6.8 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 { useMemo } from "react";
import Link from "next/link";
import { AppShell } from "@/components/app-shell";
import { KPICards } from "@/components/cases/kpi-cards";
import { StatusDonut } from "@/components/cases/status-donut";
import { AppealTypeBars, subtypeOf } from "@/components/cases/appeal-type-bars";
import { CasesTable } from "@/components/cases/cases-table";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { useCases, type Case } from "@/lib/api/cases";
import { usePendingApprovals } from "@/lib/api/chair";
export default function HomePage() {
const { data, isPending, error } = useCases(true);
// INV-IA1 pointer: surface the single approvals aggregate on the dashboard,
// deep-linking to /approvals (the owner) — never a competing client counter.
const { data: approvals } = usePendingApprovals();
const { permits, levies } = useMemo(() => {
const permits: Case[] = [];
const levies: Case[] = [];
(data ?? []).forEach((c) => {
const s = subtypeOf(c);
if (s === "building_permit") permits.push(c);
else if (s === "betterment_levy" || s === "compensation_197") levies.push(c);
else permits.push(c); // fallback bucket — keep visible
});
return { permits, levies };
}, [data]);
return (
<AppShell>
<section className="space-y-8">
<header className="flex items-end justify-between gap-6 flex-wrap">
<div className="space-y-1.5">
<div className="text-[0.75rem] uppercase tracking-[0.12em] text-gold-deep">
ועדת ערר לתכנון ובנייה · ירושלים
</div>
<h1 className="text-navy">עוזר משפטי</h1>
<p className="text-ink-muted text-base max-w-2xl leading-relaxed">
לוח בקרה לניהול תיקי ערר, ניתוח סגנון, וכתיבת החלטות לפי ארכיטקטורת
12 הבלוקים.
</p>
</div>
<Button asChild className="bg-navy hover:bg-navy-soft text-parchment">
<Link href="/cases/new">+ תיק חדש</Link>
</Button>
</header>
<div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />
<KPICards cases={data} loading={isPending} />
<div className="grid gap-6 lg:grid-cols-[1fr_320px]">
<div className="space-y-6 min-w-0">
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5">
<div className="flex items-center justify-between gap-3 mb-4 flex-wrap">
<div className="flex items-baseline gap-3">
<h2 className="text-navy text-xl mb-0">רישוי ובנייה</h2>
<span className="text-[0.72rem] uppercase tracking-[0.08em] text-ink-muted">
עררים 1xxx
</span>
</div>
<span className="text-[0.72rem] uppercase tracking-[0.08em] text-ink-muted">
מעודכן חי
</span>
</div>
<CasesTable
cases={permits}
loading={isPending}
error={error}
emptyText="אין תיקי רישוי פעילים"
searchPlaceholder="חיפוש בעררי רישוי…"
/>
</CardContent>
</Card>
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5">
<div className="flex items-center justify-between gap-3 mb-4 flex-wrap">
<div className="flex items-baseline gap-3">
<h2 className="text-navy text-xl mb-0">היטל השבחה ופיצויים</h2>
<span className="text-[0.72rem] uppercase tracking-[0.08em] text-ink-muted">
עררים 8xxx · 9xxx
</span>
</div>
<span className="text-[0.72rem] uppercase tracking-[0.08em] text-ink-muted">
מעודכן חי
</span>
</div>
<CasesTable
cases={levies}
loading={isPending}
error={error}
emptyText="אין תיקי היטל השבחה או פיצויים פעילים"
searchPlaceholder="חיפוש בעררי השבחה ופיצויים…"
/>
</CardContent>
</Card>
</div>
<aside className="space-y-6 lg:sticky lg:top-6 lg:self-start">
{approvals && approvals.total_pending > 0 ? (
<Card className="bg-gold-wash border-gold/40 shadow-sm">
<CardContent className="px-6 py-5">
<div className="flex items-center justify-between gap-3 mb-3">
<h2 className="text-navy text-lg mb-0">מה ממתין להכרעתך</h2>
<span className="text-2xl font-semibold text-gold-deep leading-none tabular-nums">
{approvals.total_pending}
</span>
</div>
<ul className="space-y-1.5 mb-4">
{approvals.categories
.filter((c) => c.count > 0)
.map((c) => (
<li
key={c.key}
className="flex items-center justify-between gap-2 text-[0.85rem] text-ink-soft"
>
<span>{c.label}</span>
<span className="text-navy font-semibold tabular-nums">{c.count}</span>
</li>
))}
</ul>
<Button
asChild
size="sm"
className="bg-gold text-white hover:bg-gold-deep border-transparent w-full"
>
<Link href="/approvals">למרכז האישורים </Link>
</Button>
</CardContent>
</Card>
) : null}
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5">
<h2 className="text-navy text-lg mb-4">פיזור סטטוסים</h2>
<StatusDonut cases={data} />
</CardContent>
</Card>
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5">
<h2 className="text-navy text-lg mb-4">פיזור לפי תחום</h2>
<AppealTypeBars cases={data} />
</CardContent>
</Card>
</aside>
</div>
</section>
</AppShell>
);
}