diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx index 9a71c338..f3d826b5 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinEditor.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useMemo } from "react"; import { Button } from "@/components/ui/button"; -import { ArrowLeft, Save, Loader2, Grid3x3, Move, Box, Package, Truck, Check } from "lucide-react"; +import { ArrowLeft, Save, Loader2, Grid3x3, Move, Box, Package, Truck, Check, ParkingCircle } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; @@ -550,10 +550,11 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi areaKey: obj.area_key, locaKey: obj.loca_key, locType: obj.loc_type, - materialCount: obj.material_count, - materialPreview: obj.material_preview_height - ? { height: parseFloat(obj.material_preview_height) } - : undefined, + materialCount: obj.loc_type === "STP" ? undefined : obj.material_count, + materialPreview: + obj.loc_type === "STP" || !obj.material_preview_height + ? undefined + : { height: parseFloat(obj.material_preview_height) }, parentId: obj.parent_id, displayOrder: obj.display_order, locked: obj.locked, @@ -761,12 +762,9 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi // 기본 크기 설정 let objectSize = defaults.size || { x: 5, y: 5, z: 5 }; - // Location 배치 시 자재 개수에 따라 높이 자동 설정 + // Location 배치 시 자재 개수에 따라 높이 자동 설정 (BED/TMP/DES만 대상, STP는 자재 미적재) if ( - (draggedTool === "location-bed" || - draggedTool === "location-stp" || - draggedTool === "location-temp" || - draggedTool === "location-dest") && + (draggedTool === "location-bed" || draggedTool === "location-temp" || draggedTool === "location-dest") && locaKey && selectedDbConnection && hierarchyConfig?.material @@ -877,12 +875,9 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi setDraggedAreaData(null); setDraggedLocationData(null); - // Location 배치 시 자재 개수 로드 + // Location 배치 시 자재 개수 로드 (BED/TMP/DES만 대상, STP는 자재 미적재) if ( - (draggedTool === "location-bed" || - draggedTool === "location-stp" || - draggedTool === "location-temp" || - draggedTool === "location-dest") && + (draggedTool === "location-bed" || draggedTool === "location-temp" || draggedTool === "location-dest") && locaKey ) { // 새 객체 추가 후 자재 개수 로드 (약간의 딜레이를 두어 state 업데이트 완료 후 실행) @@ -965,13 +960,10 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi loadLocationsForArea(obj.areaKey); setShowMaterialPanel(false); } - // Location을 클릭한 경우, 해당 Location의 자재 목록 로드 + // Location을 클릭한 경우, 해당 Location의 자재 목록 로드 (STP는 자재 미적재이므로 제외) else if ( obj && - (obj.type === "location-bed" || - obj.type === "location-stp" || - obj.type === "location-temp" || - obj.type === "location-dest") && + (obj.type === "location-bed" || obj.type === "location-temp" || obj.type === "location-dest") && obj.locaKey && selectedDbConnection ) { @@ -988,9 +980,15 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi try { const response = await getMaterialCounts(selectedDbConnection, selectedTables.material, locaKeys); if (response.success && response.data) { - // 각 Location 객체에 자재 개수 업데이트 + // 각 Location 객체에 자재 개수 업데이트 (STP는 자재 미적재이므로 제외) setPlacedObjects((prev) => prev.map((obj) => { + if ( + !obj.locaKey || + obj.type === "location-stp" // STP는 자재 없음 + ) { + return obj; + } const materialCount = response.data?.find((mc) => mc.LOCAKEY === obj.locaKey); if (materialCount) { return { @@ -1278,7 +1276,7 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi const oldSize = actualObject.size; const newSize = { ...oldSize, ...updates.size }; - // W, D를 5 단위로 스냅 + // W, D를 5 단위로 스냅 (STP 포함) newSize.x = Math.max(5, Math.round(newSize.x / 5) * 5); newSize.z = Math.max(5, Math.round(newSize.z / 5) * 5); @@ -1391,10 +1389,11 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi areaKey: obj.area_key, locaKey: obj.loca_key, locType: obj.loc_type, - materialCount: obj.material_count, - materialPreview: obj.material_preview_height - ? { height: parseFloat(obj.material_preview_height) } - : undefined, + materialCount: obj.loc_type === "STP" ? undefined : obj.material_count, + materialPreview: + obj.loc_type === "STP" || !obj.material_preview_height + ? undefined + : { height: parseFloat(obj.material_preview_height) }, parentId: obj.parent_id, displayOrder: obj.display_order, locked: obj.locked, @@ -1798,6 +1797,8 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi {isLocationPlaced ? ( + ) : locationType === "location-stp" ? ( + ) : ( )} diff --git a/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinViewer.tsx b/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinViewer.tsx index cc34fb19..1dfe8251 100644 --- a/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinViewer.tsx +++ b/frontend/components/admin/dashboard/widgets/yard-3d/DigitalTwinViewer.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect, useMemo } from "react"; -import { Loader2, Search, X, Grid3x3, Package } from "lucide-react"; +import { Loader2, Search, X, Grid3x3, Package, ParkingCircle } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; @@ -87,10 +87,11 @@ export default function DigitalTwinViewer({ layoutId }: DigitalTwinViewerProps) areaKey: obj.area_key, locaKey: obj.loca_key, locType: obj.loc_type, - materialCount: obj.material_count, - materialPreview: obj.material_preview_height - ? { height: parseFloat(obj.material_preview_height) } - : undefined, + materialCount: obj.loc_type === "STP" ? undefined : obj.material_count, + materialPreview: + obj.loc_type === "STP" || !obj.material_preview_height + ? undefined + : { height: parseFloat(obj.material_preview_height) }, parentId: obj.parent_id, displayOrder: obj.display_order, locked: obj.locked, @@ -166,13 +167,10 @@ export default function DigitalTwinViewer({ layoutId }: DigitalTwinViewerProps) const obj = placedObjects.find((o) => o.id === objectId); setSelectedObject(obj || null); - // Location을 클릭한 경우, 자재 정보 표시 + // Location을 클릭한 경우, 자재 정보 표시 (STP는 자재 미적재이므로 제외) if ( obj && - (obj.type === "location-bed" || - obj.type === "location-stp" || - obj.type === "location-temp" || - obj.type === "location-dest") && + (obj.type === "location-bed" || obj.type === "location-temp" || obj.type === "location-dest") && obj.locaKey && externalDbConnectionId ) { @@ -471,7 +469,11 @@ export default function DigitalTwinViewer({ layoutId }: DigitalTwinViewerProps) >
- + {locationObj.type === "location-stp" ? ( + + ) : ( + + )} {locationObj.name}
- - - + // 정차포인트(STP): 회색 타원형 플랫폼 + 'P' 마크 (자재 미적재 영역) + { + const baseRadius = 0.5; // 스케일로 실제 W/D를 반영 (타원형) + const labelFontSize = Math.min(boxWidth, boxDepth) * 0.15; + const iconFontSize = Math.min(boxWidth, boxDepth) * 0.3; - {/* Location 이름 */} - {placement.name && ( + return ( + <> + {/* 타원형 플랫폼: 단위 실린더를 W/D로 스케일 */} + + + + + + {/* 상단 'P' 마크 (주차 아이콘 역할) */} - {placement.name} + P - )} - {/* 자재 개수 (STP는 정차포인트라 자재가 없을 수 있음) */} - {placement.material_count !== undefined && placement.material_count > 0 && ( - - {`자재: ${placement.material_count}개`} - - )} - - ); + {/* Location 이름 */} + {placement.name && ( + + {placement.name} + + )} + + ); + } // case "gantry-crane": // // 겐트리 크레인: 기둥 2개 + 상단 빔