STP 정차포인트를 자재 미적재 영역으로 분리하고 시각화 개선
This commit is contained in:
parent
11782536f4
commit
710ca122ea
|
|
@ -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
|
|||
</div>
|
||||
{isLocationPlaced ? (
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
) : locationType === "location-stp" ? (
|
||||
<ParkingCircle className="text-muted-foreground h-4 w-4" />
|
||||
) : (
|
||||
<Package className="text-muted-foreground h-4 w-4" />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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)
|
|||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Package className="h-3 w-3" />
|
||||
{locationObj.type === "location-stp" ? (
|
||||
<ParkingCircle className="h-3 w-3" />
|
||||
) : (
|
||||
<Package className="h-3 w-3" />
|
||||
)}
|
||||
<span className="text-xs font-medium">{locationObj.name}</span>
|
||||
</div>
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -593,52 +593,58 @@ function MaterialBox({
|
|||
);
|
||||
|
||||
case "location-stp":
|
||||
// 정차포인트(STP): 주황색 낮은 플랫폼
|
||||
return (
|
||||
<>
|
||||
<Box args={[boxWidth, boxHeight, boxDepth]}>
|
||||
<meshStandardMaterial
|
||||
color={placement.color}
|
||||
roughness={0.6}
|
||||
metalness={0.2}
|
||||
emissive={isSelected ? placement.color : "#000000"}
|
||||
emissiveIntensity={isSelected ? glowIntensity * 0.8 : 0}
|
||||
/>
|
||||
</Box>
|
||||
// 정차포인트(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로 스케일 */}
|
||||
<mesh scale={[boxWidth, 1, boxDepth]}>
|
||||
<cylinderGeometry args={[baseRadius, baseRadius, boxHeight, 32]} />
|
||||
<meshStandardMaterial
|
||||
color={placement.color}
|
||||
roughness={0.6}
|
||||
metalness={0.2}
|
||||
emissive={isSelected ? placement.color : "#000000"}
|
||||
emissiveIntensity={isSelected ? glowIntensity * 0.8 : 0}
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* 상단 'P' 마크 (주차 아이콘 역할) */}
|
||||
<Text
|
||||
position={[0, boxHeight / 2 + 0.3, 0]}
|
||||
position={[0, boxHeight / 2 + 0.05, 0]}
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
fontSize={Math.min(boxWidth, boxDepth) * 0.15}
|
||||
fontSize={iconFontSize}
|
||||
color="#ffffff"
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
outlineWidth={0.03}
|
||||
outlineWidth={0.08}
|
||||
outlineColor="#000000"
|
||||
>
|
||||
{placement.name}
|
||||
P
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* 자재 개수 (STP는 정차포인트라 자재가 없을 수 있음) */}
|
||||
{placement.material_count !== undefined && placement.material_count > 0 && (
|
||||
<Text
|
||||
position={[0, boxHeight / 2 + 0.6, 0]}
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
fontSize={Math.min(boxWidth, boxDepth) * 0.12}
|
||||
color="#fbbf24"
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
outlineWidth={0.03}
|
||||
outlineColor="#000000"
|
||||
>
|
||||
{`자재: ${placement.material_count}개`}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
{/* Location 이름 */}
|
||||
{placement.name && (
|
||||
<Text
|
||||
position={[0, boxHeight / 2 + 0.4, 0]}
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
fontSize={labelFontSize}
|
||||
color="#ffffff"
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
outlineWidth={0.03}
|
||||
outlineColor="#000000"
|
||||
>
|
||||
{placement.name}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// case "gantry-crane":
|
||||
// // 겐트리 크레인: 기둥 2개 + 상단 빔
|
||||
|
|
|
|||
Loading…
Reference in New Issue