ERP-node/frontend/components/admin/dashboard/widgets/yard-3d/Yard3DViewer.tsx

175 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import Yard3DCanvas from "./Yard3DCanvas";
import { yardLayoutApi } from "@/lib/api/yardLayoutApi";
import { Loader2 } from "lucide-react";
interface YardPlacement {
id: number;
yard_layout_id: number;
material_code?: string | null;
material_name?: string | null;
quantity?: number | null;
unit?: string | null;
position_x: number;
position_y: number;
position_z: number;
size_x: number;
size_y: number;
size_z: number;
color: string;
data_source_type?: string | null;
data_source_config?: any;
data_binding?: any;
status?: string;
memo?: string;
}
interface Yard3DViewerProps {
layoutId: number;
}
export default function Yard3DViewer({ layoutId }: Yard3DViewerProps) {
const [placements, setPlacements] = useState<YardPlacement[]>([]);
const [selectedPlacement, setSelectedPlacement] = useState<YardPlacement | null>(null);
const [layoutName, setLayoutName] = useState<string>("");
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// 선택 변경 로그
const handlePlacementClick = (placement: YardPlacement | null) => {
console.log("Yard3DViewer - Placement clicked:", placement?.material_name);
setSelectedPlacement(placement);
};
// 선택 상태 변경 감지
useEffect(() => {
console.log("selectedPlacement changed:", selectedPlacement?.material_name);
}, [selectedPlacement]);
// 야드 레이아웃 및 배치 데이터 로드
useEffect(() => {
const loadData = async () => {
try {
setIsLoading(true);
setError(null);
// 야드 레이아웃 정보 조회
const layoutResponse = await yardLayoutApi.getLayoutById(layoutId);
if (layoutResponse.success) {
setLayoutName(layoutResponse.data.name);
}
// 배치 데이터 조회
const placementsResponse = await yardLayoutApi.getPlacementsByLayoutId(layoutId);
if (placementsResponse.success) {
setPlacements(placementsResponse.data);
} else {
setError("배치 데이터를 불러올 수 없습니다.");
}
} catch (err) {
console.error("데이터 로드 실패:", err);
setError("데이터를 불러오는 중 오류가 발생했습니다.");
} finally {
setIsLoading(false);
}
};
loadData();
}, [layoutId]);
if (isLoading) {
return (
<div className="flex h-full w-full items-center justify-center bg-gray-50">
<div className="text-center">
<Loader2 className="mx-auto h-8 w-8 animate-spin text-blue-600" />
<div className="mt-2 text-sm text-gray-600">3D ...</div>
</div>
</div>
);
}
if (error) {
return (
<div className="flex h-full w-full items-center justify-center bg-gray-50">
<div className="text-center">
<div className="mb-2 text-4xl"></div>
<div className="text-sm font-medium text-gray-600">{error}</div>
</div>
</div>
);
}
if (placements.length === 0) {
return (
<div className="flex h-full w-full items-center justify-center bg-gray-50">
<div className="text-center">
<div className="mb-2 text-4xl">📦</div>
<div className="text-sm font-medium text-gray-600"> </div>
</div>
</div>
);
}
return (
<div className="relative h-full w-full">
{/* 3D 캔버스 */}
<Yard3DCanvas
placements={placements}
selectedPlacementId={selectedPlacement?.id || null}
onPlacementClick={handlePlacementClick}
/>
{/* 야드 이름 (좌측 상단) */}
{layoutName && (
<div className="absolute top-4 left-4 z-50 rounded-lg border border-gray-300 bg-white px-4 py-2 shadow-lg">
<h2 className="text-base font-bold text-gray-900">{layoutName}</h2>
</div>
)}
{/* 선택된 자재 정보 패널 (우측 상단) */}
{selectedPlacement && (
<div className="absolute top-4 right-4 z-50 w-64 rounded-lg border border-gray-300 bg-white p-4 shadow-xl">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-800">
{selectedPlacement.material_name ? "자재 정보" : "미설정 요소"}
</h3>
<button
onClick={() => {
setSelectedPlacement(null);
}}
className="rounded-full p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
>
</button>
</div>
{selectedPlacement.material_name && selectedPlacement.quantity && selectedPlacement.unit ? (
<div className="space-y-2">
<div>
<label className="text-xs font-medium text-gray-500"></label>
<div className="mt-1 text-sm font-semibold text-gray-900">{selectedPlacement.material_name}</div>
</div>
<div>
<label className="text-xs font-medium text-gray-500"></label>
<div className="mt-1 text-sm font-semibold text-gray-900">
{selectedPlacement.quantity} {selectedPlacement.unit}
</div>
</div>
</div>
) : (
<div className="rounded-lg bg-orange-50 p-3 text-center">
<div className="mb-2 text-2xl"></div>
<div className="text-sm font-medium text-orange-700"> </div>
<div className="text-sm font-medium text-orange-700"> </div>
<div className="mt-2 text-xs text-orange-600"> </div>
</div>
)}
</div>
)}
</div>
);
}