"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import Map from "@/uhm/components/Map"; import BackgroundLayersPanel from "@/uhm/components/BackgroundLayersPanel"; import TimelineBar from "@/uhm/components/TimelineBar"; import { fetchGeometriesByBBox } from "@/uhm/api/geometries"; import { ApiError } from "@/uhm/api/http"; import { API_BASE_URL } from "@/uhm/api/config"; import { BackgroundLayerId, BackgroundLayerVisibility, DEFAULT_BACKGROUND_LAYER_VISIBILITY, HIDDEN_BACKGROUND_LAYER_VISIBILITY, } from "@/uhm/lib/backgroundLayers"; import { loadBackgroundLayerVisibilityFromStorage, persistBackgroundLayerVisibility, } from "@/uhm/lib/editor/background/backgroundVisibilityStorage"; import { EMPTY_FEATURE_COLLECTION, WORLD_BBOX } from "@/uhm/lib/geo/constants"; import { clampYearToFixedRange, TIMELINE_DEBOUNCE_MS } from "@/uhm/lib/timeline"; import type { Feature, FeatureCollection } from "@/uhm/types/geo"; const CURRENT_YEAR = new Date().getUTCFullYear(); export default function Page() { const [data, setData] = useState(EMPTY_FEATURE_COLLECTION); const [selectedFeatureId, setSelectedFeatureId] = useState(null); const [timelineYear, setTimelineYear] = useState(() => clampYearToFixedRange(CURRENT_YEAR)); const [timelineDraftYear, setTimelineDraftYear] = useState(() => clampYearToFixedRange(CURRENT_YEAR)); const [isTimelineLoading, setIsTimelineLoading] = useState(false); const [timelineStatus, setTimelineStatus] = useState(null); const [backgroundVisibility, setBackgroundVisibility] = useState( () => ({ ...HIDDEN_BACKGROUND_LAYER_VISIBILITY }) ); const [isBackgroundVisibilityReady, setIsBackgroundVisibilityReady] = useState(false); const timelineFetchRequestRef = useRef(0); const [lastLoadedAt, setLastLoadedAt] = useState(null); const selectedFeature: Feature | null = useMemo(() => { if (selectedFeatureId === null) return null; return ( data.features.find((feature) => String(feature.properties.id) === String(selectedFeatureId)) || null ); }, [data.features, selectedFeatureId]); useEffect(() => { if (selectedFeatureId === null) return; const stillExists = data.features.some((feature) => String(feature.properties.id) === String(selectedFeatureId)); if (!stillExists) setSelectedFeatureId(null); }, [data.features, selectedFeatureId]); useEffect(() => { const timeoutId = window.setTimeout(() => { if (timelineDraftYear !== timelineYear) setTimelineYear(timelineDraftYear); }, TIMELINE_DEBOUNCE_MS); return () => window.clearTimeout(timeoutId); }, [timelineDraftYear, timelineYear]); useEffect(() => { setBackgroundVisibility(loadBackgroundLayerVisibilityFromStorage()); setIsBackgroundVisibilityReady(true); }, []); useEffect(() => { let disposed = false; const requestId = ++timelineFetchRequestRef.current; async function loadByTimeline() { setIsTimelineLoading(true); setTimelineStatus(null); try { const next = await fetchGeometriesByBBox({ ...WORLD_BBOX, time: timelineYear }); if (disposed || requestId !== timelineFetchRequestRef.current) return; setData(next); setLastLoadedAt(new Date().toISOString()); } catch (err) { if (err instanceof ApiError) { console.error("Load timeline data failed", err.body); } else { console.error("Load timeline data failed", err); } if (!disposed && requestId === timelineFetchRequestRef.current) { setTimelineStatus("Không tải được geometry tại mốc thời gian đã chọn."); } } finally { if (!disposed && requestId === timelineFetchRequestRef.current) { setIsTimelineLoading(false); } } } loadByTimeline(); return () => { disposed = true; }; }, [timelineYear]); const updateBackgroundVisibility = (updater: (prev: BackgroundLayerVisibility) => BackgroundLayerVisibility) => { setBackgroundVisibility((prev) => { const next = updater(prev); persistBackgroundLayerVisibility(next); return next; }); }; const handleToggleBackgroundLayer = (id: BackgroundLayerId) => { updateBackgroundVisibility((prev) => ({ ...prev, [id]: !prev[id] })); }; const handleShowAllBackgroundLayers = () => { updateBackgroundVisibility(() => ({ ...DEFAULT_BACKGROUND_LAYER_VISIBILITY })); }; const handleHideAllBackgroundLayers = () => { updateBackgroundVisibility(() => ({ ...HIDDEN_BACKGROUND_LAYER_VISIBILITY })); }; const handleTimelineYearChange = (nextYear: number) => { setTimelineDraftYear(clampYearToFixedRange(Math.trunc(nextYear))); }; return (
{isBackgroundVisibilityReady ? ( ) : (
)}
Viewer
API: {API_BASE_URL}
Year: {timelineYear} | Features: {data.features.length}
{isTimelineLoading ? "Loading geometries..." : lastLoadedAt ? `Loaded: ${lastLoadedAt}` : "Not loaded yet"}
{selectedFeature ? `ID: ${String(selectedFeature.properties.id)}` : "Chưa chọn geometry"}
{selectedFeature?.properties?.type ? `Type: ${String(selectedFeature.properties.type)}` : "Type: -"}
} />
); }