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:
@@ -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">
|
<GeoportalModule />
|
||||||
<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>
|
|
||||||
</FeatureGate>
|
</FeatureGate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,66 +93,54 @@ 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" />
|
<MapViewer
|
||||||
<div>
|
ref={mapHandleRef}
|
||||||
<h2 className="text-lg font-semibold">Geoportal</h2>
|
className="h-full w-full"
|
||||||
<p className="text-sm text-muted-foreground">
|
basemap={basemap}
|
||||||
Harta interactiva cu parcele cadastrale, cladiri si limite UAT
|
selectionMode={selectionMode}
|
||||||
</p>
|
onFeatureClick={handleFeatureClick}
|
||||||
</div>
|
onSelectionChange={handleSelectionChange}
|
||||||
</div>
|
layerVisibility={layerVisibility}
|
||||||
|
center={flyTarget?.center}
|
||||||
|
zoom={flyTarget?.zoom}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Map container */}
|
{/* Top-left controls: search + layers */}
|
||||||
<div className="relative h-[calc(100vh-12rem)] min-h-[500px] rounded-lg border overflow-hidden">
|
<div className="absolute top-3 left-3 z-10 flex flex-col gap-2 max-w-xs">
|
||||||
<MapViewer
|
<SearchBar onResultSelect={handleSearchResult} />
|
||||||
ref={mapHandleRef}
|
<LayerPanel
|
||||||
className="h-full w-full"
|
visibility={layerVisibility}
|
||||||
basemap={basemap}
|
onVisibilityChange={handleVisibilityChange}
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</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>
|
</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>
|
</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";
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|||||||
Reference in New Issue
Block a user