feat: add Geoportal module with MapLibre GL JS + Martin vector tiles
Phase 1 of the geoportal implementation: Infrastructure: - Martin vector tile server in docker-compose (port 3010) - PostGIS setup SQL for GisUat: native geom column, Esri→PostGIS trigger, GiST index, gis_uats view for Martin auto-discovery Geoportal module (src/modules/geoportal/): - map-viewer.tsx: MapLibre GL JS canvas with OSM base, Martin MVT sources (gis_uats, gis_terenuri, gis_cladiri), click-to-inspect, zoom-level-aware layer visibility, layer styling - layer-panel.tsx: collapsible sidebar with layer toggles - geoportal-module.tsx: standalone page wrapper - Module registered in config/modules.ts, flags.ts, i18n ParcelSync integration: - 6th tab "Harta" with lazy-loaded MapViewer (ssr: false) - Centered on selected UAT Dependencies: maplibre-gl v5.21.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useCallback } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Globe } from "lucide-react";
|
||||
import { LayerPanel, getDefaultVisibility } from "./layer-panel";
|
||||
import type { MapViewerHandle } from "./map-viewer";
|
||||
import type { ClickedFeature, LayerVisibility } from "../types";
|
||||
|
||||
/* MapLibre uses WebGL — must disable SSR */
|
||||
const MapViewer = dynamic(
|
||||
() =>
|
||||
import("./map-viewer").then((m) => ({
|
||||
default: m.MapViewer,
|
||||
})),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="flex items-center justify-center h-full bg-muted/30">
|
||||
<p className="text-sm text-muted-foreground">Se incarca harta...</p>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Component */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function GeoportalModule() {
|
||||
const mapHandleRef = useRef<MapViewerHandle>(null);
|
||||
const [layerVisibility, setLayerVisibility] = useState<LayerVisibility>(
|
||||
getDefaultVisibility
|
||||
);
|
||||
|
||||
const handleFeatureClick = useCallback((feature: ClickedFeature) => {
|
||||
// Feature click is handled by the MapViewer popup internally.
|
||||
// This callback is available for future integration (e.g., detail panel).
|
||||
void feature;
|
||||
}, []);
|
||||
|
||||
const handleVisibilityChange = useCallback((vis: LayerVisibility) => {
|
||||
setLayerVisibility(vis);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Globe className="h-6 w-6 text-muted-foreground" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Geoportal</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Harta interactiva cu parcele cadastrale, cladiri si limite UAT
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Map container */}
|
||||
<div className="relative h-[calc(100vh-12rem)] min-h-[500px] rounded-lg border overflow-hidden">
|
||||
<MapViewer
|
||||
ref={mapHandleRef}
|
||||
className="h-full w-full"
|
||||
onFeatureClick={handleFeatureClick}
|
||||
layerVisibility={layerVisibility}
|
||||
/>
|
||||
|
||||
{/* Layer panel overlay */}
|
||||
<div className="absolute top-3 left-3 z-10">
|
||||
<LayerPanel
|
||||
visibility={layerVisibility}
|
||||
onVisibilityChange={handleVisibilityChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user