From 02b1cf005cf3e1df64183d20ba42930eb2767a9f Mon Sep 17 00:00:00 2001 From: dujinkim 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') 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(initialData) const [loading, setLoading] = useState(false) const [searchQuery, setSearchQuery] = useState("") @@ -43,28 +65,16 @@ export function InformationClient({ initialData = [] }: InformationClientProps) const [sortDirection, setSortDirection] = useState("desc") const [editingInformation, setEditingInformation] = useState(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 (
@@ -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"} />
- - ) : ( - 없음 - )} - {information.isActive ? "활성" : "비활성"} -- cgit v1.2.3