import React, { useEffect, useState, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; // ✅ Go up 2 levels to reach src/supabaseClient import { supabase } from "../../supabaseClient"; // ✅ Go up 1 level to reach components folder import StatCard from "../StatCard"; import ApplicationTrendsChart from "../ApplicationTrendsChart"; import ExperienceChart from "../ExperienceChart"; import UpcomingInterviews from "../Adminfront/UpcomingInterviews"; import RecentApplications from "../Adminfront/RecentApplications"; import TopPerformers from "../Adminfront/TopPerformers"; // --- ICONS --- const UsersIcon = () => (); const BellIcon = () => ( ); export default function AdminSummary({ onNavigate, setActiveTab, selectedChatUserId, setSelectedChatUserId }) { const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.1 } } }; const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }; // --- STATE --- const [stats, setStats] = useState({ total: 0, pending: 0, accepted: 0, rejected: 0 }); const [applicants, setApplicants] = useState([]); const [loading, setLoading] = useState(true); const [userName, setUserName] = useState('Admin'); // ✅ Notification State const [notifications, setNotifications] = useState([]); const [showNotifications, setShowNotifications] = useState(false); const [latestPopup, setLatestPopup] = useState(null); // ✅ Show only latest popup const notifRef = useRef(null); // ✅ CLICK OUTSIDE LISTENER useEffect(() => { function handleClickOutside(event) { if (notifRef.current && !notifRef.current.contains(event.target)) { setShowNotifications(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [notifRef]); // --- DATA FETCHING --- useEffect(() => { fetchDashboardData(); }, []); // ✅ HELPER: Show popup notification (only latest) const showPopup = (notif) => { // Prevent showing the exact same popup multiple times const popped = localStorage.getItem('popped_messages') || '[]'; let poppedArray = []; try { poppedArray = JSON.parse(popped); } catch(e) {} if (poppedArray.includes(notif.id)) return; // Already popped ever poppedArray.push(notif.id); if (poppedArray.length > 50) poppedArray.shift(); // Keep local storage clean localStorage.setItem('popped_messages', JSON.stringify(poppedArray)); setLatestPopup(notif); // Auto-dismiss after 4 seconds setTimeout(() => { setLatestPopup(null); }, 4000); }; // ✅ MESSAGE POLLING - Check for new messages every 5 seconds useEffect(() => { let isMounted = true; let pollInterval; const startPolling = async () => { const { data: { user } } = await supabase.auth.getUser(); if (!isMounted || !user) return; let lastMessageId = null; // ✅ INITIAL FETCH: Get the latest message ID silently so we don't pop up old messages on every remount try { const { data: initialMessages } = await supabase .from('messages') .select('id') .eq('receiver_id', user.id) .order('id', { ascending: false }) .limit(1); if (!isMounted) return; if (initialMessages && initialMessages.length > 0) { lastMessageId = initialMessages[0].id; } } catch (err) { console.error("Initial fetch error:", err); } if (!isMounted) return; pollInterval = setInterval(async () => { try { const { data: newMessages } = await supabase .from('messages') .select('id, sender_id, content, created_at') .eq('receiver_id', user.id) .order('id', { ascending: false }) .limit(1); if (newMessages && newMessages.length > 0) { const msg = newMessages[0]; // ✅ Only process if it's TRULY a new message (greater than the initial fetch) if (lastMessageId === null || msg.id > lastMessageId) { lastMessageId = msg.id; // Get sender profile const { data: senderProfile } = await supabase .from('profiles') .select('full_name') .eq('id', msg.sender_id) .single(); const senderName = senderProfile?.full_name || 'Candidate'; // Create and add notification const newNotif = { id: `msg-${msg.id}`, type: 'New Message', text: `📨 New message from ${senderName}`, time: msg.created_at, color: '#10b981', preview: msg.content, senderId: msg.sender_id // ✅ Store sender ID for navigation }; setNotifications(prev => { const exists = prev.some(n => n.id === newNotif.id); if (exists) return prev; return [newNotif, ...prev]; }); // ✅ SHOW POPUP (It will now enforce showing only once via localStorage) showPopup(newNotif); } } } catch (err) { console.error("Poll error:", err); } }, 3000); // Check every 3 seconds }; startPolling(); return () => { isMounted = false; if (pollInterval) clearInterval(pollInterval); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const fetchDashboardData = async () => { try { setLoading(true); // 1. User Name const { data: { user } } = await supabase.auth.getUser(); if (user) { const { data: roleData } = await supabase.from('user_roles').select('name').eq('user_id', user.id).single(); if (roleData?.name) setUserName(roleData.name); } // 2. Dashboard Stats const { data: data, error } = await supabase.from('applications').select('id, status, created_at, experience'); if (error) throw error; if (data) { setApplicants(data); setStats({ total: data.length, pending: data.filter(a => ['Pending', 'Screening', 'Interviewing'].includes(a.status)).length, accepted: data.filter(a => ['Hired', 'Offered', 'Accepted'].includes(a.status)).length, rejected: data.filter(a => a.status === 'Rejected').length }); } // 3. Notifications Data const { data: recentApps } = await supabase.from('applications').select('id, created_at, profiles(full_name)').order('created_at', { ascending: false }).limit(3); const { data: upcomingInterviews } = await supabase.from('interviews').select('id, scheduled_time, applications(profiles(full_name))').gte('scheduled_time', new Date().toISOString()).order('scheduled_time', { ascending: true }).limit(3); const appNotifs = (recentApps || []).map(app => ({ id: `app-${app.id}`, type: 'New Applicant', text: `${app.profiles?.full_name || 'Candidate'} applied for a job.`, time: app.created_at, color: '#3b82f6' })); const intNotifs = (upcomingInterviews || []).map(int => ({ id: `int-${int.id}`, type: 'Interview', text: `Interview with ${int.applications?.profiles?.full_name || 'Candidate'}`, time: int.scheduled_time, color: '#f59e0b' })); setNotifications([...appNotifs, ...intNotifs].sort((a, b) => new Date(b.time) - new Date(a.time))); } catch (error) { console.error('Error fetching dashboard data:', error); } finally { setLoading(false); } }; if (loading) return
{latestPopup.text}
{latestPopup.preview && ({latestPopup.preview}
)}{notif.text}
{notif.preview && ({notif.preview}
)}{new Date(notif.time).toLocaleDateString()} • {new Date(notif.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}