/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useEffect, useState, useContext, useMemo } from 'react'; import { Button, Modal, Empty, Tabs, TabPane, Timeline, } from '@douyinfe/semi-ui'; import { useTranslation } from 'react-i18next'; import { API, showError, getRelativeTime } from '../../helpers'; import { marked } from 'marked'; import { IllustrationNoContent, IllustrationNoContentDark, } from '@douyinfe/semi-illustrations'; import { StatusContext } from '../../context/Status'; import { Bell, Megaphone } from 'lucide-react'; const NoticeModal = ({ visible, onClose, isMobile, defaultTab = 'inApp', unreadKeys = [], }) => { const { t } = useTranslation(); const [noticeContent, setNoticeContent] = useState(''); const [loading, setLoading] = useState(false); const [activeTab, setActiveTab] = useState(defaultTab); const [statusState] = useContext(StatusContext); const announcements = statusState?.status?.announcements || []; const unreadSet = useMemo(() => new Set(unreadKeys), [unreadKeys]); const getKeyForItem = (item) => `${item?.publishDate || ''}-${(item?.content || '').slice(0, 30)}`; const processedAnnouncements = useMemo(() => { return (announcements || []).slice(0, 20).map((item) => { const pubDate = item?.publishDate ? new Date(item.publishDate) : null; const absoluteTime = pubDate && !isNaN(pubDate.getTime()) ? `${pubDate.getFullYear()}-${String(pubDate.getMonth() + 1).padStart(2, '0')}-${String(pubDate.getDate()).padStart(2, '0')} ${String(pubDate.getHours()).padStart(2, '0')}:${String(pubDate.getMinutes()).padStart(2, '0')}` : item?.publishDate || ''; return { key: getKeyForItem(item), type: item.type || 'default', time: absoluteTime, content: item.content, extra: item.extra, relative: getRelativeTime(item.publishDate), isUnread: unreadSet.has(getKeyForItem(item)), }; }); }, [announcements, unreadSet]); const handleCloseTodayNotice = () => { const today = new Date().toDateString(); localStorage.setItem('notice_close_date', today); onClose(); }; const displayNotice = async () => { setLoading(true); try { const res = await API.get('/api/notice'); const { success, message, data } = res.data; if (success) { if (data !== '') { const htmlNotice = marked.parse(data); setNoticeContent(htmlNotice); } else { setNoticeContent(''); } } else { showError(message); } } catch (error) { showError(error.message); } finally { setLoading(false); } }; useEffect(() => { if (visible) { displayNotice(); } }, [visible]); useEffect(() => { if (visible) { setActiveTab(defaultTab); } }, [defaultTab, visible]); const renderMarkdownNotice = () => { if (loading) { return (
); } if (!noticeContent) { return (
} darkModeImage={ } description={t('暂无公告')} />
); } return (
); }; const renderAnnouncementTimeline = () => { if (processedAnnouncements.length === 0) { return (
} darkModeImage={ } description={t('暂无系统公告')} />
); } return (
{processedAnnouncements.map((item, idx) => { const htmlContent = marked.parse(item.content || ''); const htmlExtra = item.extra ? marked.parse(item.extra) : ''; return ( ) : null } className={item.isUnread ? '' : ''} >
); })}
); }; const renderBody = () => { if (activeTab === 'inApp') { return renderMarkdownNotice(); } return renderAnnouncementTimeline(); }; return ( {t('系统公告')} {t('通知')} } itemKey='inApp' /> {t('系统公告')} } itemKey='system' />
} visible={visible} onCancel={onClose} footer={
} size={isMobile ? 'full-width' : 'large'} > {renderBody()} ); }; export default NoticeModal;