import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { toPng } from 'html-to-image'; import { Event } from '../types'; import { supabase } from '../supabase'; import { CLUBS } from '../constants'; interface EventCardProps { event: Event; isBooked: boolean; onToggle: () => void; onTrackStatus?: (event: Event) => void; currentUserName?: string; userRole?: string; registration?: any; } const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => { return createPortal(children, document.body); }; const EventCard: React.FC = ({ event, isBooked, onToggle, onTrackStatus, currentUserName = 'Student', userRole, registration }) => { const [showConfirm, setShowConfirm] = useState(false); const clubInfo = useMemo(() => CLUBS.find(c => c.name === event.club), [event.club]); const [showCancelConfirm, setShowCancelConfirm] = useState(false); const [showTicket, setShowTicket] = useState(false); const [showSummary, setShowSummary] = useState(false); const [userDept, setUserDept] = useState(''); const [userYear, setUserYear] = useState(''); const [userSection, setUserSection] = useState(''); const [now, setNow] = useState(new Date()); const [isDownloading, setIsDownloading] = useState(false); const [isSaving, setIsSaving] = useState(false); const ticketRef = useRef(null); const [persistedTicket, setPersistedTicket] = useState<{id: string, qr: string} | null>(null); useEffect(() => { if (showTicket && registration?.id) { const initializeTicket = async () => { // 1. Check if already persisted if (registration.ticket_id && registration.ticket_qrcode) { setPersistedTicket({ id: registration.ticket_id, qr: registration.ticket_qrcode }); return; } // 2. Otherwise generate and save try { const newId = `RIT-EVT-${Math.random().toString(36).substring(2, 6).toUpperCase()}-${registration.id.substring(0, 4).toUpperCase()}`; const newQr = `${window.location.origin}/?verify=${registration.id}`; const { error } = await supabase .from('registrations') .update({ ticket_id: newId, ticket_qrcode: newQr }) .eq('id', registration.id); if (!error) { setPersistedTicket({ id: newId, qr: newQr }); } } catch (err) { console.error("Ticket initialization failed:", err); } }; initializeTicket(); } }, [showTicket, registration?.id]); useEffect(() => { const timer = setInterval(() => setNow(new Date()), 1000); supabase.auth.getUser().then(({ data: { user } }) => { if (user) { supabase.from('Studentusers').select('department, year, section').eq('id', user.id).single() .then(({ data }) => { if (data?.department) { setUserDept(data.department); setUserYear(data.year || ''); setUserSection(data.section || ''); } else { supabase.from('externalusers').select('department, year, section').eq('id', user.id).single() .then(({ data: extData }) => { if (extData?.department) { setUserDept(extData.department); setUserYear(extData.year || ''); setUserSection(extData.section || ''); } }); } }); } }); return () => clearInterval(timer); }, []); const admissionStatus = useMemo(() => { if (event.status === 'Completed') return 'EVENT_ENDED'; if (event.registrationDeadline) { const deadline = new Date(event.registrationDeadline); if (now > deadline) return 'DEADLINE_PASSED'; } if (userDept && event.deptLimits?.[userDept]) { const sectionLimits = event.deptSectionLimits?.[userDept]; const hasSectionLimits = sectionLimits && Object.keys(sectionLimits).length > 0; if (hasSectionLimits) { const limit = sectionLimits[userSection]; if (!limit || limit <= 0) { return 'SECTION_NOT_ALLOWED'; } const currentSectionCount = event.currentDeptSectionCounts?.[userDept]?.[userSection] || 0; if (currentSectionCount >= limit) { return 'SECTION_FULL'; } } else { const currentDeptCount = event.currentDeptCounts?.[userDept] || 0; if (currentDeptCount >= event.deptLimits[userDept]) return 'DEPT_FULL'; } } if (event.maxParticipants && (event.currentParticipants || 0) >= event.maxParticipants) { return 'TOTAL_FULL'; } return 'OPEN'; }, [event, now, userDept, userSection]); const timeLeft = useMemo(() => { const targetDate = new Date(event.date).getTime(); const distance = targetDate - now.getTime(); if (isNaN(targetDate) || distance <= 0) return { d: '00', h: '00', m: '00', s: '00' }; return { d: Math.floor(distance / (1000 * 60 * 60 * 24)).toString().padStart(2, '0'), h: Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)).toString().padStart(2, '0'), m: Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)).toString().padStart(2, '0'), s: Math.floor((distance % (1000 * 60)) / 1000).toString().padStart(2, '0') }; }, [event.date, now]); const handleDownload = async () => { if (!ticketRef.current) return; setIsDownloading(true); try { const dataUrl = await toPng(ticketRef.current, { cacheBust: true, quality: 1, backgroundColor: '#1e293b' }); const link = document.createElement('a'); link.download = `Pass_${event.title.replace(/\s+/g, '_')}.png`; link.href = dataUrl; link.click(); } catch (err) { console.error("Capture failed:", err); } finally { setIsDownloading(false); } }; const verificationLink = `${window.location.origin}/?verify=local_${event.id}`; return (
{event.title}
{event.category}
{event.registrationDeadline && (
{admissionStatus === 'DEADLINE_PASSED' ? 'Closed' : `Ends ${new Date(event.registrationDeadline).toLocaleDateString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}`}
)}
{clubInfo && (
{clubInfo.name} {clubInfo.name}
)}

{event.title}

{event.pricingType}
{event.date}{event.schedule?.[0]?.start_time ? ` • ${event.schedule[0].start_time}` : ''}
{event.durationDays && event.durationDays > 1 && (
Duration: {event.durationDays} Days
)}
{event.location}
{event.event_summary && ( )}
{event.schedule && event.schedule.length > 0 && (() => { // Group schedule entries by day_idx const dayMap = new Map(); event.schedule!.forEach(s => { if (!dayMap.has(s.day_idx)) dayMap.set(s.day_idx, []); dayMap.get(s.day_idx)!.push(s); }); const sortedDays = Array.from(dayMap.entries()).sort((a, b) => a[0] - b[0]); return (
Event Schedule
{sortedDays.map(([dayIdx, slots]) => (
Day {dayIdx} {slots![0]?.date}
{slots!.map((slot) => (
Batch {slot.batch_idx} {slot.start_time} - {slot.end_time}
))}
))}
); })()}
Global Seats {event.currentParticipants || 0} / {event.maxParticipants || (event.deptLimits && Object.keys(event.deptLimits).length > 0 ? Object.values(event.deptLimits).map(Number).reduce((a, b) => a + b, 0) : '∞')}
{userDept && event.deptLimits?.[userDept] && (() => { const sectionLimits = event.deptSectionLimits?.[userDept]; const hasSectionLimits = sectionLimits && Object.keys(sectionLimits).length > 0; if (hasSectionLimits) { const limit = sectionLimits[userSection] || 0; const current = event.currentDeptSectionCounts?.[userDept]?.[userSection] || 0; return (
{userDept} Sec {userSection || 'N/A'} Allocation {limit > 0 ? `${current} / ${limit}` : 'RESTRICTED'}
); } return (
{userDept} Allocation {event.currentDeptCounts?.[userDept] || 0} / {event.deptLimits[userDept]}
); })()}
{[ {l: 'D', v: timeLeft.d}, {l: 'H', v: timeLeft.h}, {l: 'M', v: timeLeft.m}, {l: 'S', v: timeLeft.s} ].map((t, i) => (
{t.v} {t.l}
))}
{userRole === 'ADMIN' || userRole === 'COORDINATOR' ? ( ) : isBooked ? (
{event.status !== 'Completed' && ( )} {event.status !== 'Event Ongoing' && event.status !== 'Completed' && ( )}
) : admissionStatus === 'OPEN' ? ( ) : ( )}
{showConfirm && (

{event.isTeamEvent ? 'Team Registration' : 'Confirm Pass?'}

{event.isTeamEvent ? (

Important Notice

This is a team-based event. You are about to register as an individual, after which you must create or join a team in the registrations section to participate.

Max Size {event.teamSizeLimit || '∞'} Members
Type {event.teamComposition === 'INTER_DEPT' ? 'Inter-Dept' : 'Mixed'}

Proceed with registration for "{event.title}"?

) : (

Register for "{event.title}"?

)}
)} {showCancelConfirm && (

Cancel Booking?

Are you sure you want to cancel your registration for "{event.title}"?

)} {showTicket && (
{/* Header - Now White Theme as Requested */}
RIT Logo
Secure Entry Pass
QR {persistedTicket?.id && (
{persistedTicket.id}
)}

{event.title}

Attendee

{currentUserName}

Year / Section

{registration?.year || userYear || 'N/A'} - {registration?.section || userSection || 'N/A'}

{event.isTeamEvent && registration?.team_name && (
Team Identity

{registration.team_name}

)}
Event Date

{event.date.split(',')[0]}

Dept

{registration?.dept || userDept || 'Student'}

Digital Ticket ID: {persistedTicket?.id || 'GENERATING...'}

)} {showSummary && (
{clubInfo && ( )}
{event.club || 'Organized by'} {event.coordinator}

Summary

{event.event_summary || "Details for this session will be provided soon."}

)}
); }; export default EventCard;