updated the event card in the students dashboard
This commit is contained in:
@@ -12,7 +12,8 @@ import {
|
|||||||
XCircle,
|
XCircle,
|
||||||
Timer,
|
Timer,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronDown
|
ChevronDown,
|
||||||
|
Users
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
@@ -26,6 +27,7 @@ interface Event {
|
|||||||
status: 'REQUESTED' | 'APPROVED' | 'COMPLETED' | 'CANCELLED' | 'PENDING_PR' | 'HOD_REJECTED' | 'PRINCIPAL_REJECTED';
|
status: 'REQUESTED' | 'APPROVED' | 'COMPLETED' | 'CANCELLED' | 'PENDING_PR' | 'HOD_REJECTED' | 'PRINCIPAL_REJECTED';
|
||||||
rejectionReason?: string;
|
rejectionReason?: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
|
currentParticipants?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FacultyEventManagementProps {
|
interface FacultyEventManagementProps {
|
||||||
@@ -159,6 +161,12 @@ export const FacultyEventManagement: React.FC<FacultyEventManagementProps> = ({
|
|||||||
{event.location}
|
{event.location}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 text-slate-500">
|
||||||
|
<Users className="w-3.5 h-3.5 text-brand-indigo/60" />
|
||||||
|
<span className="text-[10px] font-bold">
|
||||||
|
{event.currentParticipants || 0} Registered
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -64,13 +64,45 @@ export const StudentDashboard: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mapDepartmentToDomain = (dept: string): string => {
|
||||||
|
if (!dept) return '';
|
||||||
|
const d = dept.trim().toUpperCase();
|
||||||
|
if (d === 'AI&DS' || d === 'AIDS' || d === 'AI & DS') return 'AIDS';
|
||||||
|
if (d === 'AI&ML' || d === 'AIML' || d === 'AI & ML') return 'AIML';
|
||||||
|
if (d === 'MECH' || d === 'MECHANICAL') return 'Mechanical';
|
||||||
|
if (d === 'BIOTECH' || d === 'BIO-TECH') return 'Bio-tech';
|
||||||
|
if (d === 'EE(VLSI)' || d === 'VLSI') return 'VLSI';
|
||||||
|
return dept.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapCategoryToStudentCategory = (dbCat: string, dbType: string): 'TECHNICAL' | 'NON-TECHNICAL' | 'WORKSHOP' | 'CENTRE-ACTIVITY' => {
|
||||||
|
const cat = (dbCat || '').toUpperCase();
|
||||||
|
if (cat === 'TECHNICAL' || cat === 'NON-TECHNICAL' || cat === 'WORKSHOP' || cat === 'CENTRE-ACTIVITY') {
|
||||||
|
return cat as any;
|
||||||
|
}
|
||||||
|
if (cat === 'CENTRE') {
|
||||||
|
return 'CENTRE-ACTIVITY';
|
||||||
|
}
|
||||||
|
if (cat === 'ACADEMIC') {
|
||||||
|
return 'TECHNICAL';
|
||||||
|
}
|
||||||
|
const type = (dbType || '').toLowerCase();
|
||||||
|
if (type === 'workshop') {
|
||||||
|
return 'WORKSHOP';
|
||||||
|
}
|
||||||
|
if (type === 'cultural event' || type === 'cultural') {
|
||||||
|
return 'NON-TECHNICAL';
|
||||||
|
}
|
||||||
|
return 'TECHNICAL';
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: String(dbEvent.id),
|
id: String(dbEvent.id),
|
||||||
title: dbEvent.title || 'Untitled Event',
|
title: dbEvent.title || 'Untitled Event',
|
||||||
location: dbEvent.location || dbEvent.venue || 'TBD',
|
location: dbEvent.location || dbEvent.venue || 'TBD',
|
||||||
date: dbEvent.startDate || dbEvent.date || new Date().toISOString(),
|
date: dbEvent.startDate || dbEvent.date || new Date().toISOString(),
|
||||||
category: dbEvent.category || 'TECHNICAL',
|
category: mapCategoryToStudentCategory(dbEvent.category, dbEvent.eventType || dbEvent.type || ''),
|
||||||
domain: dbEvent.domain || dbEvent.department || '',
|
domain: mapDepartmentToDomain(dbEvent.domain || dbEvent.department || ''),
|
||||||
pricingType: dbEvent.hasRegistrationFee ? 'PAID' : 'FREE',
|
pricingType: dbEvent.hasRegistrationFee ? 'PAID' : 'FREE',
|
||||||
coordinator: dbEvent.proposer?.fullName || dbEvent.coordinator || 'Faculty Coordinator',
|
coordinator: dbEvent.proposer?.fullName || dbEvent.coordinator || 'Faculty Coordinator',
|
||||||
image: dbEvent.image || 'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&fit=crop&q=80&w=800',
|
image: dbEvent.image || 'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&fit=crop&q=80&w=800',
|
||||||
@@ -343,7 +375,12 @@ export const StudentDashboard: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero events={events} />
|
<Hero events={events} />
|
||||||
<UpcomingEventsSlider events={events} />
|
<UpcomingEventsSlider
|
||||||
|
events={events}
|
||||||
|
bookedEventIds={bookedEventIds}
|
||||||
|
onToggleBooking={handleToggleBooking}
|
||||||
|
userRole={user?.role}
|
||||||
|
/>
|
||||||
<AboutHubSection />
|
<AboutHubSection />
|
||||||
<StatsSection events={events} />
|
<StatsSection events={events} />
|
||||||
<HomeDashboard
|
<HomeDashboard
|
||||||
|
|||||||
@@ -10,9 +10,17 @@ const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||||||
|
|
||||||
interface UpcomingEventsSliderProps {
|
interface UpcomingEventsSliderProps {
|
||||||
events: Event[];
|
events: Event[];
|
||||||
|
bookedEventIds?: string[];
|
||||||
|
onToggleBooking?: (id: string) => void;
|
||||||
|
userRole?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({ events }) => {
|
const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
|
||||||
|
events,
|
||||||
|
bookedEventIds = [],
|
||||||
|
onToggleBooking,
|
||||||
|
userRole
|
||||||
|
}) => {
|
||||||
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
||||||
const clubInfo = useMemo(() => selectedEvent ? CLUBS.find(c => c.name === selectedEvent.club) : null, [selectedEvent]);
|
const clubInfo = useMemo(() => selectedEvent ? CLUBS.find(c => c.name === selectedEvent.club) : null, [selectedEvent]);
|
||||||
|
|
||||||
@@ -130,10 +138,17 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({ events }) =
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6 pb-6 mt-6 border-t border-gray-100 pt-6">
|
<div className="space-y-6 pb-6 mt-6 border-t border-gray-100 pt-6">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center justify-between gap-2 mb-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-1 h-3 bg-[#f97316] rounded-full"></div>
|
<div className="w-1 h-3 bg-[#f97316] rounded-full"></div>
|
||||||
<h4 className="text-[10px] font-black text-slate-800 uppercase tracking-[0.2em]">Summary</h4>
|
<h4 className="text-[10px] font-black text-slate-800 uppercase tracking-[0.2em]">Summary</h4>
|
||||||
</div>
|
</div>
|
||||||
|
{userRole === 'STUDENT' && bookedEventIds.includes(selectedEvent.id) && (
|
||||||
|
<span className="px-2.5 py-1 bg-emerald-50 text-emerald-600 border border-emerald-200 text-[8px] font-black rounded-lg uppercase tracking-wider">
|
||||||
|
Registered
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="relative pt-2">
|
<div className="relative pt-2">
|
||||||
<i className="fas fa-quote-right absolute top-0 right-0 text-5xl text-slate-50 pointer-events-none -z-10"></i>
|
<i className="fas fa-quote-right absolute top-0 right-0 text-5xl text-slate-50 pointer-events-none -z-10"></i>
|
||||||
<p className="text-[14px] text-slate-600 leading-relaxed whitespace-pre-wrap font-medium">
|
<p className="text-[14px] text-slate-600 leading-relaxed whitespace-pre-wrap font-medium">
|
||||||
@@ -142,7 +157,34 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({ events }) =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 shrink-0 border-t border-gray-100 pt-6">
|
<div className="mt-4 shrink-0 border-t border-gray-100 pt-6 flex flex-col gap-3">
|
||||||
|
{userRole === 'STUDENT' && onToggleBooking && (
|
||||||
|
<>
|
||||||
|
{bookedEventIds.includes(selectedEvent.id) ? (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (window.confirm(`Are you sure you want to cancel your registration for "${selectedEvent.title}"?`)) {
|
||||||
|
onToggleBooking(selectedEvent.id);
|
||||||
|
setSelectedEvent(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-full py-4 bg-rose-600 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-rose-700 transition-all shadow-xl shadow-rose-200 focus:outline-none focus:ring-4 focus:ring-rose-100"
|
||||||
|
>
|
||||||
|
Cancel Registration
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
onToggleBooking(selectedEvent.id);
|
||||||
|
setSelectedEvent(null);
|
||||||
|
}}
|
||||||
|
className="w-full py-4 bg-emerald-600 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-emerald-700 transition-all shadow-xl shadow-emerald-200 focus:outline-none focus:ring-4 focus:ring-emerald-100"
|
||||||
|
>
|
||||||
|
Get Tickets
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedEvent(null)}
|
onClick={() => setSelectedEvent(null)}
|
||||||
className="w-full py-4 bg-slate-900 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-black transition-all shadow-xl shadow-slate-200 focus:outline-none focus:ring-4 focus:ring-slate-100"
|
className="w-full py-4 bg-slate-900 text-white rounded-2xl font-black uppercase text-[10px] tracking-widest hover:bg-black transition-all shadow-xl shadow-slate-200 focus:outline-none focus:ring-4 focus:ring-slate-100"
|
||||||
|
|||||||
@@ -506,8 +506,15 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
|
|||||||
const snap = await getDocs(collection(db, 'ems_events'));
|
const snap = await getDocs(collection(db, 'ems_events'));
|
||||||
const events = snap.docs.map(d => d.data());
|
const events = snap.docs.map(d => d.data());
|
||||||
|
|
||||||
// Populate conflicts
|
// Fetch all registrations to compute current participant counts dynamically
|
||||||
|
const regsSnap = await getDocs(collection(db, 'ems_registrations'));
|
||||||
|
const registrations = regsSnap.docs.map(d => d.data());
|
||||||
|
|
||||||
|
// Populate conflicts and currentParticipants
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
const count = registrations.filter(r => String(r.eventId || r.event_id) === String(event.id)).length;
|
||||||
|
event.currentParticipants = count;
|
||||||
|
|
||||||
if (!['APPROVED', 'COMPLETED', 'CANCELLED'].includes(event.status)) {
|
if (!['APPROVED', 'COMPLETED', 'CANCELLED'].includes(event.status)) {
|
||||||
const conflictMsg = await getConflictMessage(event);
|
const conflictMsg = await getConflictMessage(event);
|
||||||
if (conflictMsg) {
|
if (conflictMsg) {
|
||||||
@@ -1049,6 +1056,11 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
|
|||||||
};
|
};
|
||||||
|
|
||||||
await setDoc(regDocRef, newReg);
|
await setDoc(regDocRef, newReg);
|
||||||
|
|
||||||
|
// Update event's currentParticipants
|
||||||
|
const eventDocRef = doc(db, 'ems_events', String(eventId));
|
||||||
|
await updateDoc(eventDocRef, { currentParticipants: currentParticipants + 1 });
|
||||||
|
|
||||||
return jsonResponse(newReg);
|
return jsonResponse(newReg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,7 +1112,26 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
|
|||||||
if (path.startsWith('/api/registrations/') && method === 'DELETE') {
|
if (path.startsWith('/api/registrations/') && method === 'DELETE') {
|
||||||
const parts = path.split('/');
|
const parts = path.split('/');
|
||||||
const id = parts[parts.length - 1];
|
const id = parts[parts.length - 1];
|
||||||
await deleteDoc(doc(db, 'ems_registrations', String(id)));
|
const regRef = doc(db, 'ems_registrations', String(id));
|
||||||
|
const regSnap = await getDoc(regRef);
|
||||||
|
|
||||||
|
if (regSnap.exists()) {
|
||||||
|
const regData = regSnap.data();
|
||||||
|
const eventId = regData.eventId || regData.event_id;
|
||||||
|
|
||||||
|
await deleteDoc(regRef);
|
||||||
|
|
||||||
|
if (eventId) {
|
||||||
|
const allRegsSnap = await getDocs(collection(db, 'ems_registrations'));
|
||||||
|
const eventRegs = allRegsSnap.docs.map(d => d.data()).filter(r => String(r.eventId || r.event_id) === String(eventId));
|
||||||
|
const currentParticipants = eventRegs.length;
|
||||||
|
|
||||||
|
await updateDoc(doc(db, 'ems_events', String(eventId)), { currentParticipants });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await deleteDoc(regRef);
|
||||||
|
}
|
||||||
|
|
||||||
return jsonResponse({ message: "Registration deleted successfully" });
|
return jsonResponse({ message: "Registration deleted successfully" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user