"use client"; import React, { useState, useMemo, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; import { Search, RotateCcw, Plus, Save, ClipboardList, Inbox, Pencil, FileText, XCircle, ArrowRight, Paperclip, Upload, Loader2, } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; import { getDesignRequestList, createDesignRequest, updateDesignRequest, addRequestHistory, getEcnList, createEcn, updateEcn, } from "@/lib/api/design"; // --- Types --- type ChangeType = "설계오류" | "원가절감" | "고객요청" | "공정개선" | "법규대응"; type EcrStatus = "요청접수" | "영향도분석" | "ECN발행" | "기각"; type EcnStatus = "ECN발행" | "도면변경" | "통보완료" | "적용완료"; type TabType = "ecr" | "ecn"; interface EcrHistory { status: string; date: string; user: string; desc: string; } interface EcrItem { id: string; _id?: string; date: string; changeType: ChangeType; urgency: "보통" | "긴급"; status: EcrStatus; target: string; drawingNo: string; reqDept: string; requester: string; reason: string; content: string; impact: string[]; applyTiming: string; ecnNo: string; history: EcrHistory[]; } interface EcnItem { id: string; _id?: string; ecrNo: string; ecrId?: string; date: string; applyDate: string; status: EcnStatus; target: string; drawingBefore: string; drawingAfter: string; designer: string; before: string; after: string; reason: string; notifyDepts: string[]; remark: string; history: EcrHistory[]; } // --- Style Helpers --- const getChangeTypeStyle = (type: ChangeType) => { switch (type) { case "설계오류": return "bg-rose-100 text-rose-800 border-rose-200"; case "원가절감": return "bg-emerald-100 text-emerald-800 border-emerald-200"; case "고객요청": return "bg-blue-100 text-blue-800 border-blue-200"; case "공정개선": return "bg-amber-100 text-amber-800 border-amber-200"; case "법규대응": return "bg-purple-100 text-purple-800 border-purple-200"; default: return "bg-gray-100 text-gray-800 border-gray-200"; } }; const getEcrStatusStyle = (status: EcrStatus) => { switch (status) { case "요청접수": return "bg-blue-100 text-blue-800 border-blue-200"; case "영향도분석": return "bg-amber-100 text-amber-800 border-amber-200"; case "ECN발행": return "bg-emerald-100 text-emerald-800 border-emerald-200"; case "기각": return "bg-slate-100 text-slate-800 border-slate-200"; default: return "bg-gray-100 text-gray-800 border-gray-200"; } }; const getEcnStatusStyle = (status: EcnStatus) => { switch (status) { case "ECN발행": return "bg-blue-100 text-blue-800 border-blue-200"; case "도면변경": return "bg-purple-100 text-purple-800 border-purple-200"; case "통보완료": return "bg-teal-100 text-teal-800 border-teal-200"; case "적용완료": return "bg-emerald-100 text-emerald-800 border-emerald-200"; default: return "bg-gray-100 text-gray-800 border-gray-200"; } }; const getImpactBadgeStyle = (impact: string) => { switch (impact) { case "BOM": return "bg-blue-100 text-blue-800 border-blue-200"; case "공정": return "bg-amber-100 text-amber-800 border-amber-200"; case "금형": return "bg-rose-100 text-rose-800 border-rose-200"; case "검사기준": return "bg-purple-100 text-purple-800 border-purple-200"; case "구매": case "원가": return "bg-emerald-100 text-emerald-800 border-emerald-200"; default: return "bg-gray-100 text-gray-800 border-gray-200"; } }; // --- Constants --- const CHANGE_TYPES: ChangeType[] = ["설계오류", "원가절감", "고객요청", "공정개선", "법규대응"]; const ECR_STATUSES: EcrStatus[] = ["요청접수", "영향도분석", "ECN발행", "기각"]; const ECN_STATUSES: EcnStatus[] = ["ECN발행", "도면변경", "통보완료", "적용완료"]; const DEPARTMENTS = ["품질팀", "생산팀", "영업팀", "구매팀", "설계팀"]; const DESIGNERS = ["이설계", "박도면", "최기구", "김전장"]; const IMPACT_OPTIONS = [ { key: "BOM", label: "BOM 변경" }, { key: "공정", label: "공정 변경" }, { key: "금형", label: "금형 변경" }, { key: "검사기준", label: "검사기준 변경" }, { key: "구매", label: "구매 변경" }, { key: "원가", label: "원가 영향" }, ]; const NOTIFY_DEPTS = [ { key: "생산팀", label: "생산팀" }, { key: "품질팀", label: "품질팀" }, { key: "구매팀", label: "구매팀" }, { key: "영업팀", label: "영업팀" }, { key: "물류팀", label: "물류팀" }, { key: "금형팀", label: "금형팀" }, ]; // --- API Response Mapping --- function mapEcrFromApi(raw: any): EcrItem { const history = (raw.history || []).map((h: any) => ({ status: h.step || h.status || "", date: h.history_date || "", user: h.user_name || "", desc: h.description || "", })); return { id: raw.request_no || raw.id || "", _id: raw.id, date: raw.request_date || "", changeType: (raw.change_type as ChangeType) || "설계오류", urgency: (raw.urgency as "보통" | "긴급") || "보통", status: (raw.status as EcrStatus) || "요청접수", target: raw.target_name || "", drawingNo: raw.drawing_no || "", reqDept: raw.req_dept || "", requester: raw.requester || "", reason: raw.reason || "", content: raw.content || "", impact: Array.isArray(raw.impact) ? raw.impact : [], applyTiming: raw.apply_timing || "", ecnNo: raw.ecn_no || "", history, }; } function mapEcnFromApi(raw: any, ecrData: EcrItem[]): EcnItem { const history = (raw.history || []).map((h: any) => ({ status: h.status || "", date: h.history_date || "", user: h.user_name || "", desc: h.description || "", })); const ecrNo = raw.ecr_id ? ecrData.find((e) => e._id === raw.ecr_id)?.id ?? raw.ecr_id : ""; return { id: raw.ecn_no || raw.id || "", _id: raw.id, ecrNo, ecrId: raw.ecr_id, date: raw.ecn_date || "", applyDate: raw.apply_date || "", status: (raw.status as EcnStatus) || "ECN발행", target: raw.target || "", drawingBefore: raw.drawing_before || "", drawingAfter: raw.drawing_after || "", designer: raw.designer || "", before: raw.before_content || "", after: raw.after_content || "", reason: raw.reason || "", notifyDepts: Array.isArray(raw.notify_depts) ? raw.notify_depts : [], remark: raw.remark || "", history, }; } // --- Timeline Component --- function Timeline({ history }: { history: EcrHistory[] }) { return (
{history.map((h, idx) => { const isLast = idx === history.length - 1; const isRejected = h.status === "기각"; const isCompleted = h.status === "적용완료"; return (
{!isLast && (
)}
{h.status}

{h.desc}

{h.date} · {h.user}

); })}
); } // --- Main Component --- export default function DesignChangeManagementPage() { const [currentTab, setCurrentTab] = useState("ecr"); const [ecrData, setEcrData] = useState([]); const [ecnData, setEcnData] = useState([]); const [loading, setLoading] = useState(true); const [selectedId, setSelectedId] = useState(null); // 검색 상태 const [searchDateFrom, setSearchDateFrom] = useState(""); const [searchDateTo, setSearchDateTo] = useState(""); const [searchStatus, setSearchStatus] = useState("all"); const [searchChangeType, setSearchChangeType] = useState("all"); const [searchKeyword, setSearchKeyword] = useState(""); // ECR 모달 const [isEcrModalOpen, setIsEcrModalOpen] = useState(false); const [isEcrEditMode, setIsEcrEditMode] = useState(false); const [ecrForm, setEcrForm] = useState>({}); const [ecrImpactChecks, setEcrImpactChecks] = useState>({}); // ECN 모달 const [isEcnModalOpen, setIsEcnModalOpen] = useState(false); const [ecnForm, setEcnForm] = useState>({}); const [ecnNotifyChecks, setEcnNotifyChecks] = useState>({}); // 기각 모달 const [isRejectModalOpen, setIsRejectModalOpen] = useState(false); const [rejectReason, setRejectReason] = useState(""); const [rejectTargetId, setRejectTargetId] = useState(""); useEffect(() => { const today = new Date(); const threeMonthsAgo = new Date(today); threeMonthsAgo.setMonth(today.getMonth() - 3); setSearchDateFrom(threeMonthsAgo.toISOString().split("T")[0]); setSearchDateTo(today.toISOString().split("T")[0]); }, []); const fetchData = useCallback(async () => { setLoading(true); try { const [ecrRes, ecnRes] = await Promise.all([ getDesignRequestList({ source_type: "ecr" }), getEcnList(), ]); if (ecrRes.success && ecrRes.data) { setEcrData((ecrRes.data as any[]).map(mapEcrFromApi)); } if (ecnRes.success && ecnRes.data) { const ecrList = ecrRes.success && ecrRes.data ? (ecrRes.data as any[]).map(mapEcrFromApi) : []; setEcnData((ecnRes.data as any[]).map((r) => mapEcnFromApi(r, ecrList))); } } catch { toast.error("데이터를 불러오는데 실패했습니다."); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); // --- Filtered Data --- const filteredEcr = useMemo(() => { return ecrData .filter((item) => { if (searchDateFrom && item.date < searchDateFrom) return false; if (searchDateTo && item.date > searchDateTo) return false; if (searchStatus !== "all" && item.status !== searchStatus) return false; if (searchChangeType !== "all" && item.changeType !== searchChangeType) return false; if (searchKeyword) { const kw = searchKeyword.toLowerCase(); const str = [item.id, item.target, item.requester, item.drawingNo].join(" ").toLowerCase(); if (!str.includes(kw)) return false; } return true; }) .sort((a, b) => b.date.localeCompare(a.date)); }, [ecrData, searchDateFrom, searchDateTo, searchStatus, searchChangeType, searchKeyword]); const filteredEcn = useMemo(() => { return ecnData .filter((item) => { if (searchDateFrom && item.date < searchDateFrom) return false; if (searchDateTo && item.date > searchDateTo) return false; if (searchStatus !== "all" && item.status !== searchStatus) return false; if (searchKeyword) { const kw = searchKeyword.toLowerCase(); const str = [item.id, item.target, item.designer, item.ecrNo].join(" ").toLowerCase(); if (!str.includes(kw)) return false; } return true; }) .sort((a, b) => b.date.localeCompare(a.date)); }, [ecnData, searchDateFrom, searchDateTo, searchStatus, searchKeyword]); // --- Status Counts --- const ecrStatusCounts = useMemo(() => { const counts: Record = {}; ECR_STATUSES.forEach((s) => (counts[s] = ecrData.filter((r) => r.status === s).length)); return counts; }, [ecrData]); const ecnStatusCounts = useMemo(() => { const counts: Record = {}; ECN_STATUSES.forEach((s) => (counts[s] = ecnData.filter((r) => r.status === s).length)); return counts; }, [ecnData]); // --- Selected Items --- const selectedEcr = useMemo( () => (currentTab === "ecr" ? ecrData.find((r) => r.id === selectedId) : null), [ecrData, selectedId, currentTab] ); const selectedEcn = useMemo( () => (currentTab === "ecn" ? ecnData.find((r) => r.id === selectedId) : null), [ecnData, selectedId, currentTab] ); // --- Tab Switch --- const handleTabSwitch = (tab: TabType) => { setCurrentTab(tab); setSelectedId(null); setSearchStatus("all"); }; // --- Search --- const handleResetSearch = () => { const today = new Date(); const threeMonthsAgo = new Date(today); threeMonthsAgo.setMonth(today.getMonth() - 3); setSearchDateFrom(threeMonthsAgo.toISOString().split("T")[0]); setSearchDateTo(today.toISOString().split("T")[0]); setSearchStatus("all"); setSearchChangeType("all"); setSearchKeyword(""); }; const handleFilterByStatus = (status: string) => { setSearchStatus(status); }; // --- ECR/ECN Navigation --- const navigateToLink = (targetId: string) => { if (targetId.startsWith("ECN")) { setCurrentTab("ecn"); setSelectedId(targetId); setSearchStatus("all"); } else if (targetId.startsWith("ECR")) { setCurrentTab("ecr"); setSelectedId(targetId); setSearchStatus("all"); } }; // --- ECR Number Generator --- const generateEcrNo = useCallback(() => { const year = new Date().getFullYear(); const prefix = `ECR-${year}-`; const existing = ecrData.filter((r) => r.id.startsWith(prefix)); const maxNum = existing.reduce((max, r) => { const num = parseInt(r.id.split("-")[2]); return num > max ? num : max; }, 0); return `${prefix}${String(maxNum + 1).padStart(4, "0")}`; }, [ecrData]); const generateEcnNo = useCallback(() => { const year = new Date().getFullYear(); const prefix = `ECN-${year}-`; const existing = ecnData.filter((r) => r.id.startsWith(prefix)); const maxNum = existing.reduce((max, r) => { const num = parseInt(r.id.split("-")[2]); return num > max ? num : max; }, 0); return `${prefix}${String(maxNum + 1).padStart(4, "0")}`; }, [ecnData]); // --- ECR Modal --- const openEcrRegisterModal = () => { setIsEcrEditMode(false); setEcrForm({ id: generateEcrNo(), date: new Date().toISOString().split("T")[0], changeType: undefined, urgency: "보통", target: "", drawingNo: "", reqDept: "", requester: "", reason: "", content: "", applyTiming: "즉시", }); setEcrImpactChecks({}); setIsEcrModalOpen(true); }; const openEcrEditModal = (id: string) => { const item = ecrData.find((r) => r.id === id); if (!item) return; setIsEcrEditMode(true); setEcrForm({ ...item }); const checks: Record = {}; IMPACT_OPTIONS.forEach((opt) => { checks[opt.key] = item.impact.includes(opt.key); }); setEcrImpactChecks(checks); setIsEcrModalOpen(true); }; const handleSaveEcr = async () => { if (!ecrForm.changeType) { toast.error("변경 유형을 선택하세요."); return; } if (!ecrForm.target?.trim()) { toast.error("대상 품목/설비를 입력하세요."); return; } if (!ecrForm.reason?.trim()) { toast.error("변경 사유를 입력하세요."); return; } if (!ecrForm.content?.trim()) { toast.error("변경 요구 내용을 입력하세요."); return; } const impact = IMPACT_OPTIONS.filter((opt) => ecrImpactChecks[opt.key]).map((opt) => opt.key); const reqDate = ecrForm.date || new Date().toISOString().split("T")[0]; const historyEntry = { step: "요청접수", history_date: reqDate, user_name: ecrForm.requester || "시스템", description: `${ecrForm.reqDept || ""}에서 ECR 등록`, }; if (isEcrEditMode && ecrForm._id) { const res = await updateDesignRequest(ecrForm._id, { request_no: ecrForm.id, request_date: reqDate, change_type: ecrForm.changeType, urgency: ecrForm.urgency || "보통", target_name: ecrForm.target, drawing_no: ecrForm.drawingNo || "", req_dept: ecrForm.reqDept || "", requester: ecrForm.requester || "", reason: ecrForm.reason, content: ecrForm.content, impact, apply_timing: ecrForm.applyTiming || "즉시", }); if (res.success) { toast.success("ECR이 수정되었습니다."); setIsEcrModalOpen(false); fetchData(); } else { toast.error(res.message || "ECR 수정에 실패했습니다."); } } else { const res = await createDesignRequest({ request_no: ecrForm.id || generateEcrNo(), source_type: "ecr", request_date: reqDate, change_type: ecrForm.changeType, urgency: ecrForm.urgency || "보통", status: "요청접수", target_name: ecrForm.target, drawing_no: ecrForm.drawingNo || "", req_dept: ecrForm.reqDept || "", requester: ecrForm.requester || "", reason: ecrForm.reason, content: ecrForm.content, impact, apply_timing: ecrForm.applyTiming || "즉시", history: [historyEntry], }); if (res.success) { toast.success("ECR이 등록되었습니다."); setIsEcrModalOpen(false); fetchData(); } else { toast.error(res.message || "ECR 등록에 실패했습니다."); } } }; // --- ECN Modal --- const openEcnIssueModal = (ecrId: string) => { const ecr = ecrData.find((r) => r.id === ecrId); if (!ecr) return; setEcnForm({ id: generateEcnNo(), ecrNo: ecrId, ecrId: ecr._id, date: new Date().toISOString().split("T")[0], target: ecr.target, reason: ecr.reason, drawingBefore: ecr.drawingNo, drawingAfter: "", designer: "", before: "", after: "", applyDate: "", remark: "", }); setEcnNotifyChecks({}); setIsEcnModalOpen(true); }; const handleSaveEcn = async () => { if (!ecnForm.after?.trim()) { toast.error("변경 후(TO-BE) 내용을 입력하세요."); return; } if (!ecnForm.applyDate) { toast.error("적용일자를 입력하세요."); return; } if (!ecnForm.ecrId) { toast.error("관련 ECR 정보가 없습니다."); return; } const notifyDepts = NOTIFY_DEPTS.filter((d) => ecnNotifyChecks[d.key]).map((d) => d.key); const ecnDate = ecnForm.date || new Date().toISOString().split("T")[0]; const historyEntry = { status: "ECN발행", history_date: ecnDate, user_name: ecnForm.designer || "시스템", description: "ECN 발행", }; const ecnNo = ecnForm.id || generateEcnNo(); const res = await createEcn({ ecn_no: ecnNo, ecr_id: ecnForm.ecrId, ecn_date: ecnDate, apply_date: ecnForm.applyDate, status: "ECN발행", target: ecnForm.target || "", drawing_before: ecnForm.drawingBefore || "", drawing_after: ecnForm.drawingAfter || "(미정)", designer: ecnForm.designer || "", before_content: ecnForm.before || "", after_content: ecnForm.after || "", reason: ecnForm.reason || "", remark: ecnForm.remark || "", notify_depts: notifyDepts, history: [historyEntry], }); if (res.success) { await updateDesignRequest(ecnForm.ecrId!, { status: "ECN발행", ecn_no: ecnNo, }); await addRequestHistory(ecnForm.ecrId!, { step: "ECN발행", history_date: ecnDate, user_name: ecnForm.designer || "시스템", description: `${ecnNo} 발행`, }); toast.success("ECN이 발행되었습니다."); setIsEcnModalOpen(false); fetchData(); } else { toast.error(res.message || "ECN 발행에 실패했습니다."); } }; // --- ECR Reject --- const openRejectModal = (id: string) => { setRejectTargetId(id); setRejectReason(""); setIsRejectModalOpen(true); }; const handleRejectSubmit = async () => { if (!rejectReason.trim()) { toast.error("기각 사유를 입력하세요."); return; } const ecr = ecrData.find((r) => r.id === rejectTargetId); if (!ecr?._id) { toast.error("ECR 정보를 찾을 수 없습니다."); return; } const updateRes = await updateDesignRequest(ecr._id, { status: "기각", review_memo: rejectReason }); if (!updateRes.success) { toast.error(updateRes.message || "ECR 기각에 실패했습니다."); return; } await addRequestHistory(ecr._id, { step: "기각", history_date: new Date().toISOString().split("T")[0], user_name: "설계팀", description: rejectReason, }); toast.success("ECR이 기각되었습니다."); setIsRejectModalOpen(false); fetchData(); }; // --- Stat Cards --- const ecrStatCards = [ { label: "요청접수", value: ecrStatusCounts["요청접수"] || 0, gradient: "from-indigo-500 to-blue-600", textColor: "text-white" }, { label: "영향도분석", value: ecrStatusCounts["영향도분석"] || 0, gradient: "from-amber-400 to-orange-500", textColor: "text-white" }, { label: "ECN발행", value: ecrStatusCounts["ECN발행"] || 0, gradient: "from-emerald-400 to-green-600", textColor: "text-white" }, ]; const ecnStatCards = [ { label: "도면변경", value: ecnStatusCounts["도면변경"] || 0, gradient: "from-purple-400 to-violet-600", textColor: "text-white" }, { label: "통보완료", value: ecnStatusCounts["통보완료"] || 0, gradient: "from-teal-400 to-cyan-600", textColor: "text-white" }, { label: "적용완료", value: ecnStatusCounts["적용완료"] || 0, gradient: "from-emerald-400 to-green-600", textColor: "text-white" }, ]; const currentStatCards = currentTab === "ecr" ? ecrStatCards : ecnStatCards; const currentList = currentTab === "ecr" ? filteredEcr : filteredEcn; const currentStatuses = currentTab === "ecr" ? ECR_STATUSES : ECN_STATUSES; return (
{loading && (
)} {/* 검색 섹션 */}
setSearchDateFrom(e.target.value)} /> ~ setSearchDateTo(e.target.value)} />
{currentTab === "ecr" && (
)}
setSearchKeyword(e.target.value)} />
{/* 메인 분할 레이아웃 */}
{/* 왼쪽: 목록 */}
{currentTab === "ecr" ? "설계변경요청(ECR) 목록" : "설계변경통지(ECN) 목록"} {currentList.length}건
{currentTab === "ecr" && ( )}
{currentTab === "ecr" ? ( No ECR번호 변경유형 상태 긴급 대상 품목/설비 도면번호 요청부서 요청자 요청일자 관련 ECN {filteredEcr.length === 0 ? (
조건에 맞는 ECR이 없습니다
) : ( filteredEcr.map((item, idx) => ( setSelectedId(item.id)} > {idx + 1} {item.id} {item.changeType} {item.status} {item.urgency === "긴급" ? ( 긴급 ) : ( "-" )} {item.target} {item.drawingNo} {item.reqDept} {item.requester} {item.date} {item.ecnNo ? ( ) : ( "-" )} )) )}
) : ( No ECN번호 상태 대상 품목/설비 도면 (변경 후) 설계담당 발행일자 적용일자 통보 부서 관련 ECR {filteredEcn.length === 0 ? (
조건에 맞는 ECN이 없습니다
) : ( filteredEcn.map((item, idx) => ( setSelectedId(item.id)} > {idx + 1} {item.id} {item.status} {item.target} {item.drawingAfter} {item.designer} {item.date} {item.applyDate} {item.notifyDepts.join(", ")} )) )}
)}
{/* 오른쪽: 상세 */}
상세 정보 {selectedEcr && (
{selectedEcr.status === "영향도분석" && ( <> )}
)}
{/* 현황 카드 */}
{currentStatCards.map((card) => ( ))}
{/* ECR 상세 */} {selectedEcr ? (

기본 정보

ECR번호 {selectedEcr.id}
상태 {selectedEcr.status}
변경 유형 {selectedEcr.changeType}
긴급도 {selectedEcr.urgency === "긴급" ? ( 긴급 ) : ( "보통" )}
대상 품목/설비 {selectedEcr.target}
도면번호 {selectedEcr.drawingNo}
요청부서 / 요청자 {selectedEcr.reqDept} / {selectedEcr.requester}
요청일자 {selectedEcr.date}
희망 적용시점 {selectedEcr.applyTiming}
관련 ECN {selectedEcr.ecnNo ? ( ) : ( 미발행 )}

변경 사유

{selectedEcr.reason}

변경 요구 내용

{selectedEcr.content}

영향 범위

{selectedEcr.impact.map((imp) => ( {imp} ))}

처리 이력

) : selectedEcn ? (

ECN 기본 정보

ECN번호 {selectedEcn.id}
상태 {selectedEcn.status}
대상 품목/설비 {selectedEcn.target}
설계담당 {selectedEcn.designer}
발행일자 {selectedEcn.date}
적용일자 {selectedEcn.applyDate}
관련 ECR
통보 부서 {selectedEcn.notifyDepts.join(", ")}

변경 전/후 비교

변경 전 ({selectedEcn.drawingBefore})
{selectedEcn.before}
변경 후 ({selectedEcn.drawingAfter})
{selectedEcn.after}

변경 사유

{selectedEcn.reason}
{selectedEcn.remark && (

비고: {selectedEcn.remark}

)}

처리 이력

) : (

좌측 목록에서 항목을 선택하세요

)}
{/* ECR 등록/수정 모달 */} {isEcrEditMode ? "설계변경요청(ECR) 수정" : "설계변경요청(ECR) 등록"} {isEcrEditMode ? "ECR 정보를 수정합니다." : "새로운 설계변경요청을 등록합니다."}
{/* 좌측: 요청 정보 */}

변경 요청 정보

setEcrForm((p) => ({ ...p, date: e.target.value }))} className="h-8 text-xs sm:h-10 sm:text-sm" />
setEcrForm((p) => ({ ...p, target: e.target.value }))} placeholder="품목코드 / 설비명" className="h-8 text-xs sm:h-10 sm:text-sm" />
setEcrForm((p) => ({ ...p, drawingNo: e.target.value }))} placeholder="DWG-XXX-XXX" className="h-8 text-xs sm:h-10 sm:text-sm" />
setEcrForm((p) => ({ ...p, requester: e.target.value }))} placeholder="요청자명" className="h-8 text-xs sm:h-10 sm:text-sm" />
{/* 우측: 변경 내용 */}

변경 내용