배치된 객체 목록 계층구조 및 아코디언 적용

This commit is contained in:
dohyeons 2025-11-25 09:35:47 +09:00
parent 216e1366ef
commit 119afcaf42
1 changed files with 112 additions and 22 deletions

View File

@ -1575,20 +1575,31 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi
)}
</div>
{/* 배치된 객체 목록 */}
<div className="flex-1 overflow-y-auto p-4">
{/* 배치된 객체 목록 (계층 구조) */}
<div className="flex-1 overflow-y-auto border-t p-4">
<h3 className="mb-3 text-sm font-semibold"> ({placedObjects.length})</h3>
{placedObjects.length === 0 ? (
<div className="text-muted-foreground text-center text-sm"> </div>
) : (
<Accordion type="multiple" className="w-full">
{/* Area별로 그룹핑 */}
{(() => {
// Area 객체들
const areaObjects = placedObjects.filter((obj) => obj.type === "area");
// Area가 없으면 기존 방식으로 표시
if (areaObjects.length === 0) {
return (
<div className="space-y-2">
{placedObjects.map((obj) => (
<div
key={obj.id}
onClick={() => handleObjectClick(obj.id)}
className={`cursor-pointer rounded-lg border p-3 transition-all ${
selectedObject?.id === obj.id ? "border-primary bg-primary/10" : "hover:border-primary/50"
selectedObject?.id === obj.id
? "border-primary bg-primary/10"
: "hover:border-primary/50"
}`}
>
<div className="flex items-center justify-between">
@ -1598,10 +1609,89 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi
<p className="text-muted-foreground mt-1 text-xs">
: ({obj.position.x.toFixed(1)}, {obj.position.z.toFixed(1)})
</p>
{obj.areaKey && <p className="text-muted-foreground mt-1 text-xs">Area: {obj.areaKey}</p>}
</div>
))}
</div>
);
}
// Area별로 Location들을 그룹핑
return areaObjects.map((areaObj) => {
// 이 Area의 자식 Location들 찾기
const childLocations = placedObjects.filter(
(obj) =>
obj.type !== "area" &&
obj.areaKey === areaObj.areaKey &&
(obj.parentId === areaObj.id || obj.externalKey === areaObj.externalKey),
);
return (
<AccordionItem key={areaObj.id} value={`area-${areaObj.id}`} className="border-b">
<AccordionTrigger className="px-2 py-3 hover:no-underline">
<div
className={`flex w-full items-center justify-between pr-2 ${
selectedObject?.id === areaObj.id ? "text-primary font-semibold" : ""
}`}
onClick={(e) => {
e.stopPropagation();
handleObjectClick(areaObj.id);
}}
>
<div className="flex items-center gap-2">
<Grid3x3 className="h-4 w-4" />
<span className="text-sm font-medium">{areaObj.name}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground text-xs">({childLocations.length})</span>
<div className="h-3 w-3 rounded-full" style={{ backgroundColor: areaObj.color }} />
</div>
</div>
</AccordionTrigger>
<AccordionContent className="px-2 pb-3">
{childLocations.length === 0 ? (
<p className="text-muted-foreground py-2 text-center text-xs">
Location이
</p>
) : (
<div className="space-y-2">
{childLocations.map((locationObj) => (
<div
key={locationObj.id}
onClick={() => handleObjectClick(locationObj.id)}
className={`cursor-pointer rounded-lg border p-2 transition-all ${
selectedObject?.id === locationObj.id
? "border-primary bg-primary/10"
: "hover:border-primary/50"
}`}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Package className="h-3 w-3" />
<span className="text-xs font-medium">{locationObj.name}</span>
</div>
<div
className="h-2.5 w-2.5 rounded-full"
style={{ backgroundColor: locationObj.color }}
/>
</div>
<p className="text-muted-foreground mt-1 text-[10px]">
: ({locationObj.position.x.toFixed(1)}, {locationObj.position.z.toFixed(1)})
</p>
{locationObj.locaKey && (
<p className="text-muted-foreground mt-0.5 text-[10px]">
Key: {locationObj.locaKey}
</p>
)}
</div>
))}
</div>
)}
</AccordionContent>
</AccordionItem>
);
});
})()}
</Accordion>
)}
</div>
</div>