Files
nadlan-mcp/web/src/components/AppraisalsTable.tsx
T
chaim 5e9963f51b Add: Web UI (React 19 + Vite + shadcn/ui) + FastAPI REST layer
Adds a self-contained web interface that wraps the MCP tools so end users
don't need to issue MCP commands. Single Docker container serves:

  /              → React SPA (RTL Hebrew, Heebo font, shadcn/ui components)
  /api/*         → FastAPI REST endpoints used by the SPA
  /api/docs      → OpenAPI / Swagger
  /mcp           → existing FastMCP streamable-http endpoint
  /health        → liveness probe

REST endpoints (in nadlan_mcp/web_app.py):
  GET  /api/search/address?q=...  — autocomplete + best-effort gush/חלקה
                                    via Govmap PARCEL_ALL layer
  POST /api/search/deals          — Govmap deals around an address
  POST /api/search/appraisals     — Decisive-appraiser search (gov.il)
  GET  /api/appraisals/pdf?url=.. — Stream PDFs through this server,
                                    carrying the upstream x-client-id
                                    header (a normal browser fetch fails)

UI flow:
  - Address search → resolves gush/חלקה → shows BOTH deals (Govmap) AND
    appraiser decisions (gov.il) for the same parcel side-by-side in tabs.
  - Block+plot search → goes straight to appraisals.
  - Appraiser-name search → all decisions by that appraiser.

Stack chosen for RTL-first usability and longevity: React 19, Vite 6,
TypeScript, Tailwind, shadcn/ui (Radix primitives), TanStack Query,
Lucide icons. ~295KB gzipped JS, no SSR overhead.

Multi-stage Dockerfile: Node 22 builds web/ in stage 1, Python 3.13
runtime serves the dist + the FastAPI app in stage 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:35:08 +00:00

97 lines
3.7 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 { Download, FileText } from "lucide-react";
import { AppraisalDecision } from "@/api/types";
import { Button } from "@/components/ui/button";
import { api } from "@/api/client";
import { formatDate } from "@/lib/utils";
interface Props {
decisions: AppraisalDecision[];
totalInDb: number;
}
export function AppraisalsTable({ decisions, totalInDb }: Props) {
if (!decisions.length) {
return (
<div className="text-center py-12 text-muted-foreground">
לא נמצאו החלטות שמאי מכריע באזור הזה
</div>
);
}
return (
<div className="space-y-3">
<div className="text-sm text-muted-foreground">
נמצאו {decisions.length} מתוך {totalInDb.toLocaleString("he-IL")} החלטות במאגר
</div>
<div className="overflow-x-auto rounded-md border">
<table className="w-full text-sm">
<thead className="bg-muted text-muted-foreground">
<tr>
<th className="p-3 text-start font-medium">תאריך הכרעה</th>
<th className="p-3 text-start font-medium">שמאי מכריע</th>
<th className="p-3 text-start font-medium">ועדה</th>
<th className="p-3 text-start font-medium">גוש/חלקה</th>
<th className="p-3 text-start font-medium">סוג שומה</th>
<th className="p-3 text-start font-medium">כותרת ההחלטה</th>
<th className="p-3 text-end font-medium">PDF</th>
</tr>
</thead>
<tbody>
{decisions.map((d) => (
<tr key={d.id} className="border-t hover:bg-accent/40 align-top">
<td className="p-3 whitespace-nowrap">{formatDate(d.decision_date)}</td>
<td className="p-3 whitespace-nowrap">{d.decisive_appraiser || "—"}</td>
<td className="p-3 whitespace-nowrap">{d.committee || "—"}</td>
<td className="p-3 whitespace-nowrap tabular-nums">
{d.block ?? "—"}
{d.plot ? ` / ${d.plot}` : ""}
</td>
<td className="p-3">{d.appraisal_type || "—"}</td>
<td className="p-3 max-w-md">
<span className="line-clamp-2">{d.appraisal_header || "—"}</span>
</td>
<td className="p-3 text-end whitespace-nowrap">
{d.pdf_url ? (
<div className="flex justify-end gap-1">
<Button
size="sm"
variant="outline"
asChild
title="פתח PDF בלשונית חדשה"
>
<a
href={api.pdfProxyUrl(d.pdf_url)}
target="_blank"
rel="noopener noreferrer"
>
<FileText />
פתח
</a>
</Button>
<Button
size="sm"
variant="ghost"
asChild
title="הורד PDF"
>
<a
href={api.pdfProxyUrl(d.pdf_url) + "&download=1"}
download={`${d.block || "appraisal"}-${d.plot || "0"}-${d.id}.pdf`}
>
<Download />
</a>
</Button>
</div>
) : (
"—"
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}