"use client"; import { type CSSProperties, useMemo, useState } from "react"; import { useShallow } from "zustand/react/shallow"; import { Feature } from "@/uhm/lib/editor/state/useEditorState"; import { GEOMETRY_TYPE_OPTIONS, GeometryPreset, GeometryTypeGroupId, findGeometryTypeOption, groupGeometryTypeOptions, } from "@/uhm/lib/map/geo/geometryTypeOptions"; import { normalizeGeoTypeKey } from "@/uhm/lib/map/geo/geoTypeMap"; import { useEditorStore } from "@/uhm/store/editorStore"; type Props = { selectedFeatures: Feature[]; onApplyGeometryMetadata: () => Promise<{ ok: boolean; error?: string }>; changeCount: number; onReplayEdit?: (id: string | number) => void; onDeleteFeatures?: (ids: (string | number)[]) => void; onDeselectAll?: () => void; }; export default function SelectedGeometryPanel({ selectedFeatures, onApplyGeometryMetadata, changeCount, onReplayEdit, onDeleteFeatures, onDeselectAll, }: Props) { const { geometryMetaForm, setGeometryMetaForm, isEntitySubmitting, } = useEditorStore( useShallow((state) => ({ geometryMetaForm: state.geometryMetaForm, setGeometryMetaForm: state.setGeometryMetaForm, isEntitySubmitting: state.isEntitySubmitting, })) ); const [collapsed, setCollapsed] = useState(false); const [geoApplyFeedback, setGeoApplyFeedback] = useState< | { kind: "ok" | "error"; text: string; signature: string; } | null >(null); const geoMetaSignature = useMemo(() => { return [ geometryMetaForm.type_key, geometryMetaForm.time_start, geometryMetaForm.time_end, geometryMetaForm.binding, ].join("|"); }, [ geometryMetaForm.binding, geometryMetaForm.time_end, geometryMetaForm.time_start, geometryMetaForm.type_key, ]); const handleApplyGeoMeta = async () => { setGeoApplyFeedback(null); const result = await onApplyGeometryMetadata(); if (result.ok) { setGeoApplyFeedback({ kind: "ok", text: "đã apply thành công", signature: geoMetaSignature }); } else if (result.error) { setGeoApplyFeedback({ kind: "error", text: result.error, signature: geoMetaSignature }); } }; const visibleGeoApplyFeedback = geoApplyFeedback && geoApplyFeedback.signature === geoMetaSignature ? geoApplyFeedback : null; const isBulkMode = selectedFeatures.length >= 2; const isMultiEditValid = useMemo(() => { if (selectedFeatures.length <= 1) return true; const firstShape = selectedFeatures[0].geometry.type; return selectedFeatures.every((f) => f.geometry.type === firstShape); }, [selectedFeatures]); if (!selectedFeatures || selectedFeatures.length === 0) return null; const representativeFeature = selectedFeatures[0]; const groupedGeometryTypeOptions = groupGeometryTypeOptions(GEOMETRY_TYPE_OPTIONS); const featureGeometryPreset = resolveFeatureGeometryPreset(representativeFeature); const allowedGroupIds = getAllowedGroupIdsForPreset(featureGeometryPreset); const groupedGeoTypeOptions = groupedGeometryTypeOptions.filter((group) => allowedGroupIds.includes(group.id) ); const selectedTypeOption = findGeometryTypeOption(geometryMetaForm.type_key); const hasCurrentVisibleTypeOption = groupedGeoTypeOptions.some((group) => group.options.some((option) => option.value === geometryMetaForm.type_key) ); return (
{isBulkMode ? `Đang chọn ${selectedFeatures.length} Geometries` : "Geometry property"}
{collapsed ? null : (
{isBulkMode && (
HÀNH ĐỘNG NHANH
)}
Thuộc tính GEO
Các giá trị này thuộc về GEO đang chọn, không phụ thuộc entity.
{!isMultiEditValid ? (
Không thể chỉnh sửa thuộc tính cho các geometry không cùng loại hình dạng (Point, Line, Polygon).
) : ( <>
Loại GEO
{selectedTypeOption ? (
Đang chọn: {selectedTypeOption.label} ({selectedTypeOption.groupLabel})
) : geometryMetaForm.type_key ? (
Đang chọn: {geometryMetaForm.type_key}
) : null} setGeometryMetaForm((prev) => ({ ...prev, time_start: event.target.value, })) } placeholder="time_start" disabled={isEntitySubmitting} style={entityInputStyle} /> setGeometryMetaForm((prev) => ({ ...prev, time_end: event.target.value, })) } placeholder="time_end" disabled={isEntitySubmitting} style={entityInputStyle} /> {onReplayEdit && !isBulkMode && selectedFeatures.length > 0 && ( )} {visibleGeoApplyFeedback ? (
{visibleGeoApplyFeedback.text}
) : null} )}
{changeCount > 0 ? (
Thay đổi sẽ vào lịch sử khi Commit.
) : null}
)}
); } const entityInputStyle: CSSProperties = { width: "100%", borderRadius: "6px", border: "1px solid #334155", background: "#111827", color: "#f8fafc", padding: "6px 8px", fontSize: "13px", }; const primaryGeometryButtonStyle: CSSProperties = { border: "none", borderRadius: "6px", padding: "7px 8px", cursor: "pointer", background: "#0f766e", color: "#ffffff", fontWeight: 600, }; function PlusIcon() { return ( ); } function MinusIcon() { return ( ); } function resolveFeatureGeometryPreset(feature: Feature): GeometryPreset { const explicitPreset = normalizeGeometryPreset(feature.properties.geometry_preset); if (explicitPreset) return explicitPreset; const semanticType = normalizeTypeId(feature.properties.type) || normalizeTypeId(feature.properties.entity_type_id); if (semanticType) { const option = findGeometryTypeOption(semanticType); if (option) return option.geometryPreset; } return mapGeometryTypeToPreset(feature.geometry.type); } function normalizeGeometryPreset(value: unknown): GeometryPreset | null { if (typeof value !== "string") return null; const normalized = value.trim().toLowerCase(); if ( normalized === "point" || normalized === "line" || normalized === "polygon" || normalized === "circle-area" ) { return normalized; } return null; } function normalizeTypeId(value: unknown): string | null { return normalizeGeoTypeKey(value); } function mapGeometryTypeToPreset( geometryType: Feature["geometry"]["type"] ): GeometryPreset { if (geometryType === "Point" || geometryType === "MultiPoint") { return "point"; } if (geometryType === "LineString" || geometryType === "MultiLineString") { return "line"; } return "polygon"; } function getAllowedGroupIdsForPreset( geometryPreset: GeometryPreset ): GeometryTypeGroupId[] { if (geometryPreset === "point") { return ["point"]; } if (geometryPreset === "line") { return ["line"]; } if (geometryPreset === "circle-area") { return ["circle"]; } return ["polygon"]; }