From c64264ac25716256c0c7c4f56e6f459747f4ef11 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Thu, 23 Oct 2025 11:50:00 +0900 Subject: (김준회) 결재 이력조회 처리 및 취소는 상세로 이동처리 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/knox/approval/ApprovalDetail.tsx | 156 +++++++++++++++- components/knox/approval/ApprovalList.tsx | 263 +++++++++------------------ components/knox/approval/ApprovalManager.tsx | 47 +---- 3 files changed, 238 insertions(+), 228 deletions(-) (limited to 'components/knox') diff --git a/components/knox/approval/ApprovalDetail.tsx b/components/knox/approval/ApprovalDetail.tsx index 1be58d21..e3197522 100644 --- a/components/knox/approval/ApprovalDetail.tsx +++ b/components/knox/approval/ApprovalDetail.tsx @@ -7,11 +7,13 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; +import { Textarea } from '@/components/ui/textarea'; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; import { toast } from 'sonner'; -import { Loader2, Search, FileText, Clock, User, AlertCircle } from 'lucide-react'; +import { Loader2, Search, FileText, Clock, User, AlertCircle, XCircle } from 'lucide-react'; // API 함수 및 타입 -import { getApprovalDetail, getApprovalContent } from '@/lib/knox-api/approval/approval'; +import { getApprovalDetail, getApprovalContent, cancelApproval } from '@/lib/knox-api/approval/approval'; import type { ApprovalDetailResponse, ApprovalContentResponse, ApprovalLine } from '@/lib/knox-api/approval/approval'; import { formatDate } from '@/lib/utils'; @@ -70,6 +72,8 @@ export default function ApprovalDetail({ const [approvalData, setApprovalData] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const [isCancelling, setIsCancelling] = useState(false); + const [cancelOpinion, setCancelOpinion] = useState(''); const fetchApprovalDetail = async (id: string) => { if (!id.trim()) { @@ -142,6 +146,49 @@ export default function ApprovalDetail({ } }; + const canCancelApproval = (status: string) => { + // 진행중(1), 보류(0) 상태에서만 취소 가능 + return ['0', '1'].includes(status); + }; + + const handleCancelApproval = async () => { + if (!approvalData) return; + + if (!cancelOpinion.trim()) { + toast.error('상신취소 의견을 입력해주세요.'); + return; + } + + setIsCancelling(true); + + try { + const response = await cancelApproval(approvalData.detail.apInfId, cancelOpinion); + + if (response.result === 'success') { + toast.success('결재가 성공적으로 취소되었습니다.'); + + // 상태 업데이트 + setApprovalData({ + ...approvalData, + detail: { + ...approvalData.detail, + status: '4' // 상신취소 + } + }); + + // 의견 초기화 + setCancelOpinion(''); + } else { + toast.error('결재 취소에 실패했습니다.'); + } + } catch (err) { + console.error('결재 취소 오류:', err); + toast.error('결재 취소 중 오류가 발생했습니다.'); + } finally { + setIsCancelling(false); + } + }; + // 첨부파일 다운로드 헬퍼 const handleDownload = async (attachment: ApprovalAttachment) => { try { @@ -301,6 +348,111 @@ export default function ApprovalDetail({ + {/* 결재 취소 섹션 */} +
+

+ + 결재 취소 +

+ +
+
+ + {canCancelApproval(approvalData.detail.status) ? '취소 가능' : '취소 불가'} + +
+

+ {canCancelApproval(approvalData.detail.status) + ? '이 결재는 취소할 수 있습니다.' + : '현재 상태에서는 취소할 수 없습니다.'} +

+
+ + {/* 취소 의견 및 버튼 */} + {canCancelApproval(approvalData.detail.status) && ( +
+
+ +