Files
Event-Management-System/RIT-EVENT-MANAGEMENT--main/components/FacultyParticipantsView.tsx

639 lines
34 KiB
TypeScript

import React, { useState, useMemo, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { Event } from '../types';
import { CATEGORIES, DOMAIN_MAP } from '../constants';
import { supabase } from '../supabase';
import * as XLSX from 'xlsx-js-style';
type ParticipantsSubView = 'CATEGORIES' | 'DOMAINS' | 'EVENTS' | 'STUDENTS';
interface FacultyParticipantsViewProps {
events: Event[];
localRegistrations?: any[];
currentUserId?: string;
}
const FacultyParticipantsView: React.FC<FacultyParticipantsViewProps> = ({ events, localRegistrations = [], currentUserId }) => {
const [currentView, setCurrentView] = useState<ParticipantsSubView>('STUDENTS');
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [selectedDomain, setSelectedDomain] = useState<string | null>(null);
const [selectedEventId, setSelectedEventId] = useState<string | null>(null);
const [searchTerm, setSearchTerm] = useState('');
const [showEventFilter, setShowEventFilter] = useState(false);
const [showSideFilter, setShowSideFilter] = useState(false);
const [selectedDepts, setSelectedDepts] = useState<string[]>([]);
const [selectedYears, setSelectedYears] = useState<string[]>([]);
const [activeFilterTab, setActiveFilterTab] = useState<'DEPT' | 'YEAR'>('DEPT');
const [registrations, setRegistrations] = useState<any[]>(localRegistrations);
const [isLoading, setIsLoading] = useState(true);
const [selectedStudentIds, setSelectedStudentIds] = useState<Set<string>>(new Set());
const [studentTypeFilter, setStudentTypeFilter] = useState<'INTERNAL' | 'EXTERNAL'>('INTERNAL');
const [externalUserIds, setExternalUserIds] = useState<Set<string>>(new Set());
// Fetch registrations directly from Supabase for reliability
useEffect(() => {
const fetchRegistrations = async () => {
setIsLoading(true);
let { data, error } = await supabase
.from('participants')
.select('*')
.order('created_at', { ascending: false });
if (error) {
// Fallback to registrations if participants table doesn't exist yet
const fallback = await supabase
.from('registrations')
.select('*')
.order('registered_at', { ascending: false });
data = fallback.data;
error = fallback.error;
}
if (data && !error) {
setRegistrations(data);
} else if (localRegistrations.length > 0) {
setRegistrations(localRegistrations);
}
const { data: extUsers } = await supabase.from('externalusers').select('id');
if (extUsers) {
setExternalUserIds(new Set(extUsers.map(u => u.id)));
}
setIsLoading(false);
};
fetchRegistrations();
}, []);
const departments = ['CSBS', 'AIDS', 'AIML', 'ECE', 'VLSI', 'H&S', 'CCE', 'CSE', 'MECH', 'BIO-TECH', 'Information Technology (IT)', 'Electrical & Electronics Engineering (EEE)', 'Civil Engineering', 'Biomedical Engineering', 'Chemical Engineering', 'Aeronautical / Aerospace Engineering', 'Mechatronics Engineering', 'Others'];
const years = ['1st Year', '2nd Year', '3rd Year', '4th Year', '5th Year'];
const handleCategorySelect = (id: string) => {
setSelectedCategory(id);
setCurrentView('DOMAINS');
};
const handleDomainSelect = (id: string) => {
setSelectedDomain(id);
setCurrentView('EVENTS');
};
const handleEventSelect = (id: string) => {
setSelectedEventId(id);
setCurrentView('STUDENTS');
};
const handleBack = () => {
if (currentView === 'STUDENTS') {
setSelectedEventId(null);
setCurrentView('EVENTS');
} else if (currentView === 'EVENTS') {
setSelectedDomain(null);
setCurrentView('DOMAINS');
} else if (currentView === 'DOMAINS') {
setSelectedCategory(null);
setCurrentView('CATEGORIES');
}
};
const filteredParticipants = useMemo(() => {
return registrations.filter(r => {
const userName = r.user_name || '';
const regNo = r.reg_no || '';
const searchLower = searchTerm.toLowerCase();
const matchesSearch = !searchTerm ||
userName.toLowerCase().includes(searchLower) ||
regNo.toLowerCase().includes(searchLower);
const matchesEvent = !selectedEventId || String(r.event_id) === String(selectedEventId);
const matchesDept = selectedDepts.length === 0 || (r.dept && selectedDepts.includes(r.dept));
const matchesYear = selectedYears.length === 0 || (r.year && selectedYears.includes(r.year));
const isExternal = r.college && r.college !== 'RAJALAKSHMI INSTITUTE OF TECHNOLOGY' && r.college !== 'Rajalakshmi Institute of Technology';
const matchesType = studentTypeFilter === 'EXTERNAL' ? isExternal : !isExternal;
return matchesSearch && matchesEvent && matchesDept && matchesYear && matchesType;
});
}, [registrations, selectedEventId, searchTerm, selectedDepts, selectedYears, studentTypeFilter, externalUserIds]);
const getEventTitle = (eventId: string) => {
return events.find(e => e.id === eventId)?.title || 'Unknown Event';
};
const handleExportSelected = () => {
if (selectedStudentIds.size === 0) return;
const selectedData = filteredParticipants.filter(p => selectedStudentIds.has(p.id));
// Formatting data for Excel
const formattedData = selectedData.map(p => ({
'Student Name': p.user_name || 'N/A',
'Reg No': String(p.reg_no || 'N/A'),
'Gender': p.gender || 'N/A',
'College': p.college || 'RIT',
'Department': p.dept || 'N/A',
'Section': p.section || 'N/A',
'Year': p.year || 'N/A',
'Email': p.user_id?.includes('@') ? p.user_id : (p.user_email || 'N/A'),
'Phone': String(p.phone || 'N/A'),
'Event': p.event_name || getEventTitle(String(p.event_id)),
'Team': p.team_name || 'N/A',
'Payment Status': p.payment_status || 'PENDING',
'Cert Status': p.certification_status === 'APPROVED' ? 'VERIFIED' : (p.certification_url ? 'PENDING' : 'NO CERT'),
'OD Status': p.od_url ? 'OD READY' : 'NO OD'
}));
// Create worksheet
const worksheet = XLSX.utils.json_to_sheet(formattedData);
// Auto-fit columns
if (formattedData.length > 0) {
const keys = Object.keys(formattedData[0]);
const wscols = keys.map(key => ({
wch: Math.max(
key.length,
...formattedData.map(row => String(row[key as keyof typeof row] || '').length)
) + 2
}));
worksheet['!cols'] = wscols;
}
// Create workbook and export
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Participants');
XLSX.writeFile(workbook, `Participants_Export_${new Date().getTime()}.xlsx`);
};
return (
<div className="animate-in slide-in-from-bottom-10 duration-500 pt-12">
<div className="flex flex-col md:flex-row md:items-end justify-between mb-8 gap-8 border-b border-slate-200 pb-8">
<div>
<h3 className="text-4xl font-black tracking-tighter uppercase mb-2 text-slate-900">
SYSTEM <span className="text-[#004a99]">REGISTRY</span>
</h3>
<p className="text-[10px] font-black text-slate-500 uppercase tracking-[0.4em]">Live Database Dossier Sync</p>
</div>
<div className="flex items-center gap-4">
{currentView !== 'CATEGORIES' && (
<button
onClick={handleBack}
className="text-slate-600 font-black uppercase tracking-widest text-[10px] bg-slate-50 px-6 py-3 rounded-full border border-slate-200 hover:bg-slate-100 hover:text-slate-900 shadow-sm transition-all"
>
Back
</button>
)}
</div>
</div>
<div className="mb-10 flex flex-col md:flex-row gap-4">
<div className="relative flex-1">
<i className="fas fa-search absolute left-6 top-1/2 -translate-y-1/2 text-slate-400"></i>
<input
type="text"
placeholder="Search registry..."
className="w-full bg-white border border-slate-200 rounded-2xl pl-14 pr-6 py-4 text-slate-900 focus:ring-2 focus:ring-[#004a99] outline-none font-bold placeholder:text-slate-400 transition-all shadow-sm"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="relative">
<button
onClick={() => setShowEventFilter(!showEventFilter)}
className="h-full px-6 py-4 bg-white border border-slate-200 rounded-2xl flex items-center gap-3 text-slate-700 font-bold hover:bg-slate-50 hover:border-slate-300 transition-all min-w-[240px] justify-between shadow-sm"
>
<div className="flex items-center gap-3">
<i className="fas fa-filter text-[#004a99] text-xs"></i>
<span className="text-xs uppercase tracking-widest truncate max-w-[150px]">
{selectedEventId ? events.find(e => e.id === selectedEventId)?.title : 'All Events'}
</span>
</div>
<i className={`fas fa-chevron-down text-[10px] transition-transform ${showEventFilter ? 'rotate-180' : ''}`}></i>
</button>
{showEventFilter && (
<div className="absolute top-full right-0 mt-2 w-80 bg-white border border-slate-200 rounded-2xl shadow-xl z-50 py-2 max-h-[400px] overflow-y-auto custom-scrollbar animate-in fade-in slide-in-from-top-2">
<button
onClick={() => { setSelectedEventId(null); setShowEventFilter(false); setCurrentView('STUDENTS'); }}
className={`w-full text-left px-6 py-3 text-[10px] font-black uppercase tracking-widest hover:bg-slate-50 transition-all ${!selectedEventId ? 'text-[#004a99] bg-blue-50/50' : 'text-slate-600'}`}
>
All Events
</button>
<div className="h-px bg-slate-100 my-2" />
{events.map(event => (
<button
key={event.id}
onClick={() => { setSelectedEventId(event.id); setShowEventFilter(false); setCurrentView('STUDENTS'); }}
className={`w-full text-left px-6 py-3 text-[10px] font-black uppercase tracking-widest hover:bg-slate-50 transition-all ${selectedEventId === event.id ? 'text-[#004a99] bg-blue-50/50' : 'text-slate-600'}`}
>
{event.title}
</button>
))}
</div>
)}
</div>
<button
onClick={() => setShowSideFilter(true)}
className="px-6 py-4 bg-white border border-slate-200 rounded-2xl text-slate-700 font-black uppercase text-[10px] tracking-widest hover:bg-slate-50 transition-all flex items-center gap-2 relative shadow-sm"
>
<i className="fas fa-sliders-h text-[#004a99]"></i>
Filters
{(selectedDepts.length > 0 || selectedYears.length > 0) && (
<span className="absolute -top-1 -right-1 w-4 h-4 bg-[#004a99] rounded-full text-[8px] flex items-center justify-center text-white shadow-md">
{selectedDepts.length + selectedYears.length}
</span>
)}
</button>
<button
onClick={() => {
setCurrentView(currentView === 'CATEGORIES' ? 'STUDENTS' : 'CATEGORIES');
setSelectedEventId(null);
setSearchTerm('');
}}
className="px-6 py-4 bg-[#004a99] text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-blue-800 transition-all flex items-center gap-2 shadow-md hover:shadow-lg active:scale-95"
>
<i className={`fas ${currentView === 'CATEGORIES' ? 'fa-list' : 'fa-grid-2'}`}></i>
{currentView === 'CATEGORIES' ? 'List View' : 'Browse System'}
</button>
</div>
<div className="flex gap-4 mb-8">
<button
onClick={() => setStudentTypeFilter('INTERNAL')}
className={`flex-1 py-4 rounded-2xl border transition-all flex items-center justify-center gap-3 ${studentTypeFilter === 'INTERNAL' ? 'bg-blue-50 border-[#004a99] text-[#004a99] shadow-sm' : 'bg-white border-slate-200 text-slate-500 hover:border-blue-300'}`}
>
<i className="fas fa-university text-sm"></i>
<span className="font-black uppercase tracking-widest text-[10px]">Internal Students</span>
</button>
<button
onClick={() => setStudentTypeFilter('EXTERNAL')}
className={`flex-1 py-4 rounded-2xl border transition-all flex items-center justify-center gap-3 ${studentTypeFilter === 'EXTERNAL' ? 'bg-orange-50 border-orange-500 text-orange-600 shadow-sm' : 'bg-white border-slate-200 text-slate-500 hover:border-orange-300'}`}
>
<i className="fas fa-globe text-sm"></i>
<span className="font-black uppercase tracking-widest text-[10px]">External Students</span>
</button>
</div>
{currentView === 'CATEGORIES' && !searchTerm && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
{CATEGORIES.map((cat) => (
<div key={cat.id} onClick={() => handleCategorySelect(cat.id)} className="group relative h-[400px] rounded-[3rem] overflow-hidden cursor-pointer bg-slate-100 border border-slate-200 hover:border-blue-300 hover:shadow-xl transition-all shadow-sm">
<img src={cat.image} className="w-full h-full object-cover opacity-60 group-hover:scale-105 transition-transform duration-700" alt={cat.name} />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div className="absolute bottom-10 left-10">
<h4 className="text-3xl font-black text-white uppercase tracking-tighter">{cat.name}</h4>
</div>
</div>
))}
</div>
)}
{currentView === 'DOMAINS' && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
{(DOMAIN_MAP[selectedCategory!] || []).map((domain) => (
<div key={domain.id} onClick={() => handleDomainSelect(domain.id)} className="group relative h-[350px] rounded-[2.5rem] overflow-hidden cursor-pointer bg-slate-100 border border-slate-200 hover:border-blue-300 hover:shadow-xl transition-all shadow-sm">
<img src={domain.image} className="w-full h-full object-cover opacity-60 group-hover:scale-105 transition-transform duration-700" alt={domain.name} />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent"></div>
<div className="absolute bottom-10 left-10"><h4 className="text-2xl font-black text-white uppercase tracking-tight">{domain.name}</h4></div>
</div>
))}
</div>
)}
{currentView === 'EVENTS' && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{events.filter(e => (!selectedCategory || selectedCategory === 'ALL' || e.category === selectedCategory) && (!selectedDomain || selectedDomain === 'ALL' || e.domain === selectedDomain)).map((event) => (
<div key={event.id} onClick={() => handleEventSelect(event.id)} className="bg-white border border-slate-200 rounded-[2.5rem] p-8 cursor-pointer hover:border-blue-300 hover:shadow-md transition-all flex flex-col h-full shadow-sm">
<h4 className="text-xl font-black uppercase mb-4 text-slate-900 line-clamp-2">{event.title}</h4>
<div className="mt-auto flex items-center justify-between">
<p className="text-[10px] font-black text-slate-500 uppercase tracking-widest">
{registrations.filter(r => String(r.event_id) === String(event.id)).length} Enrolled
</p>
<div className="w-10 h-10 rounded-full bg-slate-50 flex items-center justify-center text-[#004a99] group-hover:bg-blue-50 transition-colors">
<i className="fas fa-arrow-right"></i>
</div>
</div>
</div>
))}
</div>
)}
<FilterDrawer
isOpen={showSideFilter}
onClose={() => setShowSideFilter(false)}
activeTab={activeFilterTab}
setActiveTab={setActiveFilterTab}
departments={departments}
years={years}
selectedDepts={selectedDepts}
setSelectedDepts={setSelectedDepts}
selectedYears={selectedYears}
setSelectedYears={setSelectedYears}
/>
{(currentView === 'STUDENTS' || searchTerm) && (
<div className="space-y-8">
{isLoading ? (
<div className="col-span-full py-32 flex flex-col items-center justify-center text-center bg-white border border-dashed border-slate-200 rounded-[3rem]">
<div className="w-16 h-16 border-4 border-blue-100 border-t-[#004a99] rounded-full animate-spin mb-6"></div>
<p className="text-slate-400 font-black uppercase tracking-[0.3em] text-sm">Loading Registrations...</p>
</div>
) : (
<div className="bg-white border border-slate-200 rounded-[2.5rem] overflow-hidden shadow-sm">
<div className="overflow-x-auto custom-scrollbar">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50 border-b border-slate-200">
<th className="px-8 py-6 w-10">
<input
type="checkbox"
className="w-4 h-4 rounded border-slate-300 text-[#004a99] focus:ring-[#004a99] cursor-pointer"
checked={filteredParticipants.length > 0 && filteredParticipants.every(p => selectedStudentIds.has(p.id))}
onChange={(e) => {
if (e.target.checked) {
setSelectedStudentIds(new Set(filteredParticipants.map(p => p.id)));
} else {
setSelectedStudentIds(new Set());
}
}}
/>
</th>
<th className="px-8 py-6 text-[10px] font-black tracking-widest text-slate-400 uppercase whitespace-nowrap">Student</th>
<th className="px-8 py-6 text-[10px] font-black tracking-widest text-slate-400 uppercase whitespace-nowrap">Event Info</th>
<th className="px-8 py-6 text-[10px] font-black tracking-widest text-slate-400 uppercase whitespace-nowrap">Academic</th>
<th className="px-8 py-6 text-[10px] font-black tracking-widest text-slate-400 uppercase whitespace-nowrap">Contact</th>
<th className="px-8 py-6 text-[10px] font-black tracking-widest text-slate-400 uppercase whitespace-nowrap text-center">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100">
{filteredParticipants.length > 0 ? filteredParticipants.map((p) => (
<tr key={p.id} className="hover:bg-slate-50/50 transition-colors group">
<td className="px-8 py-6 w-10">
<input
type="checkbox"
className="w-4 h-4 rounded border-slate-300 text-[#004a99] focus:ring-[#004a99] cursor-pointer"
checked={selectedStudentIds.has(p.id)}
onChange={(e) => {
const newSet = new Set(selectedStudentIds);
if (e.target.checked) newSet.add(p.id);
else newSet.delete(p.id);
setSelectedStudentIds(newSet);
}}
/>
</td>
<td className="px-8 py-6 whitespace-nowrap">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-2xl bg-blue-50 text-[#004a99] flex items-center justify-center font-black text-lg border border-blue-100 shadow-sm">
{(p.user_name || 'N').charAt(0).toUpperCase()}
</div>
<div>
<div className="flex items-center gap-2">
<p className="text-sm font-black text-slate-900 uppercase tracking-tight group-hover:text-[#004a99] transition-colors">{p.user_name || 'N/A'}</p>
{(externalUserIds.has(p.user_id) || !!p.college) && (
<span className="bg-orange-100 text-orange-600 text-[7px] font-black px-1.5 py-0.5 rounded-md border border-orange-200 uppercase tracking-tighter">External</span>
)}
</div>
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">{p.reg_no || 'N/A'}</p>
{p.college && (
<p className="text-[9px] font-black text-orange-500 uppercase tracking-widest mt-0.5 flex items-center gap-1">
<i className="fas fa-school text-[8px]"></i> {p.college}
</p>
)}
</div>
</div>
</td>
<td className="px-8 py-6 whitespace-nowrap">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-orange-50 text-orange-500 flex items-center justify-center border border-orange-100 shrink-0">
<i className="fas fa-calendar-alt text-[10px]"></i>
</div>
<p className="text-xs font-bold text-slate-700 truncate max-w-[200px] uppercase">{p.event_name || getEventTitle(String(p.event_id))}</p>
</div>
</td>
<td className="px-8 py-6 whitespace-nowrap">
<div className="flex flex-col gap-1.5">
<div className="flex items-center gap-2">
<i className="fas fa-university text-slate-400 text-[10px]"></i>
<p className="text-[10px] font-black text-slate-700 uppercase tracking-wider">{p.dept || 'N/A'} {p.section ? `• SEC ${p.section}` : ''}</p>
</div>
<div className="flex items-center gap-2 pl-[18px]">
<p className="text-[9px] font-bold text-slate-400 uppercase tracking-widest">{p.year || 'N/A'}</p>
</div>
</div>
</td>
<td className="px-8 py-6 whitespace-nowrap">
<div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-full bg-slate-100 text-slate-400 flex items-center justify-center shrink-0">
<i className="fas fa-envelope text-[9px]"></i>
</div>
<p className="text-[11px] font-bold text-slate-600 lowercase tracking-wide">
{p.user_id?.includes('@') ? p.user_id : (p.user_email || 'N/A')}
</p>
</div>
</td>
<td className="px-8 py-6 whitespace-nowrap">
<div className="flex flex-col gap-1.5 items-center justify-center">
<span className={`text-[7px] font-black px-2 py-1 rounded-md uppercase tracking-widest border w-24 text-center shadow-sm ${p.payment_status === 'COMPLETED' ? 'bg-emerald-50 text-emerald-600 border-emerald-200' : 'bg-rose-50 text-rose-600 border-rose-200'}`}>
<i className={`fas mr-1 ${p.payment_status === 'COMPLETED' ? 'fa-check' : 'fa-clock'}`}></i> {p.payment_status || 'PENDING'}
</span>
<span className={`text-[7px] font-black px-2 py-1 rounded-md uppercase tracking-widest border w-24 text-center shadow-sm ${p.certification_status === 'APPROVED' ? 'bg-blue-50 text-[#004a99] border-blue-200' : (p.certification_url ? 'bg-amber-50 text-amber-600 border-amber-200' : 'bg-slate-50 text-slate-400 border-slate-200')}`}>
<i className={`fas mr-1 ${p.certification_status === 'APPROVED' ? 'fa-check-double' : (p.certification_url ? 'fa-spinner' : 'fa-certificate')}`}></i> {p.certification_status === 'APPROVED' ? 'VERIFIED' : (p.certification_url ? 'PENDING' : 'NO CERT')}
</span>
<span className={`text-[7px] font-black px-2 py-1 rounded-md uppercase tracking-widest border w-24 text-center shadow-sm ${p.od_url ? 'bg-teal-50 text-teal-600 border-teal-200' : 'bg-slate-50 text-slate-400 border-slate-200'}`}>
<i className={`fas mr-1 ${p.od_url ? 'fa-file-signature' : 'fa-file-excel'}`}></i> {p.od_url ? 'OD READY' : 'NO OD'}
</span>
</div>
</td>
</tr>
)) : (
<tr>
<td colSpan={6} className="px-8 py-32 text-center">
<div className="flex flex-col items-center justify-center">
<div className="w-20 h-20 bg-slate-50 border border-slate-200 rounded-full flex items-center justify-center mb-6 shadow-sm">
<i className="fas fa-search text-slate-300 text-2xl"></i>
</div>
<p className="text-slate-400 font-black uppercase tracking-[0.3em] text-sm">No active registrations for this scope</p>
<p className="text-slate-500 text-[10px] font-bold uppercase tracking-widest mt-2 mb-8">Try adjusting your search or filters</p>
<button
onClick={() => {
setSearchTerm('');
setSelectedEventId(null);
setSelectedDepts([]);
setSelectedYears([]);
}}
className="px-8 py-3 bg-white border border-slate-200 rounded-xl text-[#004a99] font-black uppercase text-[10px] tracking-widest hover:bg-slate-50 hover:border-slate-300 transition-all shadow-sm"
>
Clear All Filters
</button>
</div>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
)}
{selectedStudentIds.size > 0 && (
<div className="fixed bottom-10 left-1/2 -translate-x-1/2 z-[1000] flex justify-center animate-in slide-in-from-bottom-10 pointer-events-none">
<div className="bg-white/95 backdrop-blur-xl border border-slate-200 shadow-2xl rounded-full px-8 py-4 flex items-center gap-6 pointer-events-auto ring-1 ring-[#004a99]/10">
<span className="text-xs font-black text-slate-600 uppercase tracking-widest">
{selectedStudentIds.size} Selected
</span>
<button
onClick={handleExportSelected}
className="bg-emerald-600 text-white px-8 py-3 rounded-full text-[10px] font-black uppercase tracking-widest hover:bg-emerald-700 transition-all shadow-lg shadow-emerald-600/20 active:scale-95 flex items-center gap-2"
>
<i className="fas fa-file-excel"></i> Export as Excel
</button>
</div>
</div>
)}
</div>
)}
</div>
);
};
const FilterDrawer: React.FC<{
isOpen: boolean;
onClose: () => void;
activeTab: 'DEPT' | 'YEAR';
setActiveTab: (tab: 'DEPT' | 'YEAR') => void;
departments: string[];
years: string[];
selectedDepts: string[];
setSelectedDepts: (depts: string[]) => void;
selectedYears: string[];
setSelectedYears: (years: string[]) => void;
}> = ({
isOpen,
onClose,
activeTab,
setActiveTab,
departments,
years,
selectedDepts,
setSelectedDepts,
selectedYears,
setSelectedYears
}) => {
if (!isOpen) return null;
const toggleDept = (dept: string) => {
if (selectedDepts.includes(dept)) {
setSelectedDepts(selectedDepts.filter(d => d !== dept));
} else {
setSelectedDepts([...selectedDepts, dept]);
}
};
const toggleYear = (year: string) => {
if (selectedYears.includes(year)) {
setSelectedYears(selectedYears.filter(y => y !== year));
} else {
setSelectedYears([...selectedYears, year]);
}
};
const clearAll = () => {
setSelectedDepts([]);
setSelectedYears([]);
};
return createPortal(
<div className="fixed inset-0 z-[9999] flex justify-end">
<div className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm" onClick={onClose}></div>
<div className="relative w-full max-w-md bg-white h-full shadow-2xl flex flex-col animate-in slide-in-from-right duration-300">
<div className="p-8 border-b border-slate-100 flex items-center justify-between">
<div>
<h4 className="text-xl font-black text-slate-900 uppercase tracking-tight">Refine Results</h4>
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-1">Select multiple filters</p>
</div>
<button onClick={onClose} className="w-10 h-10 rounded-full bg-slate-50 border border-slate-200 flex items-center justify-center text-slate-400 hover:text-slate-900 hover:bg-slate-100 transition-all">
<i className="fas fa-times"></i>
</button>
</div>
<div className="flex-1 flex overflow-hidden">
{/* Sidebar Tabs */}
<div className="w-32 border-r border-slate-100 bg-slate-50/50">
<button
onClick={() => setActiveTab('DEPT')}
className={`w-full py-6 px-4 text-left relative transition-all ${activeTab === 'DEPT' ? 'bg-blue-50 text-blue-800' : 'text-slate-500 hover:text-slate-700 hover:bg-slate-100/50'}`}
>
<span className="text-[10px] font-black uppercase tracking-widest">Department</span>
{activeTab === 'DEPT' && <div className="absolute right-0 top-0 bottom-0 w-1 bg-[#004a99]"></div>}
</button>
<button
onClick={() => setActiveTab('YEAR')}
className={`w-full py-6 px-4 text-left relative transition-all ${activeTab === 'YEAR' ? 'bg-blue-50 text-blue-800' : 'text-slate-500 hover:text-slate-700 hover:bg-slate-100/50'}`}
>
<span className="text-[10px] font-black uppercase tracking-widest">Year</span>
{activeTab === 'YEAR' && <div className="absolute right-0 top-0 bottom-0 w-1 bg-[#004a99]"></div>}
</button>
</div>
{/* Options Content */}
<div className="flex-1 p-8 overflow-y-auto custom-scrollbar">
{activeTab === 'DEPT' ? (
<div className="space-y-4">
{departments.map(dept => (
<label key={dept} className="flex items-center gap-4 group cursor-pointer">
<div
onClick={() => toggleDept(dept)}
className={`w-6 h-6 rounded-lg border-2 flex items-center justify-center transition-all ${selectedDepts.includes(dept) ? 'bg-[#004a99] border-[#004a99] text-white' : 'border-slate-200 bg-white group-hover:border-blue-400'}`}
>
{selectedDepts.includes(dept) && <i className="fas fa-check text-[10px]"></i>}
</div>
<span className={`text-xs font-black uppercase tracking-widest transition-colors ${selectedDepts.includes(dept) ? 'text-slate-900' : 'text-slate-500'}`}>
{dept}
</span>
</label>
))}
</div>
) : (
<div className="space-y-4">
{years.map(year => (
<label key={year} className="flex items-center gap-4 group cursor-pointer">
<div
onClick={() => toggleYear(year)}
className={`w-6 h-6 rounded-lg border-2 flex items-center justify-center transition-all ${selectedYears.includes(year) ? 'bg-[#004a99] border-[#004a99] text-white' : 'border-slate-200 bg-white group-hover:border-blue-400'}`}
>
{selectedYears.includes(year) && <i className="fas fa-check text-[10px]"></i>}
</div>
<span className={`text-xs font-black uppercase tracking-widest transition-colors ${selectedYears.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={clearAll}
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={onClose}
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 Filters
</button>
</div>
</div>
</div>,
document.body
);
};
export default FacultyParticipantsView;