Files
legal-ai/web-ui/src/app/page.tsx
Chaim e849285806
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 32s
home: split cases table by appeal type + add appeal-type chart
Backend (cases listing)
- /api/cases: also return updated_at, created_at, practice_area,
  appeal_subtype, subject. The detail-mode response was previously
  dropping these even though db.list_cases reads them, leaving the
  UI's "תחום" and "עודכן" columns blank.

Frontend
- Split the home table into two: רישוי (1xxx) and היטל השבחה ופיצויים
  (8xxx + 9xxx), bucketing on appeal_subtype with a case-number-prefix
  fallback. The "תחום" column is now redundant and removed.
- New AppealTypeBars chart in the right rail next to the existing
  status donut.
- Donut: switch to a vertical layout (donut on top, legend below in a
  3-col grid) so labels like "חדש / בעיבוד" no longer wrap inside the
  320px sidebar; counts now align in a tabular column.
- CasesTable accepts emptyText/searchPlaceholder so each split table
  has its own copy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 15:44:41 +00:00

121 lines
5.1 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";
export default function HomePage() {
const { data, isPending, error } = useCases(true);
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">
<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>
);
}