import { useState, useEffect } from "react"; import { Get } from "../../pocketbase/Get"; import { Authentication } from "../../pocketbase/Authentication"; import { Update } from "../../pocketbase/Update"; import { FileManager } from "../../pocketbase/FileManager"; import { SendLog } from "../../pocketbase/SendLog"; import FilePreview from "./FilePreview"; // Extend Window interface declare global { interface Window { showLoading?: () => void; hideLoading?: () => void; } } interface Event { id: string; event_name: string; event_description: string; event_code: string; location: string; files: string[]; points_to_reward: number; start_date: string; end_date: string; published: boolean; has_food: boolean; attendees: AttendeeEntry[]; } interface AttendeeEntry { user_id: string; time_checked_in: string; food: string; } interface EventEditorProps { onEventSaved?: () => void; } export default function EventEditor({ onEventSaved }: EventEditorProps) { // State for form data and UI const [event, setEvent] = useState(null); const [previewUrl, setPreviewUrl] = useState(""); const [previewFilename, setPreviewFilename] = useState(""); const [showPreview, setShowPreview] = useState(false); const [selectedFiles, setSelectedFiles] = useState>(new Map()); const [filesToDelete, setFilesToDelete] = useState>(new Set()); const [isSubmitting, setIsSubmitting] = useState(false); // Initialize services const get = Get.getInstance(); const auth = Authentication.getInstance(); const update = Update.getInstance(); const fileManager = FileManager.getInstance(); const sendLog = SendLog.getInstance(); // Method to initialize form with event data const initializeEventData = async (eventId: string) => { try { const eventData = await get.getOne("events", eventId); setEvent(eventData); setSelectedFiles(new Map()); setFilesToDelete(new Set()); setShowPreview(false); } catch (error) { console.error("Failed to fetch event data:", error); alert("Failed to load event data. Please try again."); } }; // Expose initializeEventData to window useEffect(() => { (window as any).openEditModal = async (event?: Event) => { const modal = document.getElementById("editEventModal") as HTMLDialogElement; if (!modal) return; if (event?.id) { await initializeEventData(event.id); } else { setEvent(null); setSelectedFiles(new Map()); setFilesToDelete(new Set()); setShowPreview(false); } modal.showModal(); }; return () => { delete (window as any).openEditModal; }; }, []); // Handle file selection const handleFileSelect = (e: React.ChangeEvent) => { if (e.target.files) { const newFiles = Array.from(e.target.files); const updatedFiles = new Map(selectedFiles); newFiles.forEach(file => { updatedFiles.set(file.name, file); }); setSelectedFiles(updatedFiles); } }; // Handle file deletion const handleFileDelete = (filename: string) => { if (confirm("Are you sure you want to remove this file?")) { const updatedFilesToDelete = new Set(filesToDelete); updatedFilesToDelete.add(filename); setFilesToDelete(updatedFilesToDelete); } }; // Handle file deletion undo const handleUndoFileDelete = (filename: string) => { const updatedFilesToDelete = new Set(filesToDelete); updatedFilesToDelete.delete(filename); setFilesToDelete(updatedFilesToDelete); }; // Handle file preview const handlePreviewFile = (url: string, filename: string) => { setPreviewUrl(url); setPreviewFilename(filename); setShowPreview(true); }; // Handle form submission const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const form = e.target as HTMLFormElement; const formData = new FormData(form); setIsSubmitting(true); try { window.showLoading?.(); const eventData = { event_name: formData.get("editEventName"), event_code: formData.get("editEventCode"), event_description: formData.get("editEventDescription"), location: formData.get("editEventLocation"), points_to_reward: Number(formData.get("editEventPoints")), start_date: new Date(formData.get("editEventStartDate") as string).toISOString(), end_date: new Date(formData.get("editEventEndDate") as string).toISOString(), published: formData.get("editEventPublished") === "on", has_food: formData.get("editEventHasFood") === "on", }; const pb = auth.getPocketBase(); if (event?.id) { // Update existing event const currentEvent = await pb.collection("events").getOne(event.id); const currentFiles = currentEvent.files || []; // Filter out files marked for deletion const remainingFiles = currentFiles.filter( (filename: string) => !filesToDelete.has(filename) ); // Create FormData for update const updateFormData = new FormData(); // Add event data Object.entries(eventData).forEach(([key, value]) => { updateFormData.append(key, String(value)); }); // Add remaining and new files const filePromises = remainingFiles.map(async (filename: string) => { try { const response = await fetch( fileManager.getFileUrl("events", event.id, filename) ); const blob = await response.blob(); return new File([blob], filename, { type: blob.type }); } catch (error) { console.error(`Failed to fetch file ${filename}:`, error); return null; } }); const existingFiles = (await Promise.all(filePromises)).filter( (file): file is File => file !== null ); [...existingFiles, ...Array.from(selectedFiles.values())].forEach( (file: File) => { updateFormData.append("files", file); } ); await pb.collection("events").update(event.id, updateFormData); await sendLog.send( "update", "event", `Updated event: ${eventData.event_name}` ); // Log file deletions for (const filename of filesToDelete) { await sendLog.send( "delete", "event_file", `Deleted file ${filename} from event ${eventData.event_name}` ); } } else { // Create new event const createFormData = new FormData(); // Add event data Object.entries(eventData).forEach(([key, value]) => { createFormData.append(key, String(value)); }); // Initialize empty attendees array createFormData.append("attendees", JSON.stringify([])); // Add new files Array.from(selectedFiles.values()).forEach((file: File) => { createFormData.append("files", file); }); await pb.collection("events").create(createFormData); await sendLog.send( "create", "event", `Created event: ${eventData.event_name}` ); } // Close modal and reset state const modal = document.getElementById("editEventModal") as HTMLDialogElement; if (modal) modal.close(); setEvent(null); setSelectedFiles(new Map()); setFilesToDelete(new Set()); setShowPreview(false); form.reset(); // Notify parent component onEventSaved?.(); } catch (error) { console.error("Failed to save event:", error); alert("Failed to save event. Please try again."); } finally { setIsSubmitting(false); window.hideLoading?.(); } }; return ( {showPreview ? (

{previewFilename}

) : (
{/* Main Edit Form Section */}

{event?.id ? 'Edit Event' : 'Add New Event'}

{/* Event Name */}
setEvent(prev => prev ? { ...prev, event_name: e.target.value } : null)} required />
{/* Event Code */}
setEvent(prev => prev ? { ...prev, event_code: e.target.value } : null)} required />
{/* Location */}
setEvent(prev => prev ? { ...prev, location: e.target.value } : null)} required />
{/* Points to Reward */}
setEvent(prev => prev ? { ...prev, points_to_reward: Number(e.target.value) } : null)} min="0" required />
{/* Start Date */}
setEvent(prev => prev ? { ...prev, start_date: e.target.value } : null)} required />
{/* End Date */}
setEvent(prev => prev ? { ...prev, end_date: e.target.value } : null)} required />
{/* Description */}
{/* Files */}
{/* New Files */} {Array.from(selectedFiles.entries()).map(([name, file]) => (
{name}
New
))} {/* Current Files */} {event?.files && event.files.length > 0 && ( <>
Current Files
{event.files.map((filename) => (
{filename}
{filesToDelete.has(filename) ? ( ) : ( )}
))} )}
{/* Published */}
{/* Has Food */}
)}
); }