import { API_BASE_URL } from '../../lib/config'; import React, { useEffect, useState, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { CheckCircle, XCircle, Clock, User, Building2, ChevronRight, AlertCircle, ShieldCheck, MapPin, Wallet, Users, Layers, Ticket, AlertTriangle, FileText, Heart, Calendar } from 'lucide-react'; import { useAuth } from '../../context/AuthContext'; import { cn } from '../../lib/utils'; import { Pagination } from './Pagination'; interface Event { id: number; title: string; startDate: string; endDate: string; type: string; institution: string; department: string; academicYears: string[]; status: string; location: string; proposer?: { fullName: string; department: string; }; budget: number; hasRegistrationFee: boolean; registrationFee: number; category: string; targetedSections: string[]; conflictMessage?: string; description?: string; sponsors?: string[]; rejectionReason?: string; dayConfigs?: any[]; deptLimits?: Record; deptSectionLimits?: Record>; refreshment_expense?: number; transportation_expense?: number; session_coverage_fee?: number; total_expense?: number; durationDays?: number; } export const ApprovalsView: React.FC = () => { const { user } = useAuth(); const [events, setEvents] = useState([]); const [allEvents, setAllEvents] = useState([]); const [isLoading, setIsLoading] = useState(true); const [processingId, setProcessingId] = useState(null); const [selectedEvent, setSelectedEvent] = useState(null); const [selectedVenue, setSelectedVenue] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(5); const [dbVenues, setDbVenues] = useState([ 'GB 4th floor auditorium', 'Wozniak Auditorium', 'C6-02 Indoor Theatre', 'H Block Guest Lecture Theatre', 'Steve Jobs Computer Centre 1', 'Steve Jobs Computer Centre 2' ]); useEffect(() => { fetchEvents(); fetchVenues(); }, []); const fetchVenues = async () => { try { const response = await fetch(API_BASE_URL + '/api/venues'); if (response.ok) { const data = await response.json(); setDbVenues(data.map((v: any) => v.name)); } } catch (error) { console.error('Failed to fetch venues for approvals:', error); } }; const fetchEvents = async () => { try { const response = await fetch(API_BASE_URL + '/api/events'); if (response.ok) { const data = await response.json(); setAllEvents(data); // Filter based on role if (user?.role === 'HOD') { const userDepts = user.department ? user.department.split(',').map(d => d.trim().toLowerCase()) : []; setEvents(data.filter((e: Event) => e.status === 'REQUESTED' && userDepts.includes(e.department?.trim().toLowerCase()))); } else if (user?.role === 'PRINCIPAL') { setEvents(data.filter((e: Event) => e.status === 'PENDING_PR')); } } } catch (error) { console.error('Failed to fetch events:', error); } finally { setIsLoading(false); } }; const handleSelectEvent = (event: Event | null) => { setSelectedEvent(event); setSelectedVenue(''); }; const handleAction = async (id: number, action: 'approve' | 'reject', allocatedVenue?: string) => { let reason = ''; if (action === 'reject') { reason = window.prompt('Please enter a rejection reason:') || ''; if (!reason) return; } setProcessingId(id); try { let url = `${API_BASE_URL}/api/events/${id}/${action}?userId=${user?.id}`; if (allocatedVenue) { url += `&newVenue=${encodeURIComponent(allocatedVenue)}`; } const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: action === 'reject' ? JSON.stringify({ reason }) : undefined }); if (response.ok) { setEvents(events.filter(e => e.id !== id)); handleSelectEvent(null); // Refresh all events to reflect the changes fetchEvents(); } else { const err = await response.json(); alert(err.message || 'Operation failed'); } } catch (error) { console.error(`Failed to ${action} event:`, error); } finally { setProcessingId(null); } }; const getAvailableVenues = (event: Event) => { const start = new Date(event.startDate); const end = new Date(event.endDate); const busyVenues = new Set(); allEvents.forEach(other => { if ( other.id === event.id || other.status === 'CANCELLED' || other.status === 'HOD_REJECTED' || other.status === 'PRINCIPAL_REJECTED' ) return; const oStart = new Date(other.startDate); const oEnd = new Date(other.endDate); if (start < oEnd && end > oStart) { if (other.location) { busyVenues.add(other.location.trim().toLowerCase()); } } }); return dbVenues.filter(v => !busyVenues.has(v.trim().toLowerCase())); }; const sortedEvents = useMemo(() => { return [...events].sort((a: any, b: any) => { const dateA = new Date(a.updatedAt || a.createdAt || 0).getTime(); const dateB = new Date(b.updatedAt || b.createdAt || 0).getTime(); return dateB - dateA; }); }, [events]); const currentEvents = sortedEvents.slice( (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage ); return (

Pending Approvals

Review and take action on event proposals from your {user?.role === 'HOD' ? 'department' : 'institution'}.

{isLoading ? (
) : events.length === 0 ? (

Queue is Empty

All proposals have been processed. Great work!

) : ( {currentEvents.map((event) => ( handleSelectEvent(event)} className={cn( "bg-white rounded-[2rem] p-6 border transition-all flex flex-col md:flex-row items-center justify-between gap-6 cursor-pointer group", event.conflictMessage ? "border-red-200 bg-red-50/30" : "border-slate-100 hover:premium-shadow" )} >
{event.title[0]}
{event.category === 'CLUB' && ( Institutional Club )} {event.type}

{event.title}

{event.conflictMessage ? (
{event.conflictMessage}
) : (
{event.proposer?.fullName || 'Faculty Member'}
{new Date(event.startDate).toLocaleDateString()}
)}
e.stopPropagation()}>
))}
)}
{events.length > 0 && ( { setItemsPerPage(val); setCurrentPage(1); }} itemsPerPageOptions={[5, 10, 15, 20]} /> )} {/* Detail Modal */} {selectedEvent && (
setSelectedEvent(null)} className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm" />
Proposal Details {selectedEvent.institution} {selectedEvent.category === 'CLUB' && ( <> Institutional Club Event )}

{selectedEvent.title}

{/* Description Section */} {selectedEvent.description && (
Event Description

{selectedEvent.description}

)}
Schedule

{new Date(selectedEvent.startDate).toLocaleString()}

Venue

{selectedEvent.location}

Proposed By

{selectedEvent.proposer?.fullName || 'Faculty'}

{selectedEvent.department}

Target Audience

{selectedEvent.academicYears.join(', ')} Batches

{/* Sponsors Section */} {selectedEvent.sponsors && selectedEvent.sponsors.length > 0 && (
Event Sponsors
{selectedEvent.sponsors.map((sponsor, idx) => ( {sponsor} ))}
)} {/* Itinerary & Day Configs */} {selectedEvent.dayConfigs && selectedEvent.dayConfigs.length > 0 && (
Detailed Itinerary & Schedule
{selectedEvent.dayConfigs.map((day: any, dIdx: number) => { const dayDate = day.date ? new Date(day.date) : null; return (
Day {dIdx + 1}: {dayDate && !isNaN(dayDate.getTime()) ? dayDate.toLocaleDateString() : day.date || 'TBD'} {day.batches?.length || 0} {(day.batches?.length || 0) === 1 ? 'Batch' : 'Batches'}
{day.batches?.map((batch: any, bIdx: number) => (
Batch {batch.id || bIdx + 1} {batch.startTime || 'TBD'} - {batch.endTime || 'TBD'}
{batch.resourcePerson && batch.resourcePerson.name && (

Resource Person

{batch.resourcePerson.name}

{batch.resourcePerson.type || 'INTERNAL'}
{batch.resourcePerson.dept && (
Dept: {batch.resourcePerson.dept}
)} {batch.resourcePerson.college_name && (
Inst: {batch.resourcePerson.college_name}
)} {batch.resourcePerson.phone && (
Phone: {batch.resourcePerson.phone}
)} {batch.resourcePerson.email && (
Email: {batch.resourcePerson.email}
)}
)}
))}
); })}
)} {/* Department limits and quotas */} {selectedEvent.deptLimits && Object.keys(selectedEvent.deptLimits).length > 0 && (
Department Limits & Quotas
{Object.entries(selectedEvent.deptLimits).map(([dept, maxSeats]) => { const sectionLimits = selectedEvent.deptSectionLimits?.[dept]; const hasSectionLimits = sectionLimits && Object.keys(sectionLimits).length > 0; return (
{dept} Quota: {maxSeats} Seats
{hasSectionLimits && (
{Object.entries(sectionLimits).map(([sec, limit]) => (
Sec {sec} {limit}
))}
)}
); })}
)}
Estimated Budget

₹{selectedEvent.budget?.toLocaleString() || '0'}

Registration

{selectedEvent.hasRegistrationFee ? `₹${selectedEvent.registrationFee}` : 'FREE'}

{/* Financial Projections Breakdown */} {(selectedEvent.total_expense || selectedEvent.refreshment_expense || selectedEvent.transportation_expense || selectedEvent.session_coverage_fee) ? (
Financial Projection Breakdown
Refreshments ₹{selectedEvent.refreshment_expense || 0}
Transport ₹{selectedEvent.transportation_expense || 0}
Session Fee ₹{selectedEvent.session_coverage_fee || 0}
Total Expense ₹{selectedEvent.total_expense || 0}
) : null} {selectedEvent.conflictMessage && (

Critical Conflict Detected

{selectedEvent.conflictMessage}

Available Venues for Re-allocation

{getAvailableVenues(selectedEvent).length > 0 ? (

Select one of the free venues below to re-allocate and approve:

{getAvailableVenues(selectedEvent).map(v => ( ))}
) : (

No other standard venues are available during this slot. You can reject the proposal or coordinate another slot.

)}
)}
)}
); };