"use client" import React, { useState, useEffect, useTransition, useCallback } from "react" // usePathname ์ œ๊ฑฐ import { useTranslation } from "@/i18n/client" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Badge } from "@/components/ui/badge" import { Separator } from "@/components/ui/separator" import { ScrollArea } from "@/components/ui/scroll-area" import { AlertCircle, Calendar, Clock, User } from "lucide-react" import { toast } from "sonner" import { getPageNoticesForModal } from "@/lib/notice/service" import { setNoticeDontShow, isNoticeDontShowValid, } from "@/lib/notice/storage-utils" import type { Notice } from "@/db/schema/notice" import { formatDate } from "@/lib/utils" type NoticeWithAuthor = Notice & { authorName: string | null authorEmail: string | null } interface NoticeModalState { notices: NoticeWithAuthor[] currentIndex: number isOpen: boolean dontShowToday: boolean dontShowSettings: Record } interface NoticeModalManagerProps { pagePath: string // ๐Ÿ’ก ํ•„์ˆ˜ prop์œผ๋กœ ๋ณ€๊ฒฝ autoOpen?: boolean } export function NoticeModalManager({ pagePath, autoOpen = true }: NoticeModalManagerProps) { const { t } = useTranslation('ko', 'menu') // ๐Ÿ’ก usePathname() ์ œ๊ฑฐ const [state, setState] = useState({ notices: [], currentIndex: 0, isOpen: false, dontShowToday: false, dontShowSettings: {} }) const [isPending, startTransition] = useTransition() const [isLoading, setIsLoading] = useState(true) // ๐Ÿ’ก ํด๋ผ์ด์–ธํŠธ ๋งˆ์šดํŠธ ์—ฌ๋ถ€ ํ™•์ธ (ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๋ถˆ์ผ์น˜ ๋ฐฉ์ง€) const [isMounted, setIsMounted] = useState(false) // ํ˜„์žฌ ํ‘œ์‹œํ•  ๊ณต์ง€์‚ฌํ•ญ const currentNotice = state.notices[state.currentIndex] // ๐Ÿ’ก [์ˆ˜์ •] useCallback ๋Œ€์‹  useEffect ๋‚ด์—์„œ ์ง์ ‘ ๋กœ์ง ์ฒ˜๋ฆฌ (ํ›… ์ˆœ์„œ ์•ˆ์ •ํ™”) useEffect(() => { // 1. ํด๋ผ์ด์–ธํŠธ ๋งˆ์šดํŠธ ํ™•์ธ setIsMounted(true) if (!pagePath || !autoOpen) { setIsLoading(false) // ๋งˆ์šดํŠธ ๋๋Š”๋ฐ ์‹คํ–‰ ์กฐ๊ฑด์ด ์•ˆ ๋˜๋ฉด ๋กœ๋”ฉ ๋๋‚ด๊ธฐ return } // 2. ๋ฐ์ดํ„ฐ ํŽ˜์น˜ ๋กœ์ง์„ startTransition ๋‚ด์—์„œ ์‹คํ–‰ (๋‹จ์ผ ํ˜ธ์ถœ ๋ณด์žฅ) setIsLoading(true) startTransition(async () => { try { const allNotices = await getPageNoticesForModal(pagePath) if (allNotices.length === 0) { setState(prev => ({ ...prev, notices: [], isOpen: false })) return } // ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€ ์„ค์ • ํ™•์ธ ํ›„ ํ•„ํ„ฐ๋ง const filteredNotices = allNotices.filter(notice => { const dontShowDay = isNoticeDontShowValid({ noticeId: notice.id, duration: 'day' }) const dontShowNever = isNoticeDontShowValid({ noticeId: notice.id, duration: 'never' }) return !dontShowDay && !dontShowNever }) if (filteredNotices.length === 0) { setState(prev => ({ ...prev, notices: [], isOpen: false })) } else { const dontShowSettings: Record = {} filteredNotices.forEach(notice => { dontShowSettings[notice.id] = false }) setState(prev => ({ ...prev, notices: filteredNotices, currentIndex: 0, isOpen: autoOpen, dontShowSettings })) } } catch (error) { console.error("๊ณต์ง€์‚ฌํ•ญ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:", error) toast.error("๊ณต์ง€์‚ฌํ•ญ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.") } finally { setIsLoading(false) } }) }, [pagePath, autoOpen]) // startTransition์€ React์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•จ์ˆ˜์ด๋ฏ€๋กœ ์˜์กด์„ฑ์—์„œ ์ œ์™ธ // ๐Ÿ’ก [์ˆ˜์ •] '๋‹ค์‹œ ๋ณด์ง€ ์•Š๊ธฐ' ์ฒดํฌ๋ฐ•์Šค ๋ณ€๊ฒฝ ํ•ธ๋“ค๋Ÿฌ const handleDontShowChange = useCallback((noticeId: number, checked: boolean) => { setState(prev => ({ ...prev, dontShowSettings: { ...prev.dontShowSettings, [noticeId]: checked } })) }, []) // ๐Ÿ’ก [์ˆ˜์ •] ๋‹ค์Œ ๊ณต์ง€์‚ฌํ•ญ์œผ๋กœ ์ด๋™ const handleNext = useCallback(() => { if (state.currentIndex < state.notices.length - 1) { setState(prev => ({ ...prev, currentIndex: prev.currentIndex + 1 })) } }, [state.currentIndex, state.notices.length]) // ๐Ÿ’ก [์ˆ˜์ •] ์ด์ „ ๊ณต์ง€์‚ฌํ•ญ์œผ๋กœ ์ด๋™ const handlePrevious = useCallback(() => { if (state.currentIndex > 0) { setState(prev => ({ ...prev, currentIndex: prev.currentIndex - 1 })) } }, [state.currentIndex]) // ๐Ÿ’ก [์ˆ˜์ •] ๊ณต์ง€์‚ฌํ•ญ ๋‹ซ๊ธฐ (์„ค์ • ์ €์žฅ ํฌํ•จ) const handleClose = useCallback(() => { // ์ฒดํฌ๋œ '๋‹ค์‹œ ๋ณด์ง€ ์•Š๊ธฐ' ์„ค์ • ์ €์žฅ Object.entries(state.dontShowSettings).forEach(([noticeId, checked]) => { if (checked) { setNoticeDontShow({ noticeId: parseInt(noticeId), duration: 'day' }) } }) setState(prev => ({ ...prev, isOpen: false, notices: [], currentIndex: 0, dontShowSettings: {} })) if (Object.values(state.dontShowSettings).some(Boolean)) { toast.success("์„ค์ •์ด ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.") } }, [state.dontShowSettings]) // ๐Ÿ’ก 3. ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๋ถˆ์ผ์น˜ ๋ฐฉ์ง€: // ์„œ๋ฒ„ ๋ Œ๋”๋ง ์‹œ๋‚˜ ํด๋ผ์ด์–ธํŠธ ๋งˆ์šดํŠธ ์งํ›„์—๋Š” null์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์„œ๋ฒ„์˜ DOM๊ณผ ์ผ์น˜์‹œํ‚ต๋‹ˆ๋‹ค. if (!isMounted) { return null } // 4. ์ดํ›„ ์›๋ž˜์˜ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง ๋กœ์ง ์œ ์ง€ if (isLoading || state.notices.length === 0 || !currentNotice) { return null // ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์ค‘์ด๊ฑฐ๋‚˜ ๊ณต์ง€์‚ฌํ•ญ์ด ์—†๊ฑฐ๋‚˜ currentNotice๊ฐ€ ์—†์œผ๋ฉด null } return (
๊ณต์ง€์‚ฌํ•ญ {state.currentIndex + 1} / {state.notices.length}
์ค‘์š”ํ•œ ๊ณต์ง€์‚ฌํ•ญ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.
{/* ๊ณต์ง€์‚ฌํ•ญ ์ •๋ณด ํ—ค๋” */}

{currentNotice?.title}

{currentNotice?.authorName || "์•Œ ์ˆ˜ ์—†์Œ"}
{formatDate(currentNotice?.createdAt || new Date(), "KR")}
{currentNotice?.pagePath && ( {currentNotice.pagePath} )}
{/* ๊ณต์ง€์‚ฌํ•ญ ๋‚ด์šฉ */}
{/* ์œ ํšจ๊ธฐ๊ฐ„ ์ •๋ณด */} {(currentNotice?.startAt || currentNotice?.endAt) && ( <>
์œ ํšจ๊ธฐ๊ฐ„: {currentNotice.startAt && ( ์‹œ์ž‘ {formatDate(currentNotice.startAt, "KR")} )} {currentNotice.startAt && currentNotice.endAt && ~ } {currentNotice.endAt && ( ์ข…๋ฃŒ {formatDate(currentNotice.endAt, "KR")} )}
)} {/* '๋‹ค์‹œ ๋ณด์ง€ ์•Š๊ธฐ' ์„ค์ • */}
handleDontShowChange(currentNotice?.id || 0, checked as boolean) } />
{/* ํ•˜๋‹จ ๋ฒ„ํŠผ๋“ค */}
{state.currentIndex > 0 && ( )}
{state.currentIndex < state.notices.length - 1 ? ( ) : ( )}
) }