import { formatDate, formatDateTime } from '../booking-ui/dateTime'; import * as React from 'react'; import { __, sprintf } from '@wordpress/i18n'; import { CatalogApi } from './api'; import { ScheduleForm } from './ScheduleForm'; import { blockTypes, scheduleLabels, weekdays } from './scheduleFields'; import type { Config, Payload, Resource, ScheduleItem, SchedulePage } from './types'; export function ScheduleAdminApp({ config }: { config: Config & { page: SchedulePage; }; }): React.ReactElement { const api = React.useMemo(() => new CatalogApi(config), [config]); const [items, setItems] = React.useState([]); const [resources, setResources] = React.useState([]); const [page, setPage] = React.useState(1); const [pages, setPages] = React.useState(1); const [total, setTotal] = React.useState(0); const [revision, refresh] = React.useReducer((value: number) => value + 1, 0); const [loading, setLoading] = React.useState(true); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(''); const [notice, setNotice] = React.useState(''); const [editor, setEditor] = React.useState<{ item: ScheduleItem | null; } | null>(null); const [deleting, setDeleting] = React.useState(null); React.useEffect(() => { let active = true; setLoading(true); setError(''); void Promise.all([api.list(config.page, page), api.all('resources'), api.sports()]) .then(([result, allResources, sports]) => { if (active) { setItems(result.items); setPages(Math.max(1, result.pages)); setTotal(result.total); setResources(allResources.map(r => ({ ...r, sport_name: sports.find(s => s.id === r.sport_id)?.name }))); } }) .catch(cause => { if (active) setError(cause instanceof Error ? cause.message : __('The request failed. Please try again.', 'abece-booking')); }) .finally(() => { if (active) setLoading(false); }); return () => { active = false; }; }, [api, config.page, page, revision]); async function save(payload: Payload): Promise { await api.save(config.page, payload, editor?.item?.id); setEditor(null); setNotice(__('Changes saved.', 'abece-booking')); refresh(); } async function remove(): Promise { if (!deleting) return; setBusy(true); setError(''); try { await api.remove(config.page, deleting.id); setDeleting(null); setNotice(__('Changes saved.', 'abece-booking')); if (items.length === 1 && page > 1) setPage(page - 1); else refresh(); } catch (cause) { setError(cause instanceof Error ? cause.message : __('The request failed. Please try again.', 'abece-booking')); } finally { setBusy(false); } } const labels = scheduleLabels[config.page]; const hours = config.page === 'opening-hours'; const blocks = config.page === 'resource-blocks'; const headers = [__('Resource', 'abece-booking'), hours ? __('Day of week', 'abece-booking') : blocks ? __('Starts at', 'abece-booking') : __('Date', 'abece-booking'), hours ? __('Opening mode', 'abece-booking') : blocks ? __('Ends at', 'abece-booking') : __('Reason', 'abece-booking'), blocks ? __('Block type', 'abece-booking') : __('Hours', 'abece-booking'), __('Actions', 'abece-booking')]; const resourceName = (id: number | null): string => id === null ? __('Global (entire club)', 'abece-booking') : resources.find(resource => resource.id === id)?.name ?? `#${id}`; function hoursText(item: ScheduleItem): string { if (item.is_closed) return __('Closed', 'abece-booking'); if (item.opening_mode === 'sun') return `${__('Sunrise offset (minutes)', 'abece-booking')}: ${item.sunrise_offset_minutes ?? 0}, ${__('Sunset offset (minutes)', 'abece-booking')}: ${item.sunset_offset_minutes ?? 0}; ${__('Earliest opening time', 'abece-booking')}: ${String(item.min_open_time ?? '—')}; ${__('Latest closing time', 'abece-booking')}: ${String(item.max_close_time ?? '—')}`; return `${String(item.opens_at ?? '').slice(0, 5)}–${String(item.closes_at ?? '').slice(0, 5)}`; } return

{__('Operations', 'abece-booking')}

{labels.title}

{!hours &&

{blocks ? __('Temporarily prevents bookings for a selected resource, for example due to maintenance, a tournament or weather.', 'abece-booking') : __('Changes the standard opening hours for a specific date.', 'abece-booking')}

}

{sprintf(__('Times use the WordPress timezone: %s.', 'abece-booking'), config.timezone)}

{!blocks &&

{__('Resource rules replace global rules for that day. Exceptions override opening hours. A closed rule closes the selected scope.', 'abece-booking')}

} {notice &&

{notice}

} {error &&

{error}

} {editor && setEditor(null)}/>} {deleting &&

{__('Delete this rule? Removing it changes availability immediately.', 'abece-booking')}

} {loading &&

{__('Loading…', 'abece-booking')}

} {!loading && !error && <>

{sprintf(__('Total items: %d', 'abece-booking'), total)}

{headers.map(label => )} {items.length === 0 ? : items.map(item => )}
{label}
{__('No items found.', 'abece-booking')}
{item.resource_ids ? (item.target === 'all' ? __('Global (entire club)', 'abece-booking') : (item.resource_ids as number[]).map(resourceName).join(', ')) : resourceName(item.resource_id)}{item.public_title && {String(item.public_title)}}{hours ? weekdays.find(([value]) => value === String(item.day_of_week))?.[1] : blocks ? formatDateTime(String(item.starts_at)) : formatDate(String(item.exception_date))} {hours ? item.opening_mode === 'sun' ? __('Sun-based', 'abece-booking') : __('Fixed', 'abece-booking') : blocks ? formatDateTime(String(item.ends_at)) : String(item.reason ?? '—')} {blocks ? blockTypes.find(([value]) => value === item.block_type)?.[1] : hoursText(item)}
{sprintf(__('Page %1$d of %2$d', 'abece-booking'), page, pages)}
}
; }