import React, { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { DataSyncService } from '../../../scripts/database/DataSyncService'; import { Collections } from '../../../schemas/pocketbase/schema'; import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase'; import { EventRequestFormPreview } from './EventRequestFormPreview'; import type { EventRequestFormData } from './EventRequestForm'; // Declare the global window interface to include our custom function declare global { interface Window { showEventRequestFormPreview?: (formData: any) => void; } } // Extended EventRequest interface with additional properties needed for this component export interface EventRequest extends SchemaEventRequest { invoice_data?: any; } // Helper function to convert EventRequest to EventRequestFormData const convertToFormData = (request: EventRequest): EventRequestFormData => { try { // Parse itemized_invoice if it's a string let invoiceData = {}; try { if (request.itemized_invoice) { if (typeof request.itemized_invoice === 'string') { const parsedInvoice = JSON.parse(request.itemized_invoice) as Record; // Get or calculate subtotal from items const subtotal = parsedInvoice.subtotal ?? (Array.isArray(parsedInvoice.items) ? parsedInvoice.items.reduce((sum: number, item: any) => { const amount = typeof item.amount === 'number' ? item.amount : 0; return sum + amount; }, 0) : 0); // Normalize tax and tip amounts const taxAmount = Number(parsedInvoice.taxAmount ?? parsedInvoice.tax ?? 0); const tipAmount = Number(parsedInvoice.tipAmount ?? parsedInvoice.tip ?? 0); // Calculate or get total const total = parsedInvoice.total ?? (subtotal + taxAmount + tipAmount); invoiceData = { ...parsedInvoice, subtotal, taxAmount, tipAmount, total }; } else { const parsedInvoice = request.itemized_invoice as Record; // Get or calculate subtotal from items const subtotal = parsedInvoice.subtotal ?? (Array.isArray(parsedInvoice.items) ? parsedInvoice.items.reduce((sum: number, item: any) => { const amount = typeof item.amount === 'number' ? item.amount : 0; return sum + amount; }, 0) : 0); // Normalize tax and tip amounts const taxAmount = Number(parsedInvoice.taxAmount ?? parsedInvoice.tax ?? 0); const tipAmount = Number(parsedInvoice.tipAmount ?? parsedInvoice.tip ?? 0); // Calculate or get total const total = parsedInvoice.total ?? (subtotal + taxAmount + tipAmount); invoiceData = { ...parsedInvoice, subtotal, taxAmount, tipAmount, total }; } } else if (request.invoice_data) { const parsedInvoice = request.invoice_data as Record; // Get or calculate subtotal from items const subtotal = parsedInvoice.subtotal ?? (Array.isArray(parsedInvoice.items) ? parsedInvoice.items.reduce((sum: number, item: any) => { const amount = typeof item.amount === 'number' ? item.amount : 0; return sum + amount; }, 0) : 0); // Normalize tax and tip amounts const taxAmount = Number(parsedInvoice.taxAmount ?? parsedInvoice.tax ?? 0); const tipAmount = Number(parsedInvoice.tipAmount ?? parsedInvoice.tip ?? 0); // Calculate or get total const total = parsedInvoice.total ?? (subtotal + taxAmount + tipAmount); invoiceData = { ...parsedInvoice, subtotal, taxAmount, tipAmount, total }; } } catch (e) { console.error('Error parsing invoice data:', e); } // Cast to unknown first, then to EventRequestFormData to avoid type checking return { name: request.name, location: request.location, start_date_time: request.start_date_time, end_date_time: request.end_date_time, event_description: request.event_description || '', flyers_needed: request.flyers_needed || false, photography_needed: request.photography_needed || false, flyer_type: request.flyer_type || [], other_flyer_type: request.other_flyer_type || '', flyer_advertising_start_date: request.flyer_advertising_start_date || '', advertising_format: request.advertising_format || '', required_logos: request.required_logos || [], other_logos: [] as File[], // EventRequest doesn't have this as files flyer_additional_requests: request.flyer_additional_requests || '', will_or_have_room_booking: request.will_or_have_room_booking || false, room_booking: null, room_booking_confirmation: [] as File[], // EventRequest doesn't have this as files expected_attendance: request.expected_attendance || 0, food_drinks_being_served: request.food_drinks_being_served || false, needs_as_funding: request.as_funding_required || false, as_funding_required: request.as_funding_required || false, invoice: null, invoice_files: [], invoiceData: invoiceData, needs_graphics: request.flyers_needed || false, status: request.status || '', created_by: request.requested_user || '', id: request.id || '', created: request.created || '', updated: request.updated || '', itemized_invoice: request.itemized_invoice || '', } as unknown as EventRequestFormData; } catch (error) { console.error("Error converting EventRequest to EventRequestFormData:", error); // Return a minimal valid object to prevent rendering errors return { name: request?.name || "Unknown Event", location: request?.location || "", start_date_time: request?.start_date_time || new Date().toISOString(), end_date_time: request?.end_date_time || new Date().toISOString(), event_description: request?.event_description || "", flyers_needed: false, photography_needed: false, flyer_type: [], other_flyer_type: "", flyer_advertising_start_date: "", advertising_format: "", required_logos: [], other_logos: [] as File[], flyer_additional_requests: "", will_or_have_room_booking: false, room_booking: null, room_booking_confirmation: [] as File[], expected_attendance: 0, food_drinks_being_served: false, needs_as_funding: false, as_funding_required: false, invoice: null, invoice_files: [], invoiceData: {}, needs_graphics: false, status: request?.status || "", created_by: "", id: request?.id || "", created: request?.created || "", updated: request?.updated || "", itemized_invoice: "" } as unknown as EventRequestFormData; } }; interface UserEventRequestsProps { eventRequests: EventRequest[]; } // Create a portal component for the modal to ensure it's rendered at the root level const EventRequestModal: React.FC<{ isOpen: boolean, onClose: () => void, children: React.ReactNode }> = ({ isOpen, onClose, children }) => { if (!isOpen) return null; return (
e.stopPropagation()} > {children}
); }; // Add a utility function to truncate text with an ellipsis const truncateText = (text: string, maxLength: number) => { if (!text) return ''; return text.length > maxLength ? text.substring(0, maxLength) + '...' : text; }; const UserEventRequests: React.FC = ({ eventRequests: initialEventRequests }) => { const [eventRequests, setEventRequests] = useState(initialEventRequests); const [selectedRequest, setSelectedRequest] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [viewMode, setViewMode] = useState<'table' | 'cards'>('table'); const dataSync = DataSyncService.getInstance(); // Refresh event requests const refreshEventRequests = async () => { setIsRefreshing(true); try { const auth = Authentication.getInstance(); if (!auth.isAuthenticated()) { return; } const userId = auth.getUserId(); if (!userId) { return; } // Use DataSyncService to get data from IndexedDB with forced sync const updatedRequests = await dataSync.getData( Collections.EVENT_REQUESTS, true, // Force sync `requested_user="${userId}"`, '-created' ); setEventRequests(updatedRequests); } catch (err) { console.error('Failed to refresh event requests:', err); } finally { setIsRefreshing(false); } }; // Auto refresh on component mount useEffect(() => { refreshEventRequests(); }, []); // Listen for tab visibility changes and refresh data when tab becomes visible useEffect(() => { const handleTabVisible = () => { // console.log("Tab became visible, refreshing event requests..."); refreshEventRequests(); }; // Add event listener for custom dashboardTabVisible event document.addEventListener("dashboardTabVisible", handleTabVisible); // Clean up event listener on component unmount return () => { document.removeEventListener("dashboardTabVisible", handleTabVisible); }; }, []); // Format date for display const formatDate = (dateString: string) => { if (!dateString) return 'Not specified'; try { const date = new Date(dateString); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch (e) { return dateString; } }; // Get status badge class based on status const getStatusBadge = (status?: string) => { if (!status) return 'badge-warning'; switch (status.toLowerCase()) { case 'approved': case 'completed': return 'badge-success text-white'; case 'rejected': case 'declined': return 'badge-error text-white'; case 'pending': return 'badge-warning text-black'; case 'submitted': return 'badge-info text-white'; default: return 'badge-warning text-black'; } }; // Get card border class based on status const getCardBorderClass = (status?: string) => { if (!status) return 'border-l-warning'; switch (status.toLowerCase()) { case 'approved': case 'completed': return 'border-l-success'; case 'rejected': case 'declined': return 'border-l-error'; case 'pending': return 'border-l-warning'; case 'submitted': return 'border-l-info'; default: return 'border-l-warning'; } }; // Open modal with event request details const openDetailModal = (request: EventRequest) => { setSelectedRequest(request); setIsModalOpen(true); }; // Close modal const closeModal = () => { setIsModalOpen(false); setSelectedRequest(null); }; if (eventRequests.length === 0) { return (

No Event Requests Found

You haven't submitted any event requests yet. Use the form above to submit a new event request.

); } return (

Your Submissions

{viewMode === 'table' ? (
{eventRequests.map((request) => ( ))}
Event Name Date Location PR AS Submitted Status View
{truncateText(request.name, 18)}
{formatDate(request.start_date_time)}
{truncateText(request.location, 14)}
{request.flyers_needed ? ( Yes ) : ( No )} {request.as_funding_required ? ( Yes ) : ( No )}
{formatDate(request.created)}
{request.status || 'Submitted'}
) : (
{eventRequests.map((request) => (

{truncateText(request.name, 25)}

{request.status || 'Pending'}
{formatDate(request.start_date_time)}
{truncateText(request.location, 25)}
{request.flyers_needed && ( PR Materials )} {request.as_funding_required && ( AS Funding )} {request.photography_needed && ( Photography )}
))}
)}

About Your Submissions

  • Event requests are typically reviewed within 1-2 business days.
  • You'll receive email notifications when your request status changes.
  • For urgent inquiries, please contact the PR team or coordinators in the #-events Slack channel.
{/* Use the new portal component for the modal */} {isModalOpen && selectedRequest && (

{selectedRequest.name}

{selectedRequest.status || 'Pending'}
{selectedRequest ? ( ) : (
)}
)}
); }; export default UserEventRequests;