diff --git a/frontend/src/components/dashboard/ClassManagement.tsx b/frontend/src/components/dashboard/ClassManagement.tsx index 7a216a2..58d2a81 100644 --- a/frontend/src/components/dashboard/ClassManagement.tsx +++ b/frontend/src/components/dashboard/ClassManagement.tsx @@ -602,6 +602,8 @@ export const ClassManagement: React.FC = () => {
{batchDeptClassesList.map(cls => { const isChecked = batch.classes?.includes(cls); + const isAssignedElsewhere = tempBatches.some(other => other.id !== batch.id && other.classes?.includes(cls)); + if (isAssignedElsewhere) return null; return (
)} - {/* Schedule & Itinerary */} - {event.dayConfigs && event.dayConfigs.length > 0 && ( -
-

- - Detailed Schedule & Itinerary -

-
- {event.dayConfigs.map((day: any, dIdx: number) => { - const dayDate = day.date ? new Date(day.date) : null; - return ( -
-
- - Day {dIdx + 1}: {dayDate && !isNaN(dayDate.getTime()) ? format(dayDate, 'MMMM d, yyyy') : day.date || 'TBD'} - - - {day.batches?.length || 0} {(day.batches?.length || 0) === 1 ? 'Batch' : 'Batches'} - -
-
- {day.batches?.map((batch: any, bIdx: number) => ( -
-
- - Batch {batch.id || bIdx + 1} - - - - {batch.startTime || 'TBD'} - {batch.endTime || 'TBD'} - -
- {batch.resourcePerson && batch.resourcePerson.name && ( -
-
-
-

Resource Person

-

{batch.resourcePerson.name}

-
- - {batch.resourcePerson.type || 'INTERNAL'} - -
-
- {batch.resourcePerson.dept && ( -
- Dept: {batch.resourcePerson.dept} -
- )} - {batch.resourcePerson.college_name && ( -
- Inst: {batch.resourcePerson.college_name} -
- )} - {batch.resourcePerson.phone && ( -
- Phone: {batch.resourcePerson.phone} -
- )} - {batch.resourcePerson.email && ( -
- Email: {batch.resourcePerson.email} -
- )} -
-
- )} -
- ))} -
-
- ); - })} -
-
- )} +
{event.academicYears && event.academicYears.length > 0 && ( @@ -252,45 +174,7 @@ export const EventDetailsModal: React.FC = ({ isOpen, on )}
- {/* Department Limits & Quotas */} - {event.deptLimits && Object.keys(event.deptLimits).length > 0 && ( -
-

- - Department Quotas & Limits -

-
- {Object.entries(event.deptLimits).map(([dept, maxSeats]) => { - const current = event.currentDeptCounts?.[dept] || 0; - const sectionLimits = event.deptSectionLimits?.[dept]; - const hasSectionLimits = sectionLimits && Object.keys(sectionLimits).length > 0; - return ( -
-
- {dept} - - {current} / {maxSeats} Seats - -
- {hasSectionLimits && ( -
- {Object.entries(sectionLimits).map(([sec, limit]) => { - const secCount = event.currentDeptSectionCounts?.[dept]?.[sec] || 0; - return ( -
- Sec {sec} - {secCount} / {limit} -
- ); - })} -
- )} -
- ); - })} -
-
- )} + {/* Global registration capacity */} {event.maxParticipants && ( diff --git a/frontend/src/components/dashboard/EventProposalForm.tsx b/frontend/src/components/dashboard/EventProposalForm.tsx index edd17ff..0f4a0a2 100644 --- a/frontend/src/components/dashboard/EventProposalForm.tsx +++ b/frontend/src/components/dashboard/EventProposalForm.tsx @@ -205,30 +205,26 @@ export const EventProposalForm: React.FC = ({ initialDat } }; - const buildDayConfigsFromSchedule = (): any[] => { - const editData = initialData as any; - if (!editData?.dayConfigs || editData.dayConfigs.length === 0) { - const launchDateStr = editData?.startDate ? parseDateForInput(editData.startDate) : ''; - const batchStartTime = editData?.startDate ? editData.startDate.substring(11, 16) : ''; - const batchEndTime = editData?.endDate ? editData.endDate.substring(11, 16) : ''; - return [{ - date: launchDateStr, - batches: [{ id: 1, startTime: batchStartTime, endTime: batchEndTime }], - startTime: '', - endTime: '', - }]; + const formatDateTimeForInput = (dateStr?: string) => { + if (!dateStr) return ''; + try { + const date = new Date(dateStr); + if (isNaN(date.getTime())) return ''; + const offset = date.getTimezoneOffset(); + const localDate = new Date(date.getTime() - offset * 60 * 1000); + return localDate.toISOString().substring(0, 16); + } catch { + return ''; } - return editData.dayConfigs; }; - const [deptLimits, setDeptLimits] = useState>((initialData as any)?.deptLimits || {}); - const [deptSectionLimits, setDeptSectionLimits] = useState>>((initialData as any)?.deptSectionLimits || {}); - const [activeDeptForSections, setActiveDeptForSections] = useState(null); + const deptLimits = {}; + const deptSectionLimits = {}; const [formData, setFormData] = useState({ eventName: initialData?.eventName || '', - startDate: initialData?.startDate || '', - endDate: initialData?.endDate || '', + startDate: initialData?.startDate ? formatDateTimeForInput(initialData.startDate) : '', + endDate: initialData?.endDate ? formatDateTimeForInput(initialData.endDate) : '', eventType: isClubVariant ? 'CLUB' : (initialData?.eventType || 'Guest Lecture'), category: isClubVariant ? 'CLUB' : (initialData?.category || 'ACADEMIC'), institution: initialData?.institution || 'RIT', @@ -256,11 +252,6 @@ export const EventProposalForm: React.FC = ({ initialDat image: initialData?.image || '', status: initialData?.status || 'REQUESTED', targetedBatch: (initialData as any)?.targetedBatch || '', - - // New fields - launchDate: initialData?.startDate ? parseDateForInput(initialData.startDate) : '', - durationDays: (initialData as any)?.durationDays?.toString() || '1', - dayConfigs: buildDayConfigsFromSchedule(), refreshment_expense: (initialData as any)?.refreshment_expense?.toString() || '', transportation_expense: (initialData as any)?.transportation_expense?.toString() || '', session_coverage_fee: (initialData as any)?.session_coverage_fee?.toString() || '', @@ -268,39 +259,6 @@ export const EventProposalForm: React.FC = ({ initialDat maxParticipants: (initialData as any)?.maxParticipants?.toString() || '' }); - // Sync Launch Date to Day 1 configuration - useEffect(() => { - if (formData.launchDate && formData.dayConfigs.length > 0) { - setFormData(prev => { - const newConfigs = [...prev.dayConfigs]; - newConfigs[0] = { ...newConfigs[0], date: formData.launchDate }; - return { ...prev, dayConfigs: newConfigs }; - }); - } - }, [formData.launchDate]); - - // Sync Duration Days to dayConfigs array length - useEffect(() => { - const days = parseInt(formData.durationDays) || 1; - setFormData(prev => { - const newConfigs = [...prev.dayConfigs]; - if (days > newConfigs.length) { - for (let i = newConfigs.length; i < days; i++) { - let nextDate = ''; - if (newConfigs[i - 1]?.date) { - const d = new Date(newConfigs[i - 1].date); - d.setDate(d.getDate() + 1); - nextDate = d.toISOString().split('T')[0]; - } - newConfigs.push({ date: nextDate, batches: [{ id: 1, startTime: '', endTime: '' }], startTime: '', endTime: '' }); - } - } else if (days < newConfigs.length) { - newConfigs.splice(days); - } - return { ...prev, dayConfigs: newConfigs }; - }); - }, [formData.durationDays]); - // Sync financial totals useEffect(() => { const total = (parseFloat(formData.refreshment_expense) || 0) + @@ -309,141 +267,9 @@ export const EventProposalForm: React.FC = ({ initialDat setFormData(prev => ({ ...prev, total_expense: total.toFixed(2) })); }, [formData.refreshment_expense, formData.transportation_expense, formData.session_coverage_fee]); - const handleDeptLimitChange = (dept: string, value: string) => { - const numValue = parseInt(value); - setDeptLimits(prev => { - const newLimits = { ...prev }; - if (isNaN(numValue) || numValue <= 0) { - delete newLimits[dept]; - } else { - newLimits[dept] = numValue; - } - return newLimits; - }); - }; - const handleSectionLimitChange = (dept: string, sec: string, value: string) => { - const numValue = parseInt(value); - setDeptSectionLimits(prev => { - const newSectionLimits = { ...prev }; - if (!newSectionLimits[dept]) { - newSectionLimits[dept] = {}; - } - if (isNaN(numValue) || numValue <= 0) { - delete newSectionLimits[dept][sec]; - } else { - newSectionLimits[dept][sec] = numValue; - } - if (Object.keys(newSectionLimits[dept]).length === 0) { - delete newSectionLimits[dept]; - } - - const sectionSum = newSectionLimits[dept] - ? Object.values(newSectionLimits[dept]).reduce((sum: number, val: any) => sum + (Number(val) || 0), 0) - : 0; - setDeptLimits(prevDept => { - const newDeptLimits = { ...prevDept }; - if (sectionSum > 0) { - newDeptLimits[dept] = sectionSum; - } else { - delete newDeptLimits[dept]; - } - return newDeptLimits; - }); - return newSectionLimits; - }); - }; - - const handleNumBatchesChange = (dayIndex: number, value: string) => { - const count = parseInt(value); - setFormData(prev => { - const newConfigs = [...prev.dayConfigs]; - const dayBatches = [...newConfigs[dayIndex].batches]; - - if (count >= 1 && count > dayBatches.length) { - for (let i = dayBatches.length; i < count; i++) { - dayBatches.push({ id: i + 1, startTime: '', endTime: '' }); - } - } else { - dayBatches.splice(Math.max(count, 1)); - } - - newConfigs[dayIndex] = { ...newConfigs[dayIndex], batches: dayBatches }; - return { ...prev, dayConfigs: newConfigs }; - }); - }; - - const handleBatchTimeChange = (dayIndex: number, batchIndex: number, field: 'startTime' | 'endTime', value: string) => { - setFormData(prev => { - const newConfigs = [...prev.dayConfigs]; - const dayBatches = [...newConfigs[dayIndex].batches]; - dayBatches[batchIndex] = { ...dayBatches[batchIndex], [field]: value }; - newConfigs[dayIndex] = { ...newConfigs[dayIndex], batches: dayBatches }; - return { ...prev, dayConfigs: newConfigs }; - }); - }; - - const handleDayDateChange = (dayIndex: number, value: string) => { - setFormData(prev => { - const newConfigs = [...prev.dayConfigs]; - newConfigs[dayIndex] = { ...newConfigs[dayIndex], date: value }; - return { ...prev, dayConfigs: newConfigs }; - }); - }; - - const handleDayTimeChange = (dayIndex: number, field: 'startTime' | 'endTime', value: string) => { - setFormData(prev => { - const newConfigs = [...prev.dayConfigs]; - newConfigs[dayIndex] = { ...newConfigs[dayIndex], [field]: value }; - return { ...prev, dayConfigs: newConfigs }; - }); - }; - - const handleResourcePersonChange = (dayIndex: number, batchIndex: number | null, field: string, value: any) => { - setFormData(prev => { - const newConfigs = [...prev.dayConfigs]; - if (batchIndex === null) { - const currentRP = newConfigs[dayIndex].resourcePerson || { type: 'INTERNAL', name: '', phone: '', email: '' }; - newConfigs[dayIndex] = { ...newConfigs[dayIndex], resourcePerson: { ...currentRP, [field]: value } }; - } else { - const dayBatches = [...newConfigs[dayIndex].batches]; - const currentRP = dayBatches[batchIndex].resourcePerson || { type: 'INTERNAL', name: '', phone: '', email: '' }; - dayBatches[batchIndex] = { ...dayBatches[batchIndex], resourcePerson: { ...currentRP, [field]: value } }; - newConfigs[dayIndex] = { ...newConfigs[dayIndex], batches: dayBatches }; - } - return { ...prev, dayConfigs: newConfigs }; - }); - }; - - const calculateEventDateTimes = () => { - let earliestStart: Date | null = null; - let latestEnd: Date | null = null; - - for (let dIdx = 0; dIdx < formData.dayConfigs.length; dIdx++) { - const day = formData.dayConfigs[dIdx]; - if (!day.date) continue; - for (let bIdx = 0; bIdx < day.batches.length; bIdx++) { - const batch = day.batches[bIdx]; - if (!batch.startTime || !batch.endTime) continue; - - const batchStart = new Date(`${day.date}T${batch.startTime}`); - const batchEnd = new Date(`${day.date}T${batch.endTime}`); - - if (isNaN(batchStart.getTime()) || isNaN(batchEnd.getTime())) continue; - if (batchEnd <= batchStart) continue; - - if (!earliestStart || batchStart < earliestStart) { - earliestStart = batchStart; - } - if (!latestEnd || batchEnd > latestEnd) { - latestEnd = batchEnd; - } - } - } - return { earliestStart, latestEnd }; - }; const [newSponsor, setNewSponsor] = useState(''); const [showAlternateEvent, setShowAlternateEvent] = useState(false); @@ -500,14 +326,27 @@ export const EventProposalForm: React.FC = ({ initialDat const response = await fetch(API_BASE_URL + '/api/admin/users'); if (response.ok) { const data = await response.json(); + + // Count students dynamically in each class combo + const studentCounts: Record = {}; + data.forEach((u: any) => { + if (u.role === 'STUDENT' && u.department && u.year && u.section) { + const key = `${u.department.trim().toLowerCase()}_${u.year.trim().toLowerCase()}_${u.section.trim().toLowerCase()}`; + studentCounts[key] = (studentCounts[key] || 0) + 1; + } + }); + const strengths = data .filter((u: any) => u.isClassIncharge) - .map((u: any) => ({ - dept: u.inchargeClass, - year: u.inchargeBatch, - sec: u.inchargeSection, - strength: u.classStrength || 0 - })); + .map((u: any) => { + const key = `${u.inchargeClass?.trim().toLowerCase()}_${u.inchargeBatch?.trim().toLowerCase()}_${u.inchargeSection?.trim().toLowerCase()}`; + return { + dept: u.inchargeClass, + year: u.inchargeBatch, + sec: u.inchargeSection, + strength: studentCounts[key] || 0 + }; + }); setClassStrengths(strengths); } } catch (err) { @@ -612,7 +451,7 @@ export const EventProposalForm: React.FC = ({ initialDat for (const s of sectionsToTarget) { const match = classStrengths.find(cs => cs.dept === d && cs.year === y && cs.sec === s); - if (match) { + if (match && match.strength > 0) { total += match.strength; } } @@ -621,7 +460,33 @@ export const EventProposalForm: React.FC = ({ initialDat return total; }, [formData.targetDepartments, formData.department, formData.academicYears, formData.targetedSections, formData.allBatches, formData.allSections, formData.institution, classStrengths, eventScope, isPlacementCell, availableYears, classes]); - const filteredVenues = venues.filter(v => v.capacity === null || v.capacity >= totalExpectedStrength); + const filteredVenues = React.useMemo(() => { + if (totalExpectedStrength === 0) return venues; + + // Find the smallest capacity venue that can accommodate the totalExpectedStrength + const validCapacities = venues + .map(v => v.capacity) + .filter((cap): cap is number => cap !== null && cap >= totalExpectedStrength); + + const minSufficientCapacity = validCapacities.length > 0 ? Math.min(...validCapacities) : totalExpectedStrength; + + return venues.filter(v => { + if (v.name === 'N/A' || v.name === 'Others.' || v.capacity === null) return true; + + // Capacity must be at least the audience size, and not exceed the max of (1.5x audience OR the smallest sufficient capacity) + const maxAllowedCapacity = Math.max(totalExpectedStrength * 1.5, minSufficientCapacity); + return v.capacity >= totalExpectedStrength && v.capacity <= maxAllowedCapacity; + }); + }, [venues, totalExpectedStrength]); + + useEffect(() => { + if (formData.venue && formData.venue !== 'N/A' && formData.venue !== 'Others.') { + const isStillAvailable = filteredVenues.some(v => v.name === formData.venue); + if (!isStillAvailable) { + setFormData(prev => ({ ...prev, venue: 'N/A' })); + } + } + }, [filteredVenues]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -629,12 +494,11 @@ export const EventProposalForm: React.FC = ({ initialDat setError(''); const now = new Date(); - - // Calculate overall start and end dates from dayConfigs - const { earliestStart, latestEnd } = calculateEventDateTimes(); + const earliestStart = new Date(formData.startDate); + const latestEnd = new Date(formData.endDate); - if (!earliestStart || !latestEnd) { - setError('Please configure the schedule and timing for the event.'); + if (isNaN(earliestStart.getTime()) || isNaN(latestEnd.getTime())) { + setError('Please select valid start and end dates & times.'); setIsSubmitting(false); return; } @@ -645,6 +509,12 @@ export const EventProposalForm: React.FC = ({ initialDat return; } + if (latestEnd <= earliestStart) { + setError('End date/time must be after the start date/time.'); + setIsSubmitting(false); + return; + } + const groupRequestId = crypto.randomUUID(); const batchesToAssign = formData.allBatches ? availableYears : formData.academicYears; let deptsToCreate = (eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? formData.targetDepartments : [formData.department]; @@ -682,7 +552,8 @@ export const EventProposalForm: React.FC = ({ initialDat deptLimits, deptSectionLimits, maxParticipants: formData.maxParticipants === '' ? null : Number(formData.maxParticipants), - durationDays: Number(formData.durationDays), + durationDays: 1, + dayConfigs: [], refreshment_expense: Number(formData.refreshment_expense) || 0, transportation_expense: Number(formData.transportation_expense) || 0, session_coverage_fee: Number(formData.session_coverage_fee) || 0, @@ -724,7 +595,8 @@ export const EventProposalForm: React.FC = ({ initialDat deptLimits, deptSectionLimits, maxParticipants: formData.maxParticipants === '' ? null : Number(formData.maxParticipants), - durationDays: Number(formData.durationDays), + durationDays: 1, + dayConfigs: [], refreshment_expense: Number(formData.refreshment_expense) || 0, transportation_expense: Number(formData.transportation_expense) || 0, session_coverage_fee: Number(formData.session_coverage_fee) || 0, @@ -776,7 +648,8 @@ export const EventProposalForm: React.FC = ({ initialDat deptLimits, deptSectionLimits, maxParticipants: formData.maxParticipants === '' ? null : Number(formData.maxParticipants), - durationDays: Number(formData.durationDays), + durationDays: 1, + dayConfigs: [], refreshment_expense: Number(formData.refreshment_expense) || 0, transportation_expense: Number(formData.transportation_expense) || 0, session_coverage_fee: Number(formData.session_coverage_fee) || 0, @@ -1053,24 +926,29 @@ export const EventProposalForm: React.FC = ({ initialDat /> )} + {totalExpectedStrength > 0 && ( +

+ ✨ Smart Allocation: Recommending venues matching ~{totalExpectedStrength} seats +

+ )}
- + setFormData({...formData, launchDate: e.target.value})} + type="datetime-local" required + value={formData.startDate} + onChange={e => setFormData({...formData, startDate: e.target.value})} className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all" />
- + setFormData({...formData, durationDays: e.target.value})} + type="datetime-local" required + value={formData.endDate} + onChange={e => setFormData({...formData, endDate: e.target.value})} className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all" />
@@ -1081,112 +959,15 @@ export const EventProposalForm: React.FC = ({ initialDat 0 ? "Controlled by Dept Quotas" : "Unlimited"} - className={cn( - "w-full border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold transition-all focus:bg-white", - Object.keys(deptLimits).length > 0 - ? "bg-slate-100 text-slate-400 cursor-not-allowed" - : "bg-slate-50 text-text-dark focus:border-brand-indigo/30" - )} + placeholder="Unlimited" + className="w-full border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold transition-all focus:bg-white bg-slate-50 text-text-dark focus:border-brand-indigo/30" value={formData.maxParticipants} - disabled={Object.keys(deptLimits).length > 0} onChange={e => setFormData({...formData, maxParticipants: e.target.value})} /> - {Object.keys(deptLimits).length > 0 && ( -

- ↓ Controlled by Dept Quotas -

- )} -
- {/* Section 3: Itinerary Scheduling */} -
-

- - 03. Itinerary Scheduling -

-
- {formData.dayConfigs.map((day, dIdx) => ( -
-
- Day {dIdx + 1} Configuration -
-
- handleDayDateChange(dIdx, e.target.value)} /> -
-
- -
-
-
- -
- {day.batches.map((batch, bIdx) => ( -
-
- Phase 0{batch.id} Configuration -
- Start - handleBatchTimeChange(dIdx, bIdx, 'startTime', e.target.value)} /> - / - End - handleBatchTimeChange(dIdx, bIdx, 'endTime', e.target.value)} /> -
-
- - {/* Resource Person for Batch */} -
-
- -
- - -
-
-
- - handleResourcePersonChange(dIdx, bIdx, 'name', e.target.value)} /> -
- {batch.resourcePerson?.type === 'EXTERNAL' ? ( -
-
- - handleResourcePersonChange(dIdx, bIdx, 'college_name', e.target.value)} /> -
-
- - handleResourcePersonChange(dIdx, bIdx, 'dept', e.target.value)} /> -
-
- ) : ( -
- - handleResourcePersonChange(dIdx, bIdx, 'dept', e.target.value)} /> -
- )} -
- - handleResourcePersonChange(dIdx, bIdx, 'phone', e.target.value)} /> -
-
- - handleResourcePersonChange(dIdx, bIdx, 'email', e.target.value)} /> -
-
-
- ))} -
-
- ))} -
-
- -

@@ -1568,49 +1349,6 @@ export const EventProposalForm: React.FC = ({ initialDat

) : (
- {deptBatches.length > 0 && ( -
- -

Or select a standard academic year below:

-
- )} - 0) - ? "text-slate-300 bg-slate-100 cursor-not-allowed" - : hasSectionLimits - ? "bg-brand-glow text-brand-indigo cursor-not-allowed" - : "focus:bg-white focus:border-brand-indigo/30 focus:text-brand-indigo" - )} - value={deptLimits[dept] || ''} - disabled={(formData.maxParticipants !== '' && parseInt(formData.maxParticipants) > 0) || hasSectionLimits} - onChange={(e) => handleDeptLimitChange(dept, e.target.value)} - /> - -
- ); - })} -
- - {/* Per-Department Section Quota Inputs */} - {activeDeptForSections && ( -
-
-
-

- - Section limits for {activeDeptForSections} -

-

- Specify capacity for each section. Leaving a section blank will prevent students in that section from registering. -

-
- -
-
- {['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'].map(sec => { - const val = deptSectionLimits[activeDeptForSections]?.[sec] ?? ''; - return ( -
- Sec {sec} - handleSectionLimitChange(activeDeptForSections, sec, e.target.value)} - /> -
- ); - })} -
-
- )} -
-
diff --git a/frontend/src/components/dashboard/EventStatusTimeline.tsx b/frontend/src/components/dashboard/EventStatusTimeline.tsx index c057e04..877fc73 100644 --- a/frontend/src/components/dashboard/EventStatusTimeline.tsx +++ b/frontend/src/components/dashboard/EventStatusTimeline.tsx @@ -62,7 +62,7 @@ export const StatusTimeline: React.FC<{ event: Event; compact?: boolean }> = ({ } return ( -
+
@@ -101,29 +101,42 @@ export const StatusTimeline: React.FC<{ event: Event; compact?: boolean }> = ({ } -
- - {stageRejected ? (isHODRejected ? 'HoD Rejected' : isPRRejected ? 'PR Rejected' : 'Cancelled') : stage.label} - - - {isCurrent && !isRejected && ( - - Current Phase - - )} -
+ {!compact && ( +
+ + {stageRejected ? (isHODRejected ? 'HoD Rejected' : isPRRejected ? 'PR Rejected' : 'Cancelled') : stage.label} + + + {isCurrent && !isRejected && ( + + Current Phase + + )} +
+ )}
); })}
+ {compact && ( +
+ + Status: {isRejected ? (isHODRejected ? 'HoD Rejected' : isPRRejected ? 'PR Rejected' : 'Cancelled') : stages[currentStageIdx]?.label} + +
+ )} + {isRejected && event.rejectionReason && ( = ({ event, isBooked, onToggle, onTrac 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]); + }, [event, now]); const timeLeft = useMemo(() => { const targetDate = new Date(event.date).getTime(); @@ -286,69 +267,11 @@ const EventCard: React.FC = ({ event, isBooked, onToggle, onTrac
- {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) : '∞')} + {event.currentParticipants || 0} / {event.maxParticipants || '∞'}
- {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]} - -
- ); - })()}
diff --git a/frontend/src/lib/firebaseBackend.ts b/frontend/src/lib/firebaseBackend.ts index 1b7ce13..f005ef8 100644 --- a/frontend/src/lib/firebaseBackend.ts +++ b/frontend/src/lib/firebaseBackend.ts @@ -104,7 +104,7 @@ async function seedDatabaseIfEmpty() { console.log("[Firebase Backend] Seeding users..."); const defaultUsers = [ { id: 1, email: "admin@rit.edu", password: "admin123", fullName: "System Administrator", role: "ADMIN", department: "ADMIN", isClubCoordinator: false, isPlacementStaff: false, isClassIncharge: false, assignedClubs: [] }, - { id: 2, email: "faculty@rit.edu", password: "faculty123", fullName: "Dr. Faculty Member", role: "FACULTY", department: "CSE", isClubCoordinator: false, isPlacementStaff: false, isClassIncharge: true, inchargeClass: "CSE", inchargeBatch: "3rd Year", inchargeSection: "A", classStrength: 60, assignedClubs: [] }, + { id: 2, email: "faculty@rit.edu", password: "faculty123", fullName: "Dr. Faculty Member", role: "FACULTY", department: "CSE", isClubCoordinator: false, isPlacementStaff: false, isClassIncharge: true, inchargeClass: "CSE", inchargeBatch: "3rd Year", inchargeSection: "A", classStrength: 0, assignedClubs: [] }, { id: 3, email: "hod@rit.edu", password: "hod123", fullName: "Prof. Head of Dept", role: "HOD", department: "AI&ML", isClubCoordinator: false, isPlacementStaff: false, isClassIncharge: false, assignedClubs: [] }, { id: 4, email: "principal@rit.edu", password: "principal123", fullName: "Dr. College Principal", role: "PRINCIPAL", department: "ADMIN", isClubCoordinator: false, isPlacementStaff: false, isClassIncharge: false, assignedClubs: [] }, { id: 5, email: "placement@rit.edu", password: "placement123", fullName: "Placement Coordinator", role: "PLACEMENT", department: "Placement Department", isClubCoordinator: false, isPlacementStaff: true, isClassIncharge: false, assignedClubs: [] },