Convert backends to Firebase and combine projects

This commit is contained in:
2026-06-18 14:07:24 +05:30
commit 0a76feafc5
147 changed files with 35104 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
import React, { useState, useEffect } from 'react';
import { supabase } from '../supabase';
import { Announcement } from '../types';
const FacultyNotifications: React.FC = () => {
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [now, setNow] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => setNow(new Date()), 30000);
return () => clearInterval(timer);
}, []);
// Fetch announcements directly from Supabase — no prop dependency
useEffect(() => {
const fetchAnnouncements = async () => {
setIsLoading(true);
const { data, error } = await supabase
.from('announcements')
.select('*')
.order('timestamp', { ascending: false });
if (data && !error) {
setAnnouncements(data.map(ann => ({
id: String(ann.id),
title: ann.title,
message: ann.message,
type: ann.type,
eventId: ann.event_id ? String(ann.event_id) : undefined,
timestamp: ann.timestamp || ann.created_at || new Date().toISOString(),
expiresAt: ann.expires_at
})));
}
setIsLoading(false);
};
fetchAnnouncements();
// Subscribe to realtime changes on announcements
const channel = supabase
.channel('announcements-faculty')
.on('postgres_changes', { event: '*', schema: 'public', table: 'announcements' }, () => {
fetchAnnouncements();
})
.subscribe();
return () => { supabase.removeChannel(channel); };
}, []);
const getTypeIcon = (type: string) => {
switch (type) {
case 'URGENT': return 'fa-triangle-exclamation text-rose-500';
case 'DELAY': return 'fa-clock text-amber-500';
case 'INFO': return 'fa-info-circle text-blue-500';
case 'ENDED': return 'fa-flag-checkered text-purple-500';
case 'ONGOING': return 'fa-play-circle text-emerald-500';
default: return 'fa-bullhorn text-[#f97316]';
}
};
const formatTime = (timestamp: any): string => {
if (!timestamp) return 'Now';
try {
// Supabase returns ISO strings — parse directly
const date = new Date(timestamp);
if (isNaN(date.getTime())) return 'Now';
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
} catch {
return 'Now';
}
};
const formatDate = (timestamp: any): string => {
if (!timestamp) return '';
try {
const date = new Date(timestamp);
if (isNaN(date.getTime())) return '';
const today = new Date();
const isToday = date.toDateString() === today.toDateString();
if (isToday) return 'Today';
return date.toLocaleDateString([], { day: 'numeric', month: 'short' });
} catch {
return '';
}
};
const isExpired = (expiresAt: any): boolean => {
if (!expiresAt) return false;
try {
return new Date(expiresAt) < now;
} catch {
return false;
}
};
const activeAnnouncements = announcements.filter(ann => !isExpired(ann.expiresAt));
return (
<div className="animate-in fade-in slide-in-from-right-10 duration-700 max-w-4xl">
<div className="mb-12">
<h3 className="text-4xl font-black tracking-tight uppercase mb-2">Notice <span className="text-blue-500">Board</span></h3>
<p className="text-[10px] font-black text-gray-500 uppercase tracking-[0.4em]">Administrative Updates & Alerts</p>
</div>
{isLoading ? (
<div className="py-20 flex flex-col items-center justify-center">
<div className="w-12 h-12 border-4 border-blue-500/20 border-t-blue-500 rounded-full animate-spin mb-4"></div>
<p className="text-[10px] font-black text-gray-600 uppercase tracking-widest">Syncing Notices...</p>
</div>
) : (
<div className="space-y-6">
{activeAnnouncements.length > 0 ? activeAnnouncements.map((n) => (
<div key={n.id} className="bg-white/5 border border-white/5 rounded-[2.5rem] p-8 flex items-start justify-between group hover:bg-white/[0.08] transition-all cursor-pointer">
<div className="flex items-start gap-8">
<div className="w-16 h-16 rounded-[1.5rem] bg-white/5 flex items-center justify-center border border-white/10 group-hover:border-blue-500/50 transition-colors">
<i className={`fas ${getTypeIcon(n.type)} text-xl`}></i>
</div>
<div>
<h4 className="text-lg font-black uppercase tracking-tight group-hover:text-blue-500 transition-colors">{n.title}</h4>
<p className="text-gray-400 text-sm font-medium mt-1 mb-3">{n.message}</p>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className={`w-1.5 h-1.5 rounded-full ${n.type === 'URGENT' ? 'bg-rose-500 animate-ping' : n.type === 'ENDED' ? 'bg-purple-500' : n.type === 'ONGOING' ? 'bg-emerald-500 animate-pulse' : 'bg-blue-500 animate-pulse'}`}></div>
<span className={`text-[9px] font-black uppercase tracking-widest ${n.type === 'URGENT' ? 'text-rose-500' : n.type === 'ENDED' ? 'text-purple-500' : n.type === 'ONGOING' ? 'text-emerald-500' : 'text-blue-500/70'}`}>
{n.type === 'ENDED' ? 'Status: Concluded' : n.type === 'ONGOING' ? 'Status: Live' : `Priority ${n.type === 'URGENT' ? 'Critical' : 'Regular'}`}
</span>
</div>
{n.expiresAt && (
<div className="flex items-center gap-2 text-rose-500/40">
<i className="fas fa-hourglass-end text-[8px]"></i>
<span className="text-[8px] font-black uppercase tracking-widest">Temporal Notice</span>
</div>
)}
</div>
</div>
</div>
<div className="flex flex-col items-end gap-1 shrink-0 ml-4">
<span className="text-[10px] font-bold text-gray-600 uppercase tracking-widest">{formatTime(n.timestamp)}</span>
<span className="text-[9px] font-bold text-gray-700 uppercase tracking-widest">{formatDate(n.timestamp)}</span>
</div>
</div>
)) : (
<div className="py-20 text-center bg-white/5 rounded-[2.5rem] border border-dashed border-white/10">
<i className="fas fa-inbox text-4xl text-white/10 mb-4"></i>
<p className="text-[10px] font-black text-gray-600 uppercase tracking-widest">No active notices</p>
</div>
)}
</div>
)}
</div>
);
};
export default FacultyNotifications;