feat(geoportal): one-time setup banner for PostGIS views
- GET /api/geoportal/setup-views checks if zoom views exist - POST creates them (idempotent) - SetupBanner component: auto-checks on mount, shows amber banner if views missing, button to create them, success message with docker restart reminder, auto-hides when everything is ready Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { BasemapSwitcher } from "./basemap-switcher";
|
||||
import { SearchBar } from "./search-bar";
|
||||
import { SelectionToolbar, type SelectionMode } from "./selection-toolbar";
|
||||
import { FeatureInfoPanel } from "./feature-info-panel";
|
||||
import { SetupBanner } from "./setup-banner";
|
||||
import type { MapViewerHandle } from "./map-viewer";
|
||||
import type {
|
||||
BasemapId, ClickedFeature, LayerVisibility, SearchResult, SelectedFeature,
|
||||
@@ -70,6 +71,11 @@ export function GeoportalModule() {
|
||||
zoom={flyTarget?.zoom}
|
||||
/>
|
||||
|
||||
{/* Setup banner (auto-hides when ready) */}
|
||||
<div className="absolute top-3 left-1/2 -translate-x-1/2 z-20 w-auto max-w-lg">
|
||||
<SetupBanner />
|
||||
</div>
|
||||
|
||||
{/* Top-left: search + layers */}
|
||||
<div className="absolute top-3 left-3 z-10 flex flex-col gap-2 max-w-xs">
|
||||
<SearchBar onResultSelect={handleSearchResult} />
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Database, Loader2, CheckCircle2, AlertTriangle } from "lucide-react";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
|
||||
type Status = "checking" | "needed" | "running" | "done" | "error" | "ready";
|
||||
|
||||
export function SetupBanner() {
|
||||
const [status, setStatus] = useState<Status>("checking");
|
||||
const [missing, setMissing] = useState<string[]>([]);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
// Check on mount
|
||||
useEffect(() => {
|
||||
fetch("/api/geoportal/setup-views")
|
||||
.then((r) => r.json())
|
||||
.then((d: { ready: boolean; missing: string[] }) => {
|
||||
if (d.ready) setStatus("ready");
|
||||
else { setStatus("needed"); setMissing(d.missing); }
|
||||
})
|
||||
.catch(() => setStatus("ready")); // don't block if check fails
|
||||
}, []);
|
||||
|
||||
const runSetup = async () => {
|
||||
setStatus("running");
|
||||
try {
|
||||
const r = await fetch("/api/geoportal/setup-views", { method: "POST" });
|
||||
const d = await r.json();
|
||||
if (d.status === "ok") setStatus("done");
|
||||
else { setStatus("error"); setError(d.error ?? "Eroare necunoscuta"); }
|
||||
} catch (e) {
|
||||
setStatus("error");
|
||||
setError(e instanceof Error ? e.message : "Eroare retea");
|
||||
}
|
||||
};
|
||||
|
||||
// Don't show if ready or still checking
|
||||
if (status === "ready" || status === "checking") return null;
|
||||
|
||||
// Auto-hide after success
|
||||
if (status === "done") {
|
||||
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" />
|
||||
View-uri create cu succes. Restarteaza Martin pe server: <code className="bg-muted px-1 rounded">docker restart martin</code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === "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" />
|
||||
Eroare: {error}
|
||||
<Button variant="outline" size="sm" className="h-6 text-xs ml-auto" onClick={runSetup}>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">
|
||||
<Database className="h-4 w-4 text-amber-600 dark:text-amber-400 shrink-0" />
|
||||
<span className="text-amber-700 dark:text-amber-300">
|
||||
{missing.length} view-uri PostGIS lipsesc ({missing.join(", ")}). Necesare pentru tile-uri UAT optimizate.
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs ml-auto shrink-0"
|
||||
onClick={runSetup}
|
||||
disabled={status === "running"}
|
||||
>
|
||||
{status === "running" ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : null}
|
||||
{status === "running" ? "Se creaza..." : "Creaza view-uri"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user