@blazeo.com/appointment-client 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blazeo.com-appointment-client-1.0.6.tgz +0 -0
- package/dist/calendar/blazeoCalendarRelationMethods.d.ts +4 -36
- package/dist/calendar/blazeoCalendarRelationMethods.js +0 -1
- package/dist/calendar/buildUnifiedCalendarView.d.ts +31 -0
- package/dist/calendar/buildUnifiedCalendarView.js +265 -0
- package/dist/calendar/calendarCreation.d.ts +27 -0
- package/dist/calendar/calendarCreation.js +167 -0
- package/dist/calendar/calendarCreationFacade.d.ts +4 -13
- package/dist/calendar/calendarCreationFacade.js +3 -5
- package/dist/calendar/createCalendar.d.ts +67 -37
- package/dist/calendar/createCalendar.js +1 -3
- package/dist/calendar/fetchCalendarDetails.d.ts +73 -0
- package/dist/calendar/fetchCalendarDetails.js +192 -0
- package/dist/calendar/fetchCalendarWithOpeningHours.d.ts +34 -21
- package/dist/calendar/fetchCalendarWithOpeningHours.js +95 -75
- package/dist/calendar/getAllParticipantOpeningHours.d.ts +19 -0
- package/dist/calendar/getAllParticipantOpeningHours.js +17 -0
- package/dist/calendar/getOpeningHours.d.ts +5 -0
- package/dist/calendar/getOpeningHours.js +9 -0
- package/dist/calendar/getParticipantOpeningHours.d.ts +37 -0
- package/dist/calendar/getParticipantOpeningHours.js +43 -0
- package/dist/calendar/getParticipants.d.ts +4 -0
- package/dist/calendar/getParticipants.js +8 -0
- package/dist/calendar/mapCalendarBoToBlazeoSnapshot.d.ts +9 -9
- package/dist/calendar/mapCalendarBoToBlazeoSnapshot.js +43 -43
- package/dist/calendar/mapCalendarToBlazeoSnapshot.d.ts +22 -3
- package/dist/calendar/mapCalendarToBlazeoSnapshot.js +0 -1
- package/dist/config/applyBlazeoClientConfig.d.ts +2 -2
- package/dist/config/applyBlazeoClientConfig.js +13 -13
- package/dist/config/applyBlazeoDefaults.d.ts +0 -1
- package/dist/config/applyBlazeoDefaults.js +0 -1
- package/dist/config/blazeo.config.d.ts +10 -10
- package/dist/config/blazeo.config.js +10 -10
- package/dist/config/blazeoClientDefaults.d.ts +1 -2
- package/dist/config/blazeoClientDefaults.js +0 -1
- package/dist/config/initializeAppointmentClient.d.ts +4 -28
- package/dist/config/initializeAppointmentClient.js +5 -24
- package/dist/events/appointmentEventFacade.d.ts +55 -32
- package/dist/events/appointmentEventFacade.js +5 -10
- package/dist/events/mapAppointmentToEventSnapshot.d.ts +1 -4
- package/dist/events/mapAppointmentToEventSnapshot.js +0 -1
- package/dist/exampleData.d.ts +114 -10
- package/dist/exampleData.js +4 -5
- package/dist/facade/calendarCreationFacade.d.ts +39 -39
- package/dist/facade/calendarCreationFacade.js +95 -95
- package/dist/facade/mapCalendarBOToSnapshot.d.ts +9 -9
- package/dist/facade/mapCalendarBOToSnapshot.js +43 -43
- package/dist/facades/index.d.ts +11 -11
- package/dist/facades/index.js +11 -11
- package/dist/index.d.ts +23 -81
- package/dist/index.js +21 -32
- package/dist/models/CalendarRootModel.d.ts +36 -11
- package/dist/models/CalendarRootModel.js +22 -5
- package/dist/models/CalendarSlotModel.d.ts +8 -8
- package/dist/models/CalendarSlotModel.js +7 -7
- package/dist/models/EventModel.d.ts +10 -10
- package/dist/models/EventModel.js +9 -9
- package/dist/models/ParticipantModel.d.ts +8 -8
- package/dist/models/ParticipantModel.js +7 -7
- package/dist/models/index.d.ts +4 -4
- package/dist/models/index.js +4 -4
- package/dist/types/appointment.d.ts +27 -27
- package/dist/types/appointment.js +5 -5
- package/dist/types/calendar.d.ts +51 -51
- package/dist/types/calendar.js +5 -5
- package/dist/types/calendarBo.d.ts +61 -61
- package/dist/types/calendarBo.js +5 -5
- package/package.json +9 -4
- package/sample/build_error.txt +0 -0
- package/sample/demo.js +70 -0
- package/sample/package-lock.json +53 -2
- package/sample/package.json +3 -1
- package/sample/scripts/getInfoByCalendar.mjs +36 -0
- package/sample/scripts/getParticipantOpeningHours.mjs +48 -0
- package/sample/src/AllParticipantOpeningHoursTab.jsx +73 -0
- package/sample/src/App2.jsx +39 -2
- package/sample/src/BlazeoConnectionSettings.jsx +1 -1
- package/sample/src/EventTab.jsx +128 -0
- package/sample/src/FetchCalendarTab.jsx +72 -43
- package/sample/src/OpeningHoursTab.jsx +78 -0
- package/sample/src/ParticipantInfoTab.jsx +72 -0
- package/sample/src/ParticipantOpeningHoursTab.jsx +88 -0
- package/sample/src/ParticipantTab.jsx +2 -2
- package/src/calendar/blazeoCalendarRelationMethods.ts +19 -0
- package/src/calendar/buildUnifiedCalendarView.ts +322 -0
- package/src/calendar/calendarCreation.ts +179 -0
- package/src/calendar/createCalendar.ts +243 -0
- package/src/calendar/fetchCalendarDetails.ts +226 -0
- package/src/calendar/fetchCalendarWithOpeningHours.ts +99 -0
- package/src/calendar/getAllParticipantOpeningHours.ts +22 -0
- package/src/calendar/getOpeningHours.ts +10 -0
- package/src/calendar/getParticipantOpeningHours.ts +46 -0
- package/src/calendar/getParticipants.ts +9 -0
- package/src/calendar/mapCalendarToBlazeoSnapshot.ts +46 -0
- package/src/config/applyBlazeoDefaults.ts +13 -0
- package/src/config/blazeoClientDefaults.ts +11 -0
- package/src/config/initializeAppointmentClient.ts +18 -0
- package/src/events/appointmentEventFacade.ts +148 -0
- package/src/events/mapAppointmentToEventSnapshot.ts +65 -0
- package/src/exampleData.ts +79 -0
- package/src/index.ts +45 -0
- package/src/models/CalendarRootModel.ts +60 -0
- package/tsconfig.json +16 -0
- package/blazeo.com-appointment-client-1.0.4.tgz +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
|
|
2
|
+
|
|
3
|
+
const DAY_NAMES = ["SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY"] as const;
|
|
4
|
+
|
|
5
|
+
function pick<T = unknown>(row: any, ...keys: string[]): T | undefined {
|
|
6
|
+
if (row == null || typeof row !== "object") return undefined;
|
|
7
|
+
for (const k of keys) {
|
|
8
|
+
if (row[k] !== undefined && row[k] !== null) return row[k] as T;
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function toPlain<T>(value: T): T {
|
|
14
|
+
if (value == null) return value;
|
|
15
|
+
if (isStateTreeNode(value)) return getSnapshot(value) as T;
|
|
16
|
+
if (Array.isArray(value)) return value.map((x) => toPlain(x)) as T;
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normParticipantKey(v: string | number | null | undefined): string {
|
|
21
|
+
if (v == null) return "";
|
|
22
|
+
return String(v).trim().toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function coerceMemberId(v: unknown): number | string | null {
|
|
26
|
+
if (v == null || v === "") return null;
|
|
27
|
+
if (typeof v === "number" && !Number.isNaN(v)) return v;
|
|
28
|
+
if (typeof v === "string") {
|
|
29
|
+
const t = v.trim();
|
|
30
|
+
if (/^\d+$/.test(t)) return Number(t);
|
|
31
|
+
return t;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Canonical member id used in both `members[].id` and `openingHours[].member`.
|
|
38
|
+
*/
|
|
39
|
+
function resolveParticipantMemberId(calPart: Record<string, any>): number | string {
|
|
40
|
+
const n = pick<number | null>(calPart, "id", "Id");
|
|
41
|
+
if (n != null && typeof n === "number" && !Number.isNaN(n)) return n;
|
|
42
|
+
const sid = pick<string>(calPart, "participantId", "ParticipantId", "participant_id");
|
|
43
|
+
if (sid != null && String(sid).trim() !== "") {
|
|
44
|
+
const t = String(sid).trim();
|
|
45
|
+
if (/^\d+$/.test(t)) return Number(t);
|
|
46
|
+
return t;
|
|
47
|
+
}
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface UnifiedCalendarMember {
|
|
52
|
+
id: number | string;
|
|
53
|
+
name: string;
|
|
54
|
+
email: string | null;
|
|
55
|
+
/** When API exposes status; otherwise derived from participant info flags. */
|
|
56
|
+
status: number | null;
|
|
57
|
+
/** Full row from Participants/GetInfo (plain object). */
|
|
58
|
+
participantInfo?: Record<string, unknown> | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface UnifiedParticipantWithHours extends UnifiedCalendarMember {
|
|
62
|
+
openingHours: UnifiedOpeningHourRow[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface UnifiedOpeningHourRow {
|
|
66
|
+
member: number | string;
|
|
67
|
+
days: string[];
|
|
68
|
+
startHour: number;
|
|
69
|
+
startMinute: number;
|
|
70
|
+
endHour: number;
|
|
71
|
+
endMinute: number;
|
|
72
|
+
off: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function dayOrderIndex(d: string): number {
|
|
76
|
+
const u = d.toUpperCase();
|
|
77
|
+
const i = DAY_NAMES.indexOf(u as (typeof DAY_NAMES)[number]);
|
|
78
|
+
return i >= 0 ? i : 999;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Merge rows that share participant + time span + off into one row with combined `days`. */
|
|
82
|
+
function mergeOpeningHoursBySlot(rows: UnifiedOpeningHourRow[]) {
|
|
83
|
+
const map = new Map<string, UnifiedOpeningHourRow>();
|
|
84
|
+
for (const r of rows) {
|
|
85
|
+
const key = [r.member, r.startHour, r.startMinute, r.endHour, r.endMinute, r.off].join("|");
|
|
86
|
+
const existing = map.get(key);
|
|
87
|
+
if (!existing) {
|
|
88
|
+
map.set(key, { ...r, days: [...r.days] });
|
|
89
|
+
} else {
|
|
90
|
+
const set = new Set([...existing.days, ...r.days]);
|
|
91
|
+
existing.days = Array.from(set).sort((a, b) => dayOrderIndex(a) - dayOrderIndex(b));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
rows.length = 0;
|
|
95
|
+
rows.push(...map.values());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type UnifiedCalendarView = Record<string, unknown> & {
|
|
99
|
+
members: UnifiedCalendarMember[];
|
|
100
|
+
openingHours: UnifiedOpeningHourRow[];
|
|
101
|
+
participants?: UnifiedParticipantWithHours[];
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
function normalizeDaysFromRow(row: Record<string, any>): string[] {
|
|
105
|
+
const rawDays = pick<any>(row, "days", "Days");
|
|
106
|
+
if (Array.isArray(rawDays)) {
|
|
107
|
+
return rawDays.map((d) => String(d).trim().toUpperCase()).filter(Boolean);
|
|
108
|
+
}
|
|
109
|
+
const dayNum = pick<number>(row, "day", "Day");
|
|
110
|
+
if (dayNum != null && typeof dayNum === "number" && dayNum >= 0 && dayNum <= 6) {
|
|
111
|
+
return [DAY_NAMES[dayNum]];
|
|
112
|
+
}
|
|
113
|
+
const dayStr = pick<string>(row, "dayName", "DayName", "dayOfWeek", "DayOfWeek");
|
|
114
|
+
if (dayStr != null && String(dayStr).trim() !== "") {
|
|
115
|
+
return [String(dayStr).trim().toUpperCase()];
|
|
116
|
+
}
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveOpeningHourMemberId(
|
|
121
|
+
row: Record<string, any>,
|
|
122
|
+
byParticipantKey: Map<string, number | string>,
|
|
123
|
+
byCalendarParticipantKey: Map<string, number | string>
|
|
124
|
+
): number | string | null {
|
|
125
|
+
const calPartHook = pick(
|
|
126
|
+
row,
|
|
127
|
+
"calendarParticipantId",
|
|
128
|
+
"CalendarParticipantId",
|
|
129
|
+
"calendarparticipant_id"
|
|
130
|
+
);
|
|
131
|
+
if (calPartHook != null && String(calPartHook).trim() !== "") {
|
|
132
|
+
const ck = normParticipantKey(String(calPartHook));
|
|
133
|
+
if (byCalendarParticipantKey.has(ck)) return byCalendarParticipantKey.get(ck)!;
|
|
134
|
+
}
|
|
135
|
+
const pid = pick(row, "participantId", "ParticipantId", "participant_id");
|
|
136
|
+
if (pid != null && String(pid).trim() !== "") {
|
|
137
|
+
const k = normParticipantKey(String(pid));
|
|
138
|
+
if (byParticipantKey.has(k)) return byParticipantKey.get(k)!;
|
|
139
|
+
return coerceMemberId(pid);
|
|
140
|
+
}
|
|
141
|
+
const member = pick(row, "member", "Member");
|
|
142
|
+
if (member != null && typeof member === "object") {
|
|
143
|
+
const mid =
|
|
144
|
+
coerceMemberId(pick(member, "id", "Id")) ??
|
|
145
|
+
coerceMemberId(pick(member, "participantId", "ParticipantId", "participant_id"));
|
|
146
|
+
if (mid != null) return mid;
|
|
147
|
+
}
|
|
148
|
+
if (member != null && (typeof member === "number" || typeof member === "string")) {
|
|
149
|
+
const m = coerceMemberId(member);
|
|
150
|
+
if (m != null) {
|
|
151
|
+
const k = normParticipantKey(m);
|
|
152
|
+
if (byParticipantKey.has(k)) return byParticipantKey.get(k)!;
|
|
153
|
+
}
|
|
154
|
+
return m;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function deriveMemberStatus(calPart: Record<string, any>, info: Record<string, any> | undefined): number | null {
|
|
160
|
+
const direct =
|
|
161
|
+
pick<number>(calPart, "status", "Status") ?? pick<number>(info ?? {}, "status", "Status");
|
|
162
|
+
if (direct != null && typeof direct === "number" && !Number.isNaN(direct)) return direct;
|
|
163
|
+
const approved =
|
|
164
|
+
Boolean(pick<boolean>(calPart, "isApproved", "IsApproved", "is_approved")) ||
|
|
165
|
+
Boolean(pick<boolean>(info ?? {}, "isApproved", "IsApproved", "is_approved"));
|
|
166
|
+
if (approved) return approved ? 1 : 0;
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Build a consumer-friendly `{ id, name, members, openingHours }` shape where
|
|
172
|
+
* `openingHours[].member` references `members[].id`.
|
|
173
|
+
*/
|
|
174
|
+
export function buildUnifiedCalendarView(
|
|
175
|
+
calendarSnapshot: Record<string, any> | null,
|
|
176
|
+
openingHoursRows: any[],
|
|
177
|
+
participants: any[] | null | undefined,
|
|
178
|
+
participantsInfo: any[] | null | undefined
|
|
179
|
+
): UnifiedCalendarView | null {
|
|
180
|
+
if (calendarSnapshot == null || typeof calendarSnapshot !== "object") return null;
|
|
181
|
+
|
|
182
|
+
const calSnap = toPlain(calendarSnapshot) as Record<string, any>;
|
|
183
|
+
const partsPlain = participants != null ? participants.map((p) => toPlain(p) as Record<string, any>) : [];
|
|
184
|
+
const infoPlain =
|
|
185
|
+
participantsInfo != null ? participantsInfo.map((p) => toPlain(p) as Record<string, any>) : [];
|
|
186
|
+
|
|
187
|
+
const infoByPid = new Map<string, Record<string, any>>();
|
|
188
|
+
for (const inf of infoPlain) {
|
|
189
|
+
const pid = inf?.participantId ?? inf?.ParticipantId ?? inf?.participant_id;
|
|
190
|
+
if (pid != null && String(pid).trim() !== "") {
|
|
191
|
+
infoByPid.set(normParticipantKey(String(pid)), inf);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const byParticipantKey = new Map<string, number | string>();
|
|
196
|
+
const byCalendarParticipantKey = new Map<string, number | string>();
|
|
197
|
+
const members: UnifiedCalendarMember[] = [];
|
|
198
|
+
|
|
199
|
+
if (partsPlain.length > 0) {
|
|
200
|
+
for (const cp of partsPlain) {
|
|
201
|
+
const memberId = resolveParticipantMemberId(cp);
|
|
202
|
+
const pidStr = pick<string>(cp, "participantId", "ParticipantId", "participant_id") ?? "";
|
|
203
|
+
if (memberId === "" && (!pidStr || pidStr.trim() === "")) continue;
|
|
204
|
+
|
|
205
|
+
const ik = normParticipantKey(pidStr || String(memberId));
|
|
206
|
+
const inf = ik ? infoByPid.get(ik) : undefined;
|
|
207
|
+
const participantInfoPlain = inf != null ? ({ ...toPlain(inf) } as Record<string, unknown>) : null;
|
|
208
|
+
|
|
209
|
+
const alias = inf?.alias ?? inf?.Alias;
|
|
210
|
+
const name =
|
|
211
|
+
typeof alias === "string" && alias.trim() !== ""
|
|
212
|
+
? alias.trim()
|
|
213
|
+
: pidStr && pidStr.trim() !== ""
|
|
214
|
+
? pidStr.trim()
|
|
215
|
+
: String(memberId);
|
|
216
|
+
const email = (inf?.email ?? inf?.Email ?? null) as string | null;
|
|
217
|
+
const id = memberId === "" ? coerceMemberId(pidStr) ?? pidStr : memberId;
|
|
218
|
+
|
|
219
|
+
const calPartPk = pick<string | number>(cp, "calendarParticipantId", "CalendarParticipantId", "calendarparticipant_id");
|
|
220
|
+
const calPartKey = calPartPk != null ? normParticipantKey(String(calPartPk)) : "";
|
|
221
|
+
if (calPartKey) byCalendarParticipantKey.set(calPartKey, id);
|
|
222
|
+
|
|
223
|
+
const keyParticipant = ik || normParticipantKey(String(id));
|
|
224
|
+
if (keyParticipant) {
|
|
225
|
+
byParticipantKey.set(keyParticipant, id);
|
|
226
|
+
byParticipantKey.set(normParticipantKey(String(id)), id);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
members.push({
|
|
230
|
+
id,
|
|
231
|
+
name,
|
|
232
|
+
email: email ?? null,
|
|
233
|
+
status: deriveMemberStatus(cp, inf),
|
|
234
|
+
participantInfo: participantInfoPlain,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
} else if (infoPlain.length > 0) {
|
|
238
|
+
for (const inf of infoPlain) {
|
|
239
|
+
const pid = inf?.participantId ?? inf?.ParticipantId ?? inf?.participant_id;
|
|
240
|
+
if (pid == null || String(pid).trim() === "") continue;
|
|
241
|
+
const id = coerceMemberId(pid) ?? String(pid).trim();
|
|
242
|
+
const ik = normParticipantKey(String(pid));
|
|
243
|
+
const participantInfoPlain = { ...toPlain(inf) } as Record<string, unknown>;
|
|
244
|
+
|
|
245
|
+
if (ik) byParticipantKey.set(ik, id);
|
|
246
|
+
byParticipantKey.set(normParticipantKey(id), id);
|
|
247
|
+
const alias = inf.alias ?? inf.Alias ?? "";
|
|
248
|
+
const name = typeof alias === "string" && alias.trim() !== "" ? alias.trim() : String(pid);
|
|
249
|
+
const email = (inf.email ?? inf.Email ?? null) as string | null;
|
|
250
|
+
members.push({
|
|
251
|
+
id,
|
|
252
|
+
name,
|
|
253
|
+
email: email ?? null,
|
|
254
|
+
status: deriveMemberStatus({}, inf),
|
|
255
|
+
participantInfo: participantInfoPlain,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const rawRows = Array.isArray(openingHoursRows) ? openingHoursRows : [];
|
|
261
|
+
const openingHours: UnifiedOpeningHourRow[] = [];
|
|
262
|
+
|
|
263
|
+
for (const raw of rawRows) {
|
|
264
|
+
const row = toPlain(raw) as Record<string, any>;
|
|
265
|
+
let memberId = resolveOpeningHourMemberId(row, byParticipantKey, byCalendarParticipantKey);
|
|
266
|
+
if (memberId == null) {
|
|
267
|
+
const m = pick(row, "member", "Member");
|
|
268
|
+
memberId = typeof m === "number" || typeof m === "string" ? coerceMemberId(m) : null;
|
|
269
|
+
}
|
|
270
|
+
if (memberId == null) continue;
|
|
271
|
+
|
|
272
|
+
const days = normalizeDaysFromRow(row);
|
|
273
|
+
const startHour = Number(pick(row, "startHour", "StartHour", "start_hour") ?? 0) || 0;
|
|
274
|
+
const startMinute = Number(pick(row, "startMinute", "StartMinute", "start_minute") ?? 0) || 0;
|
|
275
|
+
const endHour = Number(pick(row, "endHour", "EndHour", "end_hour") ?? 0) || 0;
|
|
276
|
+
const endMinute = Number(pick(row, "endMinute", "EndMinute", "end_minute") ?? 0) || 0;
|
|
277
|
+
const off = Boolean(pick(row, "off", "Off"));
|
|
278
|
+
|
|
279
|
+
openingHours.push({
|
|
280
|
+
member: memberId,
|
|
281
|
+
days,
|
|
282
|
+
startHour,
|
|
283
|
+
startMinute,
|
|
284
|
+
endHour,
|
|
285
|
+
endMinute,
|
|
286
|
+
off,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
mergeOpeningHoursBySlot(openingHours);
|
|
291
|
+
|
|
292
|
+
const view = {
|
|
293
|
+
...calSnap,
|
|
294
|
+
members,
|
|
295
|
+
openingHours,
|
|
296
|
+
participants: buildNestedParticipants(members, openingHours),
|
|
297
|
+
} as UnifiedCalendarView;
|
|
298
|
+
|
|
299
|
+
return view;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Groups opening hours into their respective participant objects.
|
|
304
|
+
*/
|
|
305
|
+
function buildNestedParticipants(
|
|
306
|
+
members: UnifiedCalendarMember[],
|
|
307
|
+
openingHours: UnifiedOpeningHourRow[]
|
|
308
|
+
): UnifiedParticipantWithHours[] {
|
|
309
|
+
return members.map((m) => {
|
|
310
|
+
const hours = openingHours.filter((oh) => {
|
|
311
|
+
const mid = String(oh.member).trim().toLowerCase();
|
|
312
|
+
const pid = String(m.id).trim().toLowerCase();
|
|
313
|
+
return mid === pid;
|
|
314
|
+
});
|
|
315
|
+
// Remove the 'member' field from the nested opening hours as it's redundant.
|
|
316
|
+
const nestedHours = hours.map(({ member, ...rest }) => rest as any);
|
|
317
|
+
return {
|
|
318
|
+
...m,
|
|
319
|
+
openingHours: nestedHours,
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { getSnapshot } from "mobx-state-tree";
|
|
2
|
+
import { addParticipantToCalendar, saveCalendarOpeningHour } from "./blazeoCalendarRelationMethods.js";
|
|
3
|
+
import { createCalendarAsync, updateCalendarAsync, deleteCalendarAsync } from "./createCalendar.js";
|
|
4
|
+
|
|
5
|
+
function isFailureStatus(res: any) {
|
|
6
|
+
return res.status !== "success" && res.status !== "Success";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function normalizeParticipantGuid(id: any) {
|
|
10
|
+
if (id == null || !String(id).trim()) return undefined;
|
|
11
|
+
return String(id).trim().replace(/^\{|\}$/g, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function newOpeningHourId() {
|
|
15
|
+
const c = globalThis.crypto;
|
|
16
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
17
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolves Blazeo `participantId` for an opening-hour row: explicit `participantId`,
|
|
22
|
+
* matching `CalendarCreation.SaveOpeningHours`.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveParticipantIdForOpeningHour(openingHour: any) {
|
|
25
|
+
const direct = normalizeParticipantGuid(openingHour.participantId);
|
|
26
|
+
if (direct) return direct;
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function effectiveCalendarId(calendarNode: any, input: any) {
|
|
31
|
+
const snap = getSnapshot(calendarNode) as any;
|
|
32
|
+
const fromNode = snap.calendarId?.trim();
|
|
33
|
+
if (fromNode && fromNode !== "new") return fromNode;
|
|
34
|
+
return (input.calendarId?.trim() || undefined);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Orchestrates the same steps as Apex `CalendarCreation.CreateCalendarAsync`:
|
|
39
|
+
* save calendar (`POST /Calendar/Create`), then add participants, then save opening hours
|
|
40
|
+
* per day (`POST /Calendar/Participant/Availability/OpeningHour/Save`).
|
|
41
|
+
*/
|
|
42
|
+
export async function createCalendarWithRelationsAsync(calendar: any, options: any = {}) {
|
|
43
|
+
const hasMembers = (calendar.members?.length ?? 0) > 0;
|
|
44
|
+
const hasHours = (calendar.openingHours?.length ?? 0) > 0;
|
|
45
|
+
|
|
46
|
+
if (!hasMembers && !hasHours) {
|
|
47
|
+
const r = await createCalendarAsync(calendar, options);
|
|
48
|
+
if (!r.ok) return r;
|
|
49
|
+
return { ...r, membersAdded: 0, openingHoursSaved: 0 };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (options.localOnly) {
|
|
53
|
+
const r = await createCalendarAsync(calendar, options);
|
|
54
|
+
if (!r.ok) return r;
|
|
55
|
+
return { ...r, membersAdded: 0, openingHoursSaved: 0 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const created = await createCalendarAsync(calendar, options);
|
|
59
|
+
if (!created.ok) return created;
|
|
60
|
+
|
|
61
|
+
return runMembersAndOpeningHoursAfterCalendarSave(calendar, created.calendar, created);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* After calendar `create` or `update`, add members and opening hours (same order as Apex facade).
|
|
66
|
+
*/
|
|
67
|
+
async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calendarNode: any, baseSuccess: any) {
|
|
68
|
+
const calendarIdStr = effectiveCalendarId(calendarNode, calendar);
|
|
69
|
+
if (!calendarIdStr) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: "Could not resolve calendar id after save. Ensure the API returned a calendar id.",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let membersAdded = 0;
|
|
77
|
+
for (const m of calendar.members ?? []) {
|
|
78
|
+
const pid = normalizeParticipantGuid(m.id);
|
|
79
|
+
if (!pid) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
error: `Member id ${m.id}: thirdPartyMemberId is required to add a participant.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const res = await addParticipantToCalendar(calendarNode, pid);
|
|
86
|
+
if (isFailureStatus(res)) {
|
|
87
|
+
const msg =
|
|
88
|
+
res.message ??
|
|
89
|
+
(typeof res.data === "string" ? res.data : undefined) ??
|
|
90
|
+
JSON.stringify(res);
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
error: `addParticipant failed for member ${m.id}: ${msg}`,
|
|
94
|
+
apiResponse: res,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
membersAdded += 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let openingHoursSaved = 0;
|
|
101
|
+
for (const oh of calendar.openingHours ?? []) {
|
|
102
|
+
const participantId = resolveParticipantIdForOpeningHour(oh);
|
|
103
|
+
if (!participantId) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
error: `Opening hour id ${oh.id}: participantId is required.`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
for (const day of oh.days ?? []) {
|
|
110
|
+
const payload = {
|
|
111
|
+
calendarId: calendarIdStr,
|
|
112
|
+
participantId,
|
|
113
|
+
day,
|
|
114
|
+
startHour: oh.startHour,
|
|
115
|
+
startMinute: oh.startMinute,
|
|
116
|
+
endHour: oh.endHour,
|
|
117
|
+
endMinute: oh.endMinute,
|
|
118
|
+
off: oh.off,
|
|
119
|
+
openingHourId: oh.openingHourId?.trim() || newOpeningHourId(),
|
|
120
|
+
};
|
|
121
|
+
const res = await saveCalendarOpeningHour(calendarNode, payload);
|
|
122
|
+
if (isFailureStatus(res)) {
|
|
123
|
+
const msg =
|
|
124
|
+
res.message ??
|
|
125
|
+
(typeof res.data === "string" ? res.data : undefined) ??
|
|
126
|
+
JSON.stringify(res);
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: `saveOpeningHour failed (opening hour ${oh.id}, day ${day}): ${msg}`,
|
|
130
|
+
apiResponse: res,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
openingHoursSaved += 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...baseSuccess,
|
|
139
|
+
membersAdded,
|
|
140
|
+
openingHoursSaved,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Calendar body update, then same member + opening-hour saves as create (Apex-style follow-up).
|
|
146
|
+
* For member add/remove *diffs* against existing DB membership, use server-side Apex; this client
|
|
147
|
+
* only performs additive Blazeo calls matching the payload.
|
|
148
|
+
*/
|
|
149
|
+
export async function updateCalendarWithRelationsAsync(calendar: any, options: any = {}) {
|
|
150
|
+
const hasMembers = (calendar.members?.length ?? 0) > 0;
|
|
151
|
+
const hasHours = (calendar.openingHours?.length ?? 0) > 0;
|
|
152
|
+
|
|
153
|
+
if (!hasMembers && !hasHours) {
|
|
154
|
+
const r = await updateCalendarAsync(calendar, options);
|
|
155
|
+
if (!r.ok) return r;
|
|
156
|
+
return { ...r, membersAdded: 0, openingHoursSaved: 0 };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (options.localOnly) {
|
|
160
|
+
const r = await updateCalendarAsync(calendar, options);
|
|
161
|
+
if (!r.ok) return r;
|
|
162
|
+
return { ...r, membersAdded: 0, openingHoursSaved: 0 };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const updated = await updateCalendarAsync(calendar, options);
|
|
166
|
+
if (!updated.ok) return updated;
|
|
167
|
+
|
|
168
|
+
return runMembersAndOpeningHoursAfterCalendarSave(calendar, updated.calendar, updated);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Aligned with `CalendarCreation`: create/update with members & opening hours,
|
|
173
|
+
* or delete calendar only.
|
|
174
|
+
*/
|
|
175
|
+
export class CalendarCreation {
|
|
176
|
+
static createWithRelationsAsync = createCalendarWithRelationsAsync;
|
|
177
|
+
static updateWithRelationsAsync = updateCalendarWithRelationsAsync;
|
|
178
|
+
static deleteCalendarAsync = deleteCalendarAsync;
|
|
179
|
+
}
|