115 lines
3.7 KiB
TypeScript
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>
|
|
);
|
|
}
|