"use client" import React, { useState, useEffect, useTransition } from "react" import { useParams } from "next/navigation" import { useTranslation } from "@/i18n/client" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Badge } from "@/components/ui/badge" import { Search, Edit, FileText, ChevronUp, ChevronDown, Plus, Eye, Trash2 } from "lucide-react" import { toast } from "sonner" import { formatDate } from "@/lib/utils" import { getNoticeLists, deleteNotice, getPagePathList } from "@/lib/notice/service" import type { Notice } from "@/db/schema/notice" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog" import { UpdateNoticeSheet } from "./notice-edit-sheet" import { NoticeCreateDialog } from "./notice-create-dialog" import { NoticeViewDialog } from "./notice-view-dialog" type NoticeWithAuthor = Notice & { authorName: string | null authorEmail: string | null isPopup?: boolean } interface NoticeClientProps { initialData?: NoticeWithAuthor[] currentUserId?: number } type SortField = "title" | "pagePath" | "createdAt" type SortDirection = "asc" | "desc" export function NoticeClient({ initialData = [], currentUserId }: NoticeClientProps) { 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 [notices, setNotices] = useState(initialData) const [loading, setLoading] = useState(false) const [searchQuery, setSearchQuery] = useState("") const [sortField, setSortField] = useState("createdAt") const [sortDirection, setSortDirection] = useState("desc") const [, startTransition] = useTransition() const [isEditSheetOpen, setIsEditSheetOpen] = useState(false) const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) const [isViewDialogOpen, setIsViewDialogOpen] = useState(false) const [selectedNotice, setSelectedNotice] = useState(null) const [pagePathOptions, setPagePathOptions] = useState>([]) // 공지사항 목록 조회 const fetchNotices = async () => { try { setLoading(true) startTransition(async () => { const result = await getNoticeLists() if (result?.data) { setNotices(result.data as NoticeWithAuthor[]) } else { toast.error("공지사항 목록을 가져오는데 실패했습니다.") } setLoading(false) }) } catch (error) { console.error("Error fetching notices:", error) toast.error("공지사항 목록을 가져오는데 실패했습니다.") setLoading(false) } } // 검색 핸들러 (클라이언트 사이드에서 필터링하므로 별도 동작 불필요) const handleSearch = () => { // 클라이언트 사이드 필터링이므로 별도 서버 요청 불필요 } // 정렬 핸들러 const handleSort = (field: SortField) => { if (sortField === field) { setSortDirection(sortDirection === "asc" ? "desc" : "asc") } else { setSortField(field) setSortDirection("asc") } } // 삭제 핸들러 const handleDelete = async (notice: NoticeWithAuthor) => { try { const result = await deleteNotice(notice.id) if (result.success) { toast.success(result.message) setNotices(notices.filter(n => n.id !== notice.id)) } else { toast.error(result.message) } } catch (error) { console.error("Error deleting notice:", error) toast.error("공지사항 삭제에 실패했습니다.") } } // 클라이언트 사이드 필터링 및 정렬 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) || (notice.authorName && notice.authorName.toLowerCase().includes(query)) || (notice.isPopup !== undefined && notice.isPopup ? '팝업' : '일반').toLowerCase().includes(query) || (notice.dontShowDuration && notice.dontShowDuration.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 () => { try { const paths = await getPagePathList() const options = paths.map(path => ({ value: path.pagePath, label: path.pageName // i18n 키를 그대로 저장 (화면에서 번역) })) setPagePathOptions(options) } catch (error) { console.error("페이지 경로 로딩 실패:", error) } } // View 다이얼로그 열기 const handleViewNotice = (notice: NoticeWithAuthor) => { setSelectedNotice(notice) setIsViewDialogOpen(true) } // Edit Sheet 열기 const handleEditNotice = (notice: NoticeWithAuthor) => { setSelectedNotice(notice) setIsEditSheetOpen(true) } // Create Dialog 열기 const handleCreateNotice = () => { setIsCreateDialogOpen(true) } useEffect(() => { if (initialData.length > 0) { setNotices(initialData) } else { fetchNotices() } loadPagePathOptions() }, []) // 검색은 클라이언트 사이드에서 실시간으로 처리됨 return (
{/* 검색 및 추가 버튼 */}
setSearchQuery(e.target.value)} className="pl-10" />
{/* 공지사항 테이블 */}
팝업 팝업게시기간 {/* 다시보지않기 */} 작성자 상태 작업 {loading ? ( 로딩 중... ) : filteredAndSortedNotices.length === 0 ? ( {searchQuery.trim() ? "검색 결과가 없습니다." : "공지사항이 없습니다."} ) : ( filteredAndSortedNotices.map((notice) => (
{notice.title}
{notice.pagePath}
{(() => { const pageOption = pagePathOptions.find(opt => opt.value === notice.pagePath) return pageOption ? safeTranslate(pageOption.label) : notice.pagePath })()}
{notice.isPopup ? "팝업" : "일반"}
{notice.startAt && notice.endAt ? (
시작: {formatDate(notice.startAt, "KR")}
종료: {formatDate(notice.endAt, "KR")}
) : ( - )}
{/*
{notice.dontShowDuration ? ( {notice.dontShowDuration === 'day' ? '하루' : '영구'} ) : ( - )}
*/}
{notice.authorName || "알 수 없음"} {notice.authorEmail && ( {notice.authorEmail} )}
{notice.isActive ? "활성" : "비활성"} {formatDate(notice.createdAt, "KR")}
{/* View 버튼 - 다이얼로그 방식 */} {/* Edit 버튼 - 다이얼로그 방식 */} {/* 기존 페이지 방식 (비교용) */} 공지사항 삭제 이 공지사항을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. 취소 handleDelete(notice)} className="bg-red-600 hover:bg-red-700" > 삭제
)) )}
{/* 다이얼로그들과 시트 - 테이블 밖에서 단일 렌더링 */}
) }