489 lines
16 KiB
TypeScript
489 lines
16 KiB
TypeScript
import {
|
|
apiFetch,
|
|
type ApiSettings,
|
|
type PeoplePickerSearchGroup
|
|
} from "@govoplan/core-webui";
|
|
|
|
export type SchedulingStatus = "draft" | "collecting" | "closed" | "decided" | "handed_off" | "cancelled" | "archived";
|
|
export type SchedulingParticipantVisibility = "aggregates_only" | "names_and_statuses";
|
|
|
|
export type SchedulingCandidateSlot = {
|
|
id: string;
|
|
poll_option_id?: string | null;
|
|
label: string;
|
|
description?: string | null;
|
|
start_at: string;
|
|
end_at: string;
|
|
timezone: string;
|
|
location?: string | null;
|
|
position: number;
|
|
revision: string;
|
|
freebusy_checked_at?: string | null;
|
|
freebusy_status?: string | null;
|
|
freebusy_conflicts: Record<string, unknown>[];
|
|
tentative_hold_event_id?: string | null;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export type SchedulingParticipant = {
|
|
id: string;
|
|
revision?: string | null;
|
|
is_current_participant: boolean;
|
|
respondent_id?: string | null;
|
|
display_name?: string | null;
|
|
email?: string | null;
|
|
participant_type: string | null;
|
|
required: boolean | null;
|
|
status: string;
|
|
poll_invitation_id?: string | null;
|
|
invitation_token?: string | null;
|
|
last_invited_at?: string | null;
|
|
responded_at?: string | null;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export type SchedulingParticipantAggregate = {
|
|
total: number;
|
|
status_counts: Record<string, number>;
|
|
};
|
|
|
|
export type SchedulingParticipantVisibilityDecision = {
|
|
requested_visibility: SchedulingParticipantVisibility;
|
|
effective_visibility: SchedulingParticipantVisibility;
|
|
policy_applied: boolean;
|
|
reason?: string | null;
|
|
source_path: Record<string, unknown>[];
|
|
details: Record<string, unknown>;
|
|
};
|
|
|
|
export type SchedulingRequest = {
|
|
id: string;
|
|
tenant_id: string | null;
|
|
title: string;
|
|
description?: string | null;
|
|
location?: string | null;
|
|
timezone: string;
|
|
status: SchedulingStatus;
|
|
poll_id?: string | null;
|
|
selected_slot_id?: string | null;
|
|
organizer_user_id?: string | null;
|
|
deadline_at?: string | null;
|
|
allow_external_participants: boolean;
|
|
allow_participant_updates: boolean;
|
|
result_visibility: string;
|
|
participant_visibility: SchedulingParticipantVisibility;
|
|
effective_participant_visibility: SchedulingParticipantVisibility;
|
|
participant_aggregate: SchedulingParticipantAggregate;
|
|
participant_visibility_decision: SchedulingParticipantVisibilityDecision;
|
|
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;
|
|
participant_invitation_delivery_available: boolean | null;
|
|
calendar_integration_enabled: boolean | null;
|
|
calendar_id?: string | null;
|
|
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;
|
|
cancellation_notice_until?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
metadata?: Record<string, unknown>;
|
|
slots: SchedulingCandidateSlot[];
|
|
participants: SchedulingParticipant[];
|
|
};
|
|
|
|
export type SchedulingRequestListResponse = { requests: SchedulingRequest[] };
|
|
export type SchedulingStatusResponse = { request: SchedulingRequest };
|
|
|
|
export type SchedulingCandidateSlotPayload = {
|
|
label?: string | null;
|
|
description?: string | null;
|
|
start_at: string;
|
|
end_at: string;
|
|
timezone?: string | null;
|
|
location?: string | null;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export type SchedulingParticipantPayload = {
|
|
respondent_id?: string | null;
|
|
display_name?: string | null;
|
|
email?: string | null;
|
|
participant_type?: "internal" | "external" | "resource";
|
|
required?: boolean;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export type SchedulingRequestCreatePayload = {
|
|
title: string;
|
|
description?: string | null;
|
|
location?: string | null;
|
|
timezone?: string;
|
|
status?: "draft" | "collecting";
|
|
deadline_at?: string | null;
|
|
allow_external_participants?: boolean;
|
|
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;
|
|
freebusy_enabled?: boolean;
|
|
tentative_holds_enabled?: boolean;
|
|
create_event_on_decision?: boolean;
|
|
};
|
|
slots: SchedulingCandidateSlotPayload[];
|
|
participants?: SchedulingParticipantPayload[];
|
|
create_participant_invitations?: boolean;
|
|
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;
|
|
revision?: 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;
|
|
handoff_to_calendar?: boolean | null;
|
|
};
|
|
|
|
export type SchedulingAvailabilityValue = "available" | "maybe" | "unavailable";
|
|
|
|
export type SchedulingAvailabilityPayload = {
|
|
answers: Array<{
|
|
slot_id: string;
|
|
value: SchedulingAvailabilityValue;
|
|
option_revision: string;
|
|
}>;
|
|
comment?: string | null;
|
|
};
|
|
|
|
export type SchedulingAvailabilityResponse = {
|
|
request_id: string;
|
|
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;
|
|
cancelled_at?: string | null;
|
|
cancellation_notice_until?: string | null;
|
|
cancellation_notice_only: boolean;
|
|
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[];
|
|
updated_slot_ids: string[];
|
|
warnings: string[];
|
|
};
|
|
|
|
export type SchedulingNotification = {
|
|
id: string;
|
|
request_id: string;
|
|
participant_id?: string | null;
|
|
event_kind: string;
|
|
channel: string;
|
|
recipient?: string | null;
|
|
status: string;
|
|
payload: Record<string, unknown>;
|
|
error?: string | null;
|
|
sent_at?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export type SchedulingNotificationListResponse = { notifications: SchedulingNotification[] };
|
|
|
|
export type SchedulingInvitationAction = "copy" | "send" | "revoke";
|
|
|
|
export type SchedulingInvitationActionResponse = {
|
|
participant_id: string;
|
|
action: SchedulingInvitationAction;
|
|
status: string;
|
|
action_url?: string | null;
|
|
issued_at?: string | null;
|
|
replayed: boolean;
|
|
notification?: SchedulingNotification | null;
|
|
};
|
|
|
|
export type SchedulingPollOptionResult = {
|
|
option_id: string;
|
|
option_key: string;
|
|
label: string;
|
|
count: number;
|
|
score: number;
|
|
values: Record<string, number>;
|
|
};
|
|
|
|
export type SchedulingSummaryResponse = {
|
|
request: SchedulingRequest;
|
|
poll_summary: {
|
|
poll_id: string;
|
|
kind: string;
|
|
status: string;
|
|
response_count: number;
|
|
option_results: SchedulingPollOptionResult[];
|
|
leading_option_ids: string[];
|
|
};
|
|
};
|
|
|
|
export type SchedulingPeopleSearchResponse = {
|
|
groups: PeoplePickerSearchGroup[];
|
|
};
|
|
|
|
const json = (payload: unknown) => ({ method: "POST", body: JSON.stringify(payload ?? {}) });
|
|
|
|
export async function searchSchedulingPeople(
|
|
settings: ApiSettings,
|
|
query: string,
|
|
limit = 25,
|
|
signal?: AbortSignal
|
|
): Promise<PeoplePickerSearchGroup[]> {
|
|
const params = new URLSearchParams({ query, limit: String(limit) });
|
|
const response = await apiFetch<SchedulingPeopleSearchResponse>(
|
|
settings,
|
|
`/api/v1/scheduling/people?${params.toString()}`,
|
|
{ signal }
|
|
);
|
|
return response.groups;
|
|
}
|
|
|
|
export function listSchedulingRequests(settings: ApiSettings, status?: string): Promise<SchedulingRequestListResponse> {
|
|
const query = status ? `?status=${encodeURIComponent(status)}` : "";
|
|
return apiFetch<SchedulingRequestListResponse>(settings, `/api/v1/scheduling/requests${query}`);
|
|
}
|
|
|
|
export function createSchedulingRequest(settings: ApiSettings, payload: SchedulingRequestCreatePayload): Promise<SchedulingRequest> {
|
|
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,
|
|
payload: SchedulingAvailabilityPayload
|
|
): Promise<SchedulingStatusResponse> {
|
|
return apiFetch<SchedulingStatusResponse>(
|
|
settings,
|
|
`/api/v1/scheduling/requests/${requestId}/responses`,
|
|
json(payload)
|
|
);
|
|
}
|
|
|
|
export function getSchedulingAvailabilityResponse(
|
|
settings: ApiSettings,
|
|
requestId: string
|
|
): Promise<SchedulingAvailabilityResponse> {
|
|
return apiFetch<SchedulingAvailabilityResponse>(
|
|
settings,
|
|
`/api/v1/scheduling/requests/${requestId}/responses/me`
|
|
);
|
|
}
|
|
|
|
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({}));
|
|
}
|
|
|
|
export function closeSchedulingRequest(settings: ApiSettings, requestId: string): Promise<SchedulingStatusResponse> {
|
|
return apiFetch<SchedulingStatusResponse>(settings, `/api/v1/scheduling/requests/${requestId}/close`, json({}));
|
|
}
|
|
|
|
export function decideSchedulingRequest(settings: ApiSettings, requestId: string, payload: SchedulingDecisionPayload): Promise<SchedulingStatusResponse> {
|
|
return apiFetch<SchedulingStatusResponse>(settings, `/api/v1/scheduling/requests/${requestId}/decide`, json(payload));
|
|
}
|
|
|
|
export function evaluateSchedulingFreeBusy(settings: ApiSettings, requestId: string): Promise<SchedulingCalendarActionResponse> {
|
|
return apiFetch<SchedulingCalendarActionResponse>(settings, `/api/v1/scheduling/requests/${requestId}/calendar/freebusy`, json({}));
|
|
}
|
|
|
|
export function createSchedulingHolds(settings: ApiSettings, requestId: string): Promise<SchedulingCalendarActionResponse> {
|
|
return apiFetch<SchedulingCalendarActionResponse>(settings, `/api/v1/scheduling/requests/${requestId}/calendar/holds`, json({}));
|
|
}
|
|
|
|
export function createSchedulingCalendarEvent(settings: ApiSettings, requestId: string): Promise<SchedulingCalendarActionResponse> {
|
|
return apiFetch<SchedulingCalendarActionResponse>(settings, `/api/v1/scheduling/requests/${requestId}/calendar/event`, json({}));
|
|
}
|
|
|
|
export function schedulingSummary(settings: ApiSettings, requestId: string): Promise<SchedulingSummaryResponse> {
|
|
return apiFetch<SchedulingSummaryResponse>(settings, `/api/v1/scheduling/requests/${requestId}/summary`);
|
|
}
|
|
|
|
export function createSchedulingNotifications(settings: ApiSettings, requestId: string, eventKind: string): Promise<SchedulingNotificationListResponse> {
|
|
return apiFetch<SchedulingNotificationListResponse>(settings, `/api/v1/scheduling/requests/${requestId}/notifications`, json({ event_kind: eventKind }));
|
|
}
|
|
|
|
export function listSchedulingNotifications(settings: ApiSettings, requestId?: string): Promise<SchedulingNotificationListResponse> {
|
|
const query = requestId ? `?request_id=${encodeURIComponent(requestId)}` : "";
|
|
return apiFetch<SchedulingNotificationListResponse>(settings, `/api/v1/scheduling/notifications${query}`);
|
|
}
|
|
|
|
export function issueSchedulingParticipantInvitation(
|
|
settings: ApiSettings,
|
|
requestId: string,
|
|
participantId: string,
|
|
participantRevision: string,
|
|
action: Exclude<SchedulingInvitationAction, "revoke">
|
|
): Promise<SchedulingInvitationActionResponse> {
|
|
return apiFetch<SchedulingInvitationActionResponse>(
|
|
settings,
|
|
`/api/v1/scheduling/requests/${encodeURIComponent(requestId)}/participants/${encodeURIComponent(participantId)}/invitation`,
|
|
json({ action, participant_revision: participantRevision })
|
|
);
|
|
}
|
|
|
|
export function revokeSchedulingParticipantInvitation(
|
|
settings: ApiSettings,
|
|
requestId: string,
|
|
participantId: string,
|
|
participantRevision: string
|
|
): Promise<SchedulingInvitationActionResponse> {
|
|
return apiFetch<SchedulingInvitationActionResponse>(
|
|
settings,
|
|
`/api/v1/scheduling/requests/${encodeURIComponent(requestId)}/participants/${encodeURIComponent(participantId)}/invitation`,
|
|
{ method: "DELETE", body: JSON.stringify({ participant_revision: participantRevision }) }
|
|
);
|
|
}
|