"use client"; import { useMemo, useState, type CSSProperties } from "react"; import type { EntitySnapshot } from "@/uhm/types/entities"; import type { EntityFormState } from "@/uhm/lib/editor/session/sessionTypes"; import NewBadge from "@/uhm/components/editor/NewBadge"; type Props = { entityRefs: EntitySnapshot[]; entityForm: EntityFormState; onEntityFormChange: (key: keyof EntityFormState, value: string) => void; isEntitySubmitting: boolean; onCreateEntityOnly: () => void; onUpdateEntity?: (entityId: string, payload: { name: string; description: string | null }) => void; entityFormStatus: string | null; selectedGeometryEntityIds?: string[]; hasSelectedGeometry?: boolean; onToggleBindEntityForSelectedGeometry?: (entityId: string, nextChecked: boolean) => void; }; export default function ProjectEntityRefsPanel({ entityRefs, entityForm, onEntityFormChange, isEntitySubmitting, onCreateEntityOnly, onUpdateEntity, entityFormStatus, selectedGeometryEntityIds, hasSelectedGeometry, onToggleBindEntityForSelectedGeometry, }: Props) { const canBindToggle = Boolean(hasSelectedGeometry) && Array.isArray(selectedGeometryEntityIds) && typeof onToggleBindEntityForSelectedGeometry === "function"; const canEditEntity = typeof onUpdateEntity === "function"; const [isCreateOpen, setIsCreateOpen] = useState(false); const [collapsed, setCollapsed] = useState(false); const [activeEntityId, setActiveEntityId] = useState(null); const selectedEntityIdSet = useMemo( () => new Set((selectedGeometryEntityIds || []).map(String)), [selectedGeometryEntityIds] ); const sortedEntityRefs = useMemo(() => { const rows = [...(entityRefs || [])]; rows.sort((a, b) => { const aBound = selectedEntityIdSet.has(String(a.id)); const bBound = selectedEntityIdSet.has(String(b.id)); if (aBound !== bBound) return aBound ? -1 : 1; const aLabel = String(a.name || a.id || ""); const bLabel = String(b.name || b.id || ""); return aLabel.localeCompare(bLabel); }); return rows; }, [entityRefs, selectedEntityIdSet]); const activeEntity = useMemo( () => (activeEntityId ? entityRefs.find((e) => String(e.id) === String(activeEntityId)) || null : null), [activeEntityId, entityRefs] ); const [editName, setEditName] = useState(""); const [editDescription, setEditDescription] = useState(""); const openEntityEditor = (entity: EntitySnapshot) => { setActiveEntityId(String(entity.id)); setEditName(typeof entity.name === "string" ? entity.name : ""); setEditDescription(entity.description == null ? "" : String(entity.description)); }; return (
Entities
{entityRefs.length}
{collapsed ? null : sortedEntityRefs.length ? (
{sortedEntityRefs.map((e) => { const entityId = String(e.id); const isBoundToSelectedGeometry = selectedEntityIdSet.has(entityId); const isActive = activeEntityId === entityId; return (
{canBindToggle ? ( ) : null}
); })}
) : (
No entity ref yet for this project.
)} {collapsed ? null : canEditEntity && activeEntity ? (
Sua entity {isNewEntityRef(activeEntity) ? : null}
{String(activeEntity.id)}
setEditName(event.target.value)} placeholder="Ten entity" disabled={isEntitySubmitting} style={entityInputStyle} /> setEditDescription(event.target.value)} placeholder="Description" disabled={isEntitySubmitting} style={entityInputStyle} />
) : null} {collapsed ? null : ( <>
Tạo entity mới
{isCreateOpen ? ( <> onEntityFormChange("name", event.target.value)} placeholder="Tên entity mới" disabled={isEntitySubmitting} style={entityInputStyle} /> onEntityFormChange("description", event.target.value)} placeholder="Description" disabled={isEntitySubmitting} style={entityInputStyle} /> ) : null}
{entityFormStatus ? (
{entityFormStatus}
) : null} )}
); } function isNewEntityRef(entity: EntitySnapshot | null | undefined): boolean { return entity?.source === "inline" && entity?.operation === "create"; } const entityInputStyle: CSSProperties = { width: "100%", borderRadius: "6px", border: "1px solid #334155", background: "#111827", color: "#f8fafc", padding: "6px 8px", fontSize: "13px", }; const boundBadgeStyle: CSSProperties = { display: "inline-flex", alignItems: "center", justifyContent: "center", flex: "0 0 auto", height: 17, padding: "0 6px", borderRadius: 999, border: "1px solid rgba(45, 212, 191, 0.5)", background: "rgba(20, 184, 166, 0.18)", color: "#99f6e4", fontSize: 10, fontWeight: 900, lineHeight: 1, textTransform: "uppercase", letterSpacing: 0, }; function LockIcon() { return ( ); } function UnlockIcon() { return ( ); } function PlusIcon() { return ( ); } function MinusIcon() { return ( ); } function CloseIcon() { return ( ); }