map layer management

This commit is contained in:
taDuc
2026-04-07 23:32:38 +07:00
parent 2a1b4f2f2a
commit 5ac5c4c0af
10 changed files with 514 additions and 45 deletions

View File

@@ -3,10 +3,19 @@
import { useEffect, useState } from "react";
import Map from "@/components/Map";
import Editor from "@/components/Editor";
import BackgroundLayersPanel from "@/components/BackgroundLayersPanel";
import { ApiError } from "@/api/http";
import { fetchGeometriesByBBox, saveGeometryBatchChanges } from "@/api/geometries";
import {
FeatureCollection,
useEditorState,
} from "@/lib/useEditorState";
import {
BackgroundLayerId,
BackgroundLayerVisibility,
DEFAULT_BACKGROUND_LAYER_VISIBILITY,
HIDDEN_BACKGROUND_LAYER_VISIBILITY,
} from "@/lib/backgroundLayers";
const EMPTY_FC: FeatureCollection = { type: "FeatureCollection", features: [] };
@@ -14,22 +23,21 @@ export default function Page() {
const [mode, setMode] = useState<"idle" | "draw" | "select" | "add-point">("idle");
const [initialData, setInitialData] = useState<FeatureCollection>(EMPTY_FC);
const [isSaving, setIsSaving] = useState(false);
const [backgroundVisibility, setBackgroundVisibility] = useState<BackgroundLayerVisibility>(
() => ({ ...DEFAULT_BACKGROUND_LAYER_VISIBILITY })
);
const editor = useEditorState(initialData);
useEffect(() => {
async function loadInitial() {
try {
const params = new URLSearchParams({
minLng: "-180",
minLat: "-90",
maxLng: "180",
maxLat: "90",
const data = await fetchGeometriesByBBox({
minLng: -180,
minLat: -90,
maxLng: 180,
maxLat: 90,
});
const res = await fetch(`http://localhost:3000/geometries?${params.toString()}`);
if (!res.ok) return;
const data = await res.json();
setInitialData(data);
} catch (err) {
console.error("Load initial data failed", err);
@@ -44,26 +52,36 @@ export default function Page() {
if (!payload.length) return;
setIsSaving(true);
try {
const res = await fetch("http://localhost:3000/geometries/batch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ changes: payload }),
});
if (!res.ok) {
const text = await res.text();
console.error("Save failed", text);
return;
}
await saveGeometryBatchChanges(payload);
editor.clearChanges();
} catch (err) {
if (err instanceof ApiError) {
console.error("Save failed", err.body);
return;
}
console.error("Save error", err);
} finally {
setIsSaving(false);
}
};
const handleToggleBackgroundLayer = (id: BackgroundLayerId) => {
setBackgroundVisibility((prev) => ({
...prev,
[id]: !prev[id],
}));
};
const handleShowAllBackgroundLayers = () => {
setBackgroundVisibility({ ...DEFAULT_BACKGROUND_LAYER_VISIBILITY });
};
const handleHideAllBackgroundLayers = () => {
setBackgroundVisibility({ ...HIDDEN_BACKGROUND_LAYER_VISIBILITY });
};
return (
<div style={{ display: "flex" }}>
<div style={{ display: "flex", minHeight: "100vh" }}>
<Editor
mode={mode}
setMode={setMode}
@@ -80,6 +98,14 @@ export default function Page() {
onCreateFeature={editor.createFeature}
onDeleteFeature={editor.deleteFeature}
onUpdateFeature={editor.updateFeature}
backgroundVisibility={backgroundVisibility}
/>
<BackgroundLayersPanel
visibility={backgroundVisibility}
onToggleLayer={handleToggleBackgroundLayer}
onShowAll={handleShowAllBackgroundLayers}
onHideAll={handleHideAllBackgroundLayers}
/>
</div>
);