@blazeo.com/appointment-client 1.0.8 → 1.0.10
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.10.tgz +0 -0
- package/dist/calendar/calendarCreation.js +36 -19
- package/dist/calendar/getCalendarsByCompany.d.ts +9 -0
- package/dist/calendar/getCalendarsByCompany.js +107 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +11 -1
- package/package.json +1 -1
- package/sample/src/FetchCalendarTab.jsx +10 -33
- package/src/calendar/calendarCreation.ts +44 -20
- package/src/calendar/getCalendarsByCompany.ts +125 -0
- package/src/index.ts +72 -51
- package/blazeo.com-appointment-client-1.0.8.tgz +0 -0
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSnapshot } from "mobx-state-tree";
|
|
2
|
-
import { addParticipantToCalendar,
|
|
2
|
+
import { addParticipantToCalendar, saveCalendarOpeningHoursBatch } from "./blazeoCalendarRelationMethods.js";
|
|
3
3
|
import { createCalendarAsync, updateCalendarAsync, deleteCalendarAsync } from "./createCalendar.js";
|
|
4
4
|
function isFailureStatus(res) {
|
|
5
5
|
return res.status !== "success" && res.status !== "Success";
|
|
@@ -90,8 +90,10 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar, calendarNode
|
|
|
90
90
|
}
|
|
91
91
|
membersAdded += 1;
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
// 2. Save Opening Hours (Plan V2: Grouped by participant with explicit off-days per slot)
|
|
94
|
+
const openingHours = calendar.openingHours ?? [];
|
|
95
|
+
const hoursByParticipant = new Map();
|
|
96
|
+
for (const oh of openingHours) {
|
|
95
97
|
const participantId = resolveParticipantIdForOpeningHour(oh);
|
|
96
98
|
if (!participantId) {
|
|
97
99
|
return {
|
|
@@ -99,8 +101,17 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar, calendarNode
|
|
|
99
101
|
error: `Opening hour id ${oh.id}: participantId is required.`,
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
if (!hoursByParticipant.has(participantId)) {
|
|
105
|
+
hoursByParticipant.set(participantId, []);
|
|
106
|
+
}
|
|
107
|
+
// Plan V2 Logic: For every opening hour object, generate EXACTLY 7 entries (days 0-6).
|
|
108
|
+
// If the day is in oh.days, it's ON. If not, it's OFF.
|
|
109
|
+
const activeDays = oh.days ?? [];
|
|
110
|
+
const openingHourId = oh.openingHourId?.trim() || newOpeningHourId();
|
|
111
|
+
for (let day = 0; day <= 6; day++) {
|
|
112
|
+
const isIncluded = activeDays.includes(day);
|
|
113
|
+
const isOff = isIncluded ? !!oh.off : true; // If not in days array, it's explicitly OFF
|
|
114
|
+
hoursByParticipant.get(participantId)?.push({
|
|
104
115
|
calendarId: calendarIdStr,
|
|
105
116
|
participantId,
|
|
106
117
|
day,
|
|
@@ -108,22 +119,28 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar, calendarNode
|
|
|
108
119
|
startMinute: oh.startMinute,
|
|
109
120
|
endHour: oh.endHour,
|
|
110
121
|
endMinute: oh.endMinute,
|
|
111
|
-
off:
|
|
112
|
-
openingHourId:
|
|
122
|
+
off: isOff,
|
|
123
|
+
openingHourId: openingHourId,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
let openingHoursSaved = 0;
|
|
128
|
+
for (const [participantId, payload] of hoursByParticipant.entries()) {
|
|
129
|
+
if (payload.length === 0)
|
|
130
|
+
continue;
|
|
131
|
+
// Use the batch save method (plural)
|
|
132
|
+
const res = await saveCalendarOpeningHoursBatch(calendarNode, payload);
|
|
133
|
+
if (isFailureStatus(res)) {
|
|
134
|
+
const msg = res.message ??
|
|
135
|
+
(typeof res.data === "string" ? res.data : undefined) ??
|
|
136
|
+
JSON.stringify(res);
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
error: `saveOpeningHours batch failed for participant ${participantId}: ${msg}`,
|
|
140
|
+
apiResponse: res,
|
|
113
141
|
};
|
|
114
|
-
const res = await saveCalendarOpeningHour(calendarNode, payload);
|
|
115
|
-
if (isFailureStatus(res)) {
|
|
116
|
-
const msg = res.message ??
|
|
117
|
-
(typeof res.data === "string" ? res.data : undefined) ??
|
|
118
|
-
JSON.stringify(res);
|
|
119
|
-
return {
|
|
120
|
-
ok: false,
|
|
121
|
-
error: `saveOpeningHour failed (opening hour ${oh.id}, day ${day}): ${msg}`,
|
|
122
|
-
apiResponse: res,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
openingHoursSaved += 1;
|
|
126
142
|
}
|
|
143
|
+
openingHoursSaved += payload.length;
|
|
127
144
|
}
|
|
128
145
|
return {
|
|
129
146
|
...baseSuccess,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches all calendars for a company and populates each with its members (participants).
|
|
3
|
+
* Fetches both Participant List and Participant Info to ensure names and emails are included,
|
|
4
|
+
* while still skipping heavy data like opening hours.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getCalendarsByCompany(companyKey: string, connection?: {
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
consumer?: string;
|
|
9
|
+
}): Promise<any[]>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { CalendarModel, CalendarParticipantModel } from "@blazeo.com/calendar-client";
|
|
2
|
+
import { ensureBlazeoHttpReady } from "../config/ensureBlazeoHttpReady.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fetches all calendars for a company and populates each with its members (participants).
|
|
5
|
+
* Fetches both Participant List and Participant Info to ensure names and emails are included,
|
|
6
|
+
* while still skipping heavy data like opening hours.
|
|
7
|
+
*/
|
|
8
|
+
export async function getCalendarsByCompany(companyKey, connection = {}) {
|
|
9
|
+
const ready = ensureBlazeoHttpReady(connection);
|
|
10
|
+
if (!ready.ok) {
|
|
11
|
+
throw new Error(ready.error);
|
|
12
|
+
}
|
|
13
|
+
// 1. Get all calendars for the company
|
|
14
|
+
const result = await CalendarModel.getByCompany(companyKey);
|
|
15
|
+
const calendars = Array.isArray(result) ? result : result?.calendars ?? [];
|
|
16
|
+
if (!calendars || calendars.length === 0) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
// 2. Fetch lightweight members for each calendar in parallel
|
|
20
|
+
const enrichedCalendars = await Promise.all(calendars.map(async (cal) => {
|
|
21
|
+
const calendarId = cal.calendarId ?? String(cal.id ?? "");
|
|
22
|
+
if (!calendarId)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
// We need both List and Info to get the names/emails
|
|
26
|
+
const [partsRaw, infoRaw] = await Promise.all([
|
|
27
|
+
CalendarParticipantModel.getByCalendar(calendarId),
|
|
28
|
+
CalendarParticipantModel.getInfoByCalendar(calendarId)
|
|
29
|
+
]);
|
|
30
|
+
const parts = Array.isArray(partsRaw) ? partsRaw : partsRaw?.participants ?? [];
|
|
31
|
+
const info = Array.isArray(infoRaw) ? infoRaw : infoRaw?.info ?? [];
|
|
32
|
+
// Merge logic to ensure names are matched to IDs
|
|
33
|
+
const membersMap = new Map();
|
|
34
|
+
// Use participantId GUID as the primary key
|
|
35
|
+
const getAnyId = (obj) => obj.participantId ?? obj.ParticipantId ?? obj.participant_id ?? obj.id ?? obj.Id;
|
|
36
|
+
// 1. Initialize with basic participant data
|
|
37
|
+
parts.forEach((p) => {
|
|
38
|
+
const mid = getAnyId(p);
|
|
39
|
+
if (mid) {
|
|
40
|
+
membersMap.set(String(mid).toLowerCase(), {
|
|
41
|
+
id: mid,
|
|
42
|
+
name: p.name ?? p.Name ?? "Member",
|
|
43
|
+
email: p.email ?? p.Email,
|
|
44
|
+
status: p.status ?? p.Status ?? 1,
|
|
45
|
+
uuId: mid
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// 2. Enrich with detailed info (Name, Email, Alias)
|
|
50
|
+
info.forEach((i) => {
|
|
51
|
+
const mid = getAnyId(i);
|
|
52
|
+
if (!mid)
|
|
53
|
+
return;
|
|
54
|
+
const key = String(mid).toLowerCase();
|
|
55
|
+
const existing = membersMap.get(key);
|
|
56
|
+
const resolvedEmail = i.email ?? i.Email ?? i.userSsoEmail ?? i.UserSsoEmail ?? existing?.email;
|
|
57
|
+
const memberData = {
|
|
58
|
+
id: mid,
|
|
59
|
+
name: i.name ?? i.Name ?? i.alias ?? i.Alias ?? (existing?.name || "Member"),
|
|
60
|
+
email: resolvedEmail,
|
|
61
|
+
alias: i.alias ?? i.Alias ?? i.name ?? i.Name,
|
|
62
|
+
userSsoEmail: resolvedEmail,
|
|
63
|
+
uuId: mid,
|
|
64
|
+
status: i.status ?? i.Status ?? existing?.status ?? 1
|
|
65
|
+
};
|
|
66
|
+
if (!existing) {
|
|
67
|
+
membersMap.set(key, memberData);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
Object.assign(existing, memberData);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const members = Array.from(membersMap.values());
|
|
74
|
+
// Map to the EXACT schema requested by the user
|
|
75
|
+
return {
|
|
76
|
+
id: cal.id ?? cal.Id,
|
|
77
|
+
calendarLink: cal.calendarLink ?? cal.CalendarLink ?? "",
|
|
78
|
+
uuid: calendarId,
|
|
79
|
+
createdOn: cal.createdOn ?? cal.CreatedOn,
|
|
80
|
+
name: cal.name ?? cal.Name,
|
|
81
|
+
timeZoneId: cal.timeZoneId ?? cal.TimeZoneId,
|
|
82
|
+
description: cal.description ?? cal.Description ?? "",
|
|
83
|
+
assignmentType: cal.assignmentMethod ?? cal.AssignmentMethod ?? cal.assignmentType,
|
|
84
|
+
status: cal.status ?? cal.Status ?? 1,
|
|
85
|
+
location: cal.location ?? cal.Location ?? "",
|
|
86
|
+
members
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
console.error(`[getCalendarsByCompany] Error fetching members for ${calendarId}:`, err);
|
|
91
|
+
return {
|
|
92
|
+
id: cal.id ?? cal.Id,
|
|
93
|
+
calendarLink: cal.calendarLink ?? cal.CalendarLink ?? "",
|
|
94
|
+
uuid: calendarId,
|
|
95
|
+
createdOn: cal.createdOn ?? cal.CreatedOn,
|
|
96
|
+
name: cal.name ?? cal.Name,
|
|
97
|
+
timeZoneId: cal.timeZoneId ?? cal.TimeZoneId,
|
|
98
|
+
description: cal.description ?? cal.Description ?? "",
|
|
99
|
+
assignmentType: cal.assignmentMethod ?? cal.AssignmentMethod ?? cal.assignmentType,
|
|
100
|
+
status: cal.status ?? cal.Status ?? 1,
|
|
101
|
+
location: cal.location ?? cal.Location ?? "",
|
|
102
|
+
members: []
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}));
|
|
106
|
+
return enrichedCalendars.filter(c => c !== null);
|
|
107
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { blazeoClientConfig } from "./config/blazeoClientDefaults.js";
|
|
|
6
6
|
export { applyBlazeoClientConfig } from "./config/applyBlazeoDefaults.js";
|
|
7
7
|
export { createCalendarRoot, CalendarRootModel, CalendarSlotModel, EventModel, ParticipantModel } from "./models/CalendarRootModel.js";
|
|
8
8
|
export { fetchCalendarDetails, fetchCalendarBundle, normalizeOpeningHours } from "./calendar/fetchCalendarDetails.js";
|
|
9
|
+
export { getCalendarsByCompany } from "./calendar/getCalendarsByCompany.js";
|
|
9
10
|
export { buildUnifiedCalendarView, type UnifiedCalendarMember, type UnifiedCalendarView, type UnifiedOpeningHourRow, type UnifiedParticipantWithHours, } from "./calendar/buildUnifiedCalendarView.js";
|
|
10
11
|
export { fetchCalendarWithOpeningHours, unwrapCalendarGetData, pickOpeningHoursArrayFromCalendarPayload, normalizeParticipantOpeningHoursResponse } from "./calendar/fetchCalendarWithOpeningHours.js";
|
|
11
12
|
export { getOpeningHours } from "./calendar/getOpeningHours.js";
|
|
@@ -18,7 +19,8 @@ export { CalendarCreation, createCalendarWithRelationsAsync, updateCalendarWithR
|
|
|
18
19
|
export { addParticipantToCalendar, removeParticipantFromCalendar, saveCalendarOpeningHour, saveCalendarOpeningHoursBatch } from "./calendar/blazeoCalendarRelationMethods.js";
|
|
19
20
|
export { createAppointmentEventAsync, rescheduleAppointmentEventAsync, cancelAppointmentEventAsync } from "./events/appointmentEventFacade.js";
|
|
20
21
|
export { mapAppointmentToEventSnapshot } from "./events/mapAppointmentToEventSnapshot.js";
|
|
21
|
-
|
|
22
|
+
import { CalendarModel as CoreCalendarModel, EventModel as CoreEventModel, ParticipantModel as CoreParticipantModel, CalendarParticipantModel as CoreCalendarParticipantModel, configure, getConfig } from "@blazeo.com/calendar-client";
|
|
23
|
+
export { CoreCalendarModel as CalendarModel, CoreEventModel as CoreEventModel, CoreParticipantModel as CoreParticipantModel, CoreCalendarParticipantModel as CalendarParticipantModel, configure, getConfig };
|
|
22
24
|
export declare const packageName = "@blazeo.com/appointment-client";
|
|
23
25
|
export declare class CalendarClient {
|
|
24
26
|
name: string;
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { blazeoClientConfig } from "./config/blazeoClientDefaults.js";
|
|
|
6
6
|
export { applyBlazeoClientConfig } from "./config/applyBlazeoDefaults.js";
|
|
7
7
|
export { createCalendarRoot, CalendarRootModel, CalendarSlotModel, EventModel, ParticipantModel } from "./models/CalendarRootModel.js";
|
|
8
8
|
export { fetchCalendarDetails, fetchCalendarBundle, normalizeOpeningHours } from "./calendar/fetchCalendarDetails.js";
|
|
9
|
+
export { getCalendarsByCompany } from "./calendar/getCalendarsByCompany.js";
|
|
9
10
|
export { buildUnifiedCalendarView, } from "./calendar/buildUnifiedCalendarView.js";
|
|
10
11
|
export { fetchCalendarWithOpeningHours, unwrapCalendarGetData, pickOpeningHoursArrayFromCalendarPayload, normalizeParticipantOpeningHoursResponse } from "./calendar/fetchCalendarWithOpeningHours.js";
|
|
11
12
|
export { getOpeningHours } from "./calendar/getOpeningHours.js";
|
|
@@ -19,7 +20,16 @@ export { CalendarCreation, createCalendarWithRelationsAsync, updateCalendarWithR
|
|
|
19
20
|
export { addParticipantToCalendar, removeParticipantFromCalendar, saveCalendarOpeningHour, saveCalendarOpeningHoursBatch } from "./calendar/blazeoCalendarRelationMethods.js";
|
|
20
21
|
export { createAppointmentEventAsync, rescheduleAppointmentEventAsync, cancelAppointmentEventAsync } from "./events/appointmentEventFacade.js";
|
|
21
22
|
export { mapAppointmentToEventSnapshot } from "./events/mapAppointmentToEventSnapshot.js";
|
|
22
|
-
|
|
23
|
+
import { fetchCalendarDetails, fetchCalendarBundle } from "./calendar/fetchCalendarDetails.js";
|
|
24
|
+
import { fetchCalendarWithOpeningHours } from "./calendar/fetchCalendarWithOpeningHours.js";
|
|
25
|
+
import { getCalendarsByCompany } from "./calendar/getCalendarsByCompany.js";
|
|
26
|
+
import { CalendarModel as CoreCalendarModel, EventModel as CoreEventModel, ParticipantModel as CoreParticipantModel, CalendarParticipantModel as CoreCalendarParticipantModel, configure, getConfig } from "@blazeo.com/calendar-client";
|
|
27
|
+
// Attach new methods to CalendarModel for easier access
|
|
28
|
+
CoreCalendarModel.fetchCalendarDetails = fetchCalendarDetails;
|
|
29
|
+
CoreCalendarModel.fetchCalendarBundle = fetchCalendarBundle;
|
|
30
|
+
CoreCalendarModel.fetchCalendarWithOpeningHours = fetchCalendarWithOpeningHours;
|
|
31
|
+
CoreCalendarModel.getCalendarsByCompany = getCalendarsByCompany;
|
|
32
|
+
export { CoreCalendarModel as CalendarModel, CoreEventModel as CoreEventModel, CoreParticipantModel as CoreParticipantModel, CoreCalendarParticipantModel as CalendarParticipantModel, configure, getConfig };
|
|
23
33
|
export const packageName = "@blazeo.com/appointment-client";
|
|
24
34
|
export class CalendarClient {
|
|
25
35
|
name = "CalendarClient";
|
package/package.json
CHANGED
|
@@ -291,41 +291,18 @@ export function FetchCalendarTab() {
|
|
|
291
291
|
|
|
292
292
|
setBusy(true);
|
|
293
293
|
try {
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
294
|
+
// Use the new optimized method that includes members automatically
|
|
295
|
+
const enriched = await CalendarModel.getCalendarsByCompany(key, {
|
|
296
|
+
baseUrl: effective.baseUrl,
|
|
297
|
+
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (!enriched || enriched.length === 0) {
|
|
301
|
+
setNote("getCalendarsByCompany returned an empty list.");
|
|
302
|
+
setOutput("[]");
|
|
301
303
|
} else {
|
|
302
|
-
const enriched = await Promise.all(
|
|
303
|
-
list.map(async (c) => {
|
|
304
|
-
const id = c.calendarId ?? String(c.id ?? "");
|
|
305
|
-
if (!id) return { calendar: getSnapshot(c), openingHours: [], meta: { error: "no id" } };
|
|
306
|
-
try {
|
|
307
|
-
// fetchCalendarDetails now returns the flat unified view directly
|
|
308
|
-
const b = await fetchCalendarDetails(id, {
|
|
309
|
-
...connectionOpts,
|
|
310
|
-
baseUrl: effective.baseUrl,
|
|
311
|
-
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
312
|
-
});
|
|
313
|
-
// b IS the unified calendarView: members/openingHours/participants at top level
|
|
314
|
-
return b ?? { calendar: getSnapshot(c), openingHours: [], meta: { error: "null response" } };
|
|
315
|
-
} catch (err) {
|
|
316
|
-
return {
|
|
317
|
-
calendar: getSnapshot(c),
|
|
318
|
-
openingHours: [],
|
|
319
|
-
meta: {
|
|
320
|
-
error: err instanceof Error ? err.message : String(err),
|
|
321
|
-
},
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
})
|
|
325
|
-
);
|
|
326
|
-
const total = byCompany?.totalCount ?? list.length;
|
|
327
304
|
setNote(
|
|
328
|
-
`Loaded ${
|
|
305
|
+
`Loaded ${enriched.length} calendar(s) with members using optimized getCalendarsByCompany.`
|
|
329
306
|
);
|
|
330
307
|
setOutput(toDisplayJson(enriched));
|
|
331
308
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSnapshot } from "mobx-state-tree";
|
|
2
|
-
import { addParticipantToCalendar, saveCalendarOpeningHour } from "./blazeoCalendarRelationMethods.js";
|
|
2
|
+
import { addParticipantToCalendar, saveCalendarOpeningHour, saveCalendarOpeningHoursBatch } from "./blazeoCalendarRelationMethods.js";
|
|
3
3
|
import { createCalendarAsync, updateCalendarAsync, deleteCalendarAsync } from "./createCalendar.js";
|
|
4
4
|
|
|
5
5
|
function isFailureStatus(res: any) {
|
|
@@ -97,8 +97,11 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
|
|
|
97
97
|
membersAdded += 1;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// 2. Save Opening Hours (Plan V2: Grouped by participant with explicit off-days per slot)
|
|
101
|
+
const openingHours = calendar.openingHours ?? [];
|
|
102
|
+
const hoursByParticipant = new Map<string, any[]>();
|
|
103
|
+
|
|
104
|
+
for (const oh of openingHours) {
|
|
102
105
|
const participantId = resolveParticipantIdForOpeningHour(oh);
|
|
103
106
|
if (!participantId) {
|
|
104
107
|
return {
|
|
@@ -106,8 +109,21 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
|
|
|
106
109
|
error: `Opening hour id ${oh.id}: participantId is required.`,
|
|
107
110
|
};
|
|
108
111
|
}
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
|
|
113
|
+
if (!hoursByParticipant.has(participantId)) {
|
|
114
|
+
hoursByParticipant.set(participantId, []);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Plan V2 Logic: For every opening hour object, generate EXACTLY 7 entries (days 0-6).
|
|
118
|
+
// If the day is in oh.days, it's ON. If not, it's OFF.
|
|
119
|
+
const activeDays = oh.days ?? [];
|
|
120
|
+
const openingHourId = oh.openingHourId?.trim() || newOpeningHourId();
|
|
121
|
+
|
|
122
|
+
for (let day = 0; day <= 6; day++) {
|
|
123
|
+
const isIncluded = activeDays.includes(day);
|
|
124
|
+
const isOff = isIncluded ? !!oh.off : true; // If not in days array, it's explicitly OFF
|
|
125
|
+
|
|
126
|
+
hoursByParticipant.get(participantId)?.push({
|
|
111
127
|
calendarId: calendarIdStr,
|
|
112
128
|
participantId,
|
|
113
129
|
day,
|
|
@@ -115,23 +131,31 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
|
|
|
115
131
|
startMinute: oh.startMinute,
|
|
116
132
|
endHour: oh.endHour,
|
|
117
133
|
endMinute: oh.endMinute,
|
|
118
|
-
off:
|
|
119
|
-
openingHourId:
|
|
134
|
+
off: isOff,
|
|
135
|
+
openingHourId: openingHourId,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let openingHoursSaved = 0;
|
|
141
|
+
for (const [participantId, payload] of hoursByParticipant.entries()) {
|
|
142
|
+
if (payload.length === 0) continue;
|
|
143
|
+
|
|
144
|
+
// Use the batch save method (plural)
|
|
145
|
+
const res = await saveCalendarOpeningHoursBatch(calendarNode, payload);
|
|
146
|
+
|
|
147
|
+
if (isFailureStatus(res)) {
|
|
148
|
+
const msg =
|
|
149
|
+
res.message ??
|
|
150
|
+
(typeof res.data === "string" ? res.data : undefined) ??
|
|
151
|
+
JSON.stringify(res);
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
error: `saveOpeningHours batch failed for participant ${participantId}: ${msg}`,
|
|
155
|
+
apiResponse: res,
|
|
120
156
|
};
|
|
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
157
|
}
|
|
158
|
+
openingHoursSaved += payload.length;
|
|
135
159
|
}
|
|
136
160
|
|
|
137
161
|
return {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { CalendarModel, CalendarParticipantModel } from "@blazeo.com/calendar-client";
|
|
2
|
+
import { ensureBlazeoHttpReady } from "../config/ensureBlazeoHttpReady.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fetches all calendars for a company and populates each with its members (participants).
|
|
6
|
+
* Fetches both Participant List and Participant Info to ensure names and emails are included,
|
|
7
|
+
* while still skipping heavy data like opening hours.
|
|
8
|
+
*/
|
|
9
|
+
export async function getCalendarsByCompany(
|
|
10
|
+
companyKey: string,
|
|
11
|
+
connection: { baseUrl?: string; consumer?: string } = {}
|
|
12
|
+
) {
|
|
13
|
+
const ready = ensureBlazeoHttpReady(connection);
|
|
14
|
+
if (!ready.ok) {
|
|
15
|
+
throw new Error(ready.error);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 1. Get all calendars for the company
|
|
19
|
+
const result = await CalendarModel.getByCompany(companyKey);
|
|
20
|
+
const calendars = Array.isArray(result) ? result : (result as any)?.calendars ?? [];
|
|
21
|
+
|
|
22
|
+
if (!calendars || calendars.length === 0) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. Fetch lightweight members for each calendar in parallel
|
|
27
|
+
const enrichedCalendars = await Promise.all(
|
|
28
|
+
calendars.map(async (cal: any) => {
|
|
29
|
+
const calendarId = cal.calendarId ?? String(cal.id ?? "");
|
|
30
|
+
if (!calendarId) return null;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// We need both List and Info to get the names/emails
|
|
34
|
+
const [partsRaw, infoRaw] = await Promise.all([
|
|
35
|
+
CalendarParticipantModel.getByCalendar(calendarId),
|
|
36
|
+
CalendarParticipantModel.getInfoByCalendar(calendarId)
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const parts = Array.isArray(partsRaw) ? partsRaw : (partsRaw as any)?.participants ?? [];
|
|
40
|
+
const info = Array.isArray(infoRaw) ? infoRaw : (infoRaw as any)?.info ?? [];
|
|
41
|
+
|
|
42
|
+
// Merge logic to ensure names are matched to IDs
|
|
43
|
+
const membersMap = new Map<string, any>();
|
|
44
|
+
|
|
45
|
+
// Use participantId GUID as the primary key
|
|
46
|
+
const getAnyId = (obj: any) =>
|
|
47
|
+
obj.participantId ?? obj.ParticipantId ?? obj.participant_id ?? obj.id ?? obj.Id;
|
|
48
|
+
|
|
49
|
+
// 1. Initialize with basic participant data
|
|
50
|
+
parts.forEach((p: any) => {
|
|
51
|
+
const mid = getAnyId(p);
|
|
52
|
+
if (mid) {
|
|
53
|
+
membersMap.set(String(mid).toLowerCase(), {
|
|
54
|
+
id: mid,
|
|
55
|
+
name: p.name ?? p.Name ?? "Member",
|
|
56
|
+
email: p.email ?? p.Email,
|
|
57
|
+
status: p.status ?? p.Status ?? 1,
|
|
58
|
+
uuId: mid
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 2. Enrich with detailed info (Name, Email, Alias)
|
|
64
|
+
info.forEach((i: any) => {
|
|
65
|
+
const mid = getAnyId(i);
|
|
66
|
+
if (!mid) return;
|
|
67
|
+
const key = String(mid).toLowerCase();
|
|
68
|
+
const existing = membersMap.get(key);
|
|
69
|
+
|
|
70
|
+
const resolvedEmail = i.email ?? i.Email ?? i.userSsoEmail ?? i.UserSsoEmail ?? existing?.email;
|
|
71
|
+
|
|
72
|
+
const memberData = {
|
|
73
|
+
id: mid,
|
|
74
|
+
name: i.name ?? i.Name ?? i.alias ?? i.Alias ?? (existing?.name || "Member"),
|
|
75
|
+
email: resolvedEmail,
|
|
76
|
+
alias: i.alias ?? i.Alias ?? i.name ?? i.Name,
|
|
77
|
+
userSsoEmail: resolvedEmail,
|
|
78
|
+
uuId: mid,
|
|
79
|
+
status: i.status ?? i.Status ?? existing?.status ?? 1
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (!existing) {
|
|
83
|
+
membersMap.set(key, memberData);
|
|
84
|
+
} else {
|
|
85
|
+
Object.assign(existing, memberData);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const members = Array.from(membersMap.values());
|
|
90
|
+
|
|
91
|
+
// Map to the EXACT schema requested by the user
|
|
92
|
+
return {
|
|
93
|
+
id: cal.id ?? cal.Id,
|
|
94
|
+
calendarLink: cal.calendarLink ?? cal.CalendarLink ?? "",
|
|
95
|
+
uuid: calendarId,
|
|
96
|
+
createdOn: cal.createdOn ?? cal.CreatedOn,
|
|
97
|
+
name: cal.name ?? cal.Name,
|
|
98
|
+
timeZoneId: cal.timeZoneId ?? cal.TimeZoneId,
|
|
99
|
+
description: cal.description ?? cal.Description ?? "",
|
|
100
|
+
assignmentType: cal.assignmentMethod ?? cal.AssignmentMethod ?? cal.assignmentType,
|
|
101
|
+
status: cal.status ?? cal.Status ?? 1,
|
|
102
|
+
location: cal.location ?? cal.Location ?? "",
|
|
103
|
+
members
|
|
104
|
+
};
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error(`[getCalendarsByCompany] Error fetching members for ${calendarId}:`, err);
|
|
107
|
+
return {
|
|
108
|
+
id: cal.id ?? cal.Id,
|
|
109
|
+
calendarLink: cal.calendarLink ?? cal.CalendarLink ?? "",
|
|
110
|
+
uuid: calendarId,
|
|
111
|
+
createdOn: cal.createdOn ?? cal.CreatedOn,
|
|
112
|
+
name: cal.name ?? cal.Name,
|
|
113
|
+
timeZoneId: cal.timeZoneId ?? cal.TimeZoneId,
|
|
114
|
+
description: cal.description ?? cal.Description ?? "",
|
|
115
|
+
assignmentType: cal.assignmentMethod ?? cal.AssignmentMethod ?? cal.assignmentType,
|
|
116
|
+
status: cal.status ?? cal.Status ?? 1,
|
|
117
|
+
location: cal.location ?? cal.Location ?? "",
|
|
118
|
+
members: []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return enrichedCalendars.filter(c => c !== null);
|
|
125
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,51 +1,72 @@
|
|
|
1
|
-
import { getExampleSlots } from "./exampleData.js";
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
initializeAppointmentClient,
|
|
5
|
-
isAppointmentClientConfigured,
|
|
6
|
-
} from "./config/initializeAppointmentClient.js";
|
|
7
|
-
export { syncBlazeoConnection } from "./config/syncBlazeoConnection.js";
|
|
8
|
-
export { ensureBlazeoHttpReady } from "./config/ensureBlazeoHttpReady.js";
|
|
9
|
-
export type { EnsureBlazeoHttpOptions } from "./config/ensureBlazeoHttpReady.js";
|
|
10
|
-
export { blazeoClientConfig } from "./config/blazeoClientDefaults.js";
|
|
11
|
-
export { applyBlazeoClientConfig } from "./config/applyBlazeoDefaults.js";
|
|
12
|
-
export { createCalendarRoot, CalendarRootModel, CalendarSlotModel, EventModel, ParticipantModel } from "./models/CalendarRootModel.js";
|
|
13
|
-
export { fetchCalendarDetails, fetchCalendarBundle, normalizeOpeningHours } from "./calendar/fetchCalendarDetails.js";
|
|
14
|
-
export {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export
|
|
30
|
-
export {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
import { getExampleSlots } from "./exampleData.js";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
initializeAppointmentClient,
|
|
5
|
+
isAppointmentClientConfigured,
|
|
6
|
+
} from "./config/initializeAppointmentClient.js";
|
|
7
|
+
export { syncBlazeoConnection } from "./config/syncBlazeoConnection.js";
|
|
8
|
+
export { ensureBlazeoHttpReady } from "./config/ensureBlazeoHttpReady.js";
|
|
9
|
+
export type { EnsureBlazeoHttpOptions } from "./config/ensureBlazeoHttpReady.js";
|
|
10
|
+
export { blazeoClientConfig } from "./config/blazeoClientDefaults.js";
|
|
11
|
+
export { applyBlazeoClientConfig } from "./config/applyBlazeoDefaults.js";
|
|
12
|
+
export { createCalendarRoot, CalendarRootModel, CalendarSlotModel, EventModel, ParticipantModel } from "./models/CalendarRootModel.js";
|
|
13
|
+
export { fetchCalendarDetails, fetchCalendarBundle, normalizeOpeningHours } from "./calendar/fetchCalendarDetails.js";
|
|
14
|
+
export { getCalendarsByCompany } from "./calendar/getCalendarsByCompany.js";
|
|
15
|
+
export {
|
|
16
|
+
buildUnifiedCalendarView,
|
|
17
|
+
type UnifiedCalendarMember,
|
|
18
|
+
type UnifiedCalendarView,
|
|
19
|
+
type UnifiedOpeningHourRow,
|
|
20
|
+
type UnifiedParticipantWithHours,
|
|
21
|
+
} from "./calendar/buildUnifiedCalendarView.js";
|
|
22
|
+
export { fetchCalendarWithOpeningHours, unwrapCalendarGetData, pickOpeningHoursArrayFromCalendarPayload, normalizeParticipantOpeningHoursResponse } from "./calendar/fetchCalendarWithOpeningHours.js";
|
|
23
|
+
export { getOpeningHours } from "./calendar/getOpeningHours.js";
|
|
24
|
+
export { getParticipantOpeningHours } from "./calendar/getParticipantOpeningHours.js";
|
|
25
|
+
export { getAllParticipantOpeningHours } from "./calendar/getAllParticipantOpeningHours.js";
|
|
26
|
+
export { getParticipants } from "./calendar/getParticipants.js";
|
|
27
|
+
export { getExampleSlots, getExampleEvents, getExampleParticipants, getExampleCalendarRoot, getExampleCalendarRootSnapshot } from "./exampleData.js";
|
|
28
|
+
|
|
29
|
+
// Re-export core models from calendar-client for convenience
|
|
30
|
+
export { createCalendarAsync, updateCalendarAsync, deleteCalendarAsync, resolveBlazeoConnection } from "./calendar/createCalendar.js";
|
|
31
|
+
export { CalendarCreation, createCalendarWithRelationsAsync, updateCalendarWithRelationsAsync, resolveParticipantIdForOpeningHour } from "./calendar/calendarCreation.js";
|
|
32
|
+
export { addParticipantToCalendar, removeParticipantFromCalendar, saveCalendarOpeningHour, saveCalendarOpeningHoursBatch } from "./calendar/blazeoCalendarRelationMethods.js";
|
|
33
|
+
export { createAppointmentEventAsync, rescheduleAppointmentEventAsync, cancelAppointmentEventAsync } from "./events/appointmentEventFacade.js";
|
|
34
|
+
export { mapAppointmentToEventSnapshot } from "./events/mapAppointmentToEventSnapshot.js";
|
|
35
|
+
|
|
36
|
+
import { fetchCalendarDetails, fetchCalendarBundle } from "./calendar/fetchCalendarDetails.js";
|
|
37
|
+
import { fetchCalendarWithOpeningHours } from "./calendar/fetchCalendarWithOpeningHours.js";
|
|
38
|
+
import { getCalendarsByCompany } from "./calendar/getCalendarsByCompany.js";
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
CalendarModel as CoreCalendarModel,
|
|
42
|
+
EventModel as CoreEventModel,
|
|
43
|
+
ParticipantModel as CoreParticipantModel,
|
|
44
|
+
CalendarParticipantModel as CoreCalendarParticipantModel,
|
|
45
|
+
configure,
|
|
46
|
+
getConfig
|
|
47
|
+
} from "@blazeo.com/calendar-client";
|
|
48
|
+
|
|
49
|
+
// Attach new methods to CalendarModel for easier access
|
|
50
|
+
(CoreCalendarModel as any).fetchCalendarDetails = fetchCalendarDetails;
|
|
51
|
+
(CoreCalendarModel as any).fetchCalendarBundle = fetchCalendarBundle;
|
|
52
|
+
(CoreCalendarModel as any).fetchCalendarWithOpeningHours = fetchCalendarWithOpeningHours;
|
|
53
|
+
(CoreCalendarModel as any).getCalendarsByCompany = getCalendarsByCompany;
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
CoreCalendarModel as CalendarModel,
|
|
57
|
+
CoreEventModel as CoreEventModel,
|
|
58
|
+
CoreParticipantModel as CoreParticipantModel,
|
|
59
|
+
CoreCalendarParticipantModel as CalendarParticipantModel,
|
|
60
|
+
configure,
|
|
61
|
+
getConfig
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
export const packageName = "@blazeo.com/appointment-client";
|
|
66
|
+
|
|
67
|
+
export class CalendarClient {
|
|
68
|
+
name = "CalendarClient";
|
|
69
|
+
getExampleSlots() {
|
|
70
|
+
return getExampleSlots();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
Binary file
|