import React, { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import toast from 'react-hot-toast'; import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { Update } from '../../../scripts/pocketbase/Update'; import { FileManager } from '../../../scripts/pocketbase/FileManager'; import { DataSyncService } from '../../../scripts/database/DataSyncService'; import { Collections } from '../../../schemas/pocketbase/schema'; import { EventRequestStatus } from '../../../schemas/pocketbase'; // Form sections import PRSection from './PRSection'; import EventDetailsSection from './EventDetailsSection'; import TAPFormSection from './TAPFormSection'; import ASFundingSection from './ASFundingSection'; import { EventRequestFormPreview } from './EventRequestFormPreview'; import type { InvoiceData, InvoiceItem } from './InvoiceBuilder'; // Animation variants const containerVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 30, staggerChildren: 0.1 } } }; const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 24 } } }; // Form data interface - based on the schema EventRequest but with form-specific fields export interface EventRequestFormData { // Fields from EventRequest name: string; location: string; start_date_time: string; end_date_time: string; event_description: string; flyers_needed: boolean; photography_needed: boolean; as_funding_required: boolean; food_drinks_being_served: boolean; itemized_invoice?: string; status?: string; created_by?: string; id?: string; created?: string; updated?: string; // Additional form-specific fields flyer_type: string[]; other_flyer_type: string; flyer_advertising_start_date: string; flyer_additional_requests: string; required_logos: string[]; other_logos: File[]; // Form uses File objects, schema uses strings advertising_format: string; will_or_have_room_booking: boolean; expected_attendance: number; room_booking: File | null; invoice: File | null; invoice_files: File[]; invoiceData: InvoiceData; needs_graphics?: boolean | null; needs_as_funding?: boolean | null; formReviewed?: boolean; // Track if the form has been reviewed } // Add CustomAlert import import CustomAlert from '../universal/CustomAlert'; const EventRequestForm: React.FC = () => { const [currentStep, setCurrentStep] = useState(1); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); // Initialize form data const [formData, setFormData] = useState({ name: '', location: '', start_date_time: '', end_date_time: '', event_description: '', flyers_needed: false, flyer_type: [], other_flyer_type: '', flyer_advertising_start_date: '', flyer_additional_requests: '', photography_needed: false, required_logos: [], other_logos: [], advertising_format: '', will_or_have_room_booking: false, expected_attendance: 0, room_booking: null, as_funding_required: false, food_drinks_being_served: false, itemized_invoice: '', invoice: null, invoice_files: [], // Initialize empty array for multiple invoice files needs_graphics: null, needs_as_funding: false, invoiceData: { items: [], subtotal: 0, taxAmount: 0, tipAmount: 0, total: 0, vendor: '' }, formReviewed: false // Initialize as false }); // Save form data to localStorage useEffect(() => { const formDataToSave = { ...formData }; // Remove file objects before saving to localStorage const dataToStore = { ...formDataToSave, other_logos: [], room_booking: null, invoice: null, invoice_files: [] }; localStorage.setItem('eventRequestFormData', JSON.stringify(dataToStore)); // Also update the preview data window.dispatchEvent(new CustomEvent('formDataUpdated', { detail: { formData: formDataToSave } })); }, [formData]); // Load form data from localStorage on initial load useEffect(() => { const savedData = localStorage.getItem('eventRequestFormData'); if (savedData) { try { const parsedData = JSON.parse(savedData); setFormData(prevData => ({ ...prevData, ...parsedData })); } catch (e) { console.error('Error parsing saved form data:', e); } } }, []); // Handle form section data changes const handleSectionDataChange = (sectionData: Partial) => { // Ensure both needs_graphics and flyers_needed are synchronized if (sectionData.flyers_needed !== undefined && sectionData.needs_graphics === undefined) { sectionData.needs_graphics = sectionData.flyers_needed ? true : false; } // Ensure both needs_as_funding and as_funding_required are synchronized if (sectionData.needs_as_funding !== undefined && sectionData.as_funding_required === undefined) { sectionData.as_funding_required = sectionData.needs_as_funding ? true : false; } setFormData(prevData => { // Save to localStorage const updatedData = { ...prevData, ...sectionData }; localStorage.setItem('eventRequestFormData', JSON.stringify(updatedData)); return updatedData; }); }; // Add this function before the handleSubmit function const resetForm = () => { setFormData({ name: '', location: '', start_date_time: '', end_date_time: '', event_description: '', flyers_needed: false, flyer_type: [], other_flyer_type: '', flyer_advertising_start_date: '', flyer_additional_requests: '', photography_needed: false, required_logos: [], other_logos: [], advertising_format: '', will_or_have_room_booking: false, expected_attendance: 0, room_booking: null, // No room booking by default as_funding_required: false, food_drinks_being_served: false, itemized_invoice: '', invoice: null, invoice_files: [], // Reset multiple invoice files needs_graphics: null, needs_as_funding: false, invoiceData: { items: [], subtotal: 0, taxAmount: 0, tipAmount: 0, total: 0, vendor: '' }, formReviewed: false // Reset review status }); // Reset to first step setCurrentStep(1); }; // Handle form submission const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Check if the form has been reviewed if (!formData.formReviewed) { toast.error('Please review your form before submitting'); return; } setIsSubmitting(true); setError(null); try { const auth = Authentication.getInstance(); const update = Update.getInstance(); const fileManager = FileManager.getInstance(); const dataSync = DataSyncService.getInstance(); if (!auth.isAuthenticated()) { // Don't show error toast on dashboard page for unauthenticated users if (!window.location.pathname.includes('/dashboard')) { toast.error('You must be logged in to submit an event request'); } throw new Error('You must be logged in to submit an event request'); } // Create the event request record const userId = auth.getUserId(); if (!userId) { // Don't show error toast on dashboard page for unauthenticated users if (auth.isAuthenticated() || !window.location.pathname.includes('/dashboard')) { toast.error('User ID not found'); } throw new Error('User ID not found'); } // Prepare data for submission const submissionData = { requested_user: userId, name: formData.name, location: formData.location, start_date_time: new Date(formData.start_date_time).toISOString(), end_date_time: formData.end_date_time ? new Date(formData.end_date_time).toISOString() : new Date(formData.start_date_time).toISOString(), event_description: formData.event_description, flyers_needed: formData.flyers_needed, photography_needed: formData.photography_needed, as_funding_required: formData.needs_as_funding, food_drinks_being_served: formData.food_drinks_being_served, itemized_invoice: formData.itemized_invoice, flyer_type: formData.flyer_type, other_flyer_type: formData.other_flyer_type, flyer_advertising_start_date: formData.flyer_advertising_start_date ? new Date(formData.flyer_advertising_start_date).toISOString() : '', flyer_additional_requests: formData.flyer_additional_requests, required_logos: formData.required_logos, advertising_format: formData.advertising_format, will_or_have_room_booking: formData.will_or_have_room_booking, expected_attendance: formData.expected_attendance, needs_graphics: formData.needs_graphics, needs_as_funding: formData.needs_as_funding, invoice_data: { items: formData.invoiceData.items.map(item => ({ item: item.description, quantity: item.quantity, unit_price: item.unitPrice })), taxAmount: formData.invoiceData.taxAmount, tipAmount: formData.invoiceData.tipAmount, total: formData.invoiceData.total, vendor: formData.invoiceData.vendor }, }; // Create the record using the Update service // This will send the data to the server const record = await update.create('event_request', submissionData); // Force sync the event requests collection to update IndexedDB await dataSync.syncCollection(Collections.EVENT_REQUESTS); // Upload files if they exist if (formData.other_logos.length > 0) { await fileManager.uploadFiles('event_request', record.id, 'other_logos', formData.other_logos); } if (formData.room_booking) { await fileManager.uploadFile('event_request', record.id, 'room_booking', formData.room_booking); } // Upload multiple invoice files if (formData.invoice_files && formData.invoice_files.length > 0) { await fileManager.appendFiles('event_request', record.id, 'invoice_files', formData.invoice_files); // For backward compatibility, also upload the first file as the main invoice if (formData.invoice || formData.invoice_files[0]) { const mainInvoice = formData.invoice || formData.invoice_files[0]; await fileManager.uploadFile('event_request', record.id, 'invoice', mainInvoice); } } else if (formData.invoice) { await fileManager.uploadFile('event_request', record.id, 'invoice', formData.invoice); } // Clear form data from localStorage localStorage.removeItem('eventRequestFormData'); // Keep success toast for form submission since it's a user action toast.success('Event request submitted successfully!'); // Reset form resetForm(); // Switch to the submissions tab const submissionsTab = document.getElementById('submissions-tab'); if (submissionsTab) { submissionsTab.click(); } } catch (error) { console.error('Error submitting event request:', error); toast.error('Failed to submit event request. Please try again.'); setError('Failed to submit event request. Please try again.'); } finally { setIsSubmitting(false); } }; // Validate PR Section const validatePRSection = () => { if (formData.flyer_type.length === 0) { toast.error('Please select at least one flyer type'); return false; } if (formData.flyer_type.includes('other') && !formData.other_flyer_type) { toast.error('Please specify the other flyer type'); return false; } if (formData.flyer_type.some(type => type === 'digital_with_social' || type === 'physical_with_advertising' || type === 'newsletter' ) && !formData.flyer_advertising_start_date) { toast.error('Please specify when to start advertising'); return false; } if (formData.required_logos.includes('OTHER') && (!formData.other_logos || formData.other_logos.length === 0)) { toast.error('Please upload your logo files'); return false; } if (!formData.advertising_format) { toast.error('Please select a format'); return false; } if (formData.photography_needed === null || formData.photography_needed === undefined) { toast.error('Please specify if photography is needed'); return false; } return true; }; // Validate Event Details Section const validateEventDetailsSection = () => { let valid = true; const errors: string[] = []; if (!formData.name || formData.name.trim() === '') { errors.push('Event name is required'); valid = false; } if (!formData.event_description || formData.event_description.trim() === '') { errors.push('Event description is required'); valid = false; } if (!formData.start_date_time || formData.start_date_time.trim() === '') { errors.push('Event start date and time is required'); valid = false; } if (!formData.end_date_time) { errors.push('Event end time is required'); valid = false; } if (!formData.location || formData.location.trim() === '') { errors.push('Event location is required'); valid = false; } if (formData.will_or_have_room_booking === undefined) { errors.push('Room booking status is required'); valid = false; } if (errors.length > 0) { setError(errors[0]); return false; } return valid; }; // Validate TAP Form Section const validateTAPFormSection = () => { // Verify that all required fields are filled if (!formData.will_or_have_room_booking && formData.will_or_have_room_booking !== false) { toast.error('Please indicate whether you will or have a room booking'); return false; } if (!formData.expected_attendance || formData.expected_attendance <= 0) { toast.error('Please enter a valid expected attendance'); return false; } // Only require room booking file if will_or_have_room_booking is true if (formData.will_or_have_room_booking && !formData.room_booking) { toast.error('Please upload your room booking confirmation'); return false; } if (!formData.food_drinks_being_served && formData.food_drinks_being_served !== false) { toast.error('Please indicate whether food/drinks will be served'); return false; } // Validate AS funding question if food is being served if (formData.food_drinks_being_served && formData.needs_as_funding === undefined) { toast.error('Please indicate whether you need AS funding'); return false; } return true; }; // Validate AS Funding Section const validateASFundingSection = () => { if (formData.as_funding_required) { // Check if invoice data is present and has items if (!formData.invoiceData || !formData.invoiceData.items || formData.invoiceData.items.length === 0) { setError('Please add at least one item to your invoice'); return false; } // Calculate the total budget from invoice items const totalBudget = formData.invoiceData.items.reduce( (sum, item) => sum + (item.unitPrice * item.quantity), 0 ); // Check if the budget exceeds the maximum allowed ($5000 cap regardless of attendance) const maxBudget = Math.min(formData.expected_attendance * 10, 5000); if (totalBudget > maxBudget) { setError(`Your budget (${totalBudget.toFixed(2)} dollars) exceeds the maximum allowed (${maxBudget} dollars). The absolute maximum is $5,000.`); return false; } } return true; }; // Validate all sections before submission const validateAllSections = () => { // We no longer forcibly set end_date_time to match start_date_time // The end time is now configured separately with the same date if (!validateEventDetailsSection()) { setCurrentStep(1); return false; } // Validate TAP Form if (!validateTAPFormSection()) { return false; } // Validate PR Section if needed if (formData.needs_graphics && !validatePRSection()) { return false; } // Validate AS Funding if needed if (formData.food_drinks_being_served && formData.needs_as_funding && !validateASFundingSection()) { return false; } return true; }; // Handle next button click with validation const handleNextStep = (nextStep: number) => { let isValid = true; // Validate current section before proceeding if (currentStep === 2 && formData.needs_graphics) { isValid = validatePRSection(); } else if (currentStep === 3) { isValid = validateEventDetailsSection(); } else if (currentStep === 4) { isValid = validateTAPFormSection(); } else if (currentStep === 5 && formData.food_drinks_being_served && formData.needs_as_funding) { isValid = validateASFundingSection(); } if (!isValid) { return; // Don't proceed if validation fails } // If we're moving from step 4 to step 5 if (currentStep === 4 && nextStep === 5) { // If food and drinks aren't being served or if AS funding isn't needed, skip to step 6 (review) if (!formData.food_drinks_being_served || !formData.needs_as_funding) { nextStep = 6; } } // Set the current step setCurrentStep(nextStep); // If moving to the review step, mark the form as reviewed // but don't submit it automatically if (nextStep === 6) { setFormData(prevData => ({ ...prevData, formReviewed: true })); } }; // Handle form submission with validation const handleSubmitWithValidation = (e: React.FormEvent) => { e.preventDefault(); // If we're on the review step, we've already validated all sections // Only submit if the user explicitly clicks the submit button if (currentStep === 6 && formData.formReviewed) { handleSubmit(e); return; } // Otherwise, validate all sections before proceeding to the review step if (validateAllSections()) { // If we're not on the review step, go to the review step instead of submitting handleNextStep(6); } }; // Render the current step const renderCurrentSection = () => { // Step 1: Ask if they need graphics from the design team if (currentStep === 1) { return (

Event Request Form

Welcome to the IEEE UCSD Event Request Form. This form will help you request PR materials, provide event details, and request AS funding if needed.

Do you need graphics from the design team?

); } // Step 2: PR Section (if they need graphics) if (currentStep === 2) { return (
); } // Step 3: Event Details Section if (currentStep === 3) { return (
); } // Step 4: TAP Form Section if (currentStep === 4) { return (
); } // Step 5: AS Funding Section if (currentStep === 5) { return (

AS Funding Details

Please provide the necessary information for your AS funding request.

); } // Step 6: Review Form if (currentStep === 6) { return (

Review Your Event Request

Please review all information carefully before submitting. You can go back to any section to make changes if needed.

Ready to Submit?
); } return null; }; return ( <>
{ // Prevent default form submission behavior e.preventDefault(); // Only submit if the user explicitly clicks the submit button // The actual submission is handled by handleSubmitWithValidation }} className="space-y-6" > {error && ( )} {/* Progress indicator */}
Step {currentStep} of 6 {Math.min(Math.round((currentStep / 6) * 100), 100)}% complete
{/* Current section */} {renderCurrentSection()}
); }; export default EventRequestForm;