"use client"; import React, { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { toast } from "sonner"; import { Loader2, Send, Inbox, CheckCircle, XCircle, Clock, Eye, } from "lucide-react"; import { ScrollToTop } from "@/components/common/ScrollToTop"; import { getApprovalRequests, getApprovalRequest, getMyPendingApprovals, processApprovalLine, cancelApprovalRequest, type ApprovalRequest, type ApprovalLine, } from "@/lib/api/approval"; const STATUS_MAP: Record = { requested: { label: "요청", variant: "outline" }, in_progress: { label: "진행중", variant: "default" }, approved: { label: "승인", variant: "default" }, rejected: { label: "반려", variant: "destructive" }, cancelled: { label: "회수", variant: "secondary" }, waiting: { label: "대기", variant: "outline" }, pending: { label: "결재대기", variant: "default" }, skipped: { label: "건너뜀", variant: "secondary" }, }; function StatusBadge({ status }: { status: string }) { const info = STATUS_MAP[status] || { label: status, variant: "outline" as const }; return {info.label}; } function formatDate(dateStr?: string) { if (!dateStr) return "-"; return new Date(dateStr).toLocaleDateString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }); } // ============================================================ // 상신함 (내가 올린 결재) // ============================================================ function SentTab() { const [requests, setRequests] = useState([]); const [loading, setLoading] = useState(true); const [detailOpen, setDetailOpen] = useState(false); const [selectedRequest, setSelectedRequest] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const fetchRequests = useCallback(async () => { setLoading(true); const res = await getApprovalRequests({ my_approvals: false }); if (res.success && res.data) setRequests(res.data); setLoading(false); }, []); useEffect(() => { fetchRequests(); }, [fetchRequests]); const openDetail = async (req: ApprovalRequest) => { setDetailLoading(true); setDetailOpen(true); const res = await getApprovalRequest(req.request_id); if (res.success && res.data) { setSelectedRequest(res.data); } else { setSelectedRequest(req); } setDetailLoading(false); }; const handleCancel = async () => { if (!selectedRequest) return; const res = await cancelApprovalRequest(selectedRequest.request_id); if (res.success) { toast.success("결재가 회수되었습니다."); setDetailOpen(false); fetchRequests(); } else { toast.error(res.error || "회수 실패"); } }; return (
{loading ? (
) : requests.length === 0 ? (

상신한 결재가 없습니다.

) : (
제목 대상 테이블 진행 상태 요청일 보기 {requests.map((req) => ( {req.title} {req.target_table} {req.current_step}/{req.total_steps} {formatDate(req.created_at)} ))}
)} {/* 상세 모달 */} 결재 상세 {selectedRequest?.title} {detailLoading ? (
) : selectedRequest && (
상태
진행

{selectedRequest.current_step}/{selectedRequest.total_steps}단계

대상 테이블

{selectedRequest.target_table}

요청일

{formatDate(selectedRequest.created_at)}

{selectedRequest.description && (
사유

{selectedRequest.description}

)} {/* 결재선 */} {selectedRequest.lines && selectedRequest.lines.length > 0 && (
결재선
{selectedRequest.lines .sort((a, b) => a.step_order - b.step_order) .map((line) => (
{line.step_order}차 {line.approver_name || line.approver_id} {line.approver_position && ( ({line.approver_position}) )}
{line.processed_at && ( {formatDate(line.processed_at)} )}
))}
)}
)} {selectedRequest?.status === "requested" && ( )}
); } // ============================================================ // 수신함 (내가 결재해야 할 것) // ============================================================ function ReceivedTab() { const [pendingLines, setPendingLines] = useState([]); const [loading, setLoading] = useState(true); const [processOpen, setProcessOpen] = useState(false); const [selectedLine, setSelectedLine] = useState(null); const [comment, setComment] = useState(""); const [isProcessing, setIsProcessing] = useState(false); const fetchPending = useCallback(async () => { setLoading(true); const res = await getMyPendingApprovals(); if (res.success && res.data) setPendingLines(res.data); setLoading(false); }, []); useEffect(() => { fetchPending(); }, [fetchPending]); const openProcess = (line: ApprovalLine) => { setSelectedLine(line); setComment(""); setProcessOpen(true); }; const handleProcess = async (action: "approved" | "rejected") => { if (!selectedLine) return; setIsProcessing(true); const res = await processApprovalLine(selectedLine.line_id, { action, comment: comment.trim() || undefined, }); setIsProcessing(false); if (res.success) { toast.success(action === "approved" ? "승인되었습니다." : "반려되었습니다."); setProcessOpen(false); fetchPending(); } else { toast.error(res.error || "처리 실패"); } }; return (
{loading ? (
) : pendingLines.length === 0 ? (

결재 대기 건이 없습니다.

) : (
제목 요청자 대상 테이블 단계 요청일 처리 {pendingLines.map((line) => ( {line.title || "-"} {line.requester_name || "-"} {line.requester_dept && ( ({line.requester_dept}) )} {line.target_table || "-"} {line.step_order}차 {formatDate(line.request_created_at || line.created_at)} ))}
)} {/* 결재 처리 모달 */} 결재 처리 {selectedLine?.title}
요청자

{selectedLine?.requester_name || "-"}

결재 단계

{selectedLine?.step_order}차 결재