diff --git a/src/app/api/geoportal/setup-views/route.ts b/src/app/api/geoportal/setup-views/route.ts index eaeb980..eebea18 100644 --- a/src/app/api/geoportal/setup-views/route.ts +++ b/src/app/api/geoportal/setup-views/route.ts @@ -1,9 +1,6 @@ /** - * POST /api/geoportal/setup-views - * - * Creates the zoom-dependent UAT views for Martin vector tiles. - * Safe to re-run (CREATE OR REPLACE VIEW). - * Original geometry in GisUat.geom is NEVER modified. + * GET /api/geoportal/setup-views — check if views exist + * POST /api/geoportal/setup-views — create views (idempotent) */ import { NextResponse } from "next/server"; import { prisma } from "@/core/storage/prisma"; @@ -26,25 +23,39 @@ const VIEWS = [ }, { name: "gis_uats_z12", - sql: `CREATE OR REPLACE VIEW gis_uats_z12 AS SELECT siruta, name, county, ST_SimplifyPreserveTopology(geom, 10) AS geom FROM "GisUat" WHERE geom IS NOT NULL`, + sql: `CREATE OR REPLACE VIEW gis_uats_z12 AS SELECT siruta, name, county, geom FROM "GisUat" WHERE geom IS NOT NULL`, }, ]; +/** GET — returns { ready: boolean, missing: string[] } */ +export async function GET() { + try { + const existing = await prisma.$queryRaw` + SELECT viewname FROM pg_views + WHERE schemaname = 'public' AND viewname LIKE 'gis_uats_z%' + ` as Array<{ viewname: string }>; + + const existingNames = new Set(existing.map((r) => r.viewname)); + const missing = VIEWS.filter((v) => !existingNames.has(v.name)).map((v) => v.name); + + return NextResponse.json({ ready: missing.length === 0, missing }); + } catch (error) { + const msg = error instanceof Error ? error.message : "Eroare"; + return NextResponse.json({ ready: false, missing: VIEWS.map((v) => v.name), error: msg }); + } +} + +/** POST — creates all views (idempotent) */ export async function POST() { const results: string[] = []; - try { for (const v of VIEWS) { await prisma.$executeRawUnsafe(v.sql); results.push(`${v.name} OK`); } - return NextResponse.json({ status: "ok", results }); } catch (error) { - const msg = error instanceof Error ? error.message : "Unknown error"; - return NextResponse.json( - { status: "error", results, error: msg }, - { status: 500 } - ); + const msg = error instanceof Error ? error.message : "Eroare"; + return NextResponse.json({ status: "error", results, error: msg }, { status: 500 }); } } diff --git a/src/modules/geoportal/components/geoportal-module.tsx b/src/modules/geoportal/components/geoportal-module.tsx index 06b13c3..cbc4cac 100644 --- a/src/modules/geoportal/components/geoportal-module.tsx +++ b/src/modules/geoportal/components/geoportal-module.tsx @@ -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) */} +
+ +
+ {/* Top-left: search + layers */}
diff --git a/src/modules/geoportal/components/setup-banner.tsx b/src/modules/geoportal/components/setup-banner.tsx new file mode 100644 index 0000000..7e8bf80 --- /dev/null +++ b/src/modules/geoportal/components/setup-banner.tsx @@ -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("checking"); + const [missing, setMissing] = useState([]); + 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 ( +
+ + View-uri create cu succes. Restarteaza Martin pe server: docker restart martin +
+ ); + } + + if (status === "error") { + return ( +
+ + Eroare: {error} + +
+ ); + } + + return ( +
+ + + {missing.length} view-uri PostGIS lipsesc ({missing.join(", ")}). Necesare pentru tile-uri UAT optimizate. + + +
+ ); +}