diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 067615a..113cdea 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -60,7 +60,8 @@ const AppContent: React.FC = () => { socialProfile: event.guestSocialProfile, maxParticipants: event["total capacity"] || (event.maxParticipants ?? ''), sponsors: event.sponsors ?? [], - isEditMode: true + isEditMode: true, + openToAll: !!event.openToAll }); setActiveItem('propose'); }; diff --git a/frontend/src/components/dashboard/EventProposalForm.tsx b/frontend/src/components/dashboard/EventProposalForm.tsx index a9fefd8..fc5469e 100644 --- a/frontend/src/components/dashboard/EventProposalForm.tsx +++ b/frontend/src/components/dashboard/EventProposalForm.tsx @@ -70,6 +70,7 @@ interface Event { department: string; academicYears: string[]; targetedSections: string[]; + openToAll?: boolean; } interface EventProposalFormProps { @@ -267,6 +268,7 @@ export const EventProposalForm: React.FC = ({ initialDat centreName: initialData?.centreName || (eventScope === 'CENTRE' ? ((CENTRE_MAPPING[user?.department || ''] || CENTRE_MAPPING['Placement Department'] || [])[0] || '') : ''), isPublicEvent: initialData?.isPublicEvent || false, image: initialData?.image || '', + openToAll: (initialData as any)?.openToAll || false, status: initialData?.status || 'REQUESTED', targetedBatch: (initialData as any)?.targetedBatch || '', refreshment_expense: (initialData as any)?.refreshment_expense?.toString() || '', @@ -1304,6 +1306,22 @@ export const EventProposalForm: React.FC = ({ initialDat ) : (
+
+
+

Open to All Students

+

If enabled, this event will be visible and open to all departments, batches, and sections.

+
+ +
+
diff --git a/frontend/src/components/dashboard/StudentDashboard.tsx b/frontend/src/components/dashboard/StudentDashboard.tsx index 9b5ef6b..9445a84 100644 --- a/frontend/src/components/dashboard/StudentDashboard.tsx +++ b/frontend/src/components/dashboard/StudentDashboard.tsx @@ -128,7 +128,11 @@ export const StudentDashboard: React.FC = () => { currentDeptSectionCounts: deptSectionCounts, created_by: dbEvent.proposer?.email || dbEvent.created_by || '', participantType: dbEvent.participantType || 'BOTH', - verificationStatus: dbEvent.status === 'COMPLETED' ? 'APPROVED' : 'APPROVED' + verificationStatus: dbEvent.status === 'COMPLETED' ? 'APPROVED' : 'APPROVED', + openToAll: !!dbEvent.openToAll, + academicYears: dbEvent.academicYears || [], + targetedSections: dbEvent.targetedSections || [], + department: dbEvent.department || '' }; }, []); @@ -194,8 +198,33 @@ export const StudentDashboard: React.FC = () => { const events = useMemo(() => { const list = rawEvents.map(e => mapDbToEvent(e, allRegistrations)); // Students only see live (approved / ongoing / completed) events - return list.filter(e => e.status === 'APPROVED' || e.status === 'COMPLETED' || e.status === 'Event Ongoing' || e.status === 'ONGOING'); - }, [rawEvents, allRegistrations, mapDbToEvent]); + return list.filter(e => { + const isLive = e.status === 'APPROVED' || e.status === 'COMPLETED' || e.status === 'Event Ongoing' || e.status === 'ONGOING'; + if (!isLive) return false; + + // If it is open to all, it is visible to everyone + if (e.openToAll) return true; + + // Otherwise, filter by student's department, year, and section + const userDept = user?.department || ''; + const userYear = user?.year || ''; + const userSec = user?.section || ''; + + const normalize = (s: string) => (s || '').trim().replace(/[^a-zA-Z0-9]/g, '').toLowerCase(); + + const deptMatches = normalize(userDept) === normalize(e.domain || e.department || ''); + + const yearMatches = e.academicYears && e.academicYears.length > 0 + ? e.academicYears.map((y: string) => normalize(y)).includes(normalize(userYear)) + : true; + + const sectionMatches = e.targetedSections && e.targetedSections.length > 0 + ? e.targetedSections.map((s: string) => normalize(s)).includes(normalize(userSec)) + : true; + + return deptMatches && yearMatches && sectionMatches; + }); + }, [rawEvents, allRegistrations, mapDbToEvent, user]); const userRegistrations = useMemo(() => { return allRegistrations.filter(r => String(r.userId || r.user_id) === String(user?.id)); diff --git a/frontend/src/components/student/EventCard.tsx b/frontend/src/components/student/EventCard.tsx index dba8902..bb92ba0 100644 --- a/frontend/src/components/student/EventCard.tsx +++ b/frontend/src/components/student/EventCard.tsx @@ -153,8 +153,35 @@ const EventCard: React.FC = ({ event, isBooked, onToggle, onTrac return 'TOTAL_FULL'; } + if (!event.openToAll && user && user.role === 'STUDENT') { + const normalize = (s: string) => (s || '').trim().replace(/[^a-zA-Z0-9]/g, '').toLowerCase(); + + const userDept = normalize(user.department); + const eventDept = normalize(event.domain || event.department); + + if (userDept === 'others' || !eventDept || userDept !== eventDept) { + return 'DEPT_RESTRICTED'; + } + + const userYear = normalize(user.year); + if (event.academicYears && event.academicYears.length > 0) { + const yearMatches = event.academicYears.map((y: string) => normalize(y)).includes(userYear); + if (!yearMatches) { + return 'YEAR_RESTRICTED'; + } + } + + const userSec = normalize(user.section); + if (event.targetedSections && event.targetedSections.length > 0) { + const sectionMatches = event.targetedSections.map((s: string) => normalize(s)).includes(userSec); + if (!sectionMatches) { + return 'SECTION_RESTRICTED'; + } + } + } + return 'OPEN'; - }, [event, now]); + }, [event, now, user]); const timeLeft = useMemo(() => { const targetDate = new Date(event.date).getTime(); @@ -320,7 +347,13 @@ const EventCard: React.FC = ({ event, isBooked, onToggle, onTrac ? 'SEC SEATS FULL' : admissionStatus === 'SECTION_NOT_ALLOWED' ? 'SEC RESTRICTED' - : 'Event Full'} + : admissionStatus === 'DEPT_RESTRICTED' + ? 'DEPT RESTRICTED' + : admissionStatus === 'YEAR_RESTRICTED' + ? 'YEAR RESTRICTED' + : admissionStatus === 'SECTION_RESTRICTED' + ? 'SEC RESTRICTED' + : 'Event Full'} )}
diff --git a/frontend/src/lib/firebaseBackend.ts b/frontend/src/lib/firebaseBackend.ts index 6b6814b..d9a811a 100644 --- a/frontend/src/lib/firebaseBackend.ts +++ b/frontend/src/lib/firebaseBackend.ts @@ -755,6 +755,7 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro isPublicEvent: !!payload.isPublicEvent, image: payload.image || null, targetedBatch: payload.targetedBatch || null, + openToAll: !!payload.openToAll, "total capacity": payload.maxParticipants === '' || payload.maxParticipants === undefined ? null : Number(payload.maxParticipants), maxParticipants: payload.maxParticipants === '' || payload.maxParticipants === undefined ? null : Number(payload.maxParticipants), proposer: { @@ -812,7 +813,22 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro const ay = payload.targetBatch ? [payload.targetBatch] : (payload.academicYears || []); const venue = payload.venue || payload.location || "TBD"; - const proposer = payload.proposer || { id: 1, fullName: "Admin", email: "admin@rit.edu", role: "ADMIN" }; + let proposer = { id: 1, fullName: "Admin", email: "admin@rit.edu", role: "ADMIN", department: "ADMIN" }; + if (payload.proposer && payload.proposer.id) { + const userSnap = await getDoc(doc(db, 'ems_users', String(payload.proposer.id))); + if (userSnap.exists()) { + const u = userSnap.data(); + proposer = { + id: u.id, + fullName: u.fullName, + email: u.email, + role: u.role, + department: u.department || "N/A" + }; + } + } else if (payload.proposer) { + proposer = { ...proposer, ...payload.proposer }; + } const event = { id: generateNumericId(), @@ -829,7 +845,8 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro status: payload.status || "APPROVED", requirements: payload.requirements || [], image: payload.image || null, - proposer + proposer, + openToAll: !!payload.openToAll }; const conflictMsg = await getConflictMessage(event); @@ -1027,6 +1044,7 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro if (payload.centreName !== undefined) updatedFields.centreName = payload.centreName; if (payload.isPublicEvent !== undefined) updatedFields.isPublicEvent = !!payload.isPublicEvent; if (payload.image !== undefined) updatedFields.image = payload.image; + if (payload.openToAll !== undefined) updatedFields.openToAll = !!payload.openToAll; if (payload.targetedBatch !== undefined) updatedFields.targetedBatch = payload.targetedBatch; if (payload.dayConfigs !== undefined) updatedFields.dayConfigs = payload.dayConfigs; if (payload.deptLimits !== undefined) updatedFields.deptLimits = payload.deptLimits; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index c0c823f..ee3d814 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -80,6 +80,10 @@ export interface Event { request_by_faculty?: string; request_by_HOD?: string; targetedBatch?: string; + openToAll?: boolean; + academicYears?: string[]; + targetedSections?: string[]; + department?: string; } export interface Announcement {