From 02b1cf005cf3e1df64183d20ba42930eb2767a9f Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 21 Aug 2025 06:57:36 +0000
Subject: (대표님, 최겸) 설계메뉴추가, 작업사항 업데이트 설계메뉴 - 문서관리
 설계메뉴 - 벤더 데이터 gtc 메뉴 업데이트 정보시스템 - 메뉴리스트 및 정보
 업데이트 파일 라우트 업데이트 엑셀임포트 개선 기본계약 개선 벤더 가입과정
 변경 및 개선 벤더 기본정보 - pq 돌체 오류 수정 및 개선 벤더 로그인 과정
 이메일 오류 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/information/information-client.tsx | 212 ++++++++++++++++++--------
 1 file changed, 145 insertions(+), 67 deletions(-)

(limited to 'components/information/information-client.tsx')

diff --git a/components/information/information-client.tsx b/components/information/information-client.tsx
index d863175f..50bc6a39 100644
--- a/components/information/information-client.tsx
+++ b/components/information/information-client.tsx
@@ -1,7 +1,8 @@
 "use client"
 
-import { useState, useEffect, useTransition } from "react"
-import { useRouter } from "next/navigation"
+import React, { useState, useEffect, useTransition } from "react"
+import { useRouter, useParams } from "next/navigation"
+import { useTranslation } from "@/i18n/client"
 import { Button } from "@/components/ui/button"
 import { Input } from "@/components/ui/input"
 import {
@@ -19,11 +20,13 @@ import {
   FileText, 
   ChevronUp, 
   ChevronDown, 
-  Download
+  Download,
+  Database,
+  RefreshCw
 } from "lucide-react"
 import { toast } from "sonner"
 import { formatDate } from "@/lib/utils"
-import { getInformationLists } from "@/lib/information/service"
+import { getInformationLists, syncInformationFromMenuAssignments, getInformationDetail } from "@/lib/information/service"
 import type { PageInformation } from "@/db/schema/information"
 import { UpdateInformationDialog } from "@/lib/information/table/update-information-dialog"
 
@@ -36,6 +39,25 @@ type SortDirection = "asc" | "desc"
 
 export function InformationClient({ initialData = [] }: InformationClientProps) {
   const router = useRouter()
+  const params = useParams()
+  const lng = (params?.lng as string) || 'ko'
+  const { t } = useTranslation(lng, 'menu')
+  
+  // 안전한 번역 함수 (키가 없을 때 원본 키 반환)
+  const safeTranslate = (key: string): string => {
+    try {
+      const translated = t(key)
+      // 번역 키가 그대로 반환되는 경우 원본 키 사용
+      if (translated === key) {
+        return key
+      }
+      return translated || key
+    } catch (error) {
+      console.warn(`Translation failed for key: ${key}`, error)
+      return key
+    }
+  }
+  
   const [informations, setInformations] = useState<PageInformation[]>(initialData)
   const [loading, setLoading] = useState(false)
   const [searchQuery, setSearchQuery] = useState("")
@@ -43,28 +65,16 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
   const [sortDirection, setSortDirection] = useState<SortDirection>("desc")
   const [editingInformation, setEditingInformation] = useState<PageInformation | null>(null)
   const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
+  const [isSyncing, setIsSyncing] = useState(false)
   const [, startTransition] = useTransition()
 
   // 정보 목록 조회
   const fetchInformations = async () => {
     try {
       setLoading(true)
-      const search = searchQuery || undefined
 
       startTransition(async () => {
-        const result = await getInformationLists({
-          page: 1,
-          perPage: 50,
-          search: search,
-          sort: [{ id: sortField, desc: sortDirection === "desc" }],
-          flags: [],
-          filters: [],
-          joinOperator: "and",
-          pagePath: "",
-          pageName: "",
-          informationContent: "",
-          isActive: null,
-        })
+        const result = await getInformationLists()
 
         if (result?.data) {
           setInformations(result.data)
@@ -80,9 +90,51 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
     }
   }
 
-  // 검색 핸들러
+  // 클라이언트 사이드 필터링 및 정렬
+  const filteredAndSortedInformations = React.useMemo(() => {
+    let filtered = informations
+
+    // 검색 필터 (페이지명으로 검색)
+    if (searchQuery) {
+      filtered = filtered.filter(info => 
+        safeTranslate(info.pageName).toLowerCase().includes(searchQuery.toLowerCase()) ||
+        info.pagePath.toLowerCase().includes(searchQuery.toLowerCase())
+      )
+    }
+
+    // 정렬
+    filtered = filtered.sort((a, b) => {
+      let aValue: string | Date
+      let bValue: string | Date
+
+      switch (sortField) {
+        case "pageName":
+          aValue = safeTranslate(a.pageName)
+          bValue = safeTranslate(b.pageName)
+          break
+        case "pagePath":
+          aValue = a.pagePath
+          bValue = b.pagePath
+          break
+        case "createdAt":
+          aValue = new Date(a.createdAt)
+          bValue = new Date(b.createdAt)
+          break
+        default:
+          return 0
+      }
+
+      if (aValue < bValue) return sortDirection === "asc" ? -1 : 1
+      if (aValue > bValue) return sortDirection === "asc" ? 1 : -1
+      return 0
+    })
+
+    return filtered
+  }, [informations, searchQuery, sortField, sortDirection, safeTranslate])
+
+  // 검색 핸들러 (클라이언트 사이드에서 필터링하므로 별도 동작 불필요)
   const handleSearch = () => {
-    fetchInformations()
+    // 클라이언트 사이드 필터링이므로 별도 서버 요청 불필요
   }
 
   // 정렬 함수
@@ -92,8 +144,8 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
       let bValue: string | Date
 
       if (sortField === "pageName") {
-        aValue = a.pageName
-        bValue = b.pageName
+        aValue = safeTranslate(a.pageName)
+        bValue = safeTranslate(b.pageName)
       } else if (sortField === "pagePath") {
         aValue = a.pagePath
         bValue = b.pagePath
@@ -123,9 +175,23 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
   }
 
   // 편집 핸들러
-  const handleEdit = (information: PageInformation) => {
-    setEditingInformation(information)
-    setIsEditDialogOpen(true)
+  const handleEdit = async (information: PageInformation) => {
+    try {
+      // 첨부파일 정보까지 포함해서 가져오기
+      const detailData = await getInformationDetail(information.id)
+      if (detailData) {
+        setEditingInformation(detailData)
+      } else {
+        // 실패시 기본 정보라도 사용
+        setEditingInformation(information)
+      }
+      setIsEditDialogOpen(true)
+    } catch (error) {
+      console.error("Failed to load information detail:", error)
+      // 에러시 기본 정보라도 사용
+      setEditingInformation(information)
+      setIsEditDialogOpen(true)
+    }
   }
 
   // 편집 완료 핸들러
@@ -136,20 +202,48 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
     fetchInformations()
   }
 
-  // 다운로드 핸들러
-  const handleDownload = (information: PageInformation) => {
-    if (information.attachmentFilePath && information.attachmentFileName) {
-      const link = document.createElement('a')
-      link.href = information.attachmentFilePath
-      link.download = information.attachmentFileName
-      document.body.appendChild(link)
-      link.click()
-      document.body.removeChild(link)
+  // 다운로드 핸들러 (다중 첨부파일은 dialog에서 처리)
+  const handleDownload = async (information: PageInformation) => {
+    try {
+      // 첨부파일 정보까지 포함해서 가져오기
+      const detailData = await getInformationDetail(information.id)
+      if (detailData) {
+        setEditingInformation(detailData)
+      } else {
+        // 실패시 기본 정보라도 사용
+        setEditingInformation(information)
+      }
+      setIsEditDialogOpen(true)
+    } catch (error) {
+      console.error("Failed to load information detail:", error)
+      // 에러시 기본 정보라도 사용
+      setEditingInformation(information)
+      setIsEditDialogOpen(true)
+    }
+  }
+
+  // 메뉴 동기화 핸들러
+  const handleSync = async () => {
+    setIsSyncing(true)
+    try {
+      const result = await syncInformationFromMenuAssignments()
+      
+      if (result.success) {
+        toast.success(result.message)
+        // 동기화 후 데이터 새로고침
+        fetchInformations()
+      } else {
+        toast.error(result.message)
+      }
+    } catch (error) {
+      console.error("동기화 오류:", error)
+      toast.error("메뉴 동기화 중 오류가 발생했습니다.")
+    } finally {
+      setIsSyncing(false)
     }
   }
 
-  // 정렬된 정보 목록
-  const sortedInformations = sortInformations(informations)
+
 
   useEffect(() => {
     if (initialData.length > 0) {
@@ -159,13 +253,7 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
     }
   }, [])
 
-  useEffect(() => {
-    if (searchQuery !== "") {
-      fetchInformations()
-    } else if (initialData.length > 0) {
-      setInformations(initialData)
-    }
-  }, [searchQuery])
+  // searchQuery 변경 시 클라이언트 사이드 필터링으로 처리되므로 useEffect 제거
 
   return (
     <div className="space-y-6">
@@ -179,11 +267,19 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
               value={searchQuery}
               onChange={(e) => setSearchQuery(e.target.value)}
               className="pl-10"
-              onKeyPress={(e) => e.key === "Enter" && handleSearch()}
+              onKeyPress={(e) => e.key === "Enter"}
             />
           </div>
-          <Button onClick={handleSearch} variant="outline">
-            검색
+
+          <Button
+            variant="outline"
+            onClick={handleSync}
+            disabled={isSyncing}
+            className="gap-2"
+          >
+            <Database className={`h-4 w-4 ${isSyncing ? 'animate-pulse' : ''}`} />
+            <RefreshCw className={`h-4 w-4 ${isSyncing ? 'animate-spin' : ''}`} />
+            {isSyncing ? '동기화 중...' : '메뉴에서 동기화'}
           </Button>
           <Button
             variant="outline"
@@ -230,7 +326,6 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
                 </button>
               </TableHead>
               <TableHead>정보 내용</TableHead>
-              <TableHead>첨부파일</TableHead>
               <TableHead>상태</TableHead>
               <TableHead>
                 <button
@@ -257,20 +352,20 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
                   로딩 중...
                 </TableCell>
               </TableRow>
-            ) : informations.length === 0 ? (
+            ) : filteredAndSortedInformations.length === 0 ? (
               <TableRow>
                 <TableCell colSpan={7} className="text-center py-8 text-gray-500">
                   정보가 없습니다.
                 </TableCell>
               </TableRow>
             ) : (
-              sortedInformations.map((information) => (
+              filteredAndSortedInformations.map((information) => (
                 <TableRow key={information.id}>
                   <TableCell className="font-medium">
                     <div className="flex items-center gap-2">
                       <FileText className="h-4 w-4" />
                       <span className="max-w-[200px] truncate">
-                        {information.pageName}
+                        {(information as any).translatedPageName || safeTranslate(information.pageName)}
                       </span>
                     </div>
                   </TableCell>
@@ -287,23 +382,6 @@ export function InformationClient({ initialData = [] }: InformationClientProps)
                       }}
                     />
                   </TableCell>
-                  <TableCell>
-                    {information.attachmentFileName ? (
-                      <Button
-                        variant="outline"
-                        size="sm"
-                        onClick={() => handleDownload(information)}
-                        className="flex items-center gap-1"
-                      >
-                        <Download className="h-3 w-3" />
-                        <span className="max-w-[100px] truncate">
-                          {information.attachmentFileName}
-                        </span>
-                      </Button>
-                    ) : (
-                      <span className="text-gray-400">없음</span>
-                    )}
-                  </TableCell>
                   <TableCell>
                     <Badge variant={information.isActive ? "default" : "secondary"}>
                       {information.isActive ? "활성" : "비활성"}
-- 
cgit v1.2.3

