import { useSignal } from '@preact/signals'; import { useEffect } from 'preact/hooks'; import { Calendar, CalendarEvent } from '/lib/models/calendar.ts'; import { RequestBody, ResponseBody } from '/routes/api/calendar/search-events.tsx'; import { getColorAsHex } from '/lib/utils/calendar.ts'; interface SearchEventsProps { calendars: Calendar[]; onClickOpenEvent: (calendarEvent: CalendarEvent) => void; } export default function SearchEvents({ calendars, onClickOpenEvent }: SearchEventsProps) { const isSearching = useSignal(false); const areResultsVisible = useSignal(false); const calendarEvents = useSignal([]); const searchTimeout = useSignal>(0); const closeTimeout = useSignal>(0); const dateFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: 'UTC', // Calendar dates are parsed without timezone info, so we need to force to UTC so it's consistent across db, server, and client }); const calendarIds = calendars.map((calendar) => calendar.uid!); function searchEvents(searchTerm: string) { if (searchTimeout.value) { clearTimeout(searchTimeout.value); } if (searchTerm.trim().length < 2) { return; } areResultsVisible.value = false; searchTimeout.value = setTimeout(async () => { isSearching.value = true; try { const requestBody: RequestBody = { calendarIds, searchTerm }; const response = await fetch(`/api/calendar/search-events`, { method: 'POST', body: JSON.stringify(requestBody), }); const result = await response.json() as ResponseBody; if (!result.success) { throw new Error('Failed to search events!'); } calendarEvents.value = result.matchingCalendarEvents; if (calendarEvents.value.length > 0) { areResultsVisible.value = true; } } catch (error) { console.error(error); } isSearching.value = false; }, 500); } function onFocus() { if (calendarEvents.value.length > 0) { areResultsVisible.value = true; } } function onBlur() { if (closeTimeout.value) { clearTimeout(closeTimeout.value); } closeTimeout.value = setTimeout(() => { areResultsVisible.value = false; }, 300); } useEffect(() => { return () => { if (searchTimeout.value) { clearTimeout(searchTimeout.value); } if (closeTimeout.value) { clearTimeout(closeTimeout.value); } }; }, []); return ( <> searchEvents(event.currentTarget.value)} onFocus={() => onFocus()} onBlur={() => onBlur()} /> {isSearching.value ? : null} {areResultsVisible.value ? (
) : null} ); }