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,538 @@
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<FacultyAttendanceViewProps> = ({ events, onShowToast, localRegistrations = [], currentUserId }) => {
const [selectedEventId, setSelectedEventId] = useState<string | null>(null);
const [showFilters, setShowFilters] = useState(false);
const [activeFilters, setActiveFilters] = useState<FilterState>({ depts: [], years: [] });
const [activeTab, setActiveTab] = useState<'DEPT' | 'YEAR'>('DEPT');
// Track attendance in local state for the current session
const [attendanceMap, setAttendanceMap] = useState<Record<string, boolean>>({});
const [selectedDay, setSelectedDay] = useState<string>('Day 1');
const [selectedBatch, setSelectedBatch] = useState<string>('');
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<string, boolean> = {};
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 (
<div className="animate-in slide-in-from-bottom-10 duration-500">
{!selectedEventId ? (
<>
<div className="mb-12 border-b border-slate-200 pb-8">
<h3 className="text-4xl font-black tracking-tighter uppercase mb-2 text-slate-900">SYSTEM <span className="text-[#004a99]">ATTENDANCE</span></h3>
<p className="text-[10px] font-black text-slate-500 uppercase tracking-[0.4em]">Select an Event to Mark Roster</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{events.map((event) => (
<div key={event.id} onClick={() => 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">
<div className="h-48 relative overflow-hidden bg-slate-100">
<img src={event.image} alt={event.title} className="w-full h-full object-cover opacity-80 group-hover:opacity-100 transition-all duration-700 group-hover:scale-105" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div className="absolute top-6 right-6 flex flex-col items-end gap-2">
<div className="bg-white/90 backdrop-blur-md border border-slate-200 px-4 py-1.5 rounded-full text-[9px] font-black uppercase tracking-widest text-slate-700 shadow-sm">{event.category}</div>
<div className={`px-3 py-1 rounded-md text-[8px] font-black uppercase tracking-widest border ${event.pricingType === 'PAID' ? 'bg-rose-50 border-rose-200 text-rose-600' : 'bg-emerald-50 border-emerald-200 text-emerald-600'}`}>{event.pricingType}</div>
</div>
</div>
<div className="p-8 flex flex-col flex-1">
<div className="flex items-center gap-2 mb-3">
<span className="w-1.5 h-1.5 rounded-full bg-blue-500"></span>
<span className="text-[9px] font-black text-slate-500 uppercase tracking-[0.2em]">{event.domain}</span>
</div>
<h4 className="text-xl font-black uppercase mb-2 tracking-tight text-slate-900 group-hover:text-[#004a99] transition-colors line-clamp-1">{event.title}</h4>
<p className="text-[10px] font-bold text-slate-500 uppercase mb-6 tracking-widest">Coord: {event.coordinator}</p>
<div className="mt-auto flex items-center justify-between border-t border-slate-100 pt-6">
<div className="flex flex-col">
<span className="text-[8px] font-black text-slate-400 uppercase tracking-widest mb-1">Registered</span>
<span className="text-xs font-bold text-slate-700 uppercase">{localRegistrations.filter(r => r.event_id === event.id).length} Students</span>
</div>
<div className="flex items-center gap-2 text-[#004a99] font-black text-[10px] uppercase tracking-widest group-hover:translate-x-1 transition-transform">
{event.created_by === currentUserId ? "Mark Roster" : "View Roster"} <i className="fas fa-chevron-right text-[8px]"></i>
</div>
</div>
{event.created_by !== currentUserId && (
<div className="absolute top-4 left-4 bg-slate-900/10 backdrop-blur-md px-3 py-1 rounded-full text-[7px] font-black uppercase tracking-widest text-[#004a99] flex items-center gap-1 border border-white/20">
<i className="fas fa-eye"></i> Read Only
</div>
)}
</div>
</div>
))}
</div>
</>
) : (
<div className="max-w-5xl mx-auto pb-20">
<button onClick={() => { setSelectedEventId(null); clearAllFilters(); }} className="flex items-center gap-2 text-[#004a99] font-black uppercase tracking-widest mb-10 group hover:translate-x-[-5px] transition-transform text-xs"><i className="fas fa-arrow-left"></i> Back to Event List</button>
<div className="bg-white border border-slate-200 rounded-[3rem] overflow-hidden shadow-sm relative">
<div className="p-10 border-b border-slate-100 bg-slate-50">
<div className="flex flex-col md:flex-row justify-between items-center gap-8 mb-8">
<div className="flex-1">
<div className="flex items-center flex-wrap gap-3 mb-4">
<span className="bg-blue-50 text-[#004a99] px-3 py-1 rounded-full text-[8px] font-black uppercase tracking-widest border border-blue-200">{selectedEvent?.category}</span>
<span className={`px-3 py-1 rounded text-[8px] font-black uppercase tracking-widest border ${selectedEvent?.pricingType === 'PAID' ? 'bg-rose-50 border-rose-200 text-rose-600' : 'bg-emerald-50 border-emerald-200 text-emerald-600'}`}>{selectedEvent?.pricingType}</span>
</div>
<h4 className="text-4xl font-black uppercase tracking-tighter mb-2 text-slate-900">{selectedEvent?.title}</h4>
<p className="text-[10px] font-bold text-[#004a99] uppercase tracking-[0.4em] flex items-center gap-2"><i className="fas fa-barcode"></i> SESSION ACTIVE</p>
</div>
<div className="text-center md:text-right bg-white border border-slate-200 p-6 rounded-3xl min-w-[200px] shadow-sm">
<span className="block text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2">Marked Attendance</span>
<span className="text-5xl font-black text-[#004a99] tabular-nums">{filteredRoster.filter(s => s.present).length}<span className="text-slate-300 mx-2">/</span><span className="text-slate-900">{filteredRoster.length}</span></span>
</div>
</div>
<div className="flex flex-wrap items-center justify-end gap-6 pt-8 border-t border-slate-200/60 font-bold text-xs uppercase tracking-widest text-slate-400">
<span>Filter Options</span>
<div className="flex items-center gap-3">
{/* 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 ? (
<select
value={selectedDay}
onChange={(e) => {
setSelectedDay(e.target.value);
setSelectedBatch(''); // Reset batch when day changes
}}
className="bg-white border border-slate-200 px-4 py-3 rounded-xl text-[9px] font-black uppercase tracking-widest text-[#004a99] outline-none shadow-sm focus:ring-2 focus:ring-blue-100 transition-all cursor-pointer min-w-[120px]"
>
{uniqueDays.map(dayIdx => (
<option key={dayIdx} value={`Day ${dayIdx}`}>Day {dayIdx}</option>
))}
</select>
) : (
<div className="bg-slate-100 border border-slate-200 px-4 py-3 rounded-xl text-[9px] font-black uppercase tracking-widest text-slate-400 shadow-sm cursor-default">
Day 1 Only
</div>
);
})()}
{/* 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 (
<select
value={selectedBatch}
onChange={(e) => setSelectedBatch(e.target.value)}
className="bg-white border border-slate-200 px-4 py-3 rounded-xl text-[9px] font-black uppercase tracking-widest text-[#004a99] outline-none shadow-sm focus:ring-2 focus:ring-blue-100 transition-all cursor-pointer min-w-[120px]"
>
<option value="">Full Day</option>
{dayBatches.map((s) => (
<option key={s.id} value={`Batch ${s.batch_idx}`}>Batch {s.batch_idx}</option>
))}
</select>
);
}
return null;
})()}
<button
onClick={() => setShowFilters(true)}
className="group flex items-center gap-3 bg-white border border-slate-200 px-6 py-3 rounded-xl hover:bg-slate-100 text-slate-700 transition-all relative shadow-sm"
>
<i className="fas fa-sliders-h text-xs text-[#004a99]"></i>
<span className="text-[9px] font-black uppercase tracking-widest">Filters</span>
{totalActiveFilters > 0 && (
<span className="absolute -top-2 -right-2 w-5 h-5 bg-[#004a99] text-white text-[9px] font-black rounded-full flex items-center justify-center animate-bounce shadow-md">
{totalActiveFilters}
</span>
)}
</button>
</div>
</div>
</div>
<div className="overflow-x-auto min-h-[400px]">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50/50 border-b border-slate-100">
<th className="px-8 py-5 text-[9px] font-black text-slate-400 uppercase tracking-widest">#</th>
<th className="px-8 py-5 text-[9px] font-black text-slate-400 uppercase tracking-widest">Student Details</th>
<th className="px-8 py-5 text-[9px] font-black text-slate-400 uppercase tracking-widest">Academic Info</th>
<th className="px-8 py-5 text-[9px] font-black text-slate-400 uppercase tracking-widest text-right">Roster Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-50 relative">
{isSyncing && (
<div className="absolute inset-0 bg-white/60 backdrop-blur-[1px] z-10 flex items-center justify-center">
<div className="w-8 h-8 border-2 border-blue-100 border-t-[#004a99] rounded-full animate-spin"></div>
</div>
)}
{filteredRoster.length > 0 ? filteredRoster.map((student, idx) => (
<tr key={student.id} className={`group/row transition-all ${student.present ? 'bg-emerald-50/30' : 'hover:bg-slate-50/50'}`}>
<td className="px-8 py-5">
<span className={`text-[10px] font-black ${student.present ? 'text-emerald-500' : 'text-slate-300'}`}>{idx + 1}</span>
</td>
<td className="px-8 py-5">
<div className="flex items-center gap-4">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center font-black text-sm uppercase ${student.present ? 'bg-emerald-500 text-white shadow-lg shadow-emerald-500/20' : 'bg-slate-100 text-slate-400 border border-slate-200'}`}>
{student.name.charAt(0)}
</div>
<div>
<p className={`text-sm font-black uppercase tracking-tight ${student.present ? 'text-emerald-700' : 'text-slate-900'}`}>{student.name}</p>
<p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mt-0.5">{student.roll}</p>
</div>
</div>
</td>
<td className="px-8 py-5">
<div className="flex flex-col gap-0.5">
<p className="text-[9px] font-black text-slate-700 uppercase tracking-wider">{student.dept}</p>
<p className="text-[9px] font-black text-slate-400 uppercase tracking-widest">{student.year}</p>
</div>
</td>
<td className="px-8 py-5 text-right">
{student.present ? (
<div className="flex items-center justify-center gap-2 py-3 bg-emerald-500 text-white rounded-xl text-[10px] font-black uppercase tracking-[0.2em] shadow-lg shadow-emerald-500/20">
<i className="fas fa-check-circle"></i> PRESENT
</div>
) : (
<button
onClick={() => selectedEvent?.created_by === currentUserId && !isFinalized && toggleAttendance(student.id)}
disabled={selectedEvent?.created_by !== currentUserId || isFinalized}
className={`w-full px-6 py-2.5 bg-white border border-slate-200 text-slate-400 ${(selectedEvent?.created_by === currentUserId && !isFinalized) ? 'hover:border-emerald-300 hover:text-emerald-600 hover:bg-emerald-50 cursor-pointer' : 'opacity-50 cursor-not-allowed'} rounded-xl font-black uppercase text-[8px] tracking-[0.2em] transition-all active:scale-95 shadow-sm`}
>
{selectedEvent?.created_by !== currentUserId ? "READ ONLY" : isFinalized ? "FINALIZED" : "MARK PRESENT"}
</button>
)}
</td>
</tr>
)) : (
<tr>
<td colSpan={4} className="py-20 text-center">
<div className="flex flex-col items-center">
<i className="fas fa-filter-circle-xmark text-slate-200 text-3xl mb-4"></i>
<h5 className="text-xs font-black uppercase text-slate-400 tracking-widest">No matching students</h5>
<button onClick={clearAllFilters} className="mt-2 text-[#004a99] font-black text-[9px] uppercase tracking-widest hover:underline">Reset Filters</button>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="p-10 bg-slate-50 border-t border-slate-200 flex flex-col md:flex-row items-center justify-between gap-6">
<div className="flex items-center gap-6">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-emerald-100 flex items-center justify-center"><i className="fas fa-cloud-check text-emerald-600"></i></div>
<p className="text-[9px] font-black text-slate-500 uppercase tracking-widest leading-tight">Roster synced<br/>with database</p>
</div>
<button
onClick={handleDownloadExcel}
className="px-6 py-3 bg-white border border-slate-200 rounded-xl text-slate-600 text-[10px] font-black uppercase tracking-widest hover:border-blue-300 hover:text-[#004a99] transition-all flex items-center gap-3 shadow-sm"
>
<i className="fas fa-file-excel"></i> Export CSV
</button>
</div>
<button
onClick={handleFinalize}
disabled={isSyncing || selectedEvent?.created_by !== currentUserId || isFinalized}
className="w-full md:w-auto px-12 py-5 bg-[#004a99] text-white rounded-2xl font-black uppercase tracking-[0.3em] text-xs hover:bg-blue-800 transition-all shadow-md active:scale-95 flex items-center justify-center gap-4 disabled:opacity-50"
>
{isSyncing ? 'Syncing...' : (selectedEvent?.created_by !== currentUserId ? "Read Only View" : isFinalized ? <>Roster Locked <i className="fas fa-lock"></i></> : <>Finalize {sessionLabel} <i className="fas fa-cloud-arrow-up"></i></>)}
</button>
</div>
</div>
</div>
)}
{showFilters && createPortal(
<div className="fixed inset-0 z-[10000] flex justify-end">
<div
className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm animate-in fade-in duration-300"
onClick={() => setShowFilters(false)}
/>
<div className="relative w-full max-w-md bg-white h-full shadow-2xl flex flex-col animate-in slide-in-from-right duration-500 border-l border-slate-200">
<div className="p-8 border-b border-slate-100 flex justify-between items-center bg-slate-50">
<div>
<h4 className="text-xl font-black text-slate-900 uppercase tracking-tight">Refine Roster</h4>
<p className="text-[9px] font-bold text-[#004a99] uppercase tracking-widest mt-1">Multi-Category Filtering</p>
</div>
<button
onClick={() => setShowFilters(false)}
className="w-10 h-10 rounded-full bg-white border border-slate-200 flex items-center justify-center text-slate-400 hover:text-slate-900 transition-all shadow-sm"
>
<i className="fas fa-times"></i>
</button>
</div>
<div className="flex-1 flex overflow-hidden">
<div className="w-32 bg-slate-50/50 border-r border-slate-100">
{[
{ id: 'DEPT' as const, label: 'Department' },
{ id: 'YEAR' as const, label: 'Year' }
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`w-full py-6 px-4 text-left relative transition-all ${activeTab === tab.id
? 'bg-blue-50 text-blue-800'
: 'text-slate-500 hover:text-slate-700'
}`}
>
<span className="text-[10px] font-black uppercase tracking-widest">{tab.label}</span>
{activeTab === tab.id && <div className="absolute right-0 top-0 bottom-0 w-1 bg-[#004a99]" />}
</button>
))}
</div>
<div className="flex-1 p-8 overflow-y-auto custom-scrollbar">
{activeTab === 'DEPT' ? (
<div className="space-y-4">
{filterOptions.depts.map(dept => (
<label key={dept} className="flex items-center gap-4 group cursor-pointer" onClick={() => toggleFilterValue('depts', dept)}>
<div
className={`w-6 h-6 rounded-lg border-2 flex items-center justify-center transition-all ${activeFilters.depts.includes(dept)
? 'bg-[#004a99] border-[#004a99] text-white shadow-sm'
: 'border-slate-200 bg-white group-hover:border-blue-400'
}`}
>
{activeFilters.depts.includes(dept) && <i className="fas fa-check text-[10px] text-white"></i>}
</div>
<span className={`text-xs font-black uppercase tracking-widest transition-colors ${activeFilters.depts.includes(dept) ? 'text-slate-900' : 'text-slate-500'
}`}>
{dept}
</span>
</label>
))}
</div>
) : (
<div className="space-y-4">
{filterOptions.years.map(year => (
<label key={year} className="flex items-center gap-4 group cursor-pointer" onClick={() => toggleFilterValue('years', year)}>
<div
className={`w-6 h-6 rounded-lg border-2 flex items-center justify-center transition-all ${activeFilters.years.includes(year)
? 'bg-[#004a99] border-[#004a99] text-white shadow-sm'
: 'border-slate-200 bg-white group-hover:border-blue-400'
}`}
>
{activeFilters.years.includes(year) && <i className="fas fa-check text-[10px] text-white"></i>}
</div>
<span className={`text-xs font-black uppercase tracking-widest transition-colors ${activeFilters.years.includes(year) ? 'text-slate-900' : 'text-slate-500'
}`}>
{year}
</span>
</label>
))}
</div>
)}
</div>
</div>
<div className="p-8 border-t border-slate-100 bg-slate-50 flex gap-4">
<button
onClick={clearAllFilters}
className="flex-1 py-4 bg-white border border-slate-200 rounded-2xl text-[10px] font-black text-slate-500 uppercase tracking-widest hover:bg-slate-100 transition-all shadow-sm"
>
Clear All
</button>
<button
onClick={() => setShowFilters(false)}
className="flex-1 py-4 bg-[#004a99] text-white rounded-2xl text-[10px] font-black uppercase tracking-widest hover:bg-blue-800 transition-all shadow-md active:scale-95"
>
Apply Filter
</button>
</div>
</div>
</div>,
document.body
)}
</div>
);
};
export default FacultyAttendanceView;