From 7dd2b9fc1856306652f311d19697d9880955bfab Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 22 Aug 2025 02:12:15 +0000 Subject: (최겸) 공지사항, 인포메이션 기능 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/additional-info/join-form.tsx | 12 +- components/information/information-button.tsx | 113 ++++++++---- components/notice/notice-client.tsx | 116 ++++++------ components/notice/notice-create-dialog.tsx | 2 +- components/notice/notice-edit-sheet.tsx | 2 +- components/vendor-info/pq-simple-dialog.tsx | 5 +- .../document-status-dialog.tsx | 203 ++++++++++++++++----- 7 files changed, 294 insertions(+), 159 deletions(-) (limited to 'components') diff --git a/components/additional-info/join-form.tsx b/components/additional-info/join-form.tsx index ca0c60d5..90effddb 100644 --- a/components/additional-info/join-form.tsx +++ b/components/additional-info/join-form.tsx @@ -87,7 +87,6 @@ import { InformationButton } from "@/components/information/information-button" // 보안 파일 다운로드 유틸리티 import import { - downloadFile, quickDownload, smartFileAction, getFileInfo, @@ -366,6 +365,7 @@ export function InfoForm() { const downloadUrl = `/api/vendors/attachments/download?id=${fileId}&vendorId=${Number(companyId)}`; // 보안 다운로드 유틸리티 사용 + const { downloadFile } = await import('@/lib/file-download') const result = await downloadFile(downloadUrl, fileName, { action: 'download', showToast: false, // 우리가 직접 토스트 관리 @@ -413,6 +413,8 @@ export function InfoForm() { const fileName = `vendor-${companyId}-files.zip`; // 보안 다운로드 유틸리티 사용 + + const { downloadFile } = await import('@/lib/file-download') const result = await downloadFile(downloadUrl, fileName, { action: 'download', showToast: false, // 우리가 직접 토스트 관리 @@ -708,7 +710,7 @@ export function InfoForm() { } // 보안 정보 가져오기 (선택적으로 사용자에게 표시) - const securityInfo = getSecurityInfo(); + // const securityInfo = getSecurityInfo(); // Render return ( @@ -746,9 +748,9 @@ export function InfoForm() { )} {/* 보안 정보 표시 (선택적) */} -
+ {/*

📁 허용 파일 크기: {securityInfo.maxFileSizeFormatted} | 남은 다운로드: {securityInfo.remainingDownloads}/분

-
+
*/} @@ -1860,7 +1862,7 @@ export function InfoForm() { domesticCredit: "not_submitted", }, basicContracts: registrationData.basicContracts || [], - documentFiles: { + documentFiles: registrationData.documentFiles || { businessRegistration: [], creditEvaluation: [], bankCopy: [], diff --git a/components/information/information-button.tsx b/components/information/information-button.tsx index 52079767..17f10502 100644 --- a/components/information/information-button.tsx +++ b/components/information/information-button.tsx @@ -12,14 +12,15 @@ import { DialogTrigger, } from "@/components/ui/dialog" import { Info, Download, Edit, Loader2 } from "lucide-react" -import { getCachedPageInformation, getCachedEditPermission } from "@/lib/information/service" -import { getCachedPageNotices } from "@/lib/notice/service" +import { getPageInformationDirect, getEditPermissionDirect } from "@/lib/information/service" +import { getPageNotices } from "@/lib/notice/service" import { UpdateInformationDialog } from "@/lib/information/table/update-information-dialog" import { NoticeViewDialog } from "@/components/notice/notice-view-dialog" -import type { PageInformation } from "@/db/schema/information" +import type { PageInformation, InformationAttachment } from "@/db/schema/information" import type { Notice } from "@/db/schema/notice" import { useSession } from "next-auth/react" import { formatDate } from "@/lib/utils" +// downloadFile은 동적으로 import interface InformationButtonProps { pagePath: string @@ -41,7 +42,7 @@ export function InformationButton({ }: InformationButtonProps) { const { data: session } = useSession() const [isOpen, setIsOpen] = useState(false) - const [information, setInformation] = useState(null) + const [information, setInformation] = useState<(PageInformation & { attachments: InformationAttachment[] }) | null>(null) const [notices, setNotices] = useState([]) const [hasEditPermission, setHasEditPermission] = useState(false) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) @@ -59,19 +60,35 @@ export function InformationButton({ // pagePath 정규화 (앞의 / 제거) const normalizedPath = pagePath.startsWith('/') ? pagePath.slice(1) : pagePath + console.log('🔍 Information Button - 데이터 로딩:', { + originalPath: pagePath, + normalizedPath: normalizedPath, + sessionUserId: session?.user?.id + }) + // 병렬로 데이터 조회 const [infoResult, noticesResult] = await Promise.all([ - getCachedPageInformation(normalizedPath), - getCachedPageNotices(normalizedPath) + getPageInformationDirect(normalizedPath), + getPageNotices(normalizedPath) ]) + console.log('📊 조회 결과:', { + infoResult: infoResult ? { + id: infoResult.id, + pagePath: infoResult.pagePath, + pageName: infoResult.pageName, + attachmentsCount: infoResult.attachments?.length || 0 + } : null, + noticesCount: noticesResult.length + }) + setInformation(infoResult) setNotices(noticesResult) setDataLoaded(true) // 권한 확인 if (session?.user?.id) { - const hasPermission = await getCachedEditPermission(normalizedPath, session.user.id) + const hasPermission = await getEditPermissionDirect(normalizedPath, session.user.id) setHasEditPermission(hasPermission) } } catch (error) { @@ -109,16 +126,22 @@ export function InformationButton({ } // 파일 다운로드 핸들러 - const handleDownload = () => { - if (information?.attachmentFilePath) { - // window.open 대신 link 요소 사용 - const link = document.createElement('a') - link.href = information.attachmentFilePath - link.target = '_blank' - link.rel = 'noopener noreferrer' - document.body.appendChild(link) - link.click() - document.body.removeChild(link) + const handleDownload = async (attachment: InformationAttachment) => { + try { + // 동적으로 downloadFile 함수 import + const { downloadFile } = await import('@/lib/file-download') + + await downloadFile( + attachment.filePath, + attachment.fileName, + { + action: 'download', + showToast: true, + showSuccessToast: true + } + ) + } catch (error) { + console.error('파일 다운로드 실패:', error) } } @@ -145,7 +168,7 @@ export function InformationButton({
- {information?.pageName} +
@@ -231,29 +254,41 @@ export function InformationButton({ {/* 첨부파일 */}
-

첨부파일

+
+

첨부파일

+ {information?.attachments && information.attachments.length > 0 && ( + {information.attachments.length}개 + )} +
- {information?.attachmentFileName ? ( -
-
-
- {information.attachmentFileName} -
- {information.attachmentFileSize && ( -
- {information.attachmentFileSize} + {information?.attachments && information.attachments.length > 0 ? ( +
+ {information.attachments.map((attachment) => ( +
+
+
+ {attachment.fileName} +
+ {attachment.fileSize && ( +
+ {attachment.fileSize} +
+ )}
- )} -
- + +
+ ))}
) : (
diff --git a/components/notice/notice-client.tsx b/components/notice/notice-client.tsx index e5c05d84..1eb6d75f 100644 --- a/components/notice/notice-client.tsx +++ b/components/notice/notice-client.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect, useTransition } from "react" +import React, { useState, useEffect, useTransition } from "react" import { useParams } from "next/navigation" import { useTranslation } from "@/i18n/client" import { Button } from "@/components/ui/button" @@ -91,28 +91,12 @@ export function NoticeClient({ initialData = [], currentUserId }: NoticeClientPr const fetchNotices = async () => { try { setLoading(true) - const search = searchQuery || undefined startTransition(async () => { - const result = await getNoticeLists({ - page: 1, - perPage: 50, - search: search, - sort: [{ id: sortField, desc: sortDirection === "desc" }], - flags: [], - filters: [], - joinOperator: "and", - pagePath: "", - title: "", - content: "", - authorId: null, - isActive: null, - from: "", - to: "", - }) + const result = await getNoticeLists() if (result?.data) { - setNotices(result.data) + setNotices(result.data as NoticeWithAuthor[]) } else { toast.error("공지사항 목록을 가져오는데 실패했습니다.") } @@ -125,37 +109,12 @@ export function NoticeClient({ initialData = [], currentUserId }: NoticeClientPr } } - // 검색 핸들러 + // 검색 핸들러 (클라이언트 사이드에서 필터링하므로 별도 동작 불필요) const handleSearch = () => { - fetchNotices() + // 클라이언트 사이드 필터링이므로 별도 서버 요청 불필요 } - // 정렬 함수 - const sortNotices = (notices: NoticeWithAuthor[]) => { - return [...notices].sort((a, b) => { - let aValue: string | Date - let bValue: string | Date - if (sortField === "title") { - aValue = a.title - bValue = b.title - } else if (sortField === "pagePath") { - aValue = a.pagePath - bValue = b.pagePath - } else { - aValue = new Date(a.createdAt) - bValue = new Date(b.createdAt) - } - - if (aValue < bValue) { - return sortDirection === "asc" ? -1 : 1 - } - if (aValue > bValue) { - return sortDirection === "asc" ? 1 : -1 - } - return 0 - }) - } // 정렬 핸들러 const handleSort = (field: SortField) => { @@ -184,8 +143,49 @@ export function NoticeClient({ initialData = [], currentUserId }: NoticeClientPr } } - // 정렬된 공지사항 목록 - const sortedNotices = sortNotices(notices) + // 클라이언트 사이드 필터링 및 정렬 + const filteredAndSortedNotices = React.useMemo(() => { + let filtered = notices + + // 검색 필터 + if (searchQuery.trim()) { + const query = searchQuery.toLowerCase() + filtered = filtered.filter(notice => + notice.title.toLowerCase().includes(query) || + notice.pagePath.toLowerCase().includes(query) || + notice.content.toLowerCase().includes(query) + ) + } + + // 정렬 + filtered = filtered.sort((a, b) => { + let aValue: string | Date + let bValue: string | Date + + switch (sortField) { + case "title": + aValue = a.title + bValue = b.title + 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 + }, [notices, searchQuery, sortField, sortDirection]) // 페이지 경로 옵션 로딩 const loadPagePathOptions = async () => { @@ -227,13 +227,7 @@ export function NoticeClient({ initialData = [], currentUserId }: NoticeClientPr loadPagePathOptions() }, []) - useEffect(() => { - if (searchQuery !== "") { - fetchNotices() - } else if (initialData.length > 0) { - setNotices(initialData) - } - }, [searchQuery]) + // 검색은 클라이언트 사이드에서 실시간으로 처리됨 return (
@@ -243,16 +237,12 @@ export function NoticeClient({ initialData = [], currentUserId }: NoticeClientPr
setSearchQuery(e.target.value)} className="pl-10" - onKeyPress={(e) => e.key === "Enter" && handleSearch()} />
- - )} */} + )}
); @@ -262,7 +363,11 @@ export function DocumentStatusDialog({
{isCompleted && ( - -- cgit v1.2.3