Update frontend components and integrate firebase backend
This commit is contained in:
@@ -55,7 +55,11 @@ const AppContent: React.FC = () => {
|
|||||||
...event,
|
...event,
|
||||||
eventName: event.title,
|
eventName: event.title,
|
||||||
venue: event.location,
|
venue: event.location,
|
||||||
|
category: event.category,
|
||||||
|
eventType: event.type,
|
||||||
socialProfile: event.guestSocialProfile,
|
socialProfile: event.guestSocialProfile,
|
||||||
|
maxParticipants: event["total capacity"] || (event.maxParticipants ?? ''),
|
||||||
|
sponsors: event.sponsors ?? [],
|
||||||
isEditMode: true
|
isEditMode: true
|
||||||
});
|
});
|
||||||
setActiveItem('propose');
|
setActiveItem('propose');
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import { motion } from 'framer-motion';
|
|||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
MapPin,
|
MapPin,
|
||||||
Building2,
|
|
||||||
Clock,
|
Clock,
|
||||||
ChevronRight,
|
|
||||||
Filter,
|
Filter,
|
||||||
Search,
|
Search,
|
||||||
MoreVertical,
|
Trash2,
|
||||||
FileSpreadsheet
|
FileSpreadsheet
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
@@ -54,6 +52,21 @@ export const AllEvents: React.FC<AllEventsProps> = ({ onEditEvent }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteEvent = async (eventId: number) => {
|
||||||
|
if (!window.confirm('Are you sure you want to permanently delete this event? This action cannot be undone.')) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/events/${eventId}`, { method: 'DELETE' });
|
||||||
|
if (res.ok) {
|
||||||
|
setEvents(prev => prev.filter(e => e.id !== eventId));
|
||||||
|
} else {
|
||||||
|
alert('Failed to delete event. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Delete failed:', err);
|
||||||
|
alert('An error occurred while deleting the event.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getStatusColor = (status: Event['status']) => {
|
const getStatusColor = (status: Event['status']) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'REQUESTED': return 'bg-brand-glow text-brand-indigo border-brand-indigo/20';
|
case 'REQUESTED': return 'bg-brand-glow text-brand-indigo border-brand-indigo/20';
|
||||||
@@ -204,9 +217,15 @@ export const AllEvents: React.FC<AllEventsProps> = ({ onEditEvent }) => {
|
|||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button className="p-2 rounded-lg hover:bg-white hover:premium-shadow transition-all text-slate-300 hover:text-brand-indigo">
|
{user?.role === 'ADMIN' && (
|
||||||
<MoreVertical className="w-5 h-5" />
|
<button
|
||||||
</button>
|
onClick={() => deleteEvent(event.id)}
|
||||||
|
className="p-2 rounded-lg hover:bg-red-50 transition-all text-slate-300 hover:text-red-500"
|
||||||
|
title="Delete Event"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</motion.tr>
|
</motion.tr>
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import {
|
|||||||
Building2,
|
Building2,
|
||||||
Users,
|
Users,
|
||||||
Wallet,
|
Wallet,
|
||||||
Ticket
|
Ticket,
|
||||||
|
Layers
|
||||||
} 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';
|
||||||
@@ -41,13 +42,27 @@ interface Event {
|
|||||||
institution: string;
|
institution: string;
|
||||||
category: string;
|
category: string;
|
||||||
academicYears: string[];
|
academicYears: string[];
|
||||||
|
targetDepartments?: string[];
|
||||||
|
targetedSections?: string[];
|
||||||
proposer?: {
|
proposer?: {
|
||||||
fullName: string;
|
fullName: string;
|
||||||
|
email?: string;
|
||||||
|
department?: string;
|
||||||
|
role?: string;
|
||||||
};
|
};
|
||||||
description?: string;
|
description?: string;
|
||||||
sponsors?: string[];
|
sponsors?: string[];
|
||||||
hasRegistrationFee: boolean;
|
hasRegistrationFee: boolean;
|
||||||
registrationFee: number;
|
registrationFee: number;
|
||||||
|
maxParticipants?: number | null;
|
||||||
|
requirements?: string[];
|
||||||
|
refreshment_expense?: number;
|
||||||
|
transportation_expense?: number;
|
||||||
|
session_coverage_fee?: number;
|
||||||
|
total_expense?: number;
|
||||||
|
paymentLink?: string;
|
||||||
|
isClubEvent?: boolean;
|
||||||
|
eventScope?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EventHistory: React.FC = () => {
|
export const EventHistory: React.FC = () => {
|
||||||
@@ -294,38 +309,129 @@ export const EventHistory: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8">
|
{/* Event Classification */}
|
||||||
<div className="space-y-6">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-1">
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
<Calendar className="w-3.5 h-3.5" />
|
<FileText className="w-3.5 h-3.5" />
|
||||||
Date & Time
|
Event Type
|
||||||
</div>
|
|
||||||
<p className="text-sm font-bold text-text-dark">{new Date(selectedEvent.startDate).toLocaleString()}</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
|
||||||
<MapPin className="w-3.5 h-3.5" />
|
|
||||||
Location
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-bold text-text-dark">{selectedEvent.location}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-dark">{selectedEvent.type || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
<div className="space-y-6">
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
<div className="space-y-1">
|
<Layers className="w-3.5 h-3.5" />
|
||||||
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
Hub Category
|
||||||
<Building2 className="w-3.5 h-3.5" />
|
|
||||||
Department
|
|
||||||
</div>
|
|
||||||
<p className="text-sm font-bold text-text-dark">{selectedEvent.department}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<p className="text-sm font-bold text-text-dark capitalize">
|
||||||
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
{selectedEvent.category === 'TECHNICAL' ? 'Technical'
|
||||||
<Users className="w-3.5 h-3.5" />
|
: selectedEvent.category === 'NON-TECHNICAL' ? 'Non-Technical'
|
||||||
Target Batches
|
: selectedEvent.category === 'WORKSHOP' ? 'Workshop'
|
||||||
</div>
|
: selectedEvent.category === 'CENTRE-ACTIVITY' ? 'Centre Based Activity'
|
||||||
<p className="text-sm font-bold text-text-dark">{selectedEvent.academicYears?.join(', ') || 'N/A'}</p>
|
: selectedEvent.category || 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dates */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<Calendar className="w-3.5 h-3.5" />
|
||||||
|
Start Date & Time
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-dark">
|
||||||
|
{selectedEvent.startDate
|
||||||
|
? new Date(selectedEvent.startDate).toLocaleString('en-IN', { timeZone: 'Asia/Kolkata', day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }) + ' IST'
|
||||||
|
: 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<Calendar className="w-3.5 h-3.5" />
|
||||||
|
End Date & Time
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-dark">
|
||||||
|
{selectedEvent.endDate
|
||||||
|
? new Date(selectedEvent.endDate).toLocaleString('en-IN', { timeZone: 'Asia/Kolkata', day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }) + ' IST'
|
||||||
|
: 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Location & Capacity */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<MapPin className="w-3.5 h-3.5" />
|
||||||
|
Location
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-dark">{selectedEvent.location || 'N/A'}</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<Users className="w-3.5 h-3.5" />
|
||||||
|
Total Capacity
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-dark">
|
||||||
|
{selectedEvent.maxParticipants ? selectedEvent.maxParticipants.toLocaleString() : 'Unlimited'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scope */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<Building2 className="w-3.5 h-3.5" />
|
||||||
|
Event Scope
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-dark">
|
||||||
|
{selectedEvent.department === 'Institutional' ? 'Institutional Event' : 'Departmental Event'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<Building2 className="w-3.5 h-3.5" />
|
||||||
|
Proposing Department
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-dark">
|
||||||
|
{(selectedEvent.proposer?.department && selectedEvent.proposer?.department !== 'N/A')
|
||||||
|
? selectedEvent.proposer.department
|
||||||
|
: (selectedEvent.proposer?.role === 'ADMIN' ? 'System Administrator' : selectedEvent.department)
|
||||||
|
|| 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Target Departments & Batches */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<Users className="w-3.5 h-3.5" />
|
||||||
|
Target Audience
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-3">
|
||||||
|
<div className="p-3 bg-slate-50 rounded-xl">
|
||||||
|
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Departments</p>
|
||||||
|
<p className="text-xs font-bold text-text-dark">
|
||||||
|
{(selectedEvent.targetDepartments && selectedEvent.targetDepartments.length > 0)
|
||||||
|
? selectedEvent.targetDepartments.join(', ')
|
||||||
|
: selectedEvent.department || 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-slate-50 rounded-xl">
|
||||||
|
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Batches</p>
|
||||||
|
<p className="text-xs font-bold text-text-dark">
|
||||||
|
{selectedEvent.academicYears?.length > 0 ? selectedEvent.academicYears.join(', ') : 'All'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-slate-50 rounded-xl">
|
||||||
|
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Sections</p>
|
||||||
|
<p className="text-xs font-bold text-text-dark">
|
||||||
|
{(selectedEvent.targetedSections && selectedEvent.targetedSections.length > 0)
|
||||||
|
? selectedEvent.targetedSections.join(', ')
|
||||||
|
: 'All Sections'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -347,16 +453,77 @@ export const EventHistory: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8 pt-4 border-t border-slate-50">
|
{/* Event Amenities / Requirements */}
|
||||||
<div className="p-4 bg-slate-50 rounded-2xl">
|
{selectedEvent.requirements && selectedEvent.requirements.length > 0 && (
|
||||||
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted mb-1">
|
<div className="space-y-3">
|
||||||
<Wallet className="w-3.5 h-3.5" />
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
Budget
|
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-500" />
|
||||||
|
Event Amenities
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{selectedEvent.requirements.map((req, idx) => (
|
||||||
|
<span key={idx} className="px-3 py-1 bg-emerald-50 text-emerald-700 rounded-lg text-[10px] font-black uppercase tracking-widest border border-emerald-100">
|
||||||
|
{req}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg font-black text-text-dark">₹{selectedEvent.budget?.toLocaleString() || '0'}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-slate-50 rounded-2xl">
|
)}
|
||||||
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted mb-1">
|
|
||||||
|
{/* Financial Details */}
|
||||||
|
<div className="space-y-3 border-t border-slate-50 pt-4">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<Wallet className="w-3.5 h-3.5" />
|
||||||
|
Financial Details
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||||
|
<div className="p-3 bg-slate-50 rounded-xl">
|
||||||
|
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Budget</p>
|
||||||
|
<p className="text-sm font-black text-text-dark">₹{selectedEvent.budget?.toLocaleString() || '0'}</p>
|
||||||
|
</div>
|
||||||
|
{(selectedEvent.refreshment_expense || 0) > 0 && (
|
||||||
|
<div className="p-3 bg-slate-50 rounded-xl">
|
||||||
|
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Refreshments</p>
|
||||||
|
<p className="text-sm font-black text-text-dark">₹{selectedEvent.refreshment_expense?.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(selectedEvent.transportation_expense || 0) > 0 && (
|
||||||
|
<div className="p-3 bg-slate-50 rounded-xl">
|
||||||
|
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Transportation</p>
|
||||||
|
<p className="text-sm font-black text-text-dark">₹{selectedEvent.transportation_expense?.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(selectedEvent.session_coverage_fee || 0) > 0 && (
|
||||||
|
<div className="p-3 bg-slate-50 rounded-xl">
|
||||||
|
<p className="text-[9px] font-black uppercase tracking-widest text-text-muted mb-1">Session Coverage</p>
|
||||||
|
<p className="text-sm font-black text-text-dark">₹{selectedEvent.session_coverage_fee?.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="p-3 bg-brand-navy rounded-xl">
|
||||||
|
<p className="text-[9px] font-black uppercase tracking-widest text-white/60 mb-1">Total Expense</p>
|
||||||
|
<p className="text-sm font-black text-white">₹{(selectedEvent.total_expense || selectedEvent.budget || 0).toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Registration / Free Event */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
|
<Ticket className="w-3.5 h-3.5" />
|
||||||
|
Registration
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-text-dark">
|
||||||
|
{selectedEvent.hasRegistrationFee
|
||||||
|
? `Paid — ₹${selectedEvent.registrationFee?.toLocaleString()}`
|
||||||
|
: 'Free Event'}
|
||||||
|
</p>
|
||||||
|
{selectedEvent.hasRegistrationFee && selectedEvent.paymentLink && (
|
||||||
|
<a href={selectedEvent.paymentLink} target="_blank" rel="noopener noreferrer" className="text-[10px] text-brand-indigo font-bold underline">Payment Link ↗</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl space-y-1">
|
||||||
|
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||||
<Ticket className="w-3.5 h-3.5" />
|
<Ticket className="w-3.5 h-3.5" />
|
||||||
Status
|
Status
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
MapPin,
|
MapPin,
|
||||||
@@ -25,6 +27,7 @@ import {
|
|||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
FileSpreadsheet
|
FileSpreadsheet
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
import { API_BASE_URL } from '../../lib/config';
|
import { API_BASE_URL } from '../../lib/config';
|
||||||
@@ -116,7 +119,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const [venues, setVenues] = useState<{name: string, capacity: number | null}[]>([
|
const [venues, setVenues] = useState<{ name: string, capacity: number | null }[]>([
|
||||||
{ name: 'N/A', capacity: null },
|
{ name: 'N/A', capacity: null },
|
||||||
{ name: 'Others.', capacity: null }
|
{ name: 'Others.', capacity: null }
|
||||||
]);
|
]);
|
||||||
@@ -175,8 +178,21 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getInitialScope = () => {
|
||||||
|
if (initialData?.category) {
|
||||||
|
if (['TECHNICAL', 'NON-TECHNICAL', 'WORKSHOP', 'ACADEMIC'].includes(initialData.category)) {
|
||||||
|
return 'DEPARTMENT';
|
||||||
|
}
|
||||||
|
if (initialData.category === 'CENTRE-ACTIVITY') {
|
||||||
|
return 'CENTRE';
|
||||||
|
}
|
||||||
|
return initialData.category as any;
|
||||||
|
}
|
||||||
|
return initialData?.isClubEvent ? 'INSTITUTIONAL' : (isPlacementCell ? 'PLACEMENT' : 'DEPARTMENT');
|
||||||
|
};
|
||||||
|
|
||||||
const [eventScope, setEventScope] = useState<'INSTITUTIONAL' | 'DEPARTMENT' | 'CLUB' | 'PLACEMENT' | 'CENTRE'>(
|
const [eventScope, setEventScope] = useState<'INSTITUTIONAL' | 'DEPARTMENT' | 'CLUB' | 'PLACEMENT' | 'CENTRE'>(
|
||||||
initialData?.isClubEvent ? 'INSTITUTIONAL' : (isPlacementCell ? 'PLACEMENT' : 'DEPARTMENT')
|
getInitialScope()
|
||||||
);
|
);
|
||||||
const [conflictData, setConflictData] = useState<{ message: string, conflicts: any[], canOverride: boolean } | null>(null);
|
const [conflictData, setConflictData] = useState<{ message: string, conflicts: any[], canOverride: boolean } | null>(null);
|
||||||
|
|
||||||
@@ -191,6 +207,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const parseDateForInput = (dateStr?: string) => {
|
const parseDateForInput = (dateStr?: string) => {
|
||||||
if (!dateStr) return '';
|
if (!dateStr) return '';
|
||||||
try {
|
try {
|
||||||
@@ -225,8 +242,8 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
eventName: initialData?.eventName || '',
|
eventName: initialData?.eventName || '',
|
||||||
startDate: initialData?.startDate ? formatDateTimeForInput(initialData.startDate) : '',
|
startDate: initialData?.startDate ? formatDateTimeForInput(initialData.startDate) : '',
|
||||||
endDate: initialData?.endDate ? formatDateTimeForInput(initialData.endDate) : '',
|
endDate: initialData?.endDate ? formatDateTimeForInput(initialData.endDate) : '',
|
||||||
eventType: isClubVariant ? 'CLUB' : (initialData?.eventType || 'Guest Lecture'),
|
eventType: initialData?.eventType || (isClubVariant ? 'CLUB' : 'Guest Lecture'),
|
||||||
category: isClubVariant ? 'CLUB' : (initialData?.category || 'ACADEMIC'),
|
category: initialData?.category || (isClubVariant ? 'CLUB' : 'TECHNICAL'),
|
||||||
institution: initialData?.institution || 'RIT',
|
institution: initialData?.institution || 'RIT',
|
||||||
department: initialData?.department || (isFaculty ? (user?.department || 'CSE') : 'CSE'),
|
department: initialData?.department || (isFaculty ? (user?.department || 'CSE') : 'CSE'),
|
||||||
academicYears: initialData?.academicYears || [] as string[],
|
academicYears: initialData?.academicYears || [] as string[],
|
||||||
@@ -262,8 +279,8 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
// Sync financial totals
|
// Sync financial totals
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const total = (parseFloat(formData.refreshment_expense) || 0) +
|
const total = (parseFloat(formData.refreshment_expense) || 0) +
|
||||||
(parseFloat(formData.transportation_expense) || 0) +
|
(parseFloat(formData.transportation_expense) || 0) +
|
||||||
(parseFloat(formData.session_coverage_fee) || 0);
|
(parseFloat(formData.session_coverage_fee) || 0);
|
||||||
setFormData(prev => ({ ...prev, total_expense: total.toFixed(2) }));
|
setFormData(prev => ({ ...prev, total_expense: total.toFixed(2) }));
|
||||||
}, [formData.refreshment_expense, formData.transportation_expense, formData.session_coverage_fee]);
|
}, [formData.refreshment_expense, formData.transportation_expense, formData.session_coverage_fee]);
|
||||||
|
|
||||||
@@ -281,15 +298,30 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
customVenue: '',
|
customVenue: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const prevEventScope = useRef(eventScope);
|
||||||
|
const hasInitializedEdit = useRef(!!initialData?.isEditMode);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const scopeChanged = prevEventScope.current !== eventScope;
|
||||||
|
prevEventScope.current = eventScope;
|
||||||
|
|
||||||
|
// Skip reset entirely on initial mount when editing an existing event
|
||||||
|
if (hasInitializedEdit.current) {
|
||||||
|
hasInitializedEdit.current = false;
|
||||||
|
if (!scopeChanged) return; // user?.department changed, not scope — don't reset
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only reset when the scope actually changed (user clicked a different tab)
|
||||||
|
if (!scopeChanged) return;
|
||||||
|
|
||||||
// Reset event name and update default event type on scope change
|
// Reset event name and update default event type on scope change
|
||||||
const availableCentres = CENTRE_MAPPING[user?.department || ''] || CENTRE_MAPPING['Placement Department'] || [];
|
const availableCentres = CENTRE_MAPPING[user?.department || ''] || CENTRE_MAPPING['Placement Department'] || [];
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
eventName: '',
|
eventName: '',
|
||||||
eventType: eventScope === 'INSTITUTIONAL' ? 'Institutional' :
|
eventType: eventScope === 'INSTITUTIONAL' ? '' :
|
||||||
eventScope === 'CLUB' ? 'Club' :
|
eventScope === 'CLUB' ? 'Club' :
|
||||||
eventScope === 'PLACEMENT' ? 'Placement' : '',
|
eventScope === 'PLACEMENT' ? 'Placement' : '',
|
||||||
category: eventScope === 'CENTRE' ? 'CENTRE' : (eventScope === 'CLUB' ? 'CLUB' : 'ACADEMIC'),
|
category: eventScope === 'CENTRE' ? 'CENTRE' : (eventScope === 'CLUB' ? 'CLUB' : 'ACADEMIC'),
|
||||||
centreName: eventScope === 'CENTRE' ? (availableCentres[0] || '') : prev.centreName
|
centreName: eventScope === 'CENTRE' ? (availableCentres[0] || '') : prev.centreName
|
||||||
}));
|
}));
|
||||||
@@ -297,12 +329,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
|
|
||||||
const [batches, setBatches] = useState<any[]>([]);
|
const [batches, setBatches] = useState<any[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
// data-fetch effect is declared after the fetch functions below
|
||||||
fetchClasses();
|
|
||||||
fetchExistingEvents();
|
|
||||||
fetchClassStrengths();
|
|
||||||
fetchBatches();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fetchBatches = async () => {
|
const fetchBatches = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -316,6 +343,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const deptBatches = useMemo(() => {
|
const deptBatches = useMemo(() => {
|
||||||
const dept = eventScope === 'DEPARTMENT' ? formData.department : (user?.department || 'CSE');
|
const dept = eventScope === 'DEPARTMENT' ? formData.department : (user?.department || 'CSE');
|
||||||
return batches.filter(b => b.department === dept);
|
return batches.filter(b => b.department === dept);
|
||||||
@@ -378,10 +406,19 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* eslint-disable react-hooks/set-state-in-effect */
|
||||||
|
useEffect(() => {
|
||||||
|
fetchClasses();
|
||||||
|
fetchExistingEvents();
|
||||||
|
fetchClassStrengths();
|
||||||
|
fetchBatches();
|
||||||
|
}, []);
|
||||||
|
/* eslint-enable react-hooks/set-state-in-effect */
|
||||||
|
|
||||||
const availableYears = Array.from(new Set(
|
const availableYears = Array.from(new Set(
|
||||||
classes
|
classes
|
||||||
.filter(c => c.institution.trim() === formData.institution.trim() &&
|
.filter(c => c.institution.trim() === formData.institution.trim() &&
|
||||||
(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell) ? true : c.department.trim() === formData.department.trim()))
|
(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell) ? true : c.department.trim() === formData.department.trim()))
|
||||||
.map(c => c.academicYear)
|
.map(c => c.academicYear)
|
||||||
)).sort();
|
)).sort();
|
||||||
|
|
||||||
@@ -425,9 +462,9 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
const status = e.status?.toUpperCase();
|
const status = e.status?.toUpperCase();
|
||||||
const isActive = status === 'APPROVED' || status === 'COMPLETED' || status === 'PENDING_PR' || status === 'REQUESTED';
|
const isActive = status === 'APPROVED' || status === 'COMPLETED' || status === 'PENDING_PR' || status === 'REQUESTED';
|
||||||
return e.type?.toLowerCase() === formData.eventType?.toLowerCase() &&
|
return e.type?.toLowerCase() === formData.eventType?.toLowerCase() &&
|
||||||
isActive &&
|
isActive &&
|
||||||
e.targetedSections?.includes(section) &&
|
e.targetedSections?.includes(section) &&
|
||||||
formData.academicYears.some(y => e.academicYears?.includes(y));
|
formData.academicYears.some(y => e.academicYears?.includes(y));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -461,32 +498,41 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
}, [formData.targetDepartments, formData.department, formData.academicYears, formData.targetedSections, formData.allBatches, formData.allSections, formData.institution, classStrengths, eventScope, isPlacementCell, availableYears, classes]);
|
}, [formData.targetDepartments, formData.department, formData.academicYears, formData.targetedSections, formData.allBatches, formData.allSections, formData.institution, classStrengths, eventScope, isPlacementCell, availableYears, classes]);
|
||||||
|
|
||||||
const filteredVenues = React.useMemo(() => {
|
const filteredVenues = React.useMemo(() => {
|
||||||
if (totalExpectedStrength === 0) return venues;
|
let list = venues;
|
||||||
|
if (totalExpectedStrength > 0) {
|
||||||
|
// 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);
|
||||||
|
|
||||||
// Find the smallest capacity venue that can accommodate the totalExpectedStrength
|
const minSufficientCapacity = validCapacities.length > 0 ? Math.min(...validCapacities) : 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;
|
list = venues.filter(v => {
|
||||||
|
if (v.name === 'N/A' || v.name === 'Others.' || v.capacity === null) return true;
|
||||||
|
if (initialData?.venue && v.name === initialData.venue) return true;
|
||||||
|
|
||||||
return venues.filter(v => {
|
// Capacity must be at least the audience size, and not exceed the max of (1.5x audience OR the smallest sufficient capacity)
|
||||||
if (v.name === 'N/A' || v.name === 'Others.' || v.capacity === null) return true;
|
const maxAllowedCapacity = Math.max(totalExpectedStrength * 1.5, minSufficientCapacity);
|
||||||
|
return v.capacity >= totalExpectedStrength && v.capacity <= maxAllowedCapacity;
|
||||||
// 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;
|
if (initialData?.venue && !list.some(v => v.name === initialData.venue)) {
|
||||||
});
|
list = [...list, { name: initialData.venue, capacity: null }];
|
||||||
}, [venues, totalExpectedStrength]);
|
}
|
||||||
|
return list;
|
||||||
|
}, [venues, totalExpectedStrength, initialData?.venue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formData.venue && formData.venue !== 'N/A' && formData.venue !== 'Others.') {
|
if (formData.venue && formData.venue !== 'N/A' && formData.venue !== 'Others.') {
|
||||||
const isStillAvailable = filteredVenues.some(v => v.name === formData.venue);
|
const isStillAvailable = filteredVenues.some(v => v.name === formData.venue);
|
||||||
if (!isStillAvailable) {
|
if (!isStillAvailable) {
|
||||||
|
if (initialData?.isEditMode && formData.venue === initialData.venue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setFormData(prev => ({ ...prev, venue: 'N/A' }));
|
setFormData(prev => ({ ...prev, venue: 'N/A' }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [filteredVenues]);
|
}, [filteredVenues, formData.venue, initialData]);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -517,12 +563,6 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
|
|
||||||
const groupRequestId = crypto.randomUUID();
|
const groupRequestId = crypto.randomUUID();
|
||||||
const batchesToAssign = formData.allBatches ? availableYears : formData.academicYears;
|
const batchesToAssign = formData.allBatches ? availableYears : formData.academicYears;
|
||||||
let deptsToCreate = (eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? formData.targetDepartments : [formData.department];
|
|
||||||
|
|
||||||
// For institutional events, create only ONE global event record
|
|
||||||
if (eventScope === 'INSTITUTIONAL') {
|
|
||||||
deptsToCreate = ['Institutional'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventScope === 'CENTRE' && !formData.centreName) {
|
if (eventScope === 'CENTRE' && !formData.centreName) {
|
||||||
setError('Please select a centre.');
|
setError('Please select a centre.');
|
||||||
@@ -530,7 +570,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deptsToCreate.length === 0) {
|
if (formData.targetDepartments.length === 0 && (eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell))) {
|
||||||
setError(`Please select at least one target department for this ${eventScope.toLowerCase()} event.`);
|
setError(`Please select at least one target department for this ${eventScope.toLowerCase()} event.`);
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
return;
|
return;
|
||||||
@@ -572,23 +612,62 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const err = await res.json();
|
const err = await res.json();
|
||||||
throw new Error(err.message || 'Update failed');
|
throw new Error(err.message || 'Update failed');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const dept of deptsToCreate) {
|
const primaryPayload = {
|
||||||
// Filter sections that belong to THIS department
|
...formData,
|
||||||
const deptSections = (eventScope === 'DEPARTMENT' || eventScope === 'PLACEMENT')
|
startDate: earliestStart.toISOString(),
|
||||||
? formData.targetedSections.filter(s =>
|
endDate: latestEnd.toISOString(),
|
||||||
classes.some(c => c.department.trim() === dept.trim() && c.sections.includes(s))
|
deptLimits,
|
||||||
)
|
deptSectionLimits,
|
||||||
: [];
|
maxParticipants: formData.maxParticipants === '' ? null : Number(formData.maxParticipants),
|
||||||
|
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,
|
||||||
|
total_expense: Number(formData.total_expense) || 0,
|
||||||
|
department: formData.department,
|
||||||
|
venue: formData.venue === 'Others.' ? formData.customVenue : formData.venue,
|
||||||
|
groupRequestId,
|
||||||
|
academicYears: batchesToAssign,
|
||||||
|
targetedSections: formData.targetedSections,
|
||||||
|
userId: user?.id,
|
||||||
|
budget: formData.budget === '' ? 0 : Number(formData.budget),
|
||||||
|
registrationFee: formData.hasRegistrationFee ? Number(formData.registrationFee) : 0,
|
||||||
|
category: eventScope === 'INSTITUTIONAL' ? 'INSTITUTIONAL' :
|
||||||
|
eventScope === 'CLUB' ? 'CLUB' :
|
||||||
|
eventScope === 'CENTRE' ? 'CENTRE-ACTIVITY' :
|
||||||
|
eventScope === 'PLACEMENT' ? 'PLACEMENT' :
|
||||||
|
formData.category,
|
||||||
|
eventType: formData.eventType,
|
||||||
|
centreName: eventScope === 'CENTRE' ? formData.centreName : undefined,
|
||||||
|
isPublicEvent: eventScope === 'CENTRE' ? formData.isPublicEvent : undefined,
|
||||||
|
image: formData.image
|
||||||
|
};
|
||||||
|
|
||||||
// If it's a placement event and we're selective, only create for depts with selected sections
|
const res = await fetch(API_BASE_URL + '/api/events/propose', {
|
||||||
if (eventScope === 'PLACEMENT' && formData.targetedSections.length > 0 && deptSections.length === 0) {
|
method: 'POST',
|
||||||
continue;
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
...primaryPayload,
|
||||||
|
cancelConflicting: false
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = await res.json();
|
||||||
|
if (res.status === 409 && err.canOverride) {
|
||||||
|
setConflictData(err);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
throw new Error(err.message || 'Submission failed');
|
||||||
|
}
|
||||||
|
|
||||||
const primaryPayload = {
|
if (!formData.allSections && showAlternateEvent && (eventScope === 'DEPARTMENT' || eventScope === 'PLACEMENT') && leftOutSections.length > 0) {
|
||||||
|
const altPayload = {
|
||||||
...formData,
|
...formData,
|
||||||
startDate: earliestStart.toISOString(),
|
startDate: earliestStart.toISOString(),
|
||||||
endDate: latestEnd.toISOString(),
|
endDate: latestEnd.toISOString(),
|
||||||
@@ -601,78 +680,23 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
transportation_expense: Number(formData.transportation_expense) || 0,
|
transportation_expense: Number(formData.transportation_expense) || 0,
|
||||||
session_coverage_fee: Number(formData.session_coverage_fee) || 0,
|
session_coverage_fee: Number(formData.session_coverage_fee) || 0,
|
||||||
total_expense: Number(formData.total_expense) || 0,
|
total_expense: Number(formData.total_expense) || 0,
|
||||||
department: dept,
|
department: formData.department,
|
||||||
venue: formData.venue === 'Others.' ? formData.customVenue : formData.venue,
|
|
||||||
groupRequestId,
|
groupRequestId,
|
||||||
|
eventName: alternateEvent.eventName || `${formData.eventName} (Division 2)`,
|
||||||
|
venue: alternateEvent.venue === 'Others.' ? alternateEvent.customVenue : alternateEvent.venue,
|
||||||
|
targetedSections: leftOutSections,
|
||||||
academicYears: batchesToAssign,
|
academicYears: batchesToAssign,
|
||||||
targetedSections: deptSections,
|
|
||||||
userId: user?.id,
|
userId: user?.id,
|
||||||
budget: formData.budget === '' ? 0 : Number(formData.budget),
|
budget: 0,
|
||||||
registrationFee: formData.hasRegistrationFee ? Number(formData.registrationFee) : 0,
|
registrationFee: formData.hasRegistrationFee ? Number(formData.registrationFee) : 0,
|
||||||
category: eventScope === 'CLUB' ? 'CLUB' : (eventScope === 'INSTITUTIONAL' ? 'INSTITUTIONAL' : (eventScope === 'CENTRE' ? 'CENTRE' : 'ACADEMIC')),
|
eventType: formData.eventType
|
||||||
eventType: formData.eventType,
|
|
||||||
centreName: eventScope === 'CENTRE' ? formData.centreName : undefined,
|
|
||||||
isPublicEvent: eventScope === 'CENTRE' ? formData.isPublicEvent : undefined,
|
|
||||||
image: formData.image
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(API_BASE_URL + '/api/events/propose', {
|
await fetch(API_BASE_URL + '/api/events/propose', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(altPayload),
|
||||||
...primaryPayload,
|
|
||||||
cancelConflicting: false
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const err = await res.json();
|
|
||||||
if (res.status === 409 && err.canOverride) {
|
|
||||||
setConflictData(err);
|
|
||||||
setIsSubmitting(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new Error(`[${dept}] ${err.message || 'Submission failed'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formData.allSections && showAlternateEvent && (eventScope === 'DEPARTMENT' || eventScope === 'PLACEMENT') && leftOutSections.length > 0) {
|
|
||||||
const deptLeftOut = leftOutSections.filter(s =>
|
|
||||||
classes.some(c => c.department.trim() === dept.trim() && c.sections.includes(s))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (deptLeftOut.length > 0) {
|
|
||||||
const altPayload = {
|
|
||||||
...formData,
|
|
||||||
startDate: earliestStart.toISOString(),
|
|
||||||
endDate: latestEnd.toISOString(),
|
|
||||||
deptLimits,
|
|
||||||
deptSectionLimits,
|
|
||||||
maxParticipants: formData.maxParticipants === '' ? null : Number(formData.maxParticipants),
|
|
||||||
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,
|
|
||||||
total_expense: Number(formData.total_expense) || 0,
|
|
||||||
department: dept,
|
|
||||||
groupRequestId,
|
|
||||||
eventName: alternateEvent.eventName || `${formData.eventName} (Division 2)`,
|
|
||||||
venue: alternateEvent.venue === 'Others.' ? alternateEvent.customVenue : alternateEvent.venue,
|
|
||||||
targetedSections: deptLeftOut,
|
|
||||||
academicYears: batchesToAssign,
|
|
||||||
userId: user?.id,
|
|
||||||
budget: 0,
|
|
||||||
registrationFee: formData.hasRegistrationFee ? Number(formData.registrationFee) : 0,
|
|
||||||
eventType: formData.eventType
|
|
||||||
};
|
|
||||||
|
|
||||||
await fetch(API_BASE_URL + '/api/events/propose', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(altPayload),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSubmitted(true);
|
setSubmitted(true);
|
||||||
@@ -793,9 +817,9 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<div>
|
<div>
|
||||||
<h2 className="text-3xl font-black tracking-tight mb-1">
|
<h2 className="text-3xl font-black tracking-tight mb-1">
|
||||||
{eventScope === 'INSTITUTIONAL' ? 'Institutional Proposal' :
|
{eventScope === 'INSTITUTIONAL' ? 'Institutional Proposal' :
|
||||||
eventScope === 'CLUB' ? 'Club Proposal' :
|
eventScope === 'CLUB' ? 'Club Proposal' :
|
||||||
eventScope === 'CENTRE' ? 'Centre Event Proposal' :
|
eventScope === 'CENTRE' ? 'Centre Event Proposal' :
|
||||||
eventScope === 'PLACEMENT' ? 'Placement Coordinator Schedule' : 'Departmental Proposal'}
|
eventScope === 'PLACEMENT' ? 'Placement Coordinator Schedule' : 'Departmental Proposal'}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-white/60 font-medium text-sm">
|
<p className="text-white/60 font-medium text-sm">
|
||||||
{isFaculty ? `Faculty of ${user?.department}` : 'Administrative Portal'}
|
{isFaculty ? `Faculty of ${user?.department}` : 'Administrative Portal'}
|
||||||
@@ -814,7 +838,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<select
|
<select
|
||||||
required
|
required
|
||||||
value={formData.eventType}
|
value={formData.eventType}
|
||||||
onChange={e => setFormData({...formData, eventType: e.target.value})}
|
onChange={e => setFormData({ ...formData, eventType: e.target.value })}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all pr-12"
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all pr-12"
|
||||||
>
|
>
|
||||||
<option value="">Select Type</option>
|
<option value="">Select Type</option>
|
||||||
@@ -830,50 +854,80 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="text" required
|
type="text" required
|
||||||
value={formData.eventName}
|
value={formData.eventName}
|
||||||
onChange={e => setFormData({...formData, eventName: e.target.value})}
|
onChange={e => setFormData({ ...formData, eventName: 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"
|
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"
|
||||||
placeholder="e.g. Workshop on Generative AI"
|
placeholder="e.g. Workshop on Generative AI"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : eventScope === 'INSTITUTIONAL' ? (
|
) : eventScope === 'INSTITUTIONAL' ? (
|
||||||
<div className="md:col-span-2 relative group">
|
<>
|
||||||
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted mb-3 block">Select Institutional Event</label>
|
<div>
|
||||||
<div className="relative">
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted mb-3 block">Select Institutional Event</label>
|
||||||
<select
|
<div className="relative group">
|
||||||
required
|
<select
|
||||||
value={formData.eventName}
|
required
|
||||||
onChange={e => setFormData({...formData, eventName: e.target.value, eventType: 'Institutional'})}
|
value={formData.eventType}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all pr-12"
|
onChange={e => setFormData({ ...formData, eventType: e.target.value })}
|
||||||
>
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all pr-12"
|
||||||
<option value="">Choose Institutional Event...</option>
|
>
|
||||||
{availableInstitutionalEvents.map(ie => (
|
<option value="">Choose Institutional Event...</option>
|
||||||
<option key={ie.id} value={ie.name}>{ie.name}</option>
|
{availableInstitutionalEvents.map(ie => (
|
||||||
))}
|
<option key={ie.id} value={ie.name}>{ie.name}</option>
|
||||||
</select>
|
))}
|
||||||
<ChevronDown className="absolute right-6 top-1/2 -translate-y-1/2 w-4 h-4 text-text-muted pointer-events-none" />
|
</select>
|
||||||
|
<ChevronDown className="absolute right-6 top-1/2 -translate-y-1/2 w-4 h-4 text-text-muted pointer-events-none" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted mb-3 block">Event Name</label>
|
||||||
|
<input
|
||||||
|
type="text" required
|
||||||
|
value={formData.eventName}
|
||||||
|
onChange={e => setFormData({ ...formData, eventName: 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"
|
||||||
|
placeholder="e.g. Game Jam 2026"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted mb-3 block">Event Name</label>
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted mb-3 block">Event Name</label>
|
||||||
<input
|
<input
|
||||||
type="text" required
|
type="text" required
|
||||||
value={formData.eventName}
|
value={formData.eventName}
|
||||||
onChange={e => setFormData({...formData, eventName: e.target.value, eventType: eventScope === 'CLUB' ? 'Club' : 'Placement'})}
|
onChange={e => setFormData({ ...formData, eventName: e.target.value, eventType: eventScope === 'CLUB' ? 'Club' : 'Placement' })}
|
||||||
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"
|
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"
|
||||||
placeholder={eventScope === 'PLACEMENT' ? "Enter placement activity name..." : "Enter event title..."}
|
placeholder={eventScope === 'PLACEMENT' ? "Enter placement activity name..." : "Enter event title..."}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted mb-3 block">Hub Category</label>
|
||||||
|
<div className="relative group">
|
||||||
|
<select
|
||||||
|
required
|
||||||
|
value={formData.category}
|
||||||
|
onChange={e => setFormData({ ...formData, category: e.target.value })}
|
||||||
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all pr-12"
|
||||||
|
>
|
||||||
|
<option value="TECHNICAL">Technical Event</option>
|
||||||
|
<option value="NON-TECHNICAL">Non-Technical Event</option>
|
||||||
|
<option value="WORKSHOP">Workshop</option>
|
||||||
|
<option value="CENTRE-ACTIVITY">Centre Based Activity</option>
|
||||||
|
</select>
|
||||||
|
<ChevronDown className="absolute right-6 top-1/2 -translate-y-1/2 w-4 h-4 text-text-muted pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{initialData?.isEditMode && user?.role === 'ADMIN' && (
|
{initialData?.isEditMode && user?.role === 'ADMIN' && (
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted mb-3 block">Event Status (Admin Only)</label>
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted mb-3 block">Event Status (Admin Only)</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<select
|
<select
|
||||||
value={formData.status}
|
value={formData.status}
|
||||||
onChange={e => setFormData({...formData, status: e.target.value})}
|
onChange={e => setFormData({ ...formData, status: e.target.value })}
|
||||||
className="w-full bg-brand-glow border border-brand-indigo/20 rounded-2xl py-4 px-6 text-sm font-bold text-brand-indigo appearance-none cursor-pointer group-hover:bg-brand-glow/80 transition-all pr-12"
|
className="w-full bg-brand-glow border border-brand-indigo/20 rounded-2xl py-4 px-6 text-sm font-bold text-brand-indigo appearance-none cursor-pointer group-hover:bg-brand-glow/80 transition-all pr-12"
|
||||||
>
|
>
|
||||||
<option value="REQUESTED">REQUESTED</option>
|
<option value="REQUESTED">REQUESTED</option>
|
||||||
@@ -895,7 +949,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<FileText className="absolute left-5 top-5 w-5 h-5 text-text-muted" />
|
<FileText className="absolute left-5 top-5 w-5 h-5 text-text-muted" />
|
||||||
<textarea
|
<textarea
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={e => setFormData({...formData, description: e.target.value})}
|
onChange={e => setFormData({ ...formData, description: e.target.value })}
|
||||||
rows={4}
|
rows={4}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-5 pl-14 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all resize-none"
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-5 pl-14 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all resize-none"
|
||||||
placeholder="Provide brief details about the event objective and highlights..."
|
placeholder="Provide brief details about the event objective and highlights..."
|
||||||
@@ -909,7 +963,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.venue}
|
value={formData.venue}
|
||||||
onChange={e => setFormData({...formData, venue: e.target.value})}
|
onChange={e => setFormData({ ...formData, venue: e.target.value })}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all"
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all"
|
||||||
>
|
>
|
||||||
{filteredVenues.map(v => <option key={v.name} value={v.name}>{v.name} {v.capacity ? `(Max: ${v.capacity})` : ''}</option>)}
|
{filteredVenues.map(v => <option key={v.name} value={v.name}>{v.name} {v.capacity ? `(Max: ${v.capacity})` : ''}</option>)}
|
||||||
@@ -920,7 +974,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="text" required
|
type="text" required
|
||||||
value={formData.customVenue}
|
value={formData.customVenue}
|
||||||
onChange={e => setFormData({...formData, customVenue: e.target.value})}
|
onChange={e => setFormData({ ...formData, customVenue: e.target.value })}
|
||||||
className="w-full bg-slate-50 border border-brand-indigo/30 rounded-2xl py-4 px-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo transition-all"
|
className="w-full bg-slate-50 border border-brand-indigo/30 rounded-2xl py-4 px-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo transition-all"
|
||||||
placeholder="Specify custom venue location..."
|
placeholder="Specify custom venue location..."
|
||||||
/>
|
/>
|
||||||
@@ -938,7 +992,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="datetime-local" required
|
type="datetime-local" required
|
||||||
value={formData.startDate}
|
value={formData.startDate}
|
||||||
onChange={e => setFormData({...formData, startDate: e.target.value})}
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -948,7 +1002,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="datetime-local" required
|
type="datetime-local" required
|
||||||
value={formData.endDate}
|
value={formData.endDate}
|
||||||
onChange={e => setFormData({...formData, endDate: e.target.value})}
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -962,7 +1016,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
placeholder="Unlimited"
|
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"
|
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}
|
value={formData.maxParticipants}
|
||||||
onChange={e => setFormData({...formData, maxParticipants: e.target.value})}
|
onChange={e => setFormData({ ...formData, maxParticipants: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -983,7 +1037,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.budget}
|
value={formData.budget}
|
||||||
onChange={e => setFormData({...formData, budget: e.target.value})}
|
onChange={e => setFormData({ ...formData, budget: e.target.value })}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 pl-12 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all"
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 pl-12 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
/>
|
/>
|
||||||
@@ -997,7 +1051,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.refreshment_expense}
|
value={formData.refreshment_expense}
|
||||||
onChange={e => setFormData({...formData, refreshment_expense: e.target.value})}
|
onChange={e => setFormData({ ...formData, refreshment_expense: e.target.value })}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 pl-12 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all"
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 pl-12 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
/>
|
/>
|
||||||
@@ -1011,7 +1065,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.transportation_expense}
|
value={formData.transportation_expense}
|
||||||
onChange={e => setFormData({...formData, transportation_expense: e.target.value})}
|
onChange={e => setFormData({ ...formData, transportation_expense: e.target.value })}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 pl-12 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all"
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 pl-12 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
/>
|
/>
|
||||||
@@ -1025,7 +1079,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.session_coverage_fee}
|
value={formData.session_coverage_fee}
|
||||||
onChange={e => setFormData({...formData, session_coverage_fee: e.target.value})}
|
onChange={e => setFormData({ ...formData, session_coverage_fee: e.target.value })}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 pl-12 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all"
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 pl-12 pr-6 text-sm font-semibold focus:bg-white focus:border-brand-indigo/30 transition-all"
|
||||||
placeholder="0.00"
|
placeholder="0.00"
|
||||||
/>
|
/>
|
||||||
@@ -1048,7 +1102,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted block">Registration Fee Policy</label>
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted block">Registration Fee Policy</label>
|
||||||
<div
|
<div
|
||||||
onClick={() => setFormData({...formData, hasRegistrationFee: !formData.hasRegistrationFee})}
|
onClick={() => setFormData({ ...formData, hasRegistrationFee: !formData.hasRegistrationFee })}
|
||||||
className="flex items-center justify-between p-4 bg-slate-50 rounded-2xl border border-transparent hover:border-brand-indigo/20 cursor-pointer transition-all group"
|
className="flex items-center justify-between p-4 bg-slate-50 rounded-2xl border border-transparent hover:border-brand-indigo/20 cursor-pointer transition-all group"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -1093,7 +1147,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.registrationFee}
|
value={formData.registrationFee}
|
||||||
onChange={e => setFormData({...formData, registrationFee: e.target.value})}
|
onChange={e => setFormData({ ...formData, registrationFee: e.target.value })}
|
||||||
className="w-full bg-white border border-brand-indigo/20 rounded-xl py-4 pl-12 pr-6 text-sm font-bold text-brand-indigo focus:ring-2 ring-brand-indigo/20 outline-none"
|
className="w-full bg-white border border-brand-indigo/20 rounded-xl py-4 pl-12 pr-6 text-sm font-bold text-brand-indigo focus:ring-2 ring-brand-indigo/20 outline-none"
|
||||||
placeholder="Amount in INR"
|
placeholder="Amount in INR"
|
||||||
/>
|
/>
|
||||||
@@ -1105,7 +1159,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
value={formData.paymentLink}
|
value={formData.paymentLink}
|
||||||
onChange={e => setFormData({...formData, paymentLink: e.target.value})}
|
onChange={e => setFormData({ ...formData, paymentLink: e.target.value })}
|
||||||
className="w-full bg-white border border-brand-indigo/20 rounded-xl py-4 px-6 text-sm font-bold text-brand-indigo focus:ring-2 ring-brand-indigo/20 outline-none"
|
className="w-full bg-white border border-brand-indigo/20 rounded-xl py-4 px-6 text-sm font-bold text-brand-indigo focus:ring-2 ring-brand-indigo/20 outline-none"
|
||||||
placeholder="https://forms.gle/... or payment link"
|
placeholder="https://forms.gle/... or payment link"
|
||||||
/>
|
/>
|
||||||
@@ -1211,7 +1265,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<select
|
<select
|
||||||
required
|
required
|
||||||
value={formData.centreName}
|
value={formData.centreName}
|
||||||
onChange={e => setFormData({...formData, centreName: e.target.value})}
|
onChange={e => setFormData({ ...formData, centreName: e.target.value })}
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all pr-12"
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none cursor-pointer group-hover:bg-slate-100 transition-all pr-12"
|
||||||
>
|
>
|
||||||
{(CENTRE_MAPPING[user?.department || ''] || CENTRE_MAPPING['Placement Department'] || []).map(centre => (
|
{(CENTRE_MAPPING[user?.department || ''] || CENTRE_MAPPING['Placement Department'] || []).map(centre => (
|
||||||
@@ -1226,7 +1280,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<div className="flex bg-slate-50 p-2 rounded-2xl gap-2">
|
<div className="flex bg-slate-50 p-2 rounded-2xl gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setFormData({...formData, isPublicEvent: false})}
|
onClick={() => setFormData({ ...formData, isPublicEvent: false })}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all",
|
"flex-1 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all",
|
||||||
!formData.isPublicEvent ? "bg-white text-brand-indigo shadow-sm" : "text-text-muted hover:bg-slate-100"
|
!formData.isPublicEvent ? "bg-white text-brand-indigo shadow-sm" : "text-text-muted hover:bg-slate-100"
|
||||||
@@ -1236,7 +1290,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setFormData({...formData, isPublicEvent: true})}
|
onClick={() => setFormData({ ...formData, isPublicEvent: true })}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all",
|
"flex-1 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all",
|
||||||
formData.isPublicEvent ? "bg-white text-brand-indigo shadow-sm" : "text-text-muted hover:bg-slate-100"
|
formData.isPublicEvent ? "bg-white text-brand-indigo shadow-sm" : "text-text-muted hover:bg-slate-100"
|
||||||
@@ -1252,159 +1306,159 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
<div className={cn((eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) && "md:col-span-2")}>
|
<div className={cn((eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) && "md:col-span-2")}>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted block">
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted block">
|
||||||
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? 'Target Departments' : 'Proposing Department'}
|
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? 'Target Departments' : 'Proposing Department'}
|
||||||
</label>
|
</label>
|
||||||
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) && (
|
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) && (
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
const allD = formData.institution === 'RIT' ? ritDepartments : rsbDepartments;
|
|
||||||
const isAllSelected = formData.targetDepartments.length === allD.length;
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
allDepts: !isAllSelected,
|
|
||||||
targetDepartments: !isAllSelected ? allD : []
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"px-3 py-1 rounded-lg text-[8px] font-black uppercase tracking-widest border transition-all",
|
|
||||||
formData.targetDepartments.length === (formData.institution === 'RIT' ? ritDepartments : rsbDepartments).length ? "bg-brand-indigo text-white border-brand-indigo" : "bg-white text-text-muted border-slate-200"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{formData.targetDepartments.length === (formData.institution === 'RIT' ? ritDepartments : rsbDepartments).length ? 'All Departments Selected' : 'Select All Departments'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? (
|
|
||||||
<div className="flex flex-wrap gap-2 p-6 bg-slate-50 rounded-[2.5rem] border border-slate-100">
|
|
||||||
{(formData.institution === 'RIT' ? ritDepartments : rsbDepartments).map(dept => (
|
|
||||||
<button
|
<button
|
||||||
key={dept}
|
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleSelection('targetDepartments', dept)}
|
onClick={() => {
|
||||||
|
const allD = formData.institution === 'RIT' ? ritDepartments : rsbDepartments;
|
||||||
|
const isAllSelected = formData.targetDepartments.length === allD.length;
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
allDepts: !isAllSelected,
|
||||||
|
targetDepartments: !isAllSelected ? allD : []
|
||||||
|
});
|
||||||
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-4 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all border",
|
"px-3 py-1 rounded-lg text-[8px] font-black uppercase tracking-widest border transition-all",
|
||||||
formData.targetDepartments.includes(dept)
|
formData.targetDepartments.length === (formData.institution === 'RIT' ? ritDepartments : rsbDepartments).length ? "bg-brand-indigo text-white border-brand-indigo" : "bg-white text-text-muted border-slate-200"
|
||||||
? "bg-brand-navy text-white border-brand-navy shadow-md"
|
|
||||||
: "bg-white text-text-muted border-slate-200 hover:border-brand-indigo/30"
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{dept}
|
{formData.targetDepartments.length === (formData.institution === 'RIT' ? ritDepartments : rsbDepartments).length ? 'All Departments Selected' : 'Select All Departments'}
|
||||||
</button>
|
</button>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="p-4 bg-slate-50 rounded-2xl border border-slate-100 text-sm font-bold text-text-dark">
|
|
||||||
{formData.department}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cn((eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) && "md:col-span-2")}>
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
|
||||||
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted block">
|
|
||||||
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? 'Target Batches' : 'Target Batch'}
|
|
||||||
</label>
|
|
||||||
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
const isAllSelected = formData.academicYears.length === availableYears.length;
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
allBatches: !isAllSelected,
|
|
||||||
academicYears: !isAllSelected ? availableYears : []
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"px-3 py-1 rounded-lg text-[8px] font-black uppercase tracking-widest border transition-all",
|
|
||||||
formData.academicYears.length === availableYears.length ? "bg-brand-indigo text-white border-brand-indigo" : "bg-white text-text-muted border-slate-200"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{formData.academicYears.length === availableYears.length ? 'All Batches Selected' : 'Select All Batches'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? (
|
|
||||||
<div className="flex flex-wrap gap-2 p-6 bg-slate-50 rounded-[2.5rem] border border-slate-100">
|
|
||||||
{availableYears.map(year => (
|
|
||||||
<button
|
|
||||||
key={year}
|
|
||||||
type="button"
|
|
||||||
onClick={() => toggleSelection('academicYears', year)}
|
|
||||||
className={cn(
|
|
||||||
"px-4 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all border",
|
|
||||||
formData.academicYears.includes(year)
|
|
||||||
? "bg-brand-navy text-white border-brand-navy shadow-md"
|
|
||||||
: "bg-white text-text-muted border-slate-200 hover:border-brand-indigo/30"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{year}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<select
|
|
||||||
value={formData.academicYears[0] || ''}
|
|
||||||
onChange={e => setFormData({...formData, targetedBatch: '', academicYears: [e.target.value], targetedSections: [], allSections: false})}
|
|
||||||
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none"
|
|
||||||
>
|
|
||||||
<option value="">Select Academic Year</option>
|
|
||||||
{availableYears.map(year => <option key={year} value={year}>{year}</option>)}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(eventScope === 'DEPARTMENT' || eventScope === 'PLACEMENT') && formData.academicYears.length > 0 && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted block">Select Sections</label>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
const newVal = !formData.allSections;
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
allSections: newVal,
|
|
||||||
targetedSections: newVal ? availableSections.filter(s => !isSectionDisabled(s)) : []
|
|
||||||
});
|
|
||||||
if (newVal) setShowAlternateEvent(false);
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
"px-3 py-1 rounded-lg text-[8px] font-black uppercase tracking-widest border transition-all",
|
|
||||||
formData.allSections ? "bg-brand-indigo text-white border-brand-indigo" : "bg-white text-text-muted border-slate-200"
|
|
||||||
)}
|
)}
|
||||||
>
|
</div>
|
||||||
{formData.allSections ? 'All Sections Selected' : 'Select All Sections'}
|
|
||||||
</button>
|
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? (
|
||||||
|
<div className="flex flex-wrap gap-2 p-6 bg-slate-50 rounded-[2.5rem] border border-slate-100">
|
||||||
|
{(formData.institution === 'RIT' ? ritDepartments : rsbDepartments).map(dept => (
|
||||||
|
<button
|
||||||
|
key={dept}
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleSelection('targetDepartments', dept)}
|
||||||
|
className={cn(
|
||||||
|
"px-4 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all border",
|
||||||
|
formData.targetDepartments.includes(dept)
|
||||||
|
? "bg-brand-navy text-white border-brand-navy shadow-md"
|
||||||
|
: "bg-white text-text-muted border-slate-200 hover:border-brand-indigo/30"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{dept}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="p-4 bg-slate-50 rounded-2xl border border-slate-100 text-sm font-bold text-text-dark">
|
||||||
|
{formData.department}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className={cn((eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) && "md:col-span-2")}>
|
||||||
{availableSections.map(section => {
|
<div className="flex items-center justify-between mb-3">
|
||||||
const disabled = isSectionDisabled(section);
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted block">
|
||||||
return (
|
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? 'Target Batches' : 'Target Batch'}
|
||||||
|
</label>
|
||||||
|
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) && (
|
||||||
<button
|
<button
|
||||||
key={section}
|
|
||||||
type="button"
|
type="button"
|
||||||
disabled={disabled}
|
onClick={() => {
|
||||||
onClick={() => toggleSelection('targetedSections', section)}
|
const isAllSelected = formData.academicYears.length === availableYears.length;
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
allBatches: !isAllSelected,
|
||||||
|
academicYears: !isAllSelected ? availableYears : []
|
||||||
|
});
|
||||||
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-12 h-12 rounded-xl flex items-center justify-center text-sm font-black transition-all border",
|
"px-3 py-1 rounded-lg text-[8px] font-black uppercase tracking-widest border transition-all",
|
||||||
formData.targetedSections.includes(section) ? "bg-brand-indigo text-white" : disabled ? "bg-slate-100 text-slate-300 cursor-not-allowed" : "bg-slate-50 text-text-muted"
|
formData.academicYears.length === availableYears.length ? "bg-brand-indigo text-white border-brand-indigo" : "bg-white text-text-muted border-slate-200"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{section}
|
{formData.academicYears.length === availableYears.length ? 'All Batches Selected' : 'Select All Batches'}
|
||||||
</button>
|
</button>
|
||||||
);
|
)}
|
||||||
})}
|
</div>
|
||||||
|
|
||||||
|
{(eventScope === 'INSTITUTIONAL' || eventScope === 'CLUB' || eventScope === 'PLACEMENT' || (eventScope === 'DEPARTMENT' && isPlacementCell)) ? (
|
||||||
|
<div className="flex flex-wrap gap-2 p-6 bg-slate-50 rounded-[2.5rem] border border-slate-100">
|
||||||
|
{availableYears.map(year => (
|
||||||
|
<button
|
||||||
|
key={year}
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleSelection('academicYears', year)}
|
||||||
|
className={cn(
|
||||||
|
"px-4 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all border",
|
||||||
|
formData.academicYears.includes(year)
|
||||||
|
? "bg-brand-navy text-white border-brand-navy shadow-md"
|
||||||
|
: "bg-white text-text-muted border-slate-200 hover:border-brand-indigo/30"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{year}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<select
|
||||||
|
value={formData.academicYears[0] || ''}
|
||||||
|
onChange={e => setFormData({ ...formData, targetedBatch: '', academicYears: [e.target.value], targetedSections: [], allSections: false })}
|
||||||
|
className="w-full bg-slate-50 border border-transparent rounded-2xl py-4 px-6 text-sm font-semibold appearance-none"
|
||||||
|
>
|
||||||
|
<option value="">Select Academic Year</option>
|
||||||
|
{availableYears.map(year => <option key={year} value={year}>{year}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(eventScope === 'DEPARTMENT' || eventScope === 'PLACEMENT') && formData.academicYears.length > 0 && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="text-[10px] font-black uppercase tracking-widest text-text-muted block">Select Sections</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const newVal = !formData.allSections;
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
allSections: newVal,
|
||||||
|
targetedSections: newVal ? availableSections.filter(s => !isSectionDisabled(s)) : []
|
||||||
|
});
|
||||||
|
if (newVal) setShowAlternateEvent(false);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"px-3 py-1 rounded-lg text-[8px] font-black uppercase tracking-widest border transition-all",
|
||||||
|
formData.allSections ? "bg-brand-indigo text-white border-brand-indigo" : "bg-white text-text-muted border-slate-200"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formData.allSections ? 'All Sections Selected' : 'Select All Sections'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{availableSections.map(section => {
|
||||||
|
const disabled = isSectionDisabled(section);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={section}
|
||||||
|
type="button"
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => toggleSelection('targetedSections', section)}
|
||||||
|
className={cn(
|
||||||
|
"w-12 h-12 rounded-xl flex items-center justify-center text-sm font-black transition-all border",
|
||||||
|
formData.targetedSections.includes(section) ? "bg-brand-indigo text-white" : disabled ? "bg-slate-100 text-slate-300 cursor-not-allowed" : "bg-slate-50 text-text-muted"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{section}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
{formData.targetedSections.length > 0 && (
|
{formData.targetedSections.length > 0 && (
|
||||||
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="p-6 bg-slate-50 rounded-3xl space-y-4 border border-slate-100">
|
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="p-6 bg-slate-50 rounded-3xl space-y-4 border border-slate-100">
|
||||||
@@ -1416,7 +1470,7 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
{!formData.allSections && !showAlternateEvent && (
|
{!formData.allSections && !showAlternateEvent && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => { setShowAlternateEvent(true); setAlternateEvent({...alternateEvent, eventName: `${formData.eventName} (Division 2)`, startDate: formData.startDate, endDate: formData.endDate}); }}
|
onClick={() => { setShowAlternateEvent(true); setAlternateEvent({ ...alternateEvent, eventName: `${formData.eventName} (Division 2)`, startDate: formData.startDate, endDate: formData.endDate }); }}
|
||||||
className="px-4 py-2 bg-brand-indigo text-white rounded-xl text-[8px] font-black uppercase tracking-widest"
|
className="px-4 py-2 bg-brand-indigo text-white rounded-xl text-[8px] font-black uppercase tracking-widest"
|
||||||
>
|
>
|
||||||
Add Alternate Session
|
Add Alternate Session
|
||||||
@@ -1457,13 +1511,13 @@ export const EventProposalForm: React.FC<EventProposalFormProps> = ({ initialDat
|
|||||||
<X className="w-4 h-4 text-red-500 cursor-pointer" onClick={() => setShowAlternateEvent(false)} />
|
<X className="w-4 h-4 text-red-500 cursor-pointer" onClick={() => setShowAlternateEvent(false)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<input type="text" placeholder="Session Name" value={alternateEvent.eventName} onChange={e => setAlternateEvent({...alternateEvent, eventName: e.target.value})} className="w-full bg-white border border-slate-100 rounded-xl py-3 px-4 text-xs font-bold" />
|
<input type="text" placeholder="Session Name" value={alternateEvent.eventName} onChange={e => setAlternateEvent({ ...alternateEvent, eventName: e.target.value })} className="w-full bg-white border border-slate-100 rounded-xl py-3 px-4 text-xs font-bold" />
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<select value={alternateEvent.venue} onChange={e => setAlternateEvent({...alternateEvent, venue: e.target.value})} className="w-full bg-white border border-slate-100 rounded-xl py-3 px-4 text-xs font-bold">
|
<select value={alternateEvent.venue} onChange={e => setAlternateEvent({ ...alternateEvent, venue: e.target.value })} className="w-full bg-white border border-slate-100 rounded-xl py-3 px-4 text-xs font-bold">
|
||||||
{filteredVenues.map(v => <option key={v.name} value={v.name}>{v.name} {v.capacity ? `(Max: ${v.capacity})` : ''}</option>)}
|
{filteredVenues.map(v => <option key={v.name} value={v.name}>{v.name} {v.capacity ? `(Max: ${v.capacity})` : ''}</option>)}
|
||||||
</select>
|
</select>
|
||||||
{alternateEvent.venue === 'Others.' && (
|
{alternateEvent.venue === 'Others.' && (
|
||||||
<input type="text" required placeholder="Specify custom venue..." value={alternateEvent.customVenue} onChange={e => setAlternateEvent({...alternateEvent, customVenue: e.target.value})} className="w-full mt-3 bg-white border border-brand-indigo/30 rounded-xl py-3 px-4 text-xs font-bold" />
|
<input type="text" required placeholder="Specify custom venue..." value={alternateEvent.customVenue} onChange={e => setAlternateEvent({ ...alternateEvent, customVenue: e.target.value })} className="w-full mt-3 bg-white border border-brand-indigo/30 rounded-xl py-3 px-4 text-xs font-bold" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,6 +65,36 @@ export const ManageNoticesView: React.FC = () => {
|
|||||||
const [eventLink, setEventLink] = useState('');
|
const [eventLink, setEventLink] = useState('');
|
||||||
const [eventImage, setEventImage] = useState<string | null>(null);
|
const [eventImage, setEventImage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const compressImage = (base64Str: string): Promise<string> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = base64Str;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const MAX_WIDTH = 800;
|
||||||
|
const MAX_HEIGHT = 600;
|
||||||
|
let width = img.width;
|
||||||
|
let height = img.height;
|
||||||
|
if (width > height) {
|
||||||
|
if (width > MAX_WIDTH) {
|
||||||
|
height *= MAX_WIDTH / width;
|
||||||
|
width = MAX_WIDTH;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (height > MAX_HEIGHT) {
|
||||||
|
width *= MAX_HEIGHT / height;
|
||||||
|
height = MAX_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx?.drawImage(img, 0, 0, width, height);
|
||||||
|
resolve(canvas.toDataURL('image/jpeg', 0.7));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleEventImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleEventImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
@@ -73,8 +103,9 @@ export const ManageNoticesView: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = () => {
|
reader.onloadend = async () => {
|
||||||
setEventImage(reader.result as string);
|
const compressed = await compressImage(reader.result as string);
|
||||||
|
setEventImage(compressed);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
@@ -133,8 +164,9 @@ export const ManageNoticesView: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = () => {
|
reader.onloadend = async () => {
|
||||||
setNoticeImage(reader.result as string);
|
const compressed = await compressImage(reader.result as string);
|
||||||
|
setNoticeImage(compressed);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
@@ -163,6 +195,9 @@ export const ManageNoticesView: React.FC = () => {
|
|||||||
setIsNoticeModalOpen(false);
|
setIsNoticeModalOpen(false);
|
||||||
fetchInitialData();
|
fetchInitialData();
|
||||||
showAlert('Success', 'Notice posted successfully to Campus Notice Board.', 'success');
|
showAlert('Success', 'Notice posted successfully to Campus Notice Board.', 'success');
|
||||||
|
} else {
|
||||||
|
const errData = await response.json();
|
||||||
|
showAlert('Error', errData.message || 'Failed to save notice. Image may be too large.', 'error');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to post notice:', err);
|
console.error('Failed to post notice:', err);
|
||||||
@@ -222,6 +257,9 @@ export const ManageNoticesView: React.FC = () => {
|
|||||||
setEventImage(null);
|
setEventImage(null);
|
||||||
fetchInitialData();
|
fetchInitialData();
|
||||||
showAlert('Success', 'Special event added to the registry successfully.', 'success');
|
showAlert('Success', 'Special event added to the registry successfully.', 'success');
|
||||||
|
} else {
|
||||||
|
const errData = await response.json();
|
||||||
|
showAlert('Error', errData.message || 'Failed to create special event. Image may be too large.', 'error');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Post failed:', err);
|
console.error('Post failed:', err);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface CategoryGridProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CategoryGrid: React.FC<CategoryGridProps> = ({ onSelectCategory }) => {
|
const CategoryGrid: React.FC<CategoryGridProps> = ({ onSelectCategory }) => {
|
||||||
const threeCategories = CATEGORIES.filter(c => c.id !== 'CENTRE-ACTIVITY' && c.id !== 'WORKWORK' && c.id !== 'WORKSHOP');
|
const threeCategories = CATEGORIES.filter(c => c.id !== 'CENTRE-ACTIVITY');
|
||||||
const centreActivityCategory = CATEGORIES.find(c => c.id === 'CENTRE-ACTIVITY');
|
const centreActivityCategory = CATEGORIES.find(c => c.id === 'CENTRE-ACTIVITY');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -18,7 +18,7 @@ const CategoryGrid: React.FC<CategoryGridProps> = ({ onSelectCategory }) => {
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Three Standard Categories */}
|
{/* Three Standard Categories */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-8 max-w-5xl mx-auto mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 max-w-7xl mx-auto mb-8">
|
||||||
{threeCategories.map((cat) => (
|
{threeCategories.map((cat) => (
|
||||||
<div
|
<div
|
||||||
key={cat.id}
|
key={cat.id}
|
||||||
|
|||||||
@@ -31,13 +31,12 @@ const EventList: React.FC<EventListProps> = ({
|
|||||||
const [selectedDomainName, setSelectedDomainName] = useState<string | null>(null);
|
const [selectedDomainName, setSelectedDomainName] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedCategory === 'TECHNICAL' || selectedCategory === 'NON-TECHNICAL') {
|
setSelectedDomain('ALL');
|
||||||
setSelectedDomain('ALL');
|
if (selectedCategory === 'TECHNICAL') setSelectedDomainName('Technical Events');
|
||||||
setSelectedDomainName(selectedCategory === 'TECHNICAL' ? 'Technical Events' : 'Non-Technical Events');
|
else if (selectedCategory === 'NON-TECHNICAL') setSelectedDomainName('Non-Technical Events');
|
||||||
} else {
|
else if (selectedCategory === 'WORKSHOP') setSelectedDomainName('Workshops');
|
||||||
setSelectedDomain(null);
|
else if (selectedCategory === 'CENTRE-ACTIVITY') setSelectedDomainName('Centre Activities');
|
||||||
setSelectedDomainName(null);
|
else setSelectedDomainName('All Events');
|
||||||
}
|
|
||||||
}, [selectedCategory]);
|
}, [selectedCategory]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -82,17 +81,11 @@ const EventList: React.FC<EventListProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="pt-40 pb-16 px-6 md:px-12 lg:px-24 animate-in fade-in duration-500">
|
<div className="pt-40 pb-16 px-6 md:px-12 lg:px-24 animate-in fade-in duration-500">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => onSelectCategory(null)}
|
||||||
if (selectedCategory === 'TECHNICAL' || selectedCategory === 'NON-TECHNICAL') {
|
|
||||||
onSelectCategory(null);
|
|
||||||
} else {
|
|
||||||
setSelectedDomain(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="flex items-center gap-2 text-[#f97316] font-black uppercase tracking-widest mb-10 group hover:translate-x-[-5px] transition-transform"
|
className="flex items-center gap-2 text-[#f97316] font-black uppercase tracking-widest mb-10 group hover:translate-x-[-5px] transition-transform"
|
||||||
>
|
>
|
||||||
<i className="fas fa-arrow-left"></i>
|
<i className="fas fa-arrow-left"></i>
|
||||||
{(selectedCategory === 'TECHNICAL' || selectedCategory === 'NON-TECHNICAL') ? 'BACK TO CATEGORIES' : 'BACK TO DOMAINS'}
|
BACK TO CATEGORIES
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex flex-col md:flex-row md:items-end justify-between mb-12 gap-6">
|
<div className="flex flex-col md:flex-row md:items-end justify-between mb-12 gap-6">
|
||||||
|
|||||||
@@ -103,7 +103,17 @@ const EventsHeroSlider: React.FC<EventsHeroSliderProps> = ({ events }) => {
|
|||||||
<div className="flex items-center gap-4 mb-6">
|
<div className="flex items-center gap-4 mb-6">
|
||||||
<div className="w-12 h-[2px] bg-[#f97316]"></div>
|
<div className="w-12 h-[2px] bg-[#f97316]"></div>
|
||||||
<span className="text-white font-bold tracking-[0.3em] uppercase text-sm">
|
<span className="text-white font-bold tracking-[0.3em] uppercase text-sm">
|
||||||
JOIN US <span className="text-[#f97316] ml-2">{event.date}</span>
|
JOIN US <span className="text-[#f97316] ml-2">
|
||||||
|
{new Date(event.date).toLocaleString('en-IN', {
|
||||||
|
timeZone: 'Asia/Kolkata',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
}) + ' IST'}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const HomeDashboard: React.FC<HomeDashboardProps> = ({
|
|||||||
settings = {}
|
settings = {}
|
||||||
}) => {
|
}) => {
|
||||||
const [now, setNow] = useState(new Date());
|
const [now, setNow] = useState(new Date());
|
||||||
|
const [zoomedImage, setZoomedImage] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setInterval(() => setNow(new Date()), 30000);
|
const timer = setInterval(() => setNow(new Date()), 30000);
|
||||||
@@ -129,11 +130,24 @@ const HomeDashboard: React.FC<HomeDashboardProps> = ({
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{(ann as any).image && (
|
{(ann as any).image && (
|
||||||
<img
|
<div className="relative rounded-xl overflow-hidden mb-4 shrink-0 shadow-sm border border-black/10 group/img">
|
||||||
src={(ann as any).image}
|
<img
|
||||||
alt={ann.title}
|
src={(ann as any).image}
|
||||||
className="w-full h-40 object-cover rounded-xl mb-4 shrink-0 shadow-sm border border-black/10"
|
alt={ann.title}
|
||||||
/>
|
className="w-full h-40 object-cover"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setZoomedImage((ann as any).image);
|
||||||
|
}}
|
||||||
|
className="absolute top-2 right-2 bg-black/70 hover:bg-black text-white w-7 h-7 rounded-full opacity-100 sm:opacity-0 group-hover/img:opacity-100 transition-opacity duration-200 z-20 flex items-center justify-center shadow-md cursor-pointer border border-white/20"
|
||||||
|
title="Inspect Image"
|
||||||
|
>
|
||||||
|
<i className="fas fa-search-plus text-xs"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="font-sans text-gray-800 text-base flex-grow mb-6 leading-relaxed opacity-90">
|
<p className="font-sans text-gray-800 text-base flex-grow mb-6 leading-relaxed opacity-90">
|
||||||
@@ -174,6 +188,28 @@ const HomeDashboard: React.FC<HomeDashboardProps> = ({
|
|||||||
<AccreditationsSection />
|
<AccreditationsSection />
|
||||||
|
|
||||||
<SpecialEventsBanner specialEvents={specialEvents} />
|
<SpecialEventsBanner specialEvents={specialEvents} />
|
||||||
|
|
||||||
|
{/* Image Zoom Modal */}
|
||||||
|
{zoomedImage && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-[99999] flex items-center justify-center p-4 bg-black/90 backdrop-blur-sm animate-in fade-in duration-200"
|
||||||
|
onClick={() => setZoomedImage(null)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute top-6 right-6 text-white/70 hover:text-white text-2xl bg-white/10 hover:bg-white/20 w-12 h-12 rounded-full flex items-center justify-center transition-all cursor-pointer border border-white/10"
|
||||||
|
onClick={() => setZoomedImage(null)}
|
||||||
|
>
|
||||||
|
<i className="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
<img
|
||||||
|
src={zoomedImage}
|
||||||
|
alt="Inspected Notice"
|
||||||
|
className="max-w-full max-h-[90vh] object-contain rounded-lg shadow-2xl animate-in zoom-in-95 duration-200"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -224,24 +224,7 @@ const ProfileView: React.FC<ProfileViewProps> = ({ onLogout }) => {
|
|||||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden sticky top-32">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden sticky top-32">
|
||||||
<div className="h-32 bg-gradient-to-r from-orange-400 to-rose-500"></div>
|
<div className="h-32 bg-gradient-to-r from-orange-400 to-rose-500"></div>
|
||||||
<div className="px-6 pb-8">
|
<div className="px-6 pb-8">
|
||||||
<div className="relative -mt-16 mb-6 flex justify-center">
|
|
||||||
<div className="w-32 h-32 rounded-full border-4 border-white shadow-lg overflow-hidden bg-gray-100 relative group">
|
|
||||||
<img
|
|
||||||
src={avatarUrl || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user?.fullName}`}
|
|
||||||
alt="Profile"
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
{isEditing && (
|
|
||||||
<div
|
|
||||||
onClick={() => fileInputRef.current?.click()}
|
|
||||||
className="absolute inset-0 bg-black/50 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity"
|
|
||||||
>
|
|
||||||
<i className="fas fa-camera text-white text-xl"></i>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<input type="file" ref={fileInputRef} className="hidden" onChange={handlePhotoChange} accept="image/*" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-1">{user?.fullName}</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-1">{user?.fullName}</h2>
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
|
|||||||
const displayEvents = isLooping ? loopEvents : specialEvents;
|
const displayEvents = isLooping ? loopEvents : specialEvents;
|
||||||
|
|
||||||
const getCardWidth = () => {
|
const getCardWidth = () => {
|
||||||
if (isLooping) return '300px';
|
if (isLooping) return '330px';
|
||||||
const count = specialEvents.length;
|
const count = specialEvents.length;
|
||||||
if (count === 1) return '480px';
|
if (count === 1) return '510px';
|
||||||
if (count === 2) return '400px';
|
if (count === 2) return '430px';
|
||||||
if (count === 3) return '340px';
|
if (count === 3) return '370px';
|
||||||
return '290px'; // 4 events
|
return '310px'; // 4 events
|
||||||
};
|
};
|
||||||
const cardWidth = getCardWidth();
|
const cardWidth = getCardWidth();
|
||||||
|
|
||||||
@@ -110,13 +110,10 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
|
|||||||
}
|
}
|
||||||
|
|
||||||
.seb-card-img {
|
.seb-card-img {
|
||||||
transition: transform 0.7s ease;
|
transition: transform 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
will-change: transform;
|
|
||||||
backface-visibility: hidden;
|
|
||||||
-webkit-backface-visibility: hidden;
|
|
||||||
}
|
}
|
||||||
.seb-card:hover .seb-card-img {
|
.seb-card:hover .seb-card-img {
|
||||||
transform: skewX(5deg) skewY(3deg) scale(1.35) translateZ(0) !important;
|
transform: skewX(5deg) skewY(3deg) scale(1.35) !important;
|
||||||
}
|
}
|
||||||
.seb-card:hover {
|
.seb-card:hover {
|
||||||
box-shadow: 0 0 50px 6px rgba(250,204,21,0.20);
|
box-shadow: 0 0 50px 6px rgba(250,204,21,0.20);
|
||||||
@@ -203,7 +200,7 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
|
|||||||
{/* ── CARD SLIDER (Slanted, inherits skewY(-3deg) from parent) ── */}
|
{/* ── CARD SLIDER (Slanted, inherits skewY(-3deg) from parent) ── */}
|
||||||
<div
|
<div
|
||||||
className="seb-slider-container"
|
className="seb-slider-container"
|
||||||
style={{ padding: '16px 0 20px' }}
|
style={{ padding: '40px 0 40px' }}
|
||||||
>
|
>
|
||||||
<div className={isLooping ? "seb-track" : "seb-static-track"}>
|
<div className={isLooping ? "seb-track" : "seb-static-track"}>
|
||||||
{displayEvents.map((event, idx) => (
|
{displayEvents.map((event, idx) => (
|
||||||
@@ -214,10 +211,10 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
|
|||||||
style={{
|
style={{
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
maxWidth: '85vw',
|
maxWidth: '85vw',
|
||||||
height: '400px',
|
height: '440px',
|
||||||
marginRight: idx === displayEvents.length - 1 ? '0px' : '-28px',
|
marginRight: idx === displayEvents.length - 1 ? '0px' : '-28px',
|
||||||
clipPath: 'polygon(9% 0%,100% 0%,91% 100%,0% 100%)',
|
clipPath: 'polygon(9% 0%,100% 0%,91% 100%,0% 100%)',
|
||||||
transform: 'skewX(-5deg)',
|
transform: `skewX(-5deg) translateY(${idx % 2 === 0 ? '20px' : '-20px'})`,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -226,7 +223,7 @@ const SpecialEventsBanner: React.FC<SpecialEventsBannerProps> = ({ specialEvents
|
|||||||
className="seb-card-img absolute inset-0 bg-cover bg-center"
|
className="seb-card-img absolute inset-0 bg-cover bg-center"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: 'url(' + (event.image || defaultImage) + ')',
|
backgroundImage: 'url(' + (event.image || defaultImage) + ')',
|
||||||
transform: 'skewX(5deg) skewY(3deg) scale(1.22) translateZ(0)',
|
transform: 'skewX(5deg) skewY(3deg) scale(1.22)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const StatsSection: React.FC<StatsSectionProps> = ({ events }) => {
|
|||||||
if (events) {
|
if (events) {
|
||||||
const nonTech = events.filter(e => e.category === 'NON-TECHNICAL').length;
|
const nonTech = events.filter(e => e.category === 'NON-TECHNICAL').length;
|
||||||
const tech = events.filter(e => e.category === 'TECHNICAL').length;
|
const tech = events.filter(e => e.category === 'TECHNICAL').length;
|
||||||
const workshops = events.filter(e => e.category === 'WORKSHOP').length;
|
const workshops = events.filter(e => e.category === 'WORKSHOP' || e.category === 'CENTRE-ACTIVITY').length;
|
||||||
|
|
||||||
setCounts({
|
setCounts({
|
||||||
nonTechnical: nonTech,
|
nonTechnical: nonTech,
|
||||||
@@ -43,7 +43,7 @@ const StatsSection: React.FC<StatsSectionProps> = ({ events }) => {
|
|||||||
delay: 0.2
|
delay: 0.2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Workshops",
|
label: "Centre-Based Activities",
|
||||||
value: counts.workshops,
|
value: counts.workshops,
|
||||||
suffix: "+",
|
suffix: "+",
|
||||||
delay: 0.3
|
delay: 0.3
|
||||||
|
|||||||
@@ -32,26 +32,121 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
|
|||||||
});
|
});
|
||||||
}, [events]);
|
}, [events]);
|
||||||
|
|
||||||
// Duplicate events multiple times to ensure the track is always filled, then double it for the 50% translation loop
|
const displayEvents = useMemo(() => {
|
||||||
const minItems = 10;
|
if (sortedEvents.length === 0) return [];
|
||||||
const repeats = Math.max(1, Math.ceil(minItems / Math.max(1, sortedEvents.length)));
|
if (sortedEvents.length === 1) {
|
||||||
const repeatedSegment = Array(repeats).fill(sortedEvents).flat();
|
return [
|
||||||
const loopEvents = [...repeatedSegment, ...repeatedSegment];
|
...sortedEvents,
|
||||||
|
{
|
||||||
|
id: 'coming-soon-1',
|
||||||
|
title: 'Experience Coming Soon',
|
||||||
|
location: 'To Be Announced',
|
||||||
|
date: 'Stay Tuned',
|
||||||
|
category: 'UPCOMING',
|
||||||
|
image: 'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&q=80&w=800',
|
||||||
|
isComingSoon: true,
|
||||||
|
club: 'RIT Events Team',
|
||||||
|
coordinator: 'Hub Admins',
|
||||||
|
event_summary: 'We are curating the next exciting experience for you. Keep an eye out for upcoming announcements and registrations!',
|
||||||
|
status: 'APPROVED',
|
||||||
|
pricingType: 'FREE',
|
||||||
|
maxParticipants: 100,
|
||||||
|
registrationDeadline: '',
|
||||||
|
durationDays: 1,
|
||||||
|
deptLimits: {},
|
||||||
|
deptSectionLimits: {},
|
||||||
|
currentParticipants: 0,
|
||||||
|
currentDeptCounts: {},
|
||||||
|
currentDeptSectionCounts: {},
|
||||||
|
created_by: '',
|
||||||
|
participantType: 'BOTH',
|
||||||
|
verificationStatus: 'APPROVED'
|
||||||
|
} as any,
|
||||||
|
{
|
||||||
|
id: 'coming-soon-2',
|
||||||
|
title: 'More Events Unveiling Soon',
|
||||||
|
location: 'To Be Announced',
|
||||||
|
date: 'Stay Tuned',
|
||||||
|
category: 'STAY TUNED',
|
||||||
|
image: 'https://images.unsplash.com/photo-1540575467063-178a50c2df87?auto=format&fit=crop&q=80&w=800',
|
||||||
|
isComingSoon: true,
|
||||||
|
club: 'RIT Events Team',
|
||||||
|
coordinator: 'Hub Admins',
|
||||||
|
event_summary: 'We are preparing more engaging workshops and activities. Check back later for new updates!',
|
||||||
|
status: 'APPROVED',
|
||||||
|
pricingType: 'FREE',
|
||||||
|
maxParticipants: 100,
|
||||||
|
registrationDeadline: '',
|
||||||
|
durationDays: 1,
|
||||||
|
deptLimits: {},
|
||||||
|
deptSectionLimits: {},
|
||||||
|
currentParticipants: 0,
|
||||||
|
currentDeptCounts: {},
|
||||||
|
currentDeptSectionCounts: {},
|
||||||
|
created_by: '',
|
||||||
|
participantType: 'BOTH',
|
||||||
|
verificationStatus: 'APPROVED'
|
||||||
|
} as any
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (sortedEvents.length === 2) {
|
||||||
|
return [
|
||||||
|
...sortedEvents,
|
||||||
|
{
|
||||||
|
id: 'coming-soon-1',
|
||||||
|
title: 'Experience Coming Soon',
|
||||||
|
location: 'To Be Announced',
|
||||||
|
date: 'Stay Tuned',
|
||||||
|
category: 'UPCOMING',
|
||||||
|
image: 'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?auto=format&fit=crop&q=80&w=800',
|
||||||
|
isComingSoon: true,
|
||||||
|
club: 'RIT Events Team',
|
||||||
|
coordinator: 'Hub Admins',
|
||||||
|
event_summary: 'We are curating the next exciting experience for you. Keep an eye out for upcoming announcements and registrations!',
|
||||||
|
status: 'APPROVED',
|
||||||
|
pricingType: 'FREE',
|
||||||
|
maxParticipants: 100,
|
||||||
|
registrationDeadline: '',
|
||||||
|
durationDays: 1,
|
||||||
|
deptLimits: {},
|
||||||
|
deptSectionLimits: {},
|
||||||
|
currentParticipants: 0,
|
||||||
|
currentDeptCounts: {},
|
||||||
|
currentDeptSectionCounts: {},
|
||||||
|
created_by: '',
|
||||||
|
participantType: 'BOTH',
|
||||||
|
verificationStatus: 'APPROVED'
|
||||||
|
} as any
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return sortedEvents;
|
||||||
|
}, [sortedEvents]);
|
||||||
|
|
||||||
if (sortedEvents.length === 0) return null;
|
const isMarquee = sortedEvents.length >= 3;
|
||||||
|
|
||||||
|
const loopEvents = useMemo(() => {
|
||||||
|
if (!isMarquee) return displayEvents;
|
||||||
|
const minItems = 10;
|
||||||
|
const repeats = Math.max(1, Math.ceil(minItems / Math.max(1, displayEvents.length)));
|
||||||
|
const repeatedSegment = Array(repeats).fill(displayEvents).flat();
|
||||||
|
return [...repeatedSegment, ...repeatedSegment];
|
||||||
|
}, [displayEvents, isMarquee]);
|
||||||
|
|
||||||
|
if (displayEvents.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-24 px-6 md:px-12 lg:px-24 bg-white overflow-hidden">
|
<div className="py-24 px-6 md:px-12 lg:px-24 bg-white overflow-hidden">
|
||||||
<style>
|
<style>
|
||||||
{`
|
{`
|
||||||
@keyframes merryGoRound {
|
@keyframes merryGoRound {
|
||||||
0% { transform: translateX(0); }
|
0% { transform: translate3d(0, 0, 0); }
|
||||||
100% { transform: translateX(-50%); }
|
100% { transform: translate3d(-50%, 0, 0); }
|
||||||
}
|
}
|
||||||
.animate-merry-go-round {
|
.animate-merry-go-round {
|
||||||
animation: merryGoRound 45s linear infinite;
|
animation: merryGoRound 45s linear infinite;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
.animate-merry-go-round:hover {
|
.animate-merry-go-round:hover {
|
||||||
animation-play-state: paused;
|
animation-play-state: paused;
|
||||||
@@ -67,21 +162,32 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
|
|||||||
<div className="relative overflow-hidden w-full">
|
<div className="relative overflow-hidden w-full">
|
||||||
{/* Gradient masks removed to eliminate fogginess */}
|
{/* Gradient masks removed to eliminate fogginess */}
|
||||||
|
|
||||||
<div className="animate-merry-go-round gap-6 md:gap-8">
|
<div className={`${isMarquee ? 'animate-merry-go-round' : 'flex flex-wrap justify-center'} gap-6 md:gap-8`}>
|
||||||
{loopEvents.map((event, idx) => (
|
{loopEvents.map((event, idx) => (
|
||||||
<div
|
<div
|
||||||
key={`${event.id}-${idx}`}
|
key={`${event.id}-${idx}`}
|
||||||
className="w-[280px] md:w-[350px] lg:w-[400px] flex-shrink-0 bg-white rounded-[2.5rem] p-5 group transition-all shadow-xl shadow-gray-900/5 hover:-translate-y-1 border border-gray-200"
|
className={`w-[280px] md:w-[350px] lg:w-[400px] flex-shrink-0 bg-white rounded-[2.5rem] p-5 group transition-all shadow-xl shadow-gray-900/5 hover:-translate-y-1 border border-gray-200 transform-gpu ${
|
||||||
|
(event as any).isComingSoon ? 'opacity-80 hover:opacity-100' : ''
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="h-48 md:h-56 w-full rounded-[2rem] overflow-hidden mb-6 md:mb-8 relative">
|
<div className="h-48 md:h-56 w-full rounded-[2rem] overflow-hidden mb-6 md:mb-8 relative bg-slate-900">
|
||||||
<img
|
<img
|
||||||
src={event.image}
|
src={event.image}
|
||||||
alt={event.title}
|
alt={event.title}
|
||||||
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
|
className={`w-full h-full object-cover transition-transform duration-1000 ${
|
||||||
|
(event as any).isComingSoon ? 'opacity-40 grayscale group-hover:scale-105' : 'group-hover:scale-110'
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
<div className="absolute top-4 right-4 bg-white px-4 py-2 rounded-full border border-gray-200">
|
<div className="absolute top-4 right-4 bg-white px-4 py-2 rounded-full border border-gray-200">
|
||||||
<span className="text-[9px] font-black text-[#1A202C] uppercase tracking-widest">{event.category}</span>
|
<span className="text-[9px] font-black text-[#1A202C] uppercase tracking-widest">{event.category}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{(event as any).isComingSoon && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<span className="px-5 py-2.5 bg-amber-500/90 text-slate-950 text-[10px] font-black uppercase tracking-[0.2em] rounded-xl shadow-lg border border-amber-400/20 backdrop-blur-xs flex items-center gap-2">
|
||||||
|
<i className="fas fa-lock text-[8px]"></i> Coming Soon
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2 pb-2 md:pb-4">
|
<div className="px-2 pb-2 md:pb-4">
|
||||||
<h4 className="text-xl md:text-2xl font-serif text-[#1A202C] mb-4 md:mb-6 group-hover:text-[#f97316] transition-colors line-clamp-1">
|
<h4 className="text-xl md:text-2xl font-serif text-[#1A202C] mb-4 md:mb-6 group-hover:text-[#f97316] transition-colors line-clamp-1">
|
||||||
@@ -91,9 +197,21 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<i className="far fa-calendar-alt text-[#f97316]"></i>
|
<i className="far fa-calendar-alt text-[#f97316]"></i>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
{event.date}
|
{new Date(event.date).toLocaleString('en-IN', {
|
||||||
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
timeZone: 'Asia/Kolkata',
|
||||||
<span className="text-[#f97316]">{event.schedule?.[0]?.start_time || ''}</span>
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
}) + ' IST'}
|
||||||
|
{event.schedule?.[0]?.start_time && (
|
||||||
|
<>
|
||||||
|
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
||||||
|
<span className="text-[#f97316]">{event.schedule[0].start_time}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -102,12 +220,14 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 md:mt-8 pt-4 md:pt-6 border-t border-gray-200/30 flex justify-between items-center">
|
<div className="mt-6 md:mt-8 pt-4 md:pt-6 border-t border-gray-200/30 flex justify-between items-center">
|
||||||
<span className="text-[8px] md:text-[9px] text-gray-400 font-black uppercase tracking-widest">Limited Access</span>
|
<span className="text-[8px] md:text-[9px] text-gray-400 font-black uppercase tracking-widest">
|
||||||
|
{(event as any).isComingSoon ? 'Stay Tuned' : 'Limited Access'}
|
||||||
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedEvent(event)}
|
onClick={() => setSelectedEvent(event)}
|
||||||
className="text-[#1A202C] text-[9px] md:text-[10px] font-black uppercase tracking-[0.3em] hover:text-[#f97316] transition-colors flex items-center gap-2"
|
className="text-[#1A202C] text-[9px] md:text-[10px] font-black uppercase tracking-[0.3em] hover:text-[#f97316] transition-colors flex items-center gap-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
Details <i className="fas fa-arrow-right text-[8px]"></i>
|
{(event as any).isComingSoon ? 'Sneak Peek' : 'Details'} <i className="fas fa-arrow-right text-[8px]"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,11 +238,11 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
|
|||||||
|
|
||||||
{selectedEvent && (
|
{selectedEvent && (
|
||||||
<Portal>
|
<Portal>
|
||||||
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-4 sm:p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300">
|
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-4 sm:p-6 bg-slate-900/60 backdrop-blur-sm animate-in fade-in duration-300" onClick={() => setSelectedEvent(null)}>
|
||||||
<div className="bg-white rounded-[2rem] w-full max-w-lg shadow-2xl animate-in zoom-in-95 duration-500 max-h-[85vh] flex flex-col relative">
|
<div className="bg-white rounded-[2rem] w-full max-w-lg shadow-2xl animate-in zoom-in-95 duration-300 max-h-[85vh] flex flex-col relative" onClick={e => e.stopPropagation()}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedEvent(null)}
|
onClick={() => setSelectedEvent(null)}
|
||||||
className="absolute top-6 right-6 w-8 h-8 rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-all flex items-center justify-center z-10"
|
className="absolute top-6 right-6 w-8 h-8 rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 hover:text-slate-800 transition-all flex items-center justify-center z-10 cursor-pointer"
|
||||||
>
|
>
|
||||||
<i className="fas fa-times text-sm"></i>
|
<i className="fas fa-times text-sm"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -144,7 +264,9 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
|
|||||||
<div className="flex items-center justify-between gap-2 mb-2">
|
<div className="flex items-center justify-between gap-2 mb-2">
|
||||||
<div className="flex items-center gap-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]">
|
||||||
|
{(selectedEvent as any).isComingSoon ? 'Experience Preview' : 'Summary'}
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
{userRole === 'STUDENT' && bookedEventIds.includes(selectedEvent.id) && (
|
{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">
|
<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">
|
||||||
@@ -161,36 +283,10 @@ const UpcomingEventsSlider: React.FC<UpcomingEventsSliderProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 shrink-0 border-t border-gray-100 pt-6 flex flex-col gap-3">
|
<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 cursor-pointer"
|
||||||
>
|
>
|
||||||
Close Summary
|
Close Summary
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -618,6 +618,9 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
|
|||||||
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;
|
const count = registrations.filter(r => String(r.eventId || r.event_id) === String(event.id)).length;
|
||||||
event.currentParticipants = count;
|
event.currentParticipants = count;
|
||||||
|
if (event["total capacity"] !== undefined) {
|
||||||
|
event.maxParticipants = event["total capacity"];
|
||||||
|
}
|
||||||
|
|
||||||
if (!['APPROVED', 'COMPLETED', 'CANCELLED'].includes(event.status)) {
|
if (!['APPROVED', 'COMPLETED', 'CANCELLED'].includes(event.status)) {
|
||||||
const conflictMsg = await getConflictMessage(event);
|
const conflictMsg = await getConflictMessage(event);
|
||||||
@@ -741,6 +744,7 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
|
|||||||
guestSocialProfile: payload.socialProfile || null,
|
guestSocialProfile: payload.socialProfile || null,
|
||||||
requirements: payload.requirements || [],
|
requirements: payload.requirements || [],
|
||||||
targetedSections: payload.targetedSections || [],
|
targetedSections: payload.targetedSections || [],
|
||||||
|
targetDepartments: payload.targetDepartments || [],
|
||||||
groupRequestId: payload.groupRequestId || null,
|
groupRequestId: payload.groupRequestId || null,
|
||||||
sponsors: payload.sponsors || [],
|
sponsors: payload.sponsors || [],
|
||||||
budget: budgetStr === "" ? 0.0 : parseFloat(budgetStr),
|
budget: budgetStr === "" ? 0.0 : parseFloat(budgetStr),
|
||||||
@@ -751,11 +755,14 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
|
|||||||
isPublicEvent: !!payload.isPublicEvent,
|
isPublicEvent: !!payload.isPublicEvent,
|
||||||
image: payload.image || null,
|
image: payload.image || null,
|
||||||
targetedBatch: payload.targetedBatch || null,
|
targetedBatch: payload.targetedBatch || null,
|
||||||
|
"total capacity": payload.maxParticipants === '' || payload.maxParticipants === undefined ? null : Number(payload.maxParticipants),
|
||||||
|
maxParticipants: payload.maxParticipants === '' || payload.maxParticipants === undefined ? null : Number(payload.maxParticipants),
|
||||||
proposer: {
|
proposer: {
|
||||||
id: proposer.id,
|
id: proposer.id,
|
||||||
fullName: proposer.fullName,
|
fullName: proposer.fullName,
|
||||||
email: proposer.email,
|
email: proposer.email,
|
||||||
role: proposer.role
|
role: proposer.role,
|
||||||
|
department: proposer.department || "N/A"
|
||||||
},
|
},
|
||||||
dayConfigs: payload.dayConfigs || [],
|
dayConfigs: payload.dayConfigs || [],
|
||||||
deptLimits: payload.deptLimits || {},
|
deptLimits: payload.deptLimits || {},
|
||||||
@@ -995,6 +1002,12 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
|
|||||||
if (payload.institution !== undefined) updatedFields.institution = payload.institution;
|
if (payload.institution !== undefined) updatedFields.institution = payload.institution;
|
||||||
if (payload.department !== undefined) updatedFields.department = payload.department;
|
if (payload.department !== undefined) updatedFields.department = payload.department;
|
||||||
if (payload.venue !== undefined) updatedFields.location = payload.venue;
|
if (payload.venue !== undefined) updatedFields.location = payload.venue;
|
||||||
|
if (payload.category !== undefined) updatedFields.category = payload.category;
|
||||||
|
if (payload.maxParticipants !== undefined) {
|
||||||
|
const val = payload.maxParticipants === '' ? null : Number(payload.maxParticipants);
|
||||||
|
updatedFields["total capacity"] = val;
|
||||||
|
updatedFields.maxParticipants = val;
|
||||||
|
}
|
||||||
if (payload.guestName !== undefined) updatedFields.guestName = payload.guestName;
|
if (payload.guestName !== undefined) updatedFields.guestName = payload.guestName;
|
||||||
if (payload.socialProfile !== undefined) updatedFields.guestSocialProfile = payload.socialProfile;
|
if (payload.socialProfile !== undefined) updatedFields.guestSocialProfile = payload.socialProfile;
|
||||||
if (payload.academicYears !== undefined) updatedFields.academicYears = payload.academicYears;
|
if (payload.academicYears !== undefined) updatedFields.academicYears = payload.academicYears;
|
||||||
|
|||||||
Reference in New Issue
Block a user