import React, { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Bell, Trash2, Plus, Layers, Settings, Upload, Calendar, X, CheckCircle2, AlertTriangle, Globe, Loader2, FileImage } from 'lucide-react'; import { cn } from '../../lib/utils'; import { API_BASE_URL } from '../../lib/config'; import { useDialog } from '../../context/DialogContext'; interface Notice { id: string; title: string; message: string; timestamp: string; type: 'INFO' | 'URGENT' | 'DELAY'; image?: string; expiresAt?: string; } interface SpecialEvent { id: string; title: string; description: string; link: string; created_at: string; } export const ManageNoticesView: React.FC = () => { const { showAlert, showConfirm } = useDialog(); // Navigation Tabs const [activeTab, setActiveTab] = useState<'notices' | 'special-events'>('notices'); // Data States const [notices, setNotices] = useState([]); const [specialEvents, setSpecialEvents] = useState([]); const [loading, setLoading] = useState(true); // Modal States const [isNoticeModalOpen, setIsNoticeModalOpen] = useState(false); const [isEventModalOpen, setIsEventModalOpen] = useState(false); // Form States (Notice) const [noticeTitle, setNoticeTitle] = useState(''); const [noticeMessage, setNoticeMessage] = useState(''); const [noticeType, setNoticeType] = useState<'INFO' | 'URGENT' | 'DELAY'>('INFO'); const [noticeImage, setNoticeImage] = useState(null); // Form States (Special Event) const [eventTitle, setEventTitle] = useState(''); const [eventDesc, setEventDesc] = useState(''); const [eventLink, setEventLink] = useState(''); const [eventImage, setEventImage] = useState(null); const handleEventImageUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { if (file.size > 1.5 * 1024 * 1024) { showAlert('File Too Large', 'Please upload an image smaller than 1.5MB.', 'error'); return; } const reader = new FileReader(); reader.onloadend = () => { setEventImage(reader.result as string); }; reader.readAsDataURL(file); } }; useEffect(() => { fetchInitialData(); }, []); const fetchInitialData = async () => { try { setLoading(true); const [noticesRes, eventsRes] = await Promise.all([ fetch(`${API_BASE_URL}/api/announcements`), fetch(`${API_BASE_URL}/api/special-events`) ]); if (noticesRes.ok) { const noticesData = await noticesRes.json(); setNotices(noticesData); } if (eventsRes.ok) { const eventsData = await eventsRes.json(); setSpecialEvents(eventsData); } } catch (err) { console.error('Failed to load notice configuration data:', err); } finally { setLoading(false); } }; // Sticky notice limits check const handleOpenNoticeModal = () => { if (notices.length >= 3) { showAlert( 'Limit Reached', 'A maximum of 3 notices can be active on the campus notice board at any time. Please delete an existing notice first.', 'info' ); return; } setNoticeTitle(''); setNoticeMessage(''); setNoticeType('INFO'); setNoticeImage(null); setIsNoticeModalOpen(true); }; const handleImageUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { if (file.size > 1.5 * 1024 * 1024) { showAlert('File Too Large', 'Please upload an image smaller than 1.5MB.', 'error'); return; } const reader = new FileReader(); reader.onloadend = () => { setNoticeImage(reader.result as string); }; reader.readAsDataURL(file); } }; const handleAddNotice = async (e: React.FormEvent) => { e.preventDefault(); if (!noticeTitle.trim() || !noticeMessage.trim()) { showAlert('Required Fields', 'Please fill in both title and message.', 'error'); return; } try { const response = await fetch(`${API_BASE_URL}/api/announcements`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: noticeTitle.trim(), message: noticeMessage.trim(), type: noticeType, image: noticeImage }) }); if (response.ok) { setIsNoticeModalOpen(false); fetchInitialData(); showAlert('Success', 'Notice posted successfully to Campus Notice Board.', 'success'); } } catch (err) { console.error('Failed to post notice:', err); showAlert('Error', 'Failed to save notice. Please try again.', 'error'); } }; const handleDeleteNotice = async (id: string) => { showConfirm( 'Remove Notice', 'Are you sure you want to remove this notice from the board?', async () => { try { const response = await fetch(`${API_BASE_URL}/api/announcements/${id}`, { method: 'DELETE' }); if (response.ok) { fetchInitialData(); showAlert('Success', 'Notice removed successfully.', 'success'); } } catch (err) { console.error('Delete failed:', err); showAlert('Error', 'Failed to delete notice.', 'error'); } } ); }; // Special Events Updates const handleAddSpecialEvent = async (e: React.FormEvent) => { e.preventDefault(); if (!eventTitle.trim() || !eventDesc.trim() || !eventLink.trim()) { showAlert('Required Fields', 'Please fill in all special event details.', 'error'); return; } try { const response = await fetch(`${API_BASE_URL}/api/special-events`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: eventTitle.trim(), description: eventDesc.trim(), link: eventLink.trim(), created_by: 'ADMIN', image: eventImage }) }); if (response.ok) { setIsEventModalOpen(false); setEventTitle(''); setEventDesc(''); setEventLink(''); setEventImage(null); fetchInitialData(); showAlert('Success', 'Special event added to the registry successfully.', 'success'); } } catch (err) { console.error('Post failed:', err); showAlert('Error', 'Failed to create special event.', 'error'); } }; const handleDeleteSpecialEvent = async (id: string) => { showConfirm( 'Delete Special Event', 'Are you sure you want to remove this event from the registry?', async () => { try { const response = await fetch(`${API_BASE_URL}/api/special-events/${id}`, { method: 'DELETE' }); if (response.ok) { fetchInitialData(); showAlert('Success', 'Special event deleted successfully.', 'success'); } } catch (err) { console.error('Delete failed:', err); showAlert('Error', 'Failed to delete event.', 'error'); } } ); }; if (loading) { return (
); } return (
{/* Title */}

Notices & Banner Settings

Configure notice boards, scroll announcements, & special events

{/* Tabs */}
{/* Notices Tab */} {activeTab === 'notices' && (

Active Campus Notices

These cards are pinned to the student Campus Notice Board (Maximum 3 notices).

{notices.map(notice => (
{notice.type}
{notice.image && ( Notice Attachment )}

{notice.title}

{notice.message}

{new Date(notice.timestamp).toLocaleString([], { dateStyle: 'medium', timeStyle: 'short' })}
))} {notices.length === 0 && (
No active announcements on the notice board.
)}
)} {/* Special Events Tab */} {activeTab === 'special-events' && (

Special Events Registry

These events are rendered in the modern slanted registry section of the student dashboard.

{specialEvents.map(event => (
Special Event

{event.title}

{event.description}

))} {specialEvents.length === 0 && (
No special events currently registered.
)}
)} {/* Notice Dialog Modal */} {isNoticeModalOpen && (
setIsNoticeModalOpen(false)} className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm" />
{/* Header */}

Post New Notice

Configure campus notice board bulletin.

{/* Body */}
{/* Title */}
setNoticeTitle(e.target.value)} placeholder="Enter short, descriptive title" className="w-full bg-slate-50 border-transparent rounded-xl py-3 px-4 text-xs font-bold focus:bg-white focus:border-brand-indigo outline-none transition-all" />
{/* Message */}