"use client"; import React, { useState, useMemo, useCallback, useEffect } from "react"; import { Search, RotateCcw, Plus, Pencil, Trash2, Calendar, Upload, PointerIcon, Ruler, ClipboardList, FileText, Loader2, } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { ResizablePanelGroup, ResizablePanel, ResizableHandle, } from "@/components/ui/resizable"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { getDesignRequestList, createDesignRequest, updateDesignRequest, deleteDesignRequest, } from "@/lib/api/design"; // ========== 타입 ========== interface HistoryItem { id?: string; step: string; history_date: string; user_name: string; description: string; } interface DesignRequest { id: string; request_no: string; source_type: string; request_date: string; due_date: string; design_type: string; priority: string; status: string; approval_step: string; target_name: string; customer: string; req_dept: string; requester: string; designer: string; order_no: string; spec: string; change_type: string; drawing_no: string; urgency: string; reason: string; content: string; apply_timing: string; review_memo: string; project_id: string; ecn_no: string; created_date: string; updated_date: string; writer: string; company_code: string; history: HistoryItem[]; impact: string[]; } // ========== 스타일 맵 ========== const STATUS_STYLES: Record = { 신규접수: "bg-muted text-foreground", 접수대기: "bg-muted text-foreground", 검토중: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300", 설계진행: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300", 설계검토: "bg-violet-100 text-violet-800 dark:bg-violet-900/30 dark:text-violet-300", 출도완료: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300", 반려: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300", 종료: "bg-muted text-muted-foreground", }; const TYPE_STYLES: Record = { 신규설계: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300", 유사설계: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300", 개조설계: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300", }; const PRIORITY_STYLES: Record = { 긴급: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300", 높음: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300", 보통: "bg-muted text-foreground", 낮음: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300", }; const STATUS_PROGRESS: Record = { 신규접수: 0, 접수대기: 0, 검토중: 20, 설계진행: 50, 설계검토: 80, 출도완료: 100, 반려: 0, 종료: 100, }; function getProgressColor(p: number) { if (p >= 100) return "bg-emerald-500"; if (p >= 60) return "bg-amber-500"; if (p >= 20) return "bg-blue-500"; return "bg-muted"; } function getProgressTextColor(p: number) { if (p >= 100) return "text-emerald-500"; if (p >= 60) return "text-amber-500"; if (p >= 20) return "text-blue-500"; return "text-muted-foreground"; } const INITIAL_FORM = { request_no: "", request_date: "", due_date: "", design_type: "", priority: "보통", target_name: "", customer: "", req_dept: "", requester: "", designer: "", order_no: "", spec: "", drawing_no: "", content: "", }; // ========== 메인 컴포넌트 ========== export default function DesignRequestPage() { const [requests, setRequests] = useState([]); const [selectedId, setSelectedId] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [filterStatus, setFilterStatus] = useState(""); const [filterType, setFilterType] = useState(""); const [filterPriority, setFilterPriority] = useState(""); const [filterKeyword, setFilterKeyword] = useState(""); const [modalOpen, setModalOpen] = useState(false); const [isEditMode, setIsEditMode] = useState(false); const [editingId, setEditingId] = useState(null); const [form, setForm] = useState(INITIAL_FORM); const today = useMemo(() => new Date(), []); // 데이터 조회 const fetchRequests = useCallback(async () => { setLoading(true); try { const params: Record = { source_type: "dr" }; if (filterStatus && filterStatus !== "__all__") params.status = filterStatus; if (filterType && filterType !== "__all__") { // design_type은 서버에서 직접 필터링하지 않으므로 클라이언트에서 처리 } if (filterPriority && filterPriority !== "__all__") params.priority = filterPriority; if (filterKeyword) params.search = filterKeyword; const res = await getDesignRequestList(params); if (res.success && res.data) { setRequests(res.data); } else { setRequests([]); } } catch { setRequests([]); } finally { setLoading(false); } }, [filterStatus, filterPriority, filterKeyword]); useEffect(() => { fetchRequests(); }, [fetchRequests]); // 클라이언트 사이드 필터링 (design_type은 서버에서 지원하지 않으므로) const filteredRequests = useMemo(() => { let list = requests; if (filterType && filterType !== "__all__") { list = list.filter((item) => item.design_type === filterType); } return list; }, [requests, filterType]); const selectedItem = useMemo(() => { if (!selectedId) return null; return requests.find((r) => r.id === selectedId) || null; }, [selectedId, requests]); const statusCounts = useMemo(() => { return { 접수대기: requests.filter((r) => r.status === "접수대기" || r.status === "신규접수").length, 설계진행: requests.filter((r) => r.status === "설계진행").length, 출도완료: requests.filter((r) => r.status === "출도완료").length, }; }, [requests]); const handleResetFilter = useCallback(() => { setFilterStatus(""); setFilterType(""); setFilterPriority(""); setFilterKeyword(""); }, []); // 채번: 기존 데이터 기반으로 다음 번호 생성 const generateNextNo = useCallback(() => { const year = new Date().getFullYear(); const existing = requests.filter((r) => r.request_no?.startsWith(`DR-${year}-`)); const maxNum = existing.reduce((max, r) => { const parts = r.request_no?.split("-"); const num = parts?.length >= 3 ? parseInt(parts[2]) : 0; return num > max ? num : max; }, 0); return `DR-${year}-${String(maxNum + 1).padStart(4, "0")}`; }, [requests]); const handleOpenRegister = useCallback(() => { setIsEditMode(false); setEditingId(null); setForm({ ...INITIAL_FORM, request_no: generateNextNo(), request_date: new Date().toISOString().split("T")[0], }); setModalOpen(true); }, [generateNextNo]); const handleOpenEdit = useCallback(() => { if (!selectedItem) return; setIsEditMode(true); setEditingId(selectedItem.id); setForm({ request_no: selectedItem.request_no || "", request_date: selectedItem.request_date || "", due_date: selectedItem.due_date || "", design_type: selectedItem.design_type || "", priority: selectedItem.priority || "보통", target_name: selectedItem.target_name || "", customer: selectedItem.customer || "", req_dept: selectedItem.req_dept || "", requester: selectedItem.requester || "", designer: selectedItem.designer || "", order_no: selectedItem.order_no || "", spec: selectedItem.spec || "", drawing_no: selectedItem.drawing_no || "", content: selectedItem.content || "", }); setModalOpen(true); }, [selectedItem]); const handleSave = useCallback(async () => { if (!form.target_name.trim()) { alert("설비/제품명을 입력하세요."); return; } if (!form.design_type) { alert("의뢰 유형을 선택하세요."); return; } if (!form.due_date) { alert("납기를 입력하세요."); return; } if (!form.spec.trim()) { alert("요구사양을 입력하세요."); return; } setSaving(true); try { const payload = { request_no: form.request_no, source_type: "dr", request_date: form.request_date, due_date: form.due_date, design_type: form.design_type, priority: form.priority, target_name: form.target_name, customer: form.customer, req_dept: form.req_dept, requester: form.requester, designer: form.designer, order_no: form.order_no, spec: form.spec, drawing_no: form.drawing_no, content: form.content, }; let res; if (isEditMode && editingId) { res = await updateDesignRequest(editingId, payload); } else { res = await createDesignRequest({ ...payload, status: "신규접수", history: [{ step: "신규접수", history_date: form.request_date || new Date().toISOString().split("T")[0], user_name: form.requester || "시스템", description: `${form.req_dept || ""}에서 설계의뢰 등록`, }], }); } if (res.success) { setModalOpen(false); await fetchRequests(); if (isEditMode && editingId) { setSelectedId(editingId); } else if (res.data?.id) { setSelectedId(res.data.id); } } else { alert(`저장 실패: ${res.message || "알 수 없는 오류"}`); } } catch (err: any) { alert(`저장 중 오류가 발생했습니다: ${err.message}`); } finally { setSaving(false); } }, [form, isEditMode, editingId, fetchRequests]); const handleDelete = useCallback(async () => { if (!selectedId || !selectedItem) return; const displayNo = selectedItem.request_no || selectedId; if (!confirm(`${displayNo} 설계의뢰를 삭제하시겠습니까?`)) return; try { const res = await deleteDesignRequest(selectedId); if (res.success) { setSelectedId(null); await fetchRequests(); } else { alert(`삭제 실패: ${res.message || "알 수 없는 오류"}`); } } catch (err: any) { alert(`삭제 중 오류가 발생했습니다: ${err.message}`); } }, [selectedId, selectedItem, fetchRequests]); const getDueDateInfo = useCallback( (dueDate: string) => { if (!dueDate) return { text: "-", color: "text-muted-foreground" }; const due = new Date(dueDate); const diff = Math.ceil((due.getTime() - today.getTime()) / 86400000); if (diff < 0) return { text: `${Math.abs(diff)}일 초과`, color: "text-destructive" }; if (diff === 0) return { text: "오늘", color: "text-amber-500" }; if (diff <= 7) return { text: `${diff}일 남음`, color: "text-amber-500" }; return { text: `${diff}일 남음`, color: "text-emerald-500" }; }, [today] ); const getProgress = useCallback((status: string) => { return STATUS_PROGRESS[status] ?? 0; }, []); return (
{/* 검색 섹션 */}
setFilterKeyword(e.target.value)} placeholder="의뢰번호 / 설비명 / 고객명 검색" className="h-7 w-[240px] pl-7 text-xs" />
{/* 메인 영역 */} {/* 왼쪽: 목록 */}
설계의뢰 목록 ({filteredRequests.length}건)
{loading ? (
불러오는 중...
) : ( 의뢰번호 유형 상태 우선순위 설비/제품명 고객명 설계담당 납기 진행률 {filteredRequests.length === 0 && (
등록된 설계의뢰가 없습니다
)} {filteredRequests.map((item) => { const progress = getProgress(item.status); return ( setSelectedId(item.id)} > {item.request_no || "-"} {item.design_type ? ( {item.design_type} ) : "-"} {item.status} {item.priority} {item.target_name || "-"} {item.customer || "-"} {item.designer || "-"} {item.due_date || "-"}
{progress}%
); })}
)}
{/* 오른쪽: 상세 */}
상세 정보 {selectedItem && (
)}
{/* 상태 카드 */}
setFilterStatus("접수대기")} >
접수대기
{statusCounts.접수대기}
setFilterStatus("설계진행")} >
설계진행
{statusCounts.설계진행}
setFilterStatus("출도완료")} >
출도완료
{statusCounts.출도완료}
{/* 상세 내용 */} {!selectedItem ? (
좌측 목록에서 설계의뢰를 선택하세요
) : (
{/* 기본 정보 */}
기본 정보
{selectedItem.request_no || "-"}} /> {selectedItem.status}} /> {selectedItem.design_type} : "-"} /> {selectedItem.priority}} /> {selectedItem.due_date}{" "} ({getDueDateInfo(selectedItem.due_date).text}) ) : "-" } /> { const progress = getProgress(selectedItem.status); return (
{progress}%
); })() } />
{/* 요구사양 */}
요구사양
{selectedItem.spec || "-"}
{selectedItem.drawing_no && (
참조 도면: {selectedItem.drawing_no}
)} {selectedItem.content && (
비고: {selectedItem.content}
)}
{/* 진행 이력 */} {selectedItem.history && selectedItem.history.length > 0 && (
진행 이력
{selectedItem.history.map((h, idx) => { const isLast = idx === selectedItem.history.length - 1; const isDone = h.step === "출도완료" || h.step === "종료"; return (
{!isLast &&
}
{h.step}
{h.description}
{h.history_date} · {h.user_name}
); })}
)}
)}
{/* 등록/수정 모달 */} {isEditMode ? <>설계의뢰 수정 : <>설계의뢰 등록} {isEditMode ? "설계의뢰 정보를 수정합니다." : "새 설계의뢰를 등록합니다."}
{/* 좌측: 기본 정보 */}
의뢰 기본 정보
setForm((p) => ({ ...p, request_date: e.target.value }))} className="h-9 text-sm" />
setForm((p) => ({ ...p, due_date: e.target.value }))} className="h-9 text-sm" />
setForm((p) => ({ ...p, target_name: e.target.value }))} placeholder="설비 또는 제품명 입력" className="h-9 text-sm" />
setForm((p) => ({ ...p, requester: e.target.value }))} placeholder="의뢰자명" className="h-9 text-sm" />
setForm((p) => ({ ...p, customer: e.target.value }))} placeholder="고객/거래처명" className="h-9 text-sm" />
setForm((p) => ({ ...p, order_no: e.target.value }))} placeholder="관련 수주번호" className="h-9 text-sm" />
{/* 우측: 상세 내용 */}
요구사양 및 설명