// composables/useEvents.ts import type { Event, EventsResponse, EventFilters, EventCreateRequest, EventRSVPRequest } from '~/utils/types'; export const useEvents = () => { const events = ref([]); const loading = ref(false); const error = ref(null); const upcomingEvent = ref(null); const cache = reactive>(new Map()); const CACHE_TIMEOUT = 5 * 60 * 1000; // 5 minutes // Get authenticated user info const { user, userTier } = useAuth(); /** * Fetch events with optional filtering and caching */ const fetchEvents = async (filters?: EventFilters & { force?: boolean }) => { loading.value = true; error.value = null; try { // Create cache key const cacheKey = JSON.stringify(filters || {}); const cached = cache.get(cacheKey); // Check cache if not forcing refresh if (!filters?.force && cached) { const now = Date.now(); if (now - cached.timestamp < CACHE_TIMEOUT) { events.value = cached.data; loading.value = false; return cached.data; } } // Default date range (current month + 2 months ahead) const defaultFilters: EventFilters = { start_date: startOfMonth(new Date()).toISOString(), end_date: endOfMonth(addMonths(new Date(), 2)).toISOString(), user_role: userTier.value, ...filters }; const response = await $fetch('/api/events', { query: { ...defaultFilters, calendar_format: 'false' } }); if (response.success) { events.value = response.data; // Cache the results cache.set(cacheKey, { data: response.data, timestamp: Date.now() }); // Update upcoming event updateUpcomingEvent(response.data); return response.data; } else { throw new Error(response.message || 'Failed to fetch events'); } } catch (err: any) { error.value = err.message || 'Failed to load events'; console.error('Error fetching events:', err); throw err; } finally { loading.value = false; } }; /** * Create a new event (board/admin only) */ const createEvent = async (eventData: EventCreateRequest) => { loading.value = true; error.value = null; try { const response = await $fetch<{ success: boolean; data: Event; message: string }>('/api/events', { method: 'POST', body: eventData }); if (response.success) { // Clear cache and refresh events cache.clear(); await fetchEvents({ force: true }); return response.data; } else { throw new Error(response.message || 'Failed to create event'); } } catch (err: any) { error.value = err.message || 'Failed to create event'; console.error('Error creating event:', err); throw err; } finally { loading.value = false; } }; /** * RSVP to an event */ const rsvpToEvent = async (eventId: string, rsvpData: Omit) => { loading.value = true; error.value = null; try { const response = await $fetch(`/api/events/${eventId}/rsvp`, { method: 'POST', body: { ...rsvpData, event_id: eventId, member_id: user.value?.id || '' } }); if (response.success) { // Update local event data const eventIndex = events.value.findIndex(e => e.id === eventId); if (eventIndex !== -1) { events.value[eventIndex].user_rsvp = response.data; // Update attendee count if confirmed if (rsvpData.rsvp_status === 'confirmed') { const currentCount = events.value[eventIndex].current_attendees || 0; events.value[eventIndex].current_attendees = currentCount + 1; } } // Clear cache cache.clear(); return response.data; } else { throw new Error(response.message || 'Failed to RSVP'); } } catch (err: any) { error.value = err.message || 'Failed to RSVP to event'; console.error('Error RSVPing to event:', err); throw err; } finally { loading.value = false; } }; /** * Update attendance for an event (board/admin only) */ const updateAttendance = async (eventId: string, memberId: string, attended: boolean) => { loading.value = true; error.value = null; try { const response = await $fetch(`/api/events/${eventId}/attendees`, { method: 'PATCH', body: { event_id: eventId, member_id: memberId, attended } }); if (response.success) { // Update local event data const eventIndex = events.value.findIndex(e => e.id === eventId); if (eventIndex !== -1 && events.value[eventIndex].attendee_list) { const attendeeIndex = events.value[eventIndex].attendee_list!.findIndex( a => a.member_id === memberId ); if (attendeeIndex !== -1) { events.value[eventIndex].attendee_list![attendeeIndex].attended = attended ? 'true' : 'false'; } } return response.data; } else { throw new Error(response.message || 'Failed to update attendance'); } } catch (err: any) { error.value = err.message || 'Failed to update attendance'; console.error('Error updating attendance:', err); throw err; } finally { loading.value = false; } }; /** * Get events for calendar display */ const getCalendarEvents = async (start: string, end: string) => { try { const response = await $fetch('/api/events', { query: { start_date: start, end_date: end, user_role: userTier.value, calendar_format: 'true' } }); if (response.success) { return response.data; } return []; } catch (err) { console.error('Error fetching calendar events:', err); return []; } }; /** * Get upcoming events for banners/widgets */ const getUpcomingEvents = (limit = 5): Event[] => { const now = new Date(); return events.value .filter(event => new Date(event.start_datetime) >= now) .sort((a, b) => new Date(a.start_datetime).getTime() - new Date(b.start_datetime).getTime()) .slice(0, limit); }; /** * Find event by ID */ const findEventById = (eventId: string): Event | undefined => { return events.value.find(event => event.id === eventId); }; /** * Check if user has RSVP'd to an event */ const hasUserRSVP = (eventId: string): boolean => { const event = findEventById(eventId); return !!event?.user_rsvp; }; /** * Get user's RSVP status for an event */ const getUserRSVPStatus = (eventId: string): string | null => { const event = findEventById(eventId); return event?.user_rsvp?.rsvp_status || null; }; /** * Update the upcoming event reference */ const updateUpcomingEvent = (eventList: Event[]) => { const upcoming = eventList .filter(event => new Date(event.start_datetime) >= new Date()) .sort((a, b) => new Date(a.start_datetime).getTime() - new Date(b.start_datetime).getTime()); upcomingEvent.value = upcoming.length > 0 ? upcoming[0] : null; }; /** * Clear cache manually */ const clearCache = () => { cache.clear(); }; /** * Refresh events data */ const refreshEvents = async () => { clearCache(); return await fetchEvents({ force: true }); }; // Utility functions for date handling function startOfMonth(date: Date): Date { return new Date(date.getFullYear(), date.getMonth(), 1); } function endOfMonth(date: Date): Date { return new Date(date.getFullYear(), date.getMonth() + 1, 0); } function addMonths(date: Date, months: number): Date { const result = new Date(date); result.setMonth(result.getMonth() + months); return result; } return { // Reactive state events: readonly(events), loading: readonly(loading), error: readonly(error), upcomingEvent: readonly(upcomingEvent), // Methods fetchEvents, createEvent, rsvpToEvent, updateAttendance, getCalendarEvents, getUpcomingEvents, findEventById, hasUserRSVP, getUserRSVPStatus, clearCache, refreshEvents }; };