import React, { useState, useMemo, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { Event } from '../types'; import { supabase } from '../supabase'; interface FacultyAttendanceViewProps { events: Event[]; onShowToast: (msg: string) => void; localRegistrations?: any[]; currentUserId?: string; } interface FilterState { depts: string[]; years: string[]; } const FacultyAttendanceView: React.FC = ({ events, onShowToast, localRegistrations = [], currentUserId }) => { const [selectedEventId, setSelectedEventId] = useState(null); const [showFilters, setShowFilters] = useState(false); const [activeFilters, setActiveFilters] = useState({ depts: [], years: [] }); const [activeTab, setActiveTab] = useState<'DEPT' | 'YEAR'>('DEPT'); // Track attendance in local state for the current session const [attendanceMap, setAttendanceMap] = useState>({}); const [selectedDay, setSelectedDay] = useState('Day 1'); const [selectedBatch, setSelectedBatch] = useState(''); const [isSyncing, setIsSyncing] = useState(false); const [isFinalized, setIsFinalized] = useState(false); const sessionLabel = useMemo(() => { if (!selectedBatch) return selectedDay; return `${selectedDay} - ${selectedBatch}`; }, [selectedDay, selectedBatch]); // Attendance markers logic useEffect(() => { if (selectedEventId && sessionLabel) { const fetchAttendance = async () => { setIsSyncing(true); const { data, error } = await supabase .from('attendance_records') .select('registration_id, is_present') .eq('event_id', selectedEventId) .or(`day_label.eq."${selectedDay}",session_label.eq."${sessionLabel}"`) .eq('batch_label', selectedBatch || ''); if (data && !error) { const map: Record = {}; data.forEach((rec: any) => { map[rec.registration_id] = rec.is_present; }); setAttendanceMap(map); setIsFinalized(data.length > 0); } else { setAttendanceMap({}); setIsFinalized(false); } setIsSyncing(false); }; fetchAttendance(); } }, [selectedEventId, sessionLabel]); const toggleAttendance = (id: string) => { setAttendanceMap(prev => ({ ...prev, [id]: !prev[id] })); }; const handleFinalize = async () => { if (!selectedEventId) return; if (selectedEvent?.created_by !== currentUserId) { alert("Unauthorized: Only the event creator can finalize attendance."); return; } setIsSyncing(true); try { // Upsert attendance records const records = currentRoster.map(s => ({ event_id: selectedEventId, registration_id: s.id, session_label: sessionLabel, // Keep for backward compatibility day_label: selectedDay, batch_label: selectedBatch || '', is_present: !!attendanceMap[s.id], marked_at: new Date().toISOString() })); const { error } = await supabase .from('attendance_records') .upsert(records, { onConflict: 'registration_id,day_label,batch_label' }); if (error) throw error; setIsFinalized(true); onShowToast(`Attendance for ${sessionLabel} finalized and synced.`); } catch (err: any) { console.error("Failed to sync attendance:", err); alert("Failed to sync attendance. Please try again."); } finally { setIsSyncing(false); } }; const handleDownloadExcel = () => { if (!selectedEvent || filteredRoster.length === 0) return; const titleRow = [`EVENT: ${selectedEvent.title}`]; const sessionRow = [`SESSION: ${selectedDay}${selectedBatch ? ` - ${selectedBatch}` : ''}`]; const dateRow = [`EXPORTED AT: ${new Date().toLocaleString()}`]; const emptyRow = ['']; // Reg No formatting: Prefix with \t to prevent scientific notation in Excel const headers = ['Name', 'Registration No', 'Department', 'Year', 'Present', 'Absent']; const rows = filteredRoster.map(s => [ s.name, `\t${s.roll}`, s.dept, s.year, s.present ? '1' : '0', s.present ? '0' : '1' ]); const csvContent = [ titleRow.join(','), sessionRow.join(','), dateRow.join(','), emptyRow.join(','), headers.join(','), ...rows.map(row => row.map(cell => `"${cell}"`).join(',')) ].join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); const sanitizedTitle = selectedEvent.title.replace(/[^a-z0-9]/gi, '_').toLowerCase(); link.setAttribute('download', `${sanitizedTitle}_attendance_${selectedDay}_${selectedBatch || 'full'}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const selectedEvent = events.find(e => e.id === selectedEventId); // Compute the current roster based on registrations in the database for the selected event const currentRoster = useMemo(() => { if (!selectedEventId) return []; return localRegistrations .filter(reg => reg.event_id === selectedEventId) .map(reg => ({ id: reg.id, userId: reg.user_id, name: reg.user_name || 'Student', roll: reg.reg_no || 'N/A', dept: reg.dept || 'N/A', year: reg.year || 'N/A', college: reg.college, present: !!attendanceMap[reg.id] })); }, [selectedEventId, localRegistrations, attendanceMap]); // Derive unique values for filters from the dynamic roster const filterOptions = useMemo(() => { return { depts: Array.from(new Set(currentRoster.map(s => s.dept))).sort(), years: Array.from(new Set(currentRoster.map(s => s.year))).sort() }; }, [currentRoster]); // Apply filters to roster const filteredRoster = useMemo(() => { return currentRoster.filter(student => { const deptMatch = activeFilters.depts.length === 0 || activeFilters.depts.includes(student.dept); const yearMatch = activeFilters.years.length === 0 || activeFilters.years.includes(student.year); const isExternal = student.college && student.college !== 'RAJALAKSHMI INSTITUTE OF TECHNOLOGY' && student.college !== 'Rajalakshmi Institute of Technology'; return deptMatch && yearMatch && !isExternal; }); }, [currentRoster, activeFilters]); const toggleFilterValue = (category: 'depts' | 'years', value: string) => { setActiveFilters(prev => { const current = prev[category]; const next = current.includes(value) ? current.filter(v => v !== value) : [...current, value]; return { ...prev, [category]: next }; }); }; const clearAllFilters = () => { setActiveFilters({ depts: [], years: [] }); }; const totalActiveFilters = activeFilters.depts.length + activeFilters.years.length; return (
{!selectedEventId ? ( <>

SYSTEM ATTENDANCE

Select an Event to Mark Roster

{events.map((event) => (
setSelectedEventId(event.id)} className="bg-white border border-slate-200 rounded-[2.5rem] overflow-hidden group cursor-pointer hover:border-blue-300 transition-all flex flex-col h-full shadow-sm hover:shadow-md">
{event.title}
{event.category}
{event.pricingType}
{event.domain}

{event.title}

Coord: {event.coordinator}

Registered {localRegistrations.filter(r => r.event_id === event.id).length} Students
{event.created_by === currentUserId ? "Mark Roster" : "View Roster"}
{event.created_by !== currentUserId && (
Read Only
)}
))}
) : (
{selectedEvent?.category} {selectedEvent?.pricingType}

{selectedEvent?.title}

SESSION ACTIVE

Marked Attendance {filteredRoster.filter(s => s.present).length}/{filteredRoster.length}
Filter Options
{/* Day Selector */} {(() => { // Derive unique days from schedule const uniqueDays = selectedEvent?.schedule ? (Array.from(new Set(selectedEvent.schedule.map(s => s.day_idx))) as number[]).sort((a, b) => a - b) : [1]; return uniqueDays.length > 1 ? ( ) : (
Day 1 Only
); })()} {/* Batch Selector */} {(() => { const currentDayIdx = parseInt(selectedDay.split(' ')[1]) || 1; const dayBatches = selectedEvent?.schedule ? selectedEvent.schedule.filter(s => s.day_idx === currentDayIdx) : []; if (dayBatches.length > 0) { return ( ); } return null; })()}
{isSyncing && (
)} {filteredRoster.length > 0 ? filteredRoster.map((student, idx) => ( )) : ( )}
# Student Details Academic Info Roster Status
{idx + 1}
{student.name.charAt(0)}

{student.name}

{student.roll}

{student.dept}

{student.year}

{student.present ? (
PRESENT
) : ( )}
No matching students

Roster synced
with database

)} {showFilters && createPortal(
setShowFilters(false)} />

Refine Roster

Multi-Category Filtering

{[ { id: 'DEPT' as const, label: 'Department' }, { id: 'YEAR' as const, label: 'Year' } ].map(tab => ( ))}
{activeTab === 'DEPT' ? (
{filterOptions.depts.map(dept => ( ))}
) : (
{filterOptions.years.map(year => ( ))}
)}
, document.body )}
); }; export default FacultyAttendanceView;