Files
govoplan-calendar/webui/src/features/calendar/CalendarPicker.tsx

115 lines
3.7 KiB
TypeScript

import { useEffect, useId, useMemo, useState } from "react";
import {
FormField,
hasScope,
type CalendarPickerProps
} from "@govoplan/core-webui";
import { listCalendars, type CalendarCollection } from "../../api/calendar";
import {
CALENDAR_PICKER_READ_SCOPE,
calendarPickerOptions,
calendarPickerTenantId
} from "./calendarPickerLogic";
const LABEL = "i18n:govoplan-calendar.calendar.adab5090";
const SELECT_CALENDAR = "i18n:govoplan-calendar.select_calendar.f38b5ba2";
const LOADING_CALENDARS = "i18n:govoplan-calendar.loading_calendars.afbb957b";
const CALENDARS_UNAVAILABLE = "i18n:govoplan-calendar.calendars_are_unavailable.f074c862";
const NO_CALENDARS_AVAILABLE = "i18n:govoplan-calendar.no_calendars_are_available.711d2cf3";
const SELECTED_CALENDAR_UNAVAILABLE = "i18n:govoplan-calendar.the_selected_calendar_is_no_longer_available.39f0f1f5";
export default function CalendarPicker({
settings,
auth,
value,
onChange,
id,
name,
label = LABEL,
emptyLabel = SELECT_CALENDAR,
disabled = false,
required = false,
className
}: CalendarPickerProps) {
const generatedId = useId();
const selectId = id ?? `calendar-picker-${generatedId.replace(/:/g, "")}`;
const statusId = `${selectId}-status`;
const [calendars, setCalendars] = useState<CalendarCollection[]>([]);
const [loading, setLoading] = useState(false);
const [failed, setFailed] = useState(false);
const canRead = hasScope(auth, CALENDAR_PICKER_READ_SCOPE);
const tenantId = calendarPickerTenantId(auth);
useEffect(() => {
let cancelled = false;
if (!canRead) {
setCalendars([]);
setLoading(false);
setFailed(true);
return () => {
cancelled = true;
};
}
setLoading(true);
setFailed(false);
listCalendars(settings)
.then((response) => {
if (!cancelled) setCalendars(response.calendars);
})
.catch(() => {
if (!cancelled) {
setCalendars([]);
setFailed(true);
}
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [canRead, settings.accessToken, settings.apiBaseUrl, settings.apiKey, tenantId]);
const options = useMemo(() => calendarPickerOptions(calendars), [calendars]);
const selectedUnavailable = Boolean(value) && !loading && !options.some((calendar) => calendar.id === value);
const status = failed
? CALENDARS_UNAVAILABLE
: !loading && options.length === 0
? NO_CALENDARS_AVAILABLE
: selectedUnavailable
? SELECTED_CALENDAR_UNAVAILABLE
: "";
const placeholder = loading
? LOADING_CALENDARS
: failed
? CALENDARS_UNAVAILABLE
: options.length === 0
? NO_CALENDARS_AVAILABLE
: emptyLabel;
return (
<div className={["calendar-picker", className].filter(Boolean).join(" ")}>
<FormField label={label}>
<select
id={selectId}
name={name}
value={value}
required={required}
disabled={disabled || loading || failed || options.length === 0}
aria-busy={loading || undefined}
aria-describedby={status ? statusId : undefined}
onChange={(event) => onChange(event.target.value)}
>
<option value="">{placeholder}</option>
{selectedUnavailable ? <option value={value} disabled>{SELECTED_CALENDAR_UNAVAILABLE}</option> : null}
{options.map((calendar) => (
<option key={calendar.id} value={calendar.id}>{calendar.name}</option>
))}
</select>
</FormField>
{status ? <div id={statusId} className="muted small-note" role={failed ? "alert" : "status"}>{status}</div> : null}
</div>
);
}