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
-14
View File
@@ -1,26 +1,12 @@
"use client"; "use client";
import "maplibre-gl/dist/maplibre-gl.css";
import { FeatureGate } from "@/core/feature-flags"; import { FeatureGate } from "@/core/feature-flags";
import { useI18n } from "@/core/i18n";
import { GeoportalModule } from "@/modules/geoportal"; import { GeoportalModule } from "@/modules/geoportal";
export default function GeoportalPage() { export default function GeoportalPage() {
const { t } = useI18n();
return ( return (
<FeatureGate flag="module.geoportal" fallback={<ModuleDisabled />}> <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 /> <GeoportalModule />
</div>
</FeatureGate> </FeatureGate>
); );
} }
-1
View File
@@ -1,6 +1,5 @@
"use client"; "use client";
import "maplibre-gl/dist/maplibre-gl.css";
import { FeatureGate } from "@/core/feature-flags"; import { FeatureGate } from "@/core/feature-flags";
import { useI18n } from "@/core/i18n"; import { useI18n } from "@/core/i18n";
import { ParcelSyncModule } from "@/modules/parcel-sync"; import { ParcelSyncModule } from "@/modules/parcel-sync";
@@ -2,7 +2,6 @@
import { useState, useRef, useCallback } from "react"; import { useState, useRef, useCallback } from "react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { Globe } from "lucide-react";
import { LayerPanel, getDefaultVisibility } from "./layer-panel"; import { LayerPanel, getDefaultVisibility } from "./layer-panel";
import { BasemapSwitcher } from "./basemap-switcher"; import { BasemapSwitcher } from "./basemap-switcher";
import { SearchBar } from "./search-bar"; import { SearchBar } from "./search-bar";
@@ -94,20 +93,9 @@ export function GeoportalModule() {
}, []); }, []);
return ( return (
<div className="space-y-4"> <div className="relative -mx-4 -mt-4 sm:-mx-6 sm:-mt-6 lg:-mx-8 lg:-mt-8"
{/* Header */} style={{ height: "calc(100vh - 3.5rem)" }}>
<div className="flex items-center gap-3"> {/* Full-bleed map */}
<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 <MapViewer
ref={mapHandleRef} ref={mapHandleRef}
className="h-full w-full" className="h-full w-full"
@@ -129,7 +117,7 @@ export function GeoportalModule() {
/> />
</div> </div>
{/* Top-right: basemap switcher */} {/* Top-right: basemap switcher (offset to avoid nav controls) */}
<div className="absolute top-3 right-14 z-10"> <div className="absolute top-3 right-14 z-10">
<BasemapSwitcher value={basemap} onChange={setBasemap} /> <BasemapSwitcher value={basemap} onChange={setBasemap} />
</div> </div>
@@ -146,7 +134,7 @@ export function GeoportalModule() {
{/* Right side: feature info panel */} {/* Right side: feature info panel */}
{clickedFeature && !selectionMode && ( {clickedFeature && !selectionMode && (
<div className="absolute top-3 right-3 z-10 mt-12"> <div className="absolute top-16 right-3 z-10">
<FeatureInfoPanel <FeatureInfoPanel
feature={clickedFeature} feature={clickedFeature}
onClose={() => setClickedFeature(null)} onClose={() => setClickedFeature(null)}
@@ -154,6 +142,5 @@ export function GeoportalModule() {
</div> </div>
)} )}
</div> </div>
</div>
); );
} }
@@ -2,8 +2,19 @@
import { useRef, useEffect, useState, useCallback, useImperativeHandle, forwardRef } from "react"; import { useRef, useEffect, useState, useCallback, useImperativeHandle, forwardRef } from "react";
import maplibregl from "maplibre-gl"; import maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { cn } from "@/shared/lib/utils"; 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"; import type { BasemapId, ClickedFeature, LayerVisibility, SelectedFeature } from "../types";
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */