feat(scheduling): prefill participant availability
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
||||
createSchedulingRequest,
|
||||
decideSchedulingRequest,
|
||||
evaluateSchedulingFreeBusy,
|
||||
getSchedulingAvailabilityResponse,
|
||||
listSchedulingNotifications,
|
||||
listSchedulingRequests,
|
||||
openSchedulingRequest,
|
||||
@@ -176,6 +177,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
|
||||
const [slots, setSlots] = useState<SlotDraft[]>(() => initialSlots(translateText));
|
||||
const [participants, setParticipants] = useState<ParticipantDraft[]>(() => [emptyParticipant()]);
|
||||
const [availability, setAvailability] = useState<Record<string, SchedulingAvailabilityValue | "">>({});
|
||||
const [availabilityLoading, setAvailabilityLoading] = useState(false);
|
||||
|
||||
const canWrite = hasScope(auth, "scheduling:schedule:write");
|
||||
const canRespond = hasScope(auth, "scheduling:availability:write");
|
||||
@@ -240,6 +242,39 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
|
||||
void loadDetails(selected.id);
|
||||
}, [creating, selected?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selected?.id || !selectedParticipant || !canRespond || creating || selected.status !== "collecting") {
|
||||
setAvailabilityLoading(false);
|
||||
return undefined;
|
||||
}
|
||||
let cancelled = false;
|
||||
setAvailabilityLoading(true);
|
||||
void getSchedulingAvailabilityResponse(settings, selected.id)
|
||||
.then((response) => {
|
||||
if (cancelled) return;
|
||||
setAvailability(Object.fromEntries(response.answers.map((answer) => [answer.slot_id, answer.value])));
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!cancelled) setError(errorMessage(err, translateText(I18N.requestFailed)));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setAvailabilityLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
canRespond,
|
||||
creating,
|
||||
selected?.id,
|
||||
selected?.status,
|
||||
selectedParticipant?.id,
|
||||
settings.accessToken,
|
||||
settings.apiBaseUrl,
|
||||
settings.apiKey,
|
||||
translateText
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!creating || !draftDirty) return undefined;
|
||||
const preventUnsavedExit = (event: BeforeUnloadEvent) => event.preventDefault();
|
||||
@@ -366,7 +401,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
|
||||
|
||||
async function sendAvailability(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!selected || !selectedParticipant || !canRespond) return;
|
||||
if (!selected || !selectedParticipant || !canRespond || availabilityLoading) return;
|
||||
const answers = selected.slots
|
||||
.map((slot) => ({ slot_id: slot.id, value: availability[slot.id] }))
|
||||
.filter((answer): answer is { slot_id: string; value: SchedulingAvailabilityValue } => Boolean(answer.value));
|
||||
@@ -578,12 +613,16 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
|
||||
variant="primary"
|
||||
type="submit"
|
||||
form="scheduling-response-form"
|
||||
disabled={saving || !canRespond || (selectedParticipant.status === "responded" && !selected.allow_participant_updates)}>
|
||||
disabled={saving || availabilityLoading || !canRespond || (selectedParticipant.status === "responded" && !selected.allow_participant_updates)}>
|
||||
<Send aria-hidden="true" size={16} />
|
||||
{selectedParticipant.status === "responded" ? I18N.updateResponse : I18N.sendResponse}
|
||||
</Button>
|
||||
)}>
|
||||
<form id="scheduling-response-form" className="scheduling-response-card" onSubmit={(event) => void sendAvailability(event)}>
|
||||
<form
|
||||
id="scheduling-response-form"
|
||||
className="scheduling-response-card"
|
||||
aria-busy={availabilityLoading}
|
||||
onSubmit={(event) => void sendAvailability(event)}>
|
||||
<p>{selectedParticipant.status === "responded"
|
||||
? (selected.allow_participant_updates ? I18N.responseReplace : I18N.responseRecorded)
|
||||
: I18N.invitationHelp}</p>
|
||||
@@ -596,7 +635,7 @@ export default function SchedulingPage({ settings, auth }: { settings: ApiSettin
|
||||
<span><strong>{slot.label}</strong><small>{formatRange(slot.start_at, slot.end_at)}</small></span>
|
||||
<select
|
||||
value={availability[slot.id] ?? ""}
|
||||
disabled={selectedParticipant.status === "responded" && !selected.allow_participant_updates}
|
||||
disabled={availabilityLoading || (selectedParticipant.status === "responded" && !selected.allow_participant_updates)}
|
||||
onChange={(event) => setAvailability((current) => ({
|
||||
...current,
|
||||
[slot.id]: event.target.value as SchedulingAvailabilityValue | ""
|
||||
|
||||
Reference in New Issue
Block a user