"use client"; import React, { useState, useEffect, useCallback } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Plus, X, Loader2 } from "lucide-react"; import { getApprovalDefinitions, getApprovalTemplates, createApprovalRequest, type ApprovalDefinition, type ApprovalLineTemplate, } from "@/lib/api/approval"; // 결재자 행 타입 interface ApproverRow { id: string; // 로컬 임시 ID approver_id: string; approver_name: string; approver_position: string; approver_dept: string; approver_label: string; } // 모달 열기 이벤트로 전달되는 데이터 export interface ApprovalModalEventDetail { targetTable: string; targetRecordId: string; targetRecordData?: Record; definitionId?: number; screenId?: number; buttonComponentId?: string; } interface ApprovalRequestModalProps { open: boolean; onOpenChange: (open: boolean) => void; eventDetail?: ApprovalModalEventDetail | null; } function generateLocalId(): string { return `row_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`; } export const ApprovalRequestModal: React.FC = ({ open, onOpenChange, eventDetail, }) => { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [selectedDefinitionId, setSelectedDefinitionId] = useState(""); const [selectedTemplateId, setSelectedTemplateId] = useState(""); const [approvers, setApprovers] = useState([]); const [definitions, setDefinitions] = useState([]); const [templates, setTemplates] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [isLoadingDefs, setIsLoadingDefs] = useState(false); const [isLoadingTemplates, setIsLoadingTemplates] = useState(false); const [error, setError] = useState(null); // 결재 유형 목록 로딩 useEffect(() => { if (!open) return; const load = async () => { setIsLoadingDefs(true); const res = await getApprovalDefinitions({ is_active: "Y" }); if (res.success && res.data) setDefinitions(res.data); setIsLoadingDefs(false); }; load(); }, [open]); // 결재 유형 변경 시 템플릿 목록 로딩 useEffect(() => { if (!selectedDefinitionId) { setTemplates([]); setSelectedTemplateId(""); return; } const load = async () => { setIsLoadingTemplates(true); const res = await getApprovalTemplates({ definition_id: Number(selectedDefinitionId), is_active: "Y", }); if (res.success && res.data) setTemplates(res.data); setIsLoadingTemplates(false); }; load(); }, [selectedDefinitionId]); // 템플릿 선택 시 결재선 자동 세팅 useEffect(() => { if (!selectedTemplateId) return; const template = templates.find((t) => String(t.template_id) === selectedTemplateId); if (!template?.steps) return; const rows: ApproverRow[] = template.steps .sort((a, b) => a.step_order - b.step_order) .map((step) => ({ id: generateLocalId(), approver_id: step.approver_user_id || "", approver_name: step.approver_label || "", approver_position: step.approver_position || "", approver_dept: step.approver_dept_code || "", approver_label: step.approver_label || `${step.step_order}차 결재`, })); setApprovers(rows); }, [selectedTemplateId, templates]); // eventDetail에서 definitionId 자동 세팅 useEffect(() => { if (open && eventDetail?.definitionId) { setSelectedDefinitionId(String(eventDetail.definitionId)); } }, [open, eventDetail]); // 모달 닫힐 때 초기화 useEffect(() => { if (!open) { setTitle(""); setDescription(""); setSelectedDefinitionId(""); setSelectedTemplateId(""); setApprovers([]); setError(null); } }, [open]); const handleAddApprover = () => { setApprovers((prev) => [ ...prev, { id: generateLocalId(), approver_id: "", approver_name: "", approver_position: "", approver_dept: "", approver_label: `${prev.length + 1}차 결재`, }, ]); }; const handleRemoveApprover = (id: string) => { setApprovers((prev) => prev.filter((a) => a.id !== id)); }; const handleApproverChange = (id: string, field: keyof ApproverRow, value: string) => { setApprovers((prev) => prev.map((a) => (a.id === id ? { ...a, [field]: value } : a)) ); }; const handleSubmit = async () => { if (!title.trim()) { setError("결재 제목을 입력해주세요."); return; } if (approvers.length === 0) { setError("결재자를 1명 이상 추가해주세요."); return; } const emptyApprover = approvers.find((a) => !a.approver_id.trim()); if (emptyApprover) { setError("모든 결재자의 사번/ID를 입력해주세요."); return; } if (!eventDetail?.targetTable || !eventDetail?.targetRecordId) { setError("결재 대상 정보가 없습니다. 버튼 설정을 확인해주세요."); return; } setIsSubmitting(true); setError(null); const res = await createApprovalRequest({ title: title.trim(), description: description.trim() || undefined, definition_id: selectedDefinitionId ? Number(selectedDefinitionId) : undefined, target_table: eventDetail.targetTable, target_record_id: eventDetail.targetRecordId, target_record_data: eventDetail.targetRecordData, screen_id: eventDetail.screenId, button_component_id: eventDetail.buttonComponentId, approvers: approvers.map((a) => ({ approver_id: a.approver_id.trim(), approver_name: a.approver_name.trim() || undefined, approver_position: a.approver_position.trim() || undefined, approver_dept: a.approver_dept.trim() || undefined, approver_label: a.approver_label.trim() || undefined, })), }); setIsSubmitting(false); if (res.success) { onOpenChange(false); } else { setError(res.error || res.message || "결재 요청에 실패했습니다."); } }; return ( 결재 요청 결재자를 지정하고 결재를 요청합니다.
{/* 결재 제목 */}
setTitle(e.target.value)} placeholder="결재 제목을 입력하세요" className="h-8 text-xs sm:h-10 sm:text-sm" />
{/* 결재 사유 */}