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

335 lines
13 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { UserRole, DashboardView, Event, Announcement, Ticket } from '../types';
import { supabase } from '../supabase';
import Navbar from './Navbar';
import Hero from './Hero';
import HomeDashboard from './HomeDashboard';
import EventList from './EventList';
import EventCard from './EventCard';
import ProfileView from './ProfileView';
import StatusTrackerView from './StatusTrackerView';
import RegistrationsView from './RegistrationsView';
import UpcomingEventsSlider from './UpcomingEventsSlider';
import AboutHubSection from './AboutHubSection';
import StatsSection from './StatsSection';
import AboutSection from './AboutSection';
import ContactSection from './ContactSection';
import Footer from './Footer';
const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return createPortal(children, document.body);
};
interface DashboardProps {
userRole: UserRole;
events: Event[];
announcements: Announcement[];
bookedEventIds: string[];
userRegistrations: any[];
onToggleBooking: (id: string) => void;
onLogout: () => void;
onUploadCertificate: (eventId: string, data: string) => Promise<string>;
studentDocuments: Record<string, string>;
onUploadDoc: (studentId: string, data: string) => void;
currentUserName: string;
onSupabaseError?: () => void;
onBackToCoordinatorHub?: () => void;
specialEvents: any[];
}
const Dashboard: React.FC<DashboardProps> = ({
userRole,
events,
announcements,
bookedEventIds,
userRegistrations,
onToggleBooking,
onLogout,
onUploadCertificate,
studentDocuments,
onUploadDoc,
currentUserName,
onSupabaseError,
onBackToCoordinatorHub,
specialEvents
}) => {
const [activeView, setActiveView] = useState<DashboardView>('HOME');
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [trackedEvent, setTrackedEvent] = useState<Event | null>(null);
// Team Management States
const [showCreateModal, setShowCreateModal] = useState<{eventId: string, title: string} | null>(null);
const [showJoinModal, setShowJoinModal] = useState<{eventId: string, title: string} | null>(null);
const [teamName, setTeamName] = useState('');
const [teamCode, setTeamCode] = useState('');
const [isProcessing, setIsProcessing] = useState(false);
const generateCode = () => Math.random().toString(36).substring(2, 8).toUpperCase();
const handleCreateTeam = async (eventId: string) => {
if (!teamName.trim()) return alert("Enter team name");
setIsProcessing(true);
try {
const code = generateCode();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const registrationId = `${user.id}_${eventId}`;
const { error } = await supabase.from('registrations').update({
team_code: code,
team_name: teamName.trim(),
is_team_leader: true
}).eq('id', registrationId);
if (error) throw error;
setShowCreateModal(null);
setTeamName('');
window.location.reload();
} catch (err) {
console.error(err);
alert("Failed to create team");
} finally {
setIsProcessing(false);
}
};
const handleJoinTeam = async (eventId: string) => {
if (!teamCode.trim()) return alert("Enter team code");
setIsProcessing(true);
try {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data: teamRegs, error: fetchErr } = await supabase
.from('registrations')
.select('*')
.eq('event_id', eventId)
.eq('team_code', teamCode.trim().toUpperCase());
if (fetchErr) throw fetchErr;
if (!teamRegs || teamRegs.length === 0) return alert("Invalid team code for this event");
const leader = teamRegs.find(r => r.is_team_leader);
const event = events.find(e => e.id === eventId);
if (event?.teamSizeLimit && teamRegs.length >= event.teamSizeLimit) {
return alert("Team is already full");
}
const { data: myProfile } = await supabase.from('Studentusers').select('department').eq('id', user.id).single();
if (event?.teamComposition === 'INTER_DEPT' && leader && myProfile?.department !== leader.dept) {
return alert(`This event requires INTER-DEPARTMENT teams. You must join a team from ${leader.dept}`);
}
const registrationId = `${user.id}_${eventId}`;
const { error: joinErr } = await supabase.from('registrations').update({
team_code: teamCode.trim().toUpperCase(),
team_name: leader?.team_name || 'Team',
is_team_leader: false
}).eq('id', registrationId);
if (joinErr) throw joinErr;
setShowJoinModal(null);
setTeamCode('');
window.location.reload();
} catch (err) {
console.error(err);
alert("Failed to join team");
} finally {
setIsProcessing(false);
}
};
useEffect(() => {
if (activeView !== 'EVENTS' && activeView !== 'STATUS_TRACKER') {
setSelectedCategory(null);
}
}, [activeView]);
const handleTrackStatus = (event: Event) => {
setTrackedEvent(event);
setActiveView('STATUS_TRACKER');
};
const renderContent = () => {
switch (activeView) {
case 'HOME':
return (
<>
<Hero events={events} />
<UpcomingEventsSlider events={events} />
<AboutHubSection />
<StatsSection events={events} />
<HomeDashboard
events={events}
announcements={announcements}
onNavigateToEvents={() => setActiveView('EVENTS')}
specialEvents={specialEvents}
/>
</>
);
case 'ABOUT':
return (
<div className="pt-16 min-h-screen bg-white">
<AboutSection />
</div>
);
case 'CONTACT':
return (
<div className="pt-16 min-h-screen bg-white">
<ContactSection />
</div>
);
case 'EVENTS':
return (
<EventList
events={events}
selectedCategory={selectedCategory}
bookedEventIds={bookedEventIds}
onToggleBooking={onToggleBooking}
onSelectCategory={setSelectedCategory}
onTrackStatus={handleTrackStatus}
currentUserName={currentUserName}
userRole={userRole}
userRegistrations={userRegistrations}
/>
);
case 'REGISTRATIONS':
return (
<RegistrationsView
events={events}
bookedEventIds={bookedEventIds}
userRegistrations={userRegistrations}
onToggleBooking={onToggleBooking}
onTrackStatus={handleTrackStatus}
currentUserName={currentUserName}
userRole={userRole}
/>
);
case 'PROFILE':
return <ProfileView onLogout={onLogout} onSupabaseError={onSupabaseError} />;
case 'STATUS_TRACKER':
const registration = userRegistrations.find(r => String(r.event_id) === String(trackedEvent?.id));
return trackedEvent ? (
<StatusTrackerView
event={trackedEvent}
registration={registration}
onBack={() => setActiveView('REGISTRATIONS')}
onUploadCertificate={(data) => onUploadCertificate(trackedEvent.id, data)}
onShowCreateTeam={() => setShowCreateModal({ eventId: trackedEvent.id, title: trackedEvent.title })}
onShowJoinTeam={() => setShowJoinModal({ eventId: trackedEvent.id, title: trackedEvent.title })}
/>
) : null;
default:
return null;
}
};
return (
<div className="min-h-screen bg-[#F3F4F6]">
<Navbar
activeView={activeView}
onViewChange={setActiveView}
onLogout={onLogout}
onBackToCoordinatorHub={onBackToCoordinatorHub}
/>
<main className="pb-20">
{renderContent()}
</main>
<Footer />
{/* Team Management Modals */}
{showCreateModal && (
<Portal>
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300">
<div className="bg-white rounded-[3rem] w-full max-w-md p-10 md:p-12 shadow-2xl animate-in zoom-in-95 duration-500 relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-2 bg-gradient-to-r from-orange-400 to-amber-400"></div>
<div className="w-16 h-16 flex items-center justify-center mx-auto mb-8">
<img
src="https://www.facultyplus.com/wp-content/uploads/2020/11/Rit-logo.png"
alt="RIT Logo"
className="w-full h-full object-contain"
/>
</div>
<h3 className="text-2xl font-black text-slate-900 text-center uppercase mb-2 tracking-tight">Form a Team</h3>
<p className="text-[10px] text-gray-400 text-center font-bold uppercase tracking-widest mb-10 line-clamp-1 px-4">{showCreateModal.title}</p>
<div className="space-y-8">
<div>
<label className="block text-[9px] font-black text-slate-400 uppercase tracking-[0.2em] mb-3 px-2">Team Identity</label>
<input
type="text"
placeholder="Enter team name..."
className="w-full bg-slate-50 border border-slate-200 rounded-[2rem] px-8 py-5 text-sm font-bold outline-none focus:ring-4 focus:ring-orange-100 focus:border-[#f97316] transition-all placeholder:text-slate-300"
value={teamName}
onChange={e => setTeamName(e.target.value)}
autoFocus
/>
</div>
<div className="flex flex-col gap-3">
<button
onClick={() => handleCreateTeam(showCreateModal.eventId)}
disabled={isProcessing}
className="w-full py-5 bg-[#f97316] text-white rounded-[2rem] font-black uppercase text-[11px] tracking-[0.2em] shadow-xl shadow-orange-200 hover:bg-[#ea580c] transition-all active:scale-95 disabled:opacity-50"
>
{isProcessing ? 'INITIALIZING...' : 'Establish Team & Code'}
</button>
<button onClick={() => setShowCreateModal(null)} className="w-full py-4 text-gray-400 font-black uppercase text-[9px] tracking-widest hover:text-slate-600 transition-colors">Dismiss</button>
</div>
</div>
</div>
</div>
</Portal>
)}
{showJoinModal && (
<Portal>
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300">
<div className="bg-white rounded-[3rem] w-full max-w-md p-10 md:p-12 shadow-2xl animate-in zoom-in-95 duration-500 relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-2 bg-gradient-to-r from-blue-400 to-indigo-400"></div>
<div className="w-16 h-16 flex items-center justify-center mx-auto mb-8">
<img
src="https://www.facultyplus.com/wp-content/uploads/2020/11/Rit-logo.png"
alt="RIT Logo"
className="w-full h-full object-contain"
/>
</div>
<h3 className="text-2xl font-black text-slate-900 text-center uppercase mb-2 tracking-tight">Join Alliance</h3>
<p className="text-[10px] text-gray-400 text-center font-bold uppercase tracking-widest mb-10 line-clamp-1 px-4">{showJoinModal.title}</p>
<div className="space-y-8">
<div>
<label className="block text-[9px] font-black text-slate-400 uppercase tracking-[0.2em] mb-3 px-2 text-center">Team Access Token</label>
<input
type="text"
placeholder="TOKEN"
className="w-full bg-slate-50 border border-slate-200 rounded-[2rem] px-5 py-6 text-center text-3xl font-black tracking-[0.4em] outline-none focus:ring-4 focus:ring-blue-100 focus:border-blue-500 uppercase transition-all placeholder:text-slate-200"
value={teamCode}
onChange={e => setTeamCode(e.target.value)}
maxLength={6}
autoFocus
/>
</div>
<div className="flex flex-col gap-3">
<button
onClick={() => handleJoinTeam(showJoinModal.eventId)}
disabled={isProcessing}
className="w-full py-5 bg-slate-900 text-white rounded-[2rem] font-black uppercase text-[11px] tracking-[0.2em] shadow-xl shadow-gray-200 hover:bg-black transition-all active:scale-95 disabled:opacity-50"
>
{isProcessing ? 'AUTHENTICATING...' : 'Validate & Join'}
</button>
<button onClick={() => setShowJoinModal(null)} className="w-full py-4 text-gray-400 font-black uppercase text-[9px] tracking-widest hover:text-slate-600 transition-colors">Dismiss</button>
</div>
</div>
</div>
</div>
</Portal>
)}
</div>
);
};
export default Dashboard;