feat(scheduling): use central people picker

This commit is contained in:
2026-07-22 03:17:13 +02:00
parent 2cb86c90dc
commit ea2f721377
13 changed files with 529 additions and 231 deletions
@@ -1,8 +1,24 @@
import type {
SchedulingAvailabilityValue,
SchedulingParticipant,
SchedulingParticipantPayload,
SchedulingRequest
} from "../../api/scheduling";
import type { PeoplePickerItem } from "@govoplan/core-webui";
const DIRECTORY_SELECTION_METADATA_KEY = "directory_selection";
export type SchedulingParticipantDraft = PeoplePickerItem & {
draftId: string;
sourceId?: string;
respondent_id?: string | null;
display_name: string;
email: string;
participant_type: "internal" | "external" | "resource";
required: boolean;
metadata?: Record<string, unknown>;
identityLocked?: boolean;
};
export type SchedulingActor = {
accountId?: string | null;
@@ -25,6 +41,106 @@ export type SchedulingSortPhase =
| "determined"
| "past";
type DirectorySelection = {
selection_key?: string;
kind?: PeoplePickerItem["kind"];
reference_id?: string | null;
source_module?: string | null;
source_label?: string | null;
source_revision?: string | null;
};
function directorySelection(metadata?: Record<string, unknown>): DirectorySelection | null {
const value = metadata?.[DIRECTORY_SELECTION_METADATA_KEY];
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
return value as DirectorySelection;
}
function normalizedParticipantType(value: string | null): SchedulingParticipantDraft["participant_type"] {
return value === "internal" || value === "resource" ? value : "external";
}
function selectionMetadata(item: PeoplePickerItem): Record<string, unknown> {
if (item.kind === "external") return {};
return {
[DIRECTORY_SELECTION_METADATA_KEY]: {
selection_key: item.selection_key,
kind: item.kind,
reference_id: item.reference_id ?? null,
source_module: item.source_module ?? null,
source_label: item.source_label ?? null,
source_revision: item.source_revision ?? null
}
};
}
export function participantDraftFromResponse(
participant: SchedulingParticipant,
draftId: string
): SchedulingParticipantDraft {
const selection = directorySelection(participant.metadata);
const kind = selection?.kind ?? (participant.respondent_id ? "account" : "external");
const referenceId = selection?.reference_id
?? (kind === "account" ? participant.respondent_id : null);
const email = participant.email?.trim().toLowerCase() || null;
return {
selection_key: selection?.selection_key
?? (email ? `${kind}:${email}` : `${kind}:participant:${participant.id}`),
kind,
reference_id: referenceId,
display_name: participant.display_name?.trim() || email || "—",
email: email ?? "",
source_module: selection?.source_module ?? null,
source_label: selection?.source_label ?? null,
source_revision: selection?.source_revision ?? null,
draftId,
sourceId: participant.id,
respondent_id: participant.respondent_id,
participant_type: normalizedParticipantType(participant.participant_type),
required: participant.required ?? true,
metadata: participant.metadata ?? {},
identityLocked: Boolean(participant.poll_invitation_id)
};
}
export function participantDraftsFromPicker(
selected: PeoplePickerItem[],
current: SchedulingParticipantDraft[],
nextDraftId: () => string
): SchedulingParticipantDraft[] {
const currentByKey = new Map(current.map((participant) => [participant.selection_key, participant]));
return selected.map((item) => {
const existing = currentByKey.get(item.selection_key);
if (existing) return existing;
const kind = item.kind === "account" ? "account" : item.kind === "contact" ? "contact" : "external";
return {
...item,
kind,
email: item.email?.trim().toLowerCase() || "",
draftId: nextDraftId(),
respondent_id: kind === "account" ? item.reference_id ?? null : null,
participant_type: kind === "account" ? "internal" : "external",
required: true,
metadata: selectionMetadata(item),
identityLocked: false
};
});
}
export function participantPayload(
participant: SchedulingParticipantDraft
): SchedulingParticipantPayload & { id?: string } {
return {
...(participant.sourceId ? { id: participant.sourceId } : {}),
respondent_id: participant.respondent_id ?? null,
display_name: participant.display_name.trim() || null,
email: participant.email.trim() || null,
participant_type: participant.participant_type,
required: participant.required,
metadata: participant.metadata ?? {}
};
}
export function schedulingActorIds(actor: SchedulingActor): string[] {
return Array.from(new Set([
actor.accountId,