"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { ArrowLeft, Save, Loader2, Plus, Settings, Trash2 } from "lucide-react";
import { yardLayoutApi } from "@/lib/api/yardLayoutApi";
import dynamic from "next/dynamic";
import { YardLayout, YardPlacement } from "./types";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react";
const Yard3DCanvas = dynamic(() => import("./Yard3DCanvas"), {
ssr: false,
loading: () => (
),
});
// 나중에 구현할 데이터 바인딩 패널
const YardElementConfigPanel = dynamic(() => import("./YardElementConfigPanel"), {
ssr: false,
loading: () => 로딩 중...
,
});
interface YardEditorProps {
layout: YardLayout;
onBack: () => void;
}
export default function YardEditor({ layout, onBack }: YardEditorProps) {
const [placements, setPlacements] = useState([]);
const [selectedPlacement, setSelectedPlacement] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [showConfigPanel, setShowConfigPanel] = useState(false);
const [error, setError] = useState(null);
// 배치 목록 로드
useEffect(() => {
const loadPlacements = async () => {
try {
setIsLoading(true);
const response = await yardLayoutApi.getPlacementsByLayoutId(layout.id);
if (response.success) {
setPlacements(response.data as YardPlacement[]);
}
} catch (error) {
console.error("배치 목록 로드 실패:", error);
setError("배치 목록을 불러올 수 없습니다.");
} finally {
setIsLoading(false);
}
};
loadPlacements();
}, [layout.id]);
// 빈 요소 추가
const handleAddElement = async () => {
try {
const newPlacementData = {
position_x: 0,
position_y: 2.5, // size_y의 절반 (5 / 2 = 2.5)
position_z: 0,
size_x: 5,
size_y: 5,
size_z: 5,
color: "#9ca3af", // 회색 (미설정 상태)
};
console.log("요소 추가 요청:", { layoutId: layout.id, data: newPlacementData });
const response = await yardLayoutApi.addMaterialPlacement(layout.id, newPlacementData);
console.log("요소 추가 응답:", response);
if (response.success) {
const newPlacement = response.data as YardPlacement;
setPlacements((prev) => [...prev, newPlacement]);
setSelectedPlacement(newPlacement);
setShowConfigPanel(true); // 자동으로 설정 패널 표시
} else {
console.error("요소 추가 실패 (응답):", response);
setError(response.message || "요소 추가에 실패했습니다.");
}
} catch (error) {
console.error("요소 추가 실패 (예외):", error);
setError(`요소 추가에 실패했습니다: ${error instanceof Error ? error.message : String(error)}`);
}
};
// 요소 선택 (3D 캔버스 또는 목록에서)
const handleSelectPlacement = (placement: YardPlacement) => {
setSelectedPlacement(placement);
setShowConfigPanel(false); // 선택 시에는 설정 패널 닫기
};
// 설정 버튼 클릭
const handleConfigClick = (placement: YardPlacement) => {
setSelectedPlacement(placement);
setShowConfigPanel(true);
};
// 요소 삭제
const handleDeletePlacement = async (placementId: number) => {
if (!confirm("이 요소를 삭제하시겠습니까?")) {
return;
}
try {
const response = await yardLayoutApi.removePlacement(placementId);
if (response.success) {
setPlacements((prev) => prev.filter((p) => p.id !== placementId));
if (selectedPlacement?.id === placementId) {
setSelectedPlacement(null);
setShowConfigPanel(false);
}
}
} catch (error) {
console.error("요소 삭제 실패:", error);
setError("요소 삭제에 실패했습니다.");
}
};
// 자재 드래그 (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 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 handleSaveConfig = async (updatedData: Partial) => {
if (!selectedPlacement) return;
try {
const response = await yardLayoutApi.updatePlacement(selectedPlacement.id, updatedData);
if (response.success) {
const updated = response.data as YardPlacement;
// 현재 위치 정보를 유지하면서 업데이트
setPlacements((prev) =>
prev.map((p) => {
if (p.id === updated.id) {
// 현재 프론트엔드 상태의 위치를 유지
return {
...updated,
position_x: p.position_x,
position_y: p.position_y,
position_z: p.position_z,
};
}
return p;
}),
);
// 선택된 요소도 동일하게 업데이트
setSelectedPlacement({
...updated,
position_x: selectedPlacement.position_x,
position_y: selectedPlacement.position_y,
position_z: selectedPlacement.position_z,
});
setShowConfigPanel(false);
}
} catch (error) {
console.error("설정 저장 실패:", error);
setError("설정 저장에 실패했습니다.");
}
};
// 요소가 설정되었는지 확인
const isConfigured = (placement: YardPlacement): boolean => {
return !!(placement.material_name && placement.quantity && placement.unit);
};
return (
{/* 상단 툴바 */}
{layout.name}
{layout.description &&
{layout.description}
}
{/* 에러 메시지 */}
{error && (
{error}
)}
{/* 메인 컨텐츠 영역 */}
{/* 좌측: 3D 캔버스 */}
{isLoading ? (
) : (
handleSelectPlacement(placement as YardPlacement)}
onPlacementDrag={handlePlacementDrag}
/>
)}
{/* 우측: 요소 목록 또는 설정 패널 */}
{showConfigPanel && selectedPlacement ? (
// 설정 패널
setShowConfigPanel(false)}
/>
) : (
// 요소 목록
{placements.length === 0 ? (
요소가 없습니다.
{'위의 "요소 추가" 버튼을 클릭하세요.'}
) : (
{placements.map((placement) => {
const configured = isConfigured(placement);
const isSelected = selectedPlacement?.id === placement.id;
return (
handleSelectPlacement(placement)}
>
{configured ? (
<>
{placement.material_name}
수량: {placement.quantity} {placement.unit}
>
) : (
<>
요소 #{placement.id}
데이터 바인딩 설정 필요
>
)}
);
})}
)}
)}
);
}