Added time logic and edited several buttons in hod panel
This commit is contained in:
@@ -34,6 +34,17 @@ public class EventController {
|
||||
List<Registration> regs = registrationRepository.findAll();
|
||||
|
||||
for (Event event : events) {
|
||||
if (event.getStartDate() != null) {
|
||||
String sd = event.getStartDate().replace(" ", "T");
|
||||
if (!sd.endsWith("Z")) sd += "Z";
|
||||
event.setStartDate(sd);
|
||||
}
|
||||
if (event.getEndDate() != null) {
|
||||
String ed = event.getEndDate().replace(" ", "T");
|
||||
if (!ed.endsWith("Z")) ed += "Z";
|
||||
event.setEndDate(ed);
|
||||
}
|
||||
|
||||
int count = (int) regs.stream()
|
||||
.filter(r -> r.getEventId() != null && r.getEventId().equals(event.getId()))
|
||||
.count();
|
||||
|
||||
@@ -28,16 +28,25 @@ public class RegistrationController {
|
||||
public List<Registration> getRegistrations(@RequestParam(required = false) Long userId,
|
||||
@RequestParam(required = false) Long eventId,
|
||||
@RequestParam(required = false) String teamCode) {
|
||||
List<Registration> list;
|
||||
if (userId != null) {
|
||||
return registrationRepository.findByUserId(userId);
|
||||
list = registrationRepository.findByUserId(userId);
|
||||
} else if (eventId != null) {
|
||||
list = registrationRepository.findByEventId(eventId);
|
||||
} else if (teamCode != null) {
|
||||
list = registrationRepository.findByTeamCode(teamCode);
|
||||
} else {
|
||||
list = registrationRepository.findAll();
|
||||
}
|
||||
if (eventId != null) {
|
||||
return registrationRepository.findByEventId(eventId);
|
||||
|
||||
for (Registration reg : list) {
|
||||
if (reg.getRegisteredAt() != null) {
|
||||
String ra = reg.getRegisteredAt().replace(" ", "T");
|
||||
if (!ra.endsWith("Z")) ra += "Z";
|
||||
reg.setRegisteredAt(ra);
|
||||
}
|
||||
}
|
||||
if (teamCode != null) {
|
||||
return registrationRepository.findByTeamCode(teamCode);
|
||||
}
|
||||
return registrationRepository.findAll();
|
||||
return list;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
|
||||
@@ -21,6 +21,23 @@ public class SpecialEventController {
|
||||
@GetMapping
|
||||
public List<SpecialEvent> getAllSpecialEvents() {
|
||||
List<SpecialEvent> list = specialEventRepository.findAll();
|
||||
for (SpecialEvent se : list) {
|
||||
if (se.getStartDate() != null) {
|
||||
String sd = se.getStartDate().replace(" ", "T");
|
||||
if (!sd.endsWith("Z")) sd += "Z";
|
||||
se.setStartDate(sd);
|
||||
}
|
||||
if (se.getEndDate() != null) {
|
||||
String ed = se.getEndDate().replace(" ", "T");
|
||||
if (!ed.endsWith("Z")) ed += "Z";
|
||||
se.setEndDate(ed);
|
||||
}
|
||||
if (se.getCreatedAt() != null) {
|
||||
String ca = se.getCreatedAt().replace(" ", "T");
|
||||
if (!ca.endsWith("Z")) ca += "Z";
|
||||
se.setCreatedAt(ca);
|
||||
}
|
||||
}
|
||||
list.sort((a, b) -> {
|
||||
String ca = a.getCreatedAt() != null ? a.getCreatedAt() : "";
|
||||
String cb = b.getCreatedAt() != null ? b.getCreatedAt() : "";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { API_BASE_URL } from '../../lib/config';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
Calendar,
|
||||
MapPin,
|
||||
@@ -8,22 +8,46 @@ import {
|
||||
Filter,
|
||||
Search,
|
||||
Trash2,
|
||||
FileSpreadsheet
|
||||
FileSpreadsheet,
|
||||
FileText,
|
||||
Users,
|
||||
Ticket,
|
||||
Wallet,
|
||||
Heart,
|
||||
XCircle,
|
||||
AlertTriangle,
|
||||
Split,
|
||||
Layers,
|
||||
CheckCircle
|
||||
} from 'lucide-react';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { StatusTimeline, type Event } from './EventStatusTimeline';
|
||||
import { Pagination } from './Pagination';
|
||||
|
||||
interface ExtendedEvent extends Event {
|
||||
description?: string;
|
||||
sponsors?: string[];
|
||||
academicYears?: string[];
|
||||
dayConfigs?: any[];
|
||||
deptLimits?: Record<string, any>;
|
||||
deptSectionLimits?: Record<string, any>;
|
||||
budget?: number;
|
||||
hasRegistrationFee?: boolean;
|
||||
registrationFee?: number;
|
||||
conflictMessage?: string;
|
||||
}
|
||||
|
||||
interface AllEventsProps {
|
||||
onEditEvent?: (event: any) => void;
|
||||
}
|
||||
|
||||
export const AllEvents: React.FC<AllEventsProps> = ({ onEditEvent }) => {
|
||||
const { user } = useAuth();
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
const [events, setEvents] = useState<ExtendedEvent[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedEvent, setSelectedEvent] = useState<ExtendedEvent | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchEvents();
|
||||
@@ -78,7 +102,6 @@ export const AllEvents: React.FC<AllEventsProps> = ({ onEditEvent }) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||
|
||||
@@ -209,6 +232,12 @@ export const AllEvents: React.FC<AllEventsProps> = ({ onEditEvent }) => {
|
||||
</td>
|
||||
<td className="px-8 py-6 text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setSelectedEvent(event)}
|
||||
className="px-4 py-2 bg-slate-50 text-brand-indigo hover:bg-brand-indigo hover:text-white rounded-xl text-[9px] font-black uppercase tracking-widest transition-all border border-brand-indigo/10"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
{(user?.role === 'ADMIN' || (event.status === 'REQUESTED' && event.proposer?.email === user?.email)) && onEditEvent && (
|
||||
<button
|
||||
onClick={() => onEditEvent(event)}
|
||||
@@ -248,6 +277,262 @@ export const AllEvents: React.FC<AllEventsProps> = ({ onEditEvent }) => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Details Modal */}
|
||||
<AnimatePresence>
|
||||
{selectedEvent && (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
onClick={() => setSelectedEvent(null)}
|
||||
className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm"
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
className="relative w-full max-w-2xl bg-white rounded-[2.5rem] premium-shadow overflow-hidden max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<div className={cn(
|
||||
"p-8 text-white flex justify-between items-start",
|
||||
selectedEvent.status === 'CANCELLED' ? "bg-red-600" : "bg-brand-navy"
|
||||
)}>
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-[10px] font-black uppercase tracking-[0.2em] text-white/60">Event Details</span>
|
||||
<span className="w-1 h-1 bg-white/30 rounded-full" />
|
||||
<span className="text-[10px] font-black uppercase tracking-[0.2em] text-white/60">{selectedEvent.institution}</span>
|
||||
{selectedEvent.category === 'CLUB' && (
|
||||
<>
|
||||
<span className="w-1 h-1 bg-white/30 rounded-full" />
|
||||
<span className="text-[10px] font-black uppercase tracking-[0.2em] text-emerald-400">Institutional Club Event</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-2xl font-black tracking-tight">{selectedEvent.title}</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedEvent(null)}
|
||||
className="p-2 hover:bg-white/10 rounded-xl transition-all"
|
||||
>
|
||||
<XCircle className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-8 space-y-8">
|
||||
{/* Description Section */}
|
||||
{selectedEvent.description && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||
<FileText className="w-3.5 h-3.5" />
|
||||
Event Description
|
||||
</div>
|
||||
<p className="text-sm font-medium text-text-dark leading-relaxed bg-slate-50 p-4 rounded-2xl border border-slate-100">
|
||||
{selectedEvent.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-8">
|
||||
<div className="space-y-6">
|
||||
<div className="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" />
|
||||
Schedule
|
||||
</div>
|
||||
<p className="text-sm font-bold text-text-dark">
|
||||
{parseDate(selectedEvent.startDate) ? parseDate(selectedEvent.startDate)!.toLocaleString('en-IN') : 'N/A'}
|
||||
</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" />
|
||||
Venue
|
||||
</div>
|
||||
<p className="text-sm font-bold text-text-dark">{selectedEvent.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="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" />
|
||||
Proposed By
|
||||
</div>
|
||||
<p className="text-sm font-bold text-text-dark">{selectedEvent.proposer?.fullName || 'Faculty'}</p>
|
||||
<p className="text-[10px] font-black text-brand-indigo uppercase">{selectedEvent.department}</p>
|
||||
</div>
|
||||
{selectedEvent.academicYears && selectedEvent.academicYears.length > 0 && (
|
||||
<div className="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" />
|
||||
Target Audience
|
||||
</div>
|
||||
<p className="text-sm font-bold text-text-dark">{selectedEvent.academicYears.join(', ')} Batches</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sponsors Section */}
|
||||
{selectedEvent.sponsors && selectedEvent.sponsors.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-[10px] font-black uppercase tracking-widest text-text-muted">
|
||||
<Heart className="w-3.5 h-3.5 text-brand-indigo" />
|
||||
Event Sponsors
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedEvent.sponsors.map((sponsor, idx) => (
|
||||
<span key={idx} className="px-3 py-1 bg-brand-glow text-brand-indigo rounded-lg text-[10px] font-black uppercase tracking-widest border border-brand-indigo/10">
|
||||
{sponsor}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Itinerary & Day Configs */}
|
||||
{selectedEvent.dayConfigs && selectedEvent.dayConfigs.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<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 text-brand-indigo" />
|
||||
Detailed Itinerary & Schedule
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{selectedEvent.dayConfigs.map((day: any, dIdx: number) => {
|
||||
const dayDate = day.date ? new Date(day.date) : null;
|
||||
return (
|
||||
<div key={dIdx} className="p-4 rounded-2xl bg-slate-50 border border-slate-100 space-y-3">
|
||||
<div className="flex justify-between items-center border-b border-slate-200 pb-1.5">
|
||||
<span className="text-[11px] font-black text-brand-navy uppercase tracking-wider">
|
||||
Day {dIdx + 1}: {dayDate && !isNaN(dayDate.getTime()) ? dayDate.toLocaleDateString() : day.date || 'TBD'}
|
||||
</span>
|
||||
<span className="px-2 py-0.5 bg-brand-glow text-brand-indigo text-[8px] font-black uppercase tracking-widest rounded">
|
||||
{day.batches?.length || 0} {(day.batches?.length || 0) === 1 ? 'Batch' : 'Batches'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-3 divide-y divide-slate-200/50">
|
||||
{day.batches?.map((batch: any, bIdx: number) => (
|
||||
<div key={bIdx} className="pt-3 first:pt-0 space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[9px] font-black text-slate-400 uppercase tracking-widest">
|
||||
Batch {batch.id || bIdx + 1}
|
||||
</span>
|
||||
<span className="text-[10px] font-bold text-brand-indigo bg-white px-2 py-0.5 rounded-lg border border-slate-200 shadow-sm flex items-center gap-1">
|
||||
<Clock className="w-3.5 h-3.5" />
|
||||
{batch.startTime || 'TBD'} - {batch.endTime || 'TBD'}
|
||||
</span>
|
||||
</div>
|
||||
{batch.resourcePerson && batch.resourcePerson.name && (
|
||||
<div className="p-3 bg-white rounded-xl border border-slate-100 space-y-1.5">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<p className="text-[8px] font-black text-slate-400 uppercase tracking-widest">Resource Person</p>
|
||||
<p className="text-xs font-bold text-text-dark">{batch.resourcePerson.name}</p>
|
||||
</div>
|
||||
<span className={cn(
|
||||
"px-2 py-0.5 rounded text-[8px] font-black uppercase tracking-wider",
|
||||
batch.resourcePerson.type === 'EXTERNAL' ? "bg-amber-50 text-amber-600 border border-amber-100" : "bg-brand-glow text-brand-indigo border border-brand-indigo/10"
|
||||
)}>
|
||||
{batch.resourcePerson.type || 'INTERNAL'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1.5 text-[9px] text-text-muted font-semibold">
|
||||
{batch.resourcePerson.dept && (
|
||||
<div>
|
||||
<span className="font-bold text-slate-400 uppercase tracking-wider">Dept:</span> {batch.resourcePerson.dept}
|
||||
</div>
|
||||
)}
|
||||
{batch.resourcePerson.college_name && (
|
||||
<div className="col-span-2">
|
||||
<span className="font-bold text-slate-400 uppercase tracking-wider">Inst:</span> {batch.resourcePerson.college_name}
|
||||
</div>
|
||||
)}
|
||||
{batch.resourcePerson.phone && (
|
||||
<div>
|
||||
<span className="font-bold text-slate-400 tracking-wider uppercase">Phone:</span> {batch.resourcePerson.phone}
|
||||
</div>
|
||||
)}
|
||||
{batch.resourcePerson.email && (
|
||||
<div>
|
||||
<span className="font-bold text-slate-400 tracking-wider uppercase">Email:</span> {batch.resourcePerson.email}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Department limits and quotas */}
|
||||
{selectedEvent.deptLimits && Object.keys(selectedEvent.deptLimits).length > 0 && (
|
||||
<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 text-brand-indigo" />
|
||||
Department Limits & Quotas
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{Object.entries(selectedEvent.deptLimits).map(([dept, maxSeats]) => {
|
||||
const sectionLimits = selectedEvent.deptSectionLimits?.[dept];
|
||||
const hasSectionLimits = sectionLimits && Object.keys(sectionLimits).length > 0;
|
||||
return (
|
||||
<div key={dept} className="bg-slate-50 border border-slate-100 rounded-2xl p-4 space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs font-black text-brand-navy uppercase tracking-wider">{dept}</span>
|
||||
<span className="px-2 py-0.5 bg-brand-indigo text-white text-[9px] font-black uppercase rounded-md">
|
||||
Quota: {maxSeats} Seats
|
||||
</span>
|
||||
</div>
|
||||
{hasSectionLimits && (
|
||||
<div className="pt-2 border-t border-slate-200/60 grid grid-cols-3 gap-2">
|
||||
{Object.entries(sectionLimits).map(([sec, limit]) => (
|
||||
<div key={sec} className="bg-white px-2 py-1 rounded-xl border border-slate-100 text-center">
|
||||
<span className="block text-[8px] font-black text-slate-400 uppercase mb-0.5">Sec {sec}</span>
|
||||
<span className="text-[10px] font-black text-brand-navy">{limit}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-8 pt-4 border-t border-slate-50">
|
||||
<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">
|
||||
<Wallet className="w-3.5 h-3.5 text-brand-indigo" />
|
||||
Estimated Budget
|
||||
</div>
|
||||
<p className="text-lg font-black text-text-dark">₹{selectedEvent.budget?.toLocaleString() || '0'}</p>
|
||||
</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">
|
||||
<Ticket className="w-3.5 h-3.5 text-brand-indigo" />
|
||||
Registration
|
||||
</div>
|
||||
<p className="text-lg font-black text-text-dark">
|
||||
{selectedEvent.hasRegistrationFee ? `₹${selectedEvent.registrationFee}` : 'FREE'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user