Files
ArchiTools/src/modules/geoportal/components/setup-banner.tsx
T
AI Assistant 60919122d9 feat(geoportal): one-click optimize-tiles + unified setup banner
New endpoint POST /api/geoportal/optimize-tiles:
- Slims gis_features view (drops attributes, enrichment, timestamps)
- Cascades to gis_terenuri, gis_cladiri, gis_administrativ, gis_documentatii
- Makes vector tiles dramatically smaller

Setup banner now checks 3 optimizations:
1. UAT zoom views (gis_uats_z0/z5/z8/z12)
2. Pre-computed geometry (geom_z0/z5/z8 columns)
3. Slim tile views (no JSON columns)

One "Aplica toate" button runs all pending steps sequentially.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:26:08 +02:00

98 lines
3.5 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { Database, Loader2, CheckCircle2, AlertTriangle, Zap } from "lucide-react";
import { Button } from "@/shared/components/ui/button";
type Step = { label: string; endpoint: string; done: boolean };
export function SetupBanner() {
const [steps, setSteps] = useState<Step[]>([]);
const [running, setRunning] = useState("");
const [error, setError] = useState("");
const [doneMsg, setDoneMsg] = useState("");
const [checking, setChecking] = useState(true);
useEffect(() => {
(async () => {
const pending: Step[] = [];
try {
const vr = await fetch("/api/geoportal/setup-views").then((r) => r.json());
pending.push({ label: "View-uri UAT zoom", endpoint: "/api/geoportal/setup-views", done: vr.ready });
const or1 = await fetch("/api/geoportal/optimize-views").then((r) => r.json());
pending.push({ label: "Geometrie pre-calculata", endpoint: "/api/geoportal/optimize-views", done: or1.optimized });
const or2 = await fetch("/api/geoportal/optimize-tiles").then((r) => r.json());
pending.push({ label: "Tile-uri slim (fara JSON greu)", endpoint: "/api/geoportal/optimize-tiles", done: or2.optimized });
} catch { /* noop */ }
setSteps(pending);
setChecking(false);
})();
}, []);
const pendingSteps = steps.filter((s) => !s.done);
if (checking || pendingSteps.length === 0) return null;
const runAll = async () => {
setError("");
for (const step of pendingSteps) {
setRunning(step.label);
try {
const r = await fetch(step.endpoint, { method: "POST" });
const d = await r.json();
if (d.status !== "ok") {
setError(`${step.label}: ${d.error ?? "Eroare"}`);
setRunning("");
return;
}
step.done = true;
} catch (e) {
setError(`${step.label}: ${e instanceof Error ? e.message : "Eroare"}`);
setRunning("");
return;
}
}
setRunning("");
setDoneMsg("Optimizari aplicate. Restart Martin: docker restart martin");
setSteps([...steps]);
};
if (doneMsg) {
return (
<div className="bg-green-500/10 border border-green-500/30 rounded-lg px-3 py-2 flex items-center gap-2 text-xs text-green-700 dark:text-green-400">
<CheckCircle2 className="h-4 w-4 shrink-0" />
<span>{doneMsg}</span>
</div>
);
}
if (error) {
return (
<div className="bg-red-500/10 border border-red-500/30 rounded-lg px-3 py-2 flex items-center gap-2 text-xs text-red-700 dark:text-red-400">
<AlertTriangle className="h-4 w-4 shrink-0" />
<span>{error}</span>
<Button variant="outline" size="sm" className="h-6 text-xs ml-auto" onClick={runAll}>Reincearca</Button>
</div>
);
}
return (
<div className="bg-amber-500/10 border border-amber-500/30 rounded-lg px-3 py-2 flex items-center gap-2 text-xs">
<Zap className="h-4 w-4 text-amber-600 dark:text-amber-400 shrink-0" />
<span className="text-amber-700 dark:text-amber-300 flex-1">
{running
? `Se aplica: ${running}...`
: `${pendingSteps.length} optimizari necesare: ${pendingSteps.map((s) => s.label).join(", ")}`}
</span>
<Button
variant="outline" size="sm" className="h-6 text-xs shrink-0"
disabled={!!running} onClick={runAll}
>
{running ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : <Database className="h-3 w-3 mr-1" />}
{running ? "Se aplica..." : "Aplica toate"}
</Button>
</div>
);
}