5e9963f51b
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>
97 lines
3.7 KiB
TypeScript
97 lines
3.7 KiB
TypeScript
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>
|
||
);
|
||
}
|