From 795b4915069c44f500a91638e16ded67b9e16618 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 1 Jul 2025 11:46:33 +0000 Subject: (최겸) 정보시스템 공지사항 개발 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/notice/notice-client.tsx | 438 ++++++++++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 components/notice/notice-client.tsx (limited to 'components/notice/notice-client.tsx') diff --git a/components/notice/notice-client.tsx b/components/notice/notice-client.tsx new file mode 100644 index 00000000..fab0d758 --- /dev/null +++ b/components/notice/notice-client.tsx @@ -0,0 +1,438 @@ +"use client" + +import { useState, useEffect, useTransition } from "react" +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 +} + +interface NoticeClientProps { + initialData?: NoticeWithAuthor[] + currentUserId?: number +} + +type SortField = "title" | "pagePath" | "createdAt" +type SortDirection = "asc" | "desc" + +export function NoticeClient({ initialData = [], currentUserId }: NoticeClientProps) { + 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) + 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: "", + }) + + if (result?.data) { + setNotices(result.data) + } else { + toast.error("공지사항 목록을 가져오는데 실패했습니다.") + } + setLoading(false) + }) + } catch (error) { + console.error("Error fetching notices:", error) + toast.error("공지사항 목록을 가져오는데 실패했습니다.") + setLoading(false) + } + } + + // 검색 핸들러 + 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) => { + 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 sortedNotices = sortNotices(notices) + + // 페이지 경로 옵션 로딩 + const loadPagePathOptions = async () => { + try { + const paths = await getPagePathList() + const options = paths.map(path => ({ + value: path.pagePath, + label: `${path.pageName} (${path.pagePath})` + })) + 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() + }, []) + + useEffect(() => { + if (searchQuery !== "") { + fetchNotices() + } else if (initialData.length > 0) { + setNotices(initialData) + } + }, [searchQuery]) + + return ( +
+ {/* 검색 및 추가 버튼 */} +
+
+
+ + setSearchQuery(e.target.value)} + className="pl-10" + onKeyPress={(e) => e.key === "Enter" && handleSearch()} + /> +
+ + +
+ +
+ + {/* 공지사항 테이블 */} +
+ + + + + + + + + + 작성자 + 상태 + + + + 작업 + + + + {loading ? ( + + + 로딩 중... + + + ) : notices.length === 0 ? ( + + + 공지사항이 없습니다. + + + ) : ( + sortedNotices.map((notice) => ( + + +
+ + + {notice.title} + +
+
+ + + {notice.pagePath} + + + +
+ + {notice.authorName || "알 수 없음"} + + {notice.authorEmail && ( + + {notice.authorEmail} + + )} +
+
+ + + {notice.isActive ? "활성" : "비활성"} + + + + {formatDate(notice.createdAt)} + + +
+ {/* View 버튼 - 다이얼로그 방식 */} + + + {/* Edit 버튼 - 다이얼로그 방식 */} + + + {/* 기존 페이지 방식 (비교용) + + + */} + + + + + + + + 공지사항 삭제 + + 이 공지사항을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. + + + + 취소 + handleDelete(notice)} + className="bg-red-600 hover:bg-red-700" + > + 삭제 + + + + +
+
+
+ )) + )} +
+
+
+ + {/* 다이얼로그들과 시트 - 테이블 밖에서 단일 렌더링 */} + + + + + +
+ ) +} \ No newline at end of file -- cgit v1.2.3