feat(geoportal): add search, basemap switcher, feature info panel, selection + export

Major geoportal enhancements:
- Basemap switcher (OSM/Satellite/Terrain) with ESRI + OpenTopoMap tiles
- Search bar with debounced lookup (UATs by name, parcels by cadastral ref, owners by name)
- Feature info panel showing enrichment data from ParcelSync (cadastru, proprietari, suprafata, folosinta)
- Parcel selection mode with amber highlight + export (GeoJSON/DXF/GPKG via ogr2ogr)
- Next.js /tiles rewrite proxying to Martin (fixes dev + avoids mixed content)
- Fixed MapLibre web worker relative URL resolution (window.location.origin)

API routes: /api/geoportal/search, /api/geoportal/feature, /api/geoportal/export

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 16:43:01 +02:00
parent 4ea7c6dbd6
commit 1b5876524a
11 changed files with 1427 additions and 33 deletions
@@ -0,0 +1,44 @@
"use client";
import { Map, Mountain, Satellite } from "lucide-react";
import { Button } from "@/shared/components/ui/button";
import { cn } from "@/shared/lib/utils";
import type { BasemapId } from "../types";
const BASEMAPS: { id: BasemapId; label: string; icon: typeof Map }[] = [
{ id: "osm", label: "Harta", icon: Map },
{ id: "satellite", label: "Satelit", icon: Satellite },
{ id: "topo", label: "Teren", icon: Mountain },
];
type BasemapSwitcherProps = {
value: BasemapId;
onChange: (id: BasemapId) => void;
};
export function BasemapSwitcher({ value, onChange }: BasemapSwitcherProps) {
return (
<div className="bg-background/95 backdrop-blur-sm border rounded-lg shadow-lg flex p-0.5 gap-0.5">
{BASEMAPS.map((b) => {
const Icon = b.icon;
const active = value === b.id;
return (
<Button
key={b.id}
variant="ghost"
size="sm"
className={cn(
"px-2 py-1 h-7 text-xs gap-1 rounded-md",
active && "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground"
)}
onClick={() => onChange(b.id)}
title={b.label}
>
<Icon className="h-3.5 w-3.5" />
<span className="hidden sm:inline">{b.label}</span>
</Button>
);
})}
</div>
);
}