"use client"; import React, { useRef, useState, useEffect, Suspense } from "react"; import { Canvas, useFrame } from "@react-three/fiber"; import { OrbitControls, Text, Box, Html } from "@react-three/drei"; import * as THREE from "three"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Loader2, Maximize2, Info } from "lucide-react"; interface WarehouseData { id: string; name: string; position_x: number; position_y: number; position_z: number; size_x: number; size_y: number; size_z: number; color: string; capacity: number; current_usage: number; status: string; description?: string; } interface MaterialData { id: string; warehouse_id: string; name: string; material_code: string; quantity: number; unit: string; position_x: number; position_y: number; position_z: number; size_x: number; size_y: number; size_z: number; color: string; status: string; } interface Warehouse3DWidgetProps { element?: any; } // 창고 3D 박스 컴포넌트 function WarehouseBox({ warehouse, onClick, isSelected, }: { warehouse: WarehouseData; onClick: () => void; isSelected: boolean; }) { const meshRef = useRef(null); const [hovered, setHovered] = useState(false); useFrame(() => { if (meshRef.current) { if (isSelected) { meshRef.current.scale.lerp(new THREE.Vector3(1.05, 1.05, 1.05), 0.1); } else if (hovered) { meshRef.current.scale.lerp(new THREE.Vector3(1.02, 1.02, 1.02), 0.1); } else { meshRef.current.scale.lerp(new THREE.Vector3(1, 1, 1), 0.1); } } }); const usagePercentage = (warehouse.current_usage / warehouse.capacity) * 100; return ( { e.stopPropagation(); onClick(); }} onPointerOver={() => setHovered(true)} onPointerOut={() => setHovered(false)} > {/* 창고 테두리 */} {/* 창고 이름 라벨 */} {warehouse.name} {/* 사용률 표시 */}
{usagePercentage.toFixed(0)}% 사용중
); } // 자재 3D 박스 컴포넌트 function MaterialBox({ material, onClick, isSelected, }: { material: MaterialData; onClick: () => void; isSelected: boolean; }) { const meshRef = useRef(null); const [hovered, setHovered] = useState(false); useFrame(() => { if (meshRef.current && (isSelected || hovered)) { meshRef.current.rotation.y += 0.01; } }); const statusColor = { stocked: material.color, reserved: "#FFA500", urgent: "#FF0000", out_of_stock: "#808080", }[material.status] || material.color; return ( { e.stopPropagation(); onClick(); }} onPointerOver={() => setHovered(true)} onPointerOut={() => setHovered(false)} > {(hovered || isSelected) && (
{material.name}
{material.quantity} {material.unit}
)}
); } // 3D 씬 컴포넌트 function Scene({ warehouses, materials, onSelectWarehouse, onSelectMaterial, selectedWarehouse, selectedMaterial, }: { warehouses: WarehouseData[]; materials: MaterialData[]; onSelectWarehouse: (warehouse: WarehouseData | null) => void; onSelectMaterial: (material: MaterialData | null) => void; selectedWarehouse: WarehouseData | null; selectedMaterial: MaterialData | null; }) { return ( <> {/* 조명 */} {/* 바닥 그리드 */} {/* 창고들 */} {warehouses.map((warehouse) => ( { if (selectedWarehouse?.id === warehouse.id) { onSelectWarehouse(null); } else { onSelectWarehouse(warehouse); onSelectMaterial(null); } }} isSelected={selectedWarehouse?.id === warehouse.id} /> ))} {/* 자재들 */} {materials.map((material) => ( { if (selectedMaterial?.id === material.id) { onSelectMaterial(null); } else { onSelectMaterial(material); } }} isSelected={selectedMaterial?.id === material.id} /> ))} {/* 카메라 컨트롤 */} ); } export function Warehouse3DWidget({ element }: Warehouse3DWidgetProps) { const [warehouses, setWarehouses] = useState([]); const [materials, setMaterials] = useState([]); const [loading, setLoading] = useState(true); const [selectedWarehouse, setSelectedWarehouse] = useState(null); const [selectedMaterial, setSelectedMaterial] = useState(null); const [isFullscreen, setIsFullscreen] = useState(false); useEffect(() => { loadData(); }, []); const loadData = async () => { try { setLoading(true); // API 호출 (백엔드 API 구현 필요) const response = await fetch("/api/warehouse/data"); if (response.ok) { const data = await response.json(); setWarehouses(data.warehouses || []); setMaterials(data.materials || []); } else { // 임시 더미 데이터 (개발용) // console.log("API 실패, 더미 데이터 사용"); } } catch (error) { // console.error("창고 데이터 로드 실패:", error); } finally { setLoading(false); } }; if (loading) { return ( ); } return ( {element?.customTitle || "창고 현황 (3D)"}
{warehouses.length}개 창고 | {materials.length}개 자재
{/* 3D 뷰 */}
{/* 정보 패널 */}
{/* 선택된 창고 정보 */} {selectedWarehouse && ( 창고 정보
이름: {selectedWarehouse.name}
ID: {selectedWarehouse.id}
용량: {selectedWarehouse.current_usage} /{" "} {selectedWarehouse.capacity}
사용률:{" "} {((selectedWarehouse.current_usage / selectedWarehouse.capacity) * 100).toFixed(1)}%
상태:{" "} {selectedWarehouse.status}
{selectedWarehouse.description && (
설명: {selectedWarehouse.description}
)}
)} {/* 선택된 자재 정보 */} {selectedMaterial && ( 자재 정보
이름: {selectedMaterial.name}
코드: {selectedMaterial.material_code}
수량: {selectedMaterial.quantity} {selectedMaterial.unit}
위치:{" "} {warehouses.find((w) => w.id === selectedMaterial.warehouse_id)?.name}
상태:{" "} {selectedMaterial.status}
)} {/* 창고 목록 */} {!selectedWarehouse && !selectedMaterial && ( 창고 목록 {warehouses.map((warehouse) => { const warehouseMaterials = materials.filter((m) => m.warehouse_id === warehouse.id); return ( ); })} )}
); }