"use client"; import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { ArrowLeft, Save, Loader2, X } from "lucide-react"; import { yardLayoutApi, materialApi } from "@/lib/api/yardLayoutApi"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import dynamic from "next/dynamic"; const Yard3DCanvas = dynamic(() => import("./Yard3DCanvas"), { ssr: false, loading: () => (
), }); interface TempMaterial { id: number; material_code: string; material_name: string; category: string; unit: string; default_color: string; description: string; } interface YardLayout { id: number; name: string; description: string; placement_count?: number; updated_at: string; } interface YardPlacement { id: number; yard_layout_id: number; external_material_id: string; material_code: string; material_name: 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; memo?: string; } interface YardEditorProps { layout: YardLayout; onBack: () => void; } export default function YardEditor({ layout, onBack }: YardEditorProps) { const [placements, setPlacements] = useState([]); const [materials, setMaterials] = useState([]); const [selectedPlacement, setSelectedPlacement] = useState(null); const [selectedMaterial, setSelectedMaterial] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [searchTerm, setSearchTerm] = useState(""); // 배치 목록 & 자재 목록 로드 useEffect(() => { const loadData = async () => { try { setIsLoading(true); const [placementsRes, materialsRes] = await Promise.all([ yardLayoutApi.getPlacementsByLayoutId(layout.id), materialApi.getTempMaterials({ limit: 100 }), ]); if (placementsRes.success) { setPlacements(placementsRes.data); } if (materialsRes.success) { setMaterials(materialsRes.data); } } catch (error) { console.error("데이터 로드 실패:", error); } finally { setIsLoading(false); } }; loadData(); }, [layout.id]); // 자재 클릭 → 배치 추가 const handleMaterialClick = async (material: TempMaterial) => { // 이미 배치되었는지 확인 const alreadyPlaced = placements.find((p) => p.material_code === material.material_code); if (alreadyPlaced) { alert("이미 배치된 자재입니다."); return; } setSelectedMaterial(material); // 기본 위치에 배치 const placementData = { external_material_id: `TEMP-${material.id}`, material_code: material.material_code, material_name: material.material_name, quantity: 1, unit: material.unit, position_x: 0, position_y: 0, position_z: 0, size_x: 5, size_y: 5, size_z: 5, color: material.default_color, }; try { const response = await yardLayoutApi.addMaterialPlacement(layout.id, placementData); if (response.success) { setPlacements((prev) => [...prev, response.data]); setSelectedPlacement(response.data); setSelectedMaterial(null); } } catch (error: any) { console.error("자재 배치 실패:", error); alert("자재 배치에 실패했습니다."); } }; // 자재 드래그 (3D 캔버스에서) const handlePlacementDrag = (id: number, position: { x: number; y: number; z: number }) => { const updatedPosition = { position_x: Math.round(position.x * 2) / 2, position_y: position.y, position_z: Math.round(position.z * 2) / 2, }; setPlacements((prev) => prev.map((p) => p.id === id ? { ...p, ...updatedPosition, } : p, ), ); // 선택된 자재도 업데이트 if (selectedPlacement?.id === id) { setSelectedPlacement((prev) => prev ? { ...prev, ...updatedPosition, } : null, ); } }; // 자재 배치 해제 const handlePlacementRemove = async (id: number) => { try { const response = await yardLayoutApi.removePlacement(id); if (response.success) { setPlacements((prev) => prev.filter((p) => p.id !== id)); setSelectedPlacement(null); } } catch (error) { console.error("배치 해제 실패:", error); alert("배치 해제에 실패했습니다."); } }; // 위치/크기/색상 업데이트 const handlePlacementUpdate = (id: number, updates: Partial) => { setPlacements((prev) => prev.map((p) => (p.id === id ? { ...p, ...updates } : p))); }; // 저장 const handleSave = async () => { setIsSaving(true); try { const response = await yardLayoutApi.batchUpdatePlacements( layout.id, placements.map((p) => ({ id: p.id, position_x: p.position_x, position_y: p.position_y, position_z: p.position_z, size_x: p.size_x, size_y: p.size_y, size_z: p.size_z, color: p.color, })), ); if (response.success) { alert("저장되었습니다"); } } catch (error) { console.error("저장 실패:", error); alert("저장에 실패했습니다"); } finally { setIsSaving(false); } }; // 필터링된 자재 목록 const filteredMaterials = materials.filter( (m) => m.material_name.toLowerCase().includes(searchTerm.toLowerCase()) || m.material_code.toLowerCase().includes(searchTerm.toLowerCase()), ); return (
{/* 상단 툴바 */}

{layout.name}

{layout.description &&

{layout.description}

}
{/* 메인 컨텐츠 영역 */}
{/* 좌측: 3D 캔버스 */}
{isLoading ? (
) : ( )}
{/* 우측: 자재 목록 또는 편집 패널 */}
{selectedPlacement ? ( // 선택된 자재 편집 패널

자재 정보

{/* 읽기 전용 정보 */}
{selectedPlacement.material_code}
{selectedPlacement.material_name}
{selectedPlacement.quantity} {selectedPlacement.unit}
{/* 편집 가능 정보 */}
handlePlacementUpdate(selectedPlacement.id, { position_x: parseFloat(e.target.value), }) } />
handlePlacementUpdate(selectedPlacement.id, { position_y: parseFloat(e.target.value), }) } />
handlePlacementUpdate(selectedPlacement.id, { position_z: parseFloat(e.target.value), }) } />
handlePlacementUpdate(selectedPlacement.id, { size_x: parseFloat(e.target.value), }) } />
handlePlacementUpdate(selectedPlacement.id, { size_y: parseFloat(e.target.value), }) } />
handlePlacementUpdate(selectedPlacement.id, { size_z: parseFloat(e.target.value), }) } />
handlePlacementUpdate(selectedPlacement.id, { color: e.target.value })} />
) : ( // 자재 목록

자재 목록

setSearchTerm(e.target.value)} className="text-sm" />
{filteredMaterials.length === 0 ? (
검색 결과가 없습니다
) : (
{filteredMaterials.map((material) => { const isPlaced = placements.some((p) => p.material_code === material.material_code); return ( ); })}
)}
)}
); }