From 26bd5a0af8f69fd693c16d2eacb35cf138a360d1 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Wed, 10 Sep 2025 08:59:19 +0000
Subject: (김준회) 결재 이력조회 기능 추가 및 로그 테이블 확장, 테스트모듈 작성
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/knox/approval/ApprovalList.tsx | 154 +++++++++++++++++++++++-------
 1 file changed, 117 insertions(+), 37 deletions(-)

(limited to 'components/knox/approval/ApprovalList.tsx')

diff --git a/components/knox/approval/ApprovalList.tsx b/components/knox/approval/ApprovalList.tsx
index 25b9618d..ed26a375 100644
--- a/components/knox/approval/ApprovalList.tsx
+++ b/components/knox/approval/ApprovalList.tsx
@@ -9,7 +9,7 @@ import { toast } from 'sonner';
 import { Loader2, List, Eye, RefreshCw, AlertCircle } from 'lucide-react';
 
 // API 함수 및 타입
-import { getSubmissionList, getApprovalHistory } from '@/lib/knox-api/approval/approval';
+import { getSubmissionList, getApprovalHistory, getApprovalLogsAction, syncApprovalStatusAction } from '@/lib/knox-api/approval/approval';
 import type { SubmissionListResponse, ApprovalHistoryResponse } from '@/lib/knox-api/approval/approval';
 import { formatDate } from '@/lib/utils';
 
@@ -31,8 +31,13 @@ const getStatusText = (status: string) => {
 };
 
 interface ApprovalListProps {
-  type?: 'submission' | 'history';
+  type?: 'submission' | 'history' | 'database';
   onItemClick?: (apInfId: string) => void;
+  userParams?: {
+    epId?: string;
+    userId?: string;
+    emailAddress?: string;
+  };
 }
 
 type ListItem = {
@@ -48,31 +53,51 @@ type ListItem = {
 };
 
 export default function ApprovalList({ 
-  type = 'submission',
-  onItemClick
+  type = 'database',
+  onItemClick,
+  userParams
 }: ApprovalListProps) {
   const [listData, setListData] = useState<ListItem[]>([]);
   const [isLoading, setIsLoading] = useState(false);
   const [error, setError] = useState<string | null>(null);
+  const [isSyncing, setIsSyncing] = useState(false);
 
   const fetchData = async () => {
     setIsLoading(true);
     setError(null);
 
     try {
-      let response: SubmissionListResponse | ApprovalHistoryResponse;
-      
-      if (type === 'submission') {
-        response = await getSubmissionList();
+      if (type === 'database') {
+        // 새로운 데이터베이스 조회 방식
+        const response = await getApprovalLogsAction();
+        
+        if (response.success) {
+          setListData(response.data as unknown as ListItem[]);
+        } else {
+          setError(response.message);
+          toast.error(response.message);
+        }
       } else {
-        response = await getApprovalHistory();
-      }
+        // 기존 Knox API 방식
+        let response: SubmissionListResponse | ApprovalHistoryResponse;
+        
+        if (type === 'submission') {
+          if (!userParams || (!userParams.epId && !userParams.userId && !userParams.emailAddress)) {
+            setError('사용자 정보가 필요합니다. (epId, userId, 또는 emailAddress)');
+            toast.error('사용자 정보가 필요합니다.');
+            return;
+          }
+          response = await getSubmissionList(userParams);
+        } else {
+          response = await getApprovalHistory();
+        }
 
-      if (response.result === 'success') {
-        setListData(response.data as unknown as ListItem[]);
-      } else {
-        setError('목록을 가져오는데 실패했습니다.');
-        toast.error('목록을 가져오는데 실패했습니다.');
+        if (response.result === 'success') {
+          setListData(response.data as unknown as ListItem[]);
+        } else {
+          setError('목록을 가져오는데 실패했습니다.');
+          toast.error('목록을 가져오는데 실패했습니다.');
+        }
       }
     } catch (err) {
       console.error('목록 조회 오류:', err);
@@ -135,6 +160,32 @@ export default function ApprovalList({
     onItemClick?.(apInfId);
   };
 
+  // 결재 상황 동기화 함수
+  const handleSync = async () => {
+    if (type !== 'database') {
+      toast.error('데이터베이스 모드에서만 동기화가 가능합니다.');
+      return;
+    }
+
+    setIsSyncing(true);
+    try {
+      const result = await syncApprovalStatusAction();
+      
+      if (result.success) {
+        toast.success(result.message);
+        // 동기화 후 데이터 새로고침
+        await fetchData();
+      } else {
+        toast.error(result.message);
+      }
+    } catch (error) {
+      console.error('동기화 오류:', error);
+      toast.error('동기화 중 오류가 발생했습니다.');
+    } finally {
+      setIsSyncing(false);
+    }
+  };
+
   // 컴포넌트 마운트 시 데이터 로드
   useEffect(() => {
     fetchData();
@@ -145,40 +196,69 @@ export default function ApprovalList({
       <CardHeader>
         <CardTitle className="flex items-center gap-2">
           <List className="w-5 h-5" />
-          {type === 'submission' ? '상신함' : '결재 이력'}
+          {type === 'database' 
+            ? '결재 로그 (데이터베이스)' 
+            : type === 'submission' 
+              ? '상신함' 
+              : '결재 이력'
+          }
         </CardTitle>
         <CardDescription>
-          {type === 'submission' 
-            ? '상신한 결재 목록을 확인합니다.' 
-            : '결재 처리 이력을 확인합니다.'
+          {type === 'database'
+            ? '데이터베이스에 저장된 결재 로그를 확인합니다.'
+            : type === 'submission' 
+              ? '상신한 결재 목록을 확인합니다.' 
+              : '결재 처리 이력을 확인합니다.'
           }
         </CardDescription>
       </CardHeader>
 
       <CardContent className="space-y-4">
-        {/* 새로고침 버튼 */}
+        {/* 제어 버튼들 */}
         <div className="flex justify-between items-center">
           <div className="text-sm text-gray-500">
             총 {listData.length}건
           </div>
-          <Button
-            onClick={fetchData}
-            disabled={isLoading}
-            variant="outline"
-            size="sm"
-          >
-            {isLoading ? (
-              <>
-                <Loader2 className="w-4 h-4 mr-2 animate-spin" />
-                조회 중...
-              </>
-            ) : (
-              <>
-                <RefreshCw className="w-4 h-4 mr-2" />
-                새로고침
-              </>
+          <div className="flex gap-2">
+            {type === 'database' && (
+              <Button
+                onClick={handleSync}
+                disabled={isSyncing || isLoading}
+                variant="secondary"
+                size="sm"
+              >
+                {isSyncing ? (
+                  <>
+                    <Loader2 className="w-4 h-4 mr-2 animate-spin" />
+                    동기화 중...
+                  </>
+                ) : (
+                  <>
+                    <RefreshCw className="w-4 h-4 mr-2" />
+                    상태 동기화
+                  </>
+                )}
+              </Button>
             )}
-          </Button>
+            <Button
+              onClick={fetchData}
+              disabled={isLoading || isSyncing}
+              variant="outline"
+              size="sm"
+            >
+              {isLoading ? (
+                <>
+                  <Loader2 className="w-4 h-4 mr-2 animate-spin" />
+                  조회 중...
+                </>
+              ) : (
+                <>
+                  <RefreshCw className="w-4 h-4 mr-2" />
+                  새로고침
+                </>
+              )}
+            </Button>
+          </div>
         </div>
 
         {/* 에러 메시지 */}
-- 
cgit v1.2.3

