feat(scheduling-webui): build two-pane request workspace

This commit is contained in:
2026-07-21 21:17:26 +02:00
parent b1725b8f59
commit 82b158c170
7 changed files with 1559 additions and 627 deletions
+152 -7
View File
@@ -23,11 +23,12 @@ export type SchedulingCandidateSlot = {
export type SchedulingParticipant = {
id: string;
is_current_participant: boolean;
respondent_id?: string | null;
display_name?: string | null;
email?: string | null;
participant_type: string;
required: boolean;
participant_type: string | null;
required: boolean | null;
status: string;
poll_invitation_id?: string | null;
invitation_token?: string | null;
@@ -52,7 +53,7 @@ export type SchedulingParticipantVisibilityDecision = {
export type SchedulingRequest = {
id: string;
tenant_id: string;
tenant_id: string | null;
title: string;
description?: string | null;
location?: string | null;
@@ -69,11 +70,20 @@ export type SchedulingRequest = {
effective_participant_visibility: SchedulingParticipantVisibility;
participant_aggregate: SchedulingParticipantAggregate;
participant_visibility_decision: SchedulingParticipantVisibilityDecision;
calendar_integration_enabled: boolean;
notify_on_answers: boolean;
single_choice: boolean;
max_participants_per_option: number | null;
allow_maybe: boolean;
allow_comments: boolean;
participant_email_required: boolean;
anonymous_password_protection_enabled: boolean;
public_participation_policy_enforcement_available: boolean | null;
public_participation_policy_enforcement_reason?: string | null;
calendar_integration_enabled: boolean | null;
calendar_id?: string | null;
calendar_freebusy_enabled: boolean;
calendar_hold_enabled: boolean;
create_calendar_event_on_decision: boolean;
calendar_freebusy_enabled: boolean | null;
calendar_hold_enabled: boolean | null;
create_calendar_event_on_decision: boolean | null;
calendar_event_id?: string | null;
handed_off_at?: string | null;
cancelled_at?: string | null;
@@ -117,6 +127,14 @@ export type SchedulingRequestCreatePayload = {
allow_participant_updates?: boolean;
result_visibility?: "organizer" | "after_response" | "after_close" | "public";
participant_visibility?: SchedulingParticipantVisibility;
notify_on_answers?: boolean;
single_choice?: boolean;
max_participants_per_option?: number | null;
allow_maybe?: boolean;
allow_comments?: boolean;
participant_email_required?: boolean;
anonymous_password_protection_enabled?: boolean;
anonymous_password?: string;
calendar?: {
enabled?: boolean;
calendar_id?: string | null;
@@ -130,6 +148,33 @@ export type SchedulingRequestCreatePayload = {
metadata?: Record<string, unknown>;
};
export type SchedulingRequestUpdatePayload = Omit<
SchedulingRequestCreatePayload,
"timezone" | "status" | "slots" | "participants"
> & {
slots?: SchedulingCandidateSlotReconcilePayload[];
participants?: SchedulingParticipantReconcilePayload[];
};
export type SchedulingCandidateSlotReconcilePayload = SchedulingCandidateSlotPayload & {
id?: string;
revision?: string;
};
export type SchedulingParticipantReconcilePayload = SchedulingParticipantPayload & {
id?: string;
};
export type SchedulingCandidateSlotUpdatePayload = {
label?: string | null;
description?: string | null;
start_at?: string | null;
end_at?: string | null;
timezone?: string | null;
location?: string | null;
metadata?: Record<string, unknown> | null;
};
export type SchedulingDecisionPayload = {
slot_id?: string | null;
poll_option_id?: string | null;
@@ -144,6 +189,7 @@ export type SchedulingAvailabilityPayload = {
value: SchedulingAvailabilityValue;
option_revision: string;
}>;
comment?: string | null;
};
export type SchedulingAvailabilityResponse = {
@@ -151,12 +197,62 @@ export type SchedulingAvailabilityResponse = {
participant_id: string;
has_response: boolean;
submitted_at?: string | null;
comment?: string | null;
answers: Array<{
slot_id: string;
value: SchedulingAvailabilityValue;
}>;
};
export type SchedulingPublicCandidateSlot = {
id: string;
label: string;
description?: string | null;
start_at: string;
end_at: string;
timezone: string;
location?: string | null;
position: number;
revision: string;
};
export type SchedulingPublicParticipationResponse = {
request_id: string;
title: string;
description?: string | null;
location?: string | null;
timezone: string;
status: SchedulingStatus;
deadline_at?: string | null;
participant_email_required: boolean;
anonymous_password_required: boolean;
single_choice: boolean;
max_participants_per_option: number | null;
allow_maybe: boolean;
allow_comments: boolean;
allow_participant_updates: boolean;
has_response: boolean;
submitted_at?: string | null;
answers: Array<{
slot_id: string;
value: SchedulingAvailabilityValue;
}>;
comment?: string | null;
replayed: boolean;
slots: SchedulingPublicCandidateSlot[];
};
export type SchedulingPublicParticipationAccessPayload = {
participant_email?: string | null;
password?: string | null;
};
export type SchedulingPublicParticipationSubmitPayload = SchedulingPublicParticipationAccessPayload & {
answers: SchedulingAvailabilityPayload["answers"];
comment?: string | null;
idempotency_key?: string;
};
export type SchedulingCalendarActionResponse = {
request: SchedulingRequest;
created_event_ids: string[];
@@ -239,6 +335,29 @@ export function createSchedulingRequest(settings: ApiSettings, payload: Scheduli
return apiFetch<SchedulingRequest>(settings, "/api/v1/scheduling/requests", json(payload));
}
export function updateSchedulingRequest(
settings: ApiSettings,
requestId: string,
payload: SchedulingRequestUpdatePayload
): Promise<SchedulingRequest> {
return apiFetch<SchedulingRequest>(settings, `/api/v1/scheduling/requests/${requestId}`, {
method: "PATCH",
body: JSON.stringify(payload)
});
}
export function updateSchedulingCandidateSlot(
settings: ApiSettings,
requestId: string,
slotId: string,
payload: SchedulingCandidateSlotUpdatePayload
): Promise<SchedulingRequest> {
return apiFetch<SchedulingRequest>(settings, `/api/v1/scheduling/requests/${requestId}/slots/${slotId}`, {
method: "PATCH",
body: JSON.stringify(payload)
});
}
export function submitSchedulingAvailability(
settings: ApiSettings,
requestId: string,
@@ -261,6 +380,32 @@ export function getSchedulingAvailabilityResponse(
);
}
export function getPublicSchedulingParticipation(
settings: ApiSettings,
requestId: string,
token: string,
payload: SchedulingPublicParticipationAccessPayload
): Promise<SchedulingPublicParticipationResponse> {
return apiFetch<SchedulingPublicParticipationResponse>(
settings,
`/api/v1/scheduling/public/${encodeURIComponent(requestId)}/${encodeURIComponent(token)}`,
json(payload)
);
}
export function submitPublicSchedulingParticipation(
settings: ApiSettings,
requestId: string,
token: string,
payload: SchedulingPublicParticipationSubmitPayload
): Promise<SchedulingPublicParticipationResponse> {
return apiFetch<SchedulingPublicParticipationResponse>(
settings,
`/api/v1/scheduling/public/${encodeURIComponent(requestId)}/${encodeURIComponent(token)}/responses`,
json(payload)
);
}
export function openSchedulingRequest(settings: ApiSettings, requestId: string): Promise<SchedulingStatusResponse> {
return apiFetch<SchedulingStatusResponse>(settings, `/api/v1/scheduling/requests/${requestId}/open`, json({}));
}