199 lines
6.7 KiB
TypeScript
199 lines
6.7 KiB
TypeScript
|
|
"use client";
|
|||
|
|
|
|||
|
|
import { useState, useEffect } from "react";
|
|||
|
|
import { Button } from "@/components/ui/button";
|
|||
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|||
|
|
import { Plus, Check } from "lucide-react";
|
|||
|
|
import YardLayoutList from "./yard-3d/YardLayoutList";
|
|||
|
|
import YardLayoutCreateModal from "./yard-3d/YardLayoutCreateModal";
|
|||
|
|
import YardEditor from "./yard-3d/YardEditor";
|
|||
|
|
import Yard3DViewer from "./yard-3d/Yard3DViewer";
|
|||
|
|
import { yardLayoutApi } from "@/lib/api/yardLayoutApi";
|
|||
|
|
import type { YardManagementConfig } from "../types";
|
|||
|
|
|
|||
|
|
interface YardLayout {
|
|||
|
|
id: number;
|
|||
|
|
name: string;
|
|||
|
|
description: string;
|
|||
|
|
placement_count: number;
|
|||
|
|
updated_at: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface YardManagement3DWidgetProps {
|
|||
|
|
isEditMode?: boolean;
|
|||
|
|
config?: YardManagementConfig;
|
|||
|
|
onConfigChange?: (config: YardManagementConfig) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default function YardManagement3DWidget({
|
|||
|
|
isEditMode = false,
|
|||
|
|
config,
|
|||
|
|
onConfigChange,
|
|||
|
|
}: YardManagement3DWidgetProps) {
|
|||
|
|
const [layouts, setLayouts] = useState<YardLayout[]>([]);
|
|||
|
|
const [isLoading, setIsLoading] = useState(true);
|
|||
|
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|||
|
|
const [editingLayout, setEditingLayout] = useState<YardLayout | null>(null);
|
|||
|
|
|
|||
|
|
// 레이아웃 목록 로드
|
|||
|
|
const loadLayouts = async () => {
|
|||
|
|
try {
|
|||
|
|
setIsLoading(true);
|
|||
|
|
const response = await yardLayoutApi.getAllLayouts();
|
|||
|
|
if (response.success) {
|
|||
|
|
setLayouts(response.data);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error("야드 레이아웃 목록 조회 실패:", error);
|
|||
|
|
} finally {
|
|||
|
|
setIsLoading(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (isEditMode) {
|
|||
|
|
loadLayouts();
|
|||
|
|
}
|
|||
|
|
}, [isEditMode]);
|
|||
|
|
|
|||
|
|
// 레이아웃 선택 (편집 모드에서만)
|
|||
|
|
const handleSelectLayout = (layout: YardLayout) => {
|
|||
|
|
if (onConfigChange) {
|
|||
|
|
onConfigChange({
|
|||
|
|
layoutId: layout.id,
|
|||
|
|
layoutName: layout.name,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 새 레이아웃 생성
|
|||
|
|
const handleCreateLayout = async (name: string, description: string) => {
|
|||
|
|
try {
|
|||
|
|
const response = await yardLayoutApi.createLayout({ name, description });
|
|||
|
|
if (response.success) {
|
|||
|
|
await loadLayouts();
|
|||
|
|
setIsCreateModalOpen(false);
|
|||
|
|
setEditingLayout(response.data);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error("야드 레이아웃 생성 실패:", error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 편집 완료
|
|||
|
|
const handleEditComplete = () => {
|
|||
|
|
if (editingLayout && onConfigChange) {
|
|||
|
|
onConfigChange({
|
|||
|
|
layoutId: editingLayout.id,
|
|||
|
|
layoutName: editingLayout.name,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
setEditingLayout(null);
|
|||
|
|
loadLayouts();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 편집 모드: 편집 중인 경우 YardEditor 표시
|
|||
|
|
if (isEditMode && editingLayout) {
|
|||
|
|
return (
|
|||
|
|
<div className="h-full w-full">
|
|||
|
|
<YardEditor layout={editingLayout} onBack={handleEditComplete} />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 편집 모드: 레이아웃 선택 UI
|
|||
|
|
if (isEditMode) {
|
|||
|
|
return (
|
|||
|
|
<div className="flex h-full w-full flex-col bg-white">
|
|||
|
|
<div className="flex items-center justify-between border-b p-4">
|
|||
|
|
<div>
|
|||
|
|
<h3 className="text-sm font-semibold text-gray-700">야드 레이아웃 선택</h3>
|
|||
|
|
<p className="mt-1 text-xs text-gray-500">
|
|||
|
|
{config?.layoutName ? `선택됨: ${config.layoutName}` : "표시할 야드 레이아웃을 선택하세요"}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
<Button onClick={() => setIsCreateModalOpen(true)} size="sm">
|
|||
|
|
<Plus className="mr-1 h-4 w-4" />새 야드 생성
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex-1 overflow-auto p-4">
|
|||
|
|
{isLoading ? (
|
|||
|
|
<div className="flex h-full items-center justify-center">
|
|||
|
|
<div className="text-sm text-gray-500">로딩 중...</div>
|
|||
|
|
</div>
|
|||
|
|
) : layouts.length === 0 ? (
|
|||
|
|
<div className="flex h-full items-center justify-center">
|
|||
|
|
<div className="text-center">
|
|||
|
|
<div className="mb-2 text-4xl">🏗️</div>
|
|||
|
|
<div className="text-sm text-gray-600">생성된 야드 레이아웃이 없습니다</div>
|
|||
|
|
<div className="mt-1 text-xs text-gray-400">먼저 야드 레이아웃을 생성하세요</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
<div className="grid gap-3">
|
|||
|
|
{layouts.map((layout) => (
|
|||
|
|
<div
|
|||
|
|
key={layout.id}
|
|||
|
|
className={`rounded-lg border p-3 transition-all ${
|
|||
|
|
config?.layoutId === layout.id ? "border-blue-500 bg-blue-50" : "border-gray-200 bg-white"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<div className="flex items-start justify-between gap-3">
|
|||
|
|
<button onClick={() => handleSelectLayout(layout)} className="flex-1 text-left hover:opacity-80">
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<span className="font-medium text-gray-900">{layout.name}</span>
|
|||
|
|
{config?.layoutId === layout.id && <Check className="h-4 w-4 text-blue-600" />}
|
|||
|
|
</div>
|
|||
|
|
{layout.description && <p className="mt-1 text-xs text-gray-500">{layout.description}</p>}
|
|||
|
|
<div className="mt-2 text-xs text-gray-400">배치된 자재: {layout.placement_count}개</div>
|
|||
|
|
</button>
|
|||
|
|
<Button
|
|||
|
|
variant="outline"
|
|||
|
|
size="sm"
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
setEditingLayout(layout);
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
편집
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 생성 모달 */}
|
|||
|
|
<YardLayoutCreateModal
|
|||
|
|
isOpen={isCreateModalOpen}
|
|||
|
|
onClose={() => setIsCreateModalOpen(false)}
|
|||
|
|
onCreate={handleCreateLayout}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 뷰 모드: 선택된 레이아웃의 3D 뷰어 표시
|
|||
|
|
if (!config?.layoutId) {
|
|||
|
|
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 className="mt-1 text-xs text-gray-400">대시보드 편집에서 레이아웃을 선택하세요</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 선택된 레이아웃의 3D 뷰어 표시
|
|||
|
|
return (
|
|||
|
|
<div className="h-full w-full">
|
|||
|
|
<Yard3DViewer layoutId={config.layoutId} />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|