fix(geoportal): load MapLibre CSS via CDN link injection + fullscreen layout

- Static CSS import doesn't work with next/dynamic + standalone output.
  Now injects a <link> tag to unpkg CDN at module load time (bulletproof).
- Geoportal is now fullscreen: map fills entire viewport below the header,
  no duplicate title/description, negative margins bleed to edges.
- Removed page-level CSS imports (no longer needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 17:01:48 +02:00
parent 3346ec709d
commit 437d734df6
4 changed files with 58 additions and 75 deletions
+1 -15
View File
@@ -1,26 +1,12 @@
"use client";
import "maplibre-gl/dist/maplibre-gl.css";
import { FeatureGate } from "@/core/feature-flags";
import { useI18n } from "@/core/i18n";
import { GeoportalModule } from "@/modules/geoportal";
export default function GeoportalPage() {
const { t } = useI18n();
return (
<FeatureGate flag="module.geoportal" fallback={<ModuleDisabled />}>
<div className="mx-auto max-w-7xl space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight">
{t("geoportal.title")}
</h1>
<p className="text-muted-foreground">
{t("geoportal.description")}
</p>
</div>
<GeoportalModule />
</div>
<GeoportalModule />
</FeatureGate>
);
}
-1
View File
@@ -1,6 +1,5 @@
"use client";
import "maplibre-gl/dist/maplibre-gl.css";
import { FeatureGate } from "@/core/feature-flags";
import { useI18n } from "@/core/i18n";
import { ParcelSyncModule } from "@/modules/parcel-sync";
@@ -2,7 +2,6 @@
import { useState, useRef, useCallback } from "react";
import dynamic from "next/dynamic";
import { Globe } from "lucide-react";
import { LayerPanel, getDefaultVisibility } from "./layer-panel";
import { BasemapSwitcher } from "./basemap-switcher";
import { SearchBar } from "./search-bar";
@@ -94,66 +93,54 @@ export function GeoportalModule() {
}, []);
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>
<div className="relative -mx-4 -mt-4 sm:-mx-6 sm:-mt-6 lg:-mx-8 lg:-mt-8"
style={{ height: "calc(100vh - 3.5rem)" }}>
{/* Full-bleed map */}
<MapViewer
ref={mapHandleRef}
className="h-full w-full"
basemap={basemap}
selectionMode={selectionMode}
onFeatureClick={handleFeatureClick}
onSelectionChange={handleSelectionChange}
layerVisibility={layerVisibility}
center={flyTarget?.center}
zoom={flyTarget?.zoom}
/>
{/* 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"
basemap={basemap}
selectionMode={selectionMode}
onFeatureClick={handleFeatureClick}
onSelectionChange={handleSelectionChange}
layerVisibility={layerVisibility}
center={flyTarget?.center}
zoom={flyTarget?.zoom}
{/* Top-left controls: search + layers */}
<div className="absolute top-3 left-3 z-10 flex flex-col gap-2 max-w-xs">
<SearchBar onResultSelect={handleSearchResult} />
<LayerPanel
visibility={layerVisibility}
onVisibilityChange={handleVisibilityChange}
/>
{/* Top-left controls: search + layers */}
<div className="absolute top-3 left-3 z-10 flex flex-col gap-2 max-w-xs">
<SearchBar onResultSelect={handleSearchResult} />
<LayerPanel
visibility={layerVisibility}
onVisibilityChange={handleVisibilityChange}
/>
</div>
{/* Top-right: basemap switcher */}
<div className="absolute top-3 right-14 z-10">
<BasemapSwitcher value={basemap} onChange={setBasemap} />
</div>
{/* Bottom-left: selection toolbar */}
<div className="absolute bottom-8 left-3 z-10">
<SelectionToolbar
selectedFeatures={selectedFeatures}
selectionMode={selectionMode}
onToggleSelectionMode={handleToggleSelectionMode}
onClearSelection={handleClearSelection}
/>
</div>
{/* Right side: feature info panel */}
{clickedFeature && !selectionMode && (
<div className="absolute top-3 right-3 z-10 mt-12">
<FeatureInfoPanel
feature={clickedFeature}
onClose={() => setClickedFeature(null)}
/>
</div>
)}
</div>
{/* Top-right: basemap switcher (offset to avoid nav controls) */}
<div className="absolute top-3 right-14 z-10">
<BasemapSwitcher value={basemap} onChange={setBasemap} />
</div>
{/* Bottom-left: selection toolbar */}
<div className="absolute bottom-8 left-3 z-10">
<SelectionToolbar
selectedFeatures={selectedFeatures}
selectionMode={selectionMode}
onToggleSelectionMode={handleToggleSelectionMode}
onClearSelection={handleClearSelection}
/>
</div>
{/* Right side: feature info panel */}
{clickedFeature && !selectionMode && (
<div className="absolute top-16 right-3 z-10">
<FeatureInfoPanel
feature={clickedFeature}
onClose={() => setClickedFeature(null)}
/>
</div>
)}
</div>
);
}
@@ -2,8 +2,19 @@
import { useRef, useEffect, useState, useCallback, useImperativeHandle, forwardRef } from "react";
import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { cn } from "@/shared/lib/utils";
/* Ensure MapLibre CSS is loaded — static import fails with next/dynamic + standalone */
if (typeof document !== "undefined") {
const LINK_ID = "maplibre-gl-css";
if (!document.getElementById(LINK_ID)) {
const link = document.createElement("link");
link.id = LINK_ID;
link.rel = "stylesheet";
link.href = "https://unpkg.com/maplibre-gl@5.21.0/dist/maplibre-gl.css";
document.head.appendChild(link);
}
}
import type { BasemapId, ClickedFeature, LayerVisibility, SelectedFeature } from "../types";
/* ------------------------------------------------------------------ */