@blazeo.com/appointment-client 1.0.11 → 1.0.14
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.14.tgz +0 -0
- package/package.json +3 -2
- package/sample/src/EventTab.jsx +435 -395
- package/sample/src/FetchCalendarTab.jsx +19 -33
- package/sample/src/LeadTab.jsx +335 -0
- package/src/calendar/calendarCreation.ts +62 -15
- package/src/calendar/fetchCalendarDetails.ts +10 -2
- package/src/calendar/getCalendarsByCompany.ts +17 -64
- package/src/calendar/mapToDesiredResponse.ts +31 -17
- package/src/events/getAppointmentsByFilter.ts +98 -0
- package/src/index.ts +23 -14
- package/src/lead/fetchLeadDetails.ts +106 -0
- package/blazeo.com-appointment-client-1.0.11.tgz +0 -0
- package/dist/calendar/blazeoCalendarRelationMethods.d.ts +0 -8
- package/dist/calendar/blazeoCalendarRelationMethods.d.ts.map +0 -1
- package/dist/calendar/blazeoCalendarRelationMethods.js +0 -16
- package/dist/calendar/blazeoCalendarRelationMethods.js.map +0 -1
- package/dist/calendar/buildUnifiedCalendarView.d.ts +0 -39
- package/dist/calendar/buildUnifiedCalendarView.js +0 -301
- package/dist/calendar/calendarCreation.d.ts +0 -27
- package/dist/calendar/calendarCreation.js +0 -191
- package/dist/calendar/calendarCreationFacade.d.ts +0 -27
- package/dist/calendar/calendarCreationFacade.d.ts.map +0 -1
- package/dist/calendar/calendarCreationFacade.js +0 -167
- package/dist/calendar/calendarCreationFacade.js.map +0 -1
- package/dist/calendar/createCalendar.d.ts +0 -81
- package/dist/calendar/createCalendar.d.ts.map +0 -1
- package/dist/calendar/createCalendar.js +0 -206
- package/dist/calendar/createCalendar.js.map +0 -1
- package/dist/calendar/fetchCalendarDetails.d.ts +0 -41
- package/dist/calendar/fetchCalendarDetails.js +0 -262
- package/dist/calendar/fetchCalendarWithOpeningHours.d.ts +0 -25
- package/dist/calendar/fetchCalendarWithOpeningHours.js +0 -114
- package/dist/calendar/getAllParticipantOpeningHours.d.ts +0 -22
- package/dist/calendar/getAllParticipantOpeningHours.js +0 -22
- package/dist/calendar/getCalendarsByCompany.d.ts +0 -9
- package/dist/calendar/getCalendarsByCompany.js +0 -107
- package/dist/calendar/getOpeningHours.d.ts +0 -8
- package/dist/calendar/getOpeningHours.js +0 -9
- package/dist/calendar/getParticipantOpeningHours.d.ts +0 -37
- package/dist/calendar/getParticipantOpeningHours.js +0 -48
- package/dist/calendar/getParticipants.d.ts +0 -7
- package/dist/calendar/getParticipants.js +0 -13
- package/dist/calendar/mapCalendarBoToBlazeoSnapshot.d.ts +0 -10
- package/dist/calendar/mapCalendarBoToBlazeoSnapshot.d.ts.map +0 -1
- package/dist/calendar/mapCalendarBoToBlazeoSnapshot.js +0 -44
- package/dist/calendar/mapCalendarBoToBlazeoSnapshot.js.map +0 -1
- package/dist/calendar/mapCalendarToBlazeoSnapshot.d.ts +0 -29
- package/dist/calendar/mapCalendarToBlazeoSnapshot.d.ts.map +0 -1
- package/dist/calendar/mapCalendarToBlazeoSnapshot.js +0 -44
- package/dist/calendar/mapCalendarToBlazeoSnapshot.js.map +0 -1
- package/dist/calendar/mapToDesiredResponse.d.ts +0 -70
- package/dist/calendar/mapToDesiredResponse.js +0 -99
- package/dist/config/applyBlazeoClientConfig.d.ts +0 -3
- package/dist/config/applyBlazeoClientConfig.d.ts.map +0 -1
- package/dist/config/applyBlazeoClientConfig.js +0 -14
- package/dist/config/applyBlazeoClientConfig.js.map +0 -1
- package/dist/config/applyBlazeoDefaults.d.ts +0 -2
- package/dist/config/applyBlazeoDefaults.d.ts.map +0 -1
- package/dist/config/applyBlazeoDefaults.js +0 -14
- package/dist/config/applyBlazeoDefaults.js.map +0 -1
- package/dist/config/blazeo.config.d.ts +0 -11
- package/dist/config/blazeo.config.d.ts.map +0 -1
- package/dist/config/blazeo.config.js +0 -11
- package/dist/config/blazeo.config.js.map +0 -1
- package/dist/config/blazeoClientDefaults.d.ts +0 -11
- package/dist/config/blazeoClientDefaults.d.ts.map +0 -1
- package/dist/config/blazeoClientDefaults.js +0 -11
- package/dist/config/blazeoClientDefaults.js.map +0 -1
- package/dist/config/ensureBlazeoHttpReady.d.ts +0 -17
- package/dist/config/ensureBlazeoHttpReady.js +0 -31
- package/dist/config/initializeAppointmentClient.d.ts +0 -11
- package/dist/config/initializeAppointmentClient.d.ts.map +0 -1
- package/dist/config/initializeAppointmentClient.js +0 -15
- package/dist/config/initializeAppointmentClient.js.map +0 -1
- package/dist/config/syncBlazeoConnection.d.ts +0 -6
- package/dist/config/syncBlazeoConnection.js +0 -18
- package/dist/events/appointmentEventFacade.d.ts +0 -67
- package/dist/events/appointmentEventFacade.d.ts.map +0 -1
- package/dist/events/appointmentEventFacade.js +0 -124
- package/dist/events/appointmentEventFacade.js.map +0 -1
- package/dist/events/mapAppointmentToEventSnapshot.d.ts +0 -5
- package/dist/events/mapAppointmentToEventSnapshot.d.ts.map +0 -1
- package/dist/events/mapAppointmentToEventSnapshot.js +0 -57
- package/dist/events/mapAppointmentToEventSnapshot.js.map +0 -1
- package/dist/exampleData.d.ts +0 -119
- package/dist/exampleData.d.ts.map +0 -1
- package/dist/exampleData.js +0 -71
- package/dist/exampleData.js.map +0 -1
- package/dist/facade/calendarCreationFacade.d.ts +0 -40
- package/dist/facade/calendarCreationFacade.d.ts.map +0 -1
- package/dist/facade/calendarCreationFacade.js +0 -96
- package/dist/facade/calendarCreationFacade.js.map +0 -1
- package/dist/facade/mapCalendarBOToSnapshot.d.ts +0 -10
- package/dist/facade/mapCalendarBOToSnapshot.d.ts.map +0 -1
- package/dist/facade/mapCalendarBOToSnapshot.js +0 -44
- package/dist/facade/mapCalendarBOToSnapshot.js.map +0 -1
- package/dist/facades/index.d.ts +0 -12
- package/dist/facades/index.d.ts.map +0 -1
- package/dist/facades/index.js +0 -12
- package/dist/facades/index.js.map +0 -1
- package/dist/index.d.ts +0 -40
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -39
- package/dist/index.js.map +0 -1
- package/dist/models/CalendarRootModel.d.ts +0 -56
- package/dist/models/CalendarRootModel.d.ts.map +0 -1
- package/dist/models/CalendarRootModel.js +0 -54
- package/dist/models/CalendarRootModel.js.map +0 -1
- package/dist/models/CalendarSlotModel.d.ts +0 -9
- package/dist/models/CalendarSlotModel.d.ts.map +0 -1
- package/dist/models/CalendarSlotModel.js +0 -8
- package/dist/models/CalendarSlotModel.js.map +0 -1
- package/dist/models/EventModel.d.ts +0 -11
- package/dist/models/EventModel.d.ts.map +0 -1
- package/dist/models/EventModel.js +0 -10
- package/dist/models/EventModel.js.map +0 -1
- package/dist/models/ParticipantModel.d.ts +0 -9
- package/dist/models/ParticipantModel.d.ts.map +0 -1
- package/dist/models/ParticipantModel.js +0 -8
- package/dist/models/ParticipantModel.js.map +0 -1
- package/dist/models/index.d.ts +0 -5
- package/dist/models/index.d.ts.map +0 -1
- package/dist/models/index.js +0 -5
- package/dist/models/index.js.map +0 -1
- package/dist/types/appointment.d.ts +0 -28
- package/dist/types/appointment.d.ts.map +0 -1
- package/dist/types/appointment.js +0 -6
- package/dist/types/appointment.js.map +0 -1
- package/dist/types/calendar.d.ts +0 -52
- package/dist/types/calendar.d.ts.map +0 -1
- package/dist/types/calendar.js +0 -6
- package/dist/types/calendar.js.map +0 -1
- package/dist/types/calendarBo.d.ts +0 -62
- package/dist/types/calendarBo.d.ts.map +0 -1
- package/dist/types/calendarBo.js +0 -6
- package/dist/types/calendarBo.js.map +0 -1
- package/sample/package-lock.json +0 -1778
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
2
|
import {
|
|
3
|
+
CalendarCreation,
|
|
3
4
|
CalendarModel,
|
|
4
5
|
deleteCalendarAsync,
|
|
5
6
|
ensureBlazeoHttpReady,
|
|
6
|
-
fetchCalendarDetails,
|
|
7
7
|
updateCalendarAsync,
|
|
8
8
|
} from "appointment-client";
|
|
9
9
|
import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
|
|
@@ -229,40 +229,24 @@ export function FetchCalendarTab() {
|
|
|
229
229
|
|
|
230
230
|
setBusy(true);
|
|
231
231
|
try {
|
|
232
|
-
const
|
|
232
|
+
const result = await CalendarModel.fetchCalendarDetails(id, {
|
|
233
233
|
...connectionOpts,
|
|
234
234
|
baseUrl: effective.baseUrl,
|
|
235
235
|
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
setNote("CalendarModel.get returned null. Showing CalendarModel.getRaw only.");
|
|
251
|
-
setOutput(toDisplayJson(raw));
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const snap = details._cal ? getSnapshot(details._cal) : null;
|
|
256
|
-
if (snap) {
|
|
257
|
-
setLastFetchUpdatePayload(JSON.stringify(calendarSnapshotToUpdatePayload(snap), null, 2));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// If it's the new flat response, just use it directly for output.
|
|
261
|
-
setOutput(toDisplayJson(details));
|
|
262
|
-
|
|
263
|
-
// We can still try to extract meta for the UI if needed
|
|
264
|
-
if (meta) {
|
|
265
|
-
setNote(`Source: ${meta.calendarViewUsedAllParticipantOpeningHours ? "AllParticipantOpeningHours" : "Embedded/ParticipantApi"}`);
|
|
238
|
+
if (result) {
|
|
239
|
+
setOutput(toDisplayJson(result));
|
|
240
|
+
setNote(`CalendarModel.fetchCalendarDetails → Enriched Unified View (ID: ${id})`);
|
|
241
|
+
setUpdateJson(toDisplayJson(result));
|
|
242
|
+
|
|
243
|
+
const snap = result._cal ? getSnapshot(result._cal) : null;
|
|
244
|
+
if (snap) {
|
|
245
|
+
setLastFetchUpdatePayload(JSON.stringify(calendarSnapshotToUpdatePayload(snap), null, 2));
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
setNote(`Calendar not found or error fetching details for ID: ${id}`);
|
|
249
|
+
setOutput("{}");
|
|
266
250
|
}
|
|
267
251
|
} catch (err) {
|
|
268
252
|
setError(explainFetchFailure(err, effective.baseUrl));
|
|
@@ -360,17 +344,19 @@ export function FetchCalendarTab() {
|
|
|
360
344
|
}
|
|
361
345
|
setBusy(true);
|
|
362
346
|
try {
|
|
363
|
-
const result = await
|
|
347
|
+
const result = await CalendarCreation.updateWithRelationsAsync(payload, {
|
|
364
348
|
...connectionOpts,
|
|
365
349
|
baseUrl: effective.baseUrl,
|
|
366
350
|
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
367
351
|
});
|
|
368
352
|
if (result.ok) {
|
|
369
|
-
setMutateNote("
|
|
353
|
+
setMutateNote("updateWithRelationsAsync → POST /Calendar/Event/Update + Batch Opening Hours");
|
|
370
354
|
setMutateOutput(
|
|
371
355
|
JSON.stringify(
|
|
372
356
|
{
|
|
373
|
-
snapshot: getSnapshot(result.calendar),
|
|
357
|
+
snapshot: result.calendar ? getSnapshot(result.calendar) : null,
|
|
358
|
+
membersAdded: result.membersAdded,
|
|
359
|
+
openingHoursSaved: result.openingHoursSaved,
|
|
374
360
|
apiResponse: result.apiResponse ?? null,
|
|
375
361
|
},
|
|
376
362
|
null,
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ensureBlazeoHttpReady,
|
|
4
|
+
fetchLeadByEmail,
|
|
5
|
+
fetchLeadDetails,
|
|
6
|
+
fetchLeadsByCompany,
|
|
7
|
+
} from "appointment-client";
|
|
8
|
+
import {
|
|
9
|
+
configureBlazeoFromEffective,
|
|
10
|
+
useBlazeoConnection,
|
|
11
|
+
} from "./BlazeoConnectionSettings.jsx";
|
|
12
|
+
import { mapBlazeoDemoError } from "./blazeoDemoError.js";
|
|
13
|
+
|
|
14
|
+
/** Browser `fetch` often surfaces blocked requests as TypeError "Failed to fetch" (e.g. CORS). */
|
|
15
|
+
function explainFetchFailure(err, configuredBaseUrl) {
|
|
16
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17
|
+
const isNetwork =
|
|
18
|
+
msg === "Failed to fetch" ||
|
|
19
|
+
msg === "Load failed" ||
|
|
20
|
+
(err instanceof TypeError && (/fetch/i.test(msg) || /network/i.test(msg)));
|
|
21
|
+
if (!isNetwork) return mapBlazeoDemoError(msg);
|
|
22
|
+
|
|
23
|
+
const isRemote =
|
|
24
|
+
configuredBaseUrl &&
|
|
25
|
+
/^https?:\/\//i.test(configuredBaseUrl) &&
|
|
26
|
+
!/localhost|127\.0\.0\.1/i.test(configuredBaseUrl);
|
|
27
|
+
|
|
28
|
+
const proxyHint =
|
|
29
|
+
"Dev workaround (Vite proxy): create sample/.env.development with\n" +
|
|
30
|
+
" VITE_DEV_PROXY_TARGET=https://YOUR_API_ORIGIN\n" +
|
|
31
|
+
"restart npm run dev, then set Base URL to:\n" +
|
|
32
|
+
" http://localhost:5173/blazeo-api\n" +
|
|
33
|
+
"Consumer header stays the same.";
|
|
34
|
+
|
|
35
|
+
if (isRemote) {
|
|
36
|
+
return `${msg}\n\nLikely CORS / blocked browser cross-origin request.\nFix API CORS for http://localhost:5173, OR:\n${proxyHint}`;
|
|
37
|
+
}
|
|
38
|
+
return `${msg}\n\n${proxyHint}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function toDisplayJson(value) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.stringify(value, null, 2);
|
|
44
|
+
} catch {
|
|
45
|
+
return String(value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function LeadTab() {
|
|
50
|
+
const { effective, connectionOpts } = useBlazeoConnection();
|
|
51
|
+
const [leadId, setLeadId] = useState("");
|
|
52
|
+
const [email, setEmail] = useState("");
|
|
53
|
+
const [companyKey, setCompanyKey] = useState("");
|
|
54
|
+
const [listCompanyKey, setListCompanyKey] = useState("");
|
|
55
|
+
const [skip, setSkip] = useState("");
|
|
56
|
+
const [take, setTake] = useState("");
|
|
57
|
+
const [busy, setBusy] = useState(false);
|
|
58
|
+
const [error, setError] = useState("");
|
|
59
|
+
const [note, setNote] = useState("");
|
|
60
|
+
const [output, setOutput] = useState("");
|
|
61
|
+
|
|
62
|
+
function ensureBaseConfigured() {
|
|
63
|
+
if (!effective.baseUrl) {
|
|
64
|
+
setError("Set Base URL in the connection card above or in `blazeoClientDefaults.ts`.");
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function handleFetchById(e) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
setError("");
|
|
73
|
+
setNote("");
|
|
74
|
+
setOutput("");
|
|
75
|
+
const id = leadId.trim();
|
|
76
|
+
if (!id) {
|
|
77
|
+
setError("Enter a lead id.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!ensureBaseConfigured()) return;
|
|
81
|
+
configureBlazeoFromEffective(effective);
|
|
82
|
+
ensureBlazeoHttpReady({
|
|
83
|
+
baseUrl: effective.baseUrl,
|
|
84
|
+
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
setBusy(true);
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetchLeadDetails(id, {
|
|
90
|
+
...connectionOpts,
|
|
91
|
+
baseUrl: effective.baseUrl,
|
|
92
|
+
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
93
|
+
});
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
setError(mapBlazeoDemoError(res.detail ?? ""));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
setNote("LeadModel.get + getRaw → GET /lead/get (lead mapped snapshot + raw envelope).");
|
|
99
|
+
setOutput(toDisplayJson({ lead: res.lead, rawGet: res.rawGet }));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
setError(explainFetchFailure(err, effective.baseUrl));
|
|
102
|
+
} finally {
|
|
103
|
+
setBusy(false);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function handleFetchByEmail(e) {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
setError("");
|
|
110
|
+
setNote("");
|
|
111
|
+
setOutput("");
|
|
112
|
+
const em = email.trim();
|
|
113
|
+
const ck = companyKey.trim();
|
|
114
|
+
if (!em || !ck) {
|
|
115
|
+
setError("Enter email and company key.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (!ensureBaseConfigured()) return;
|
|
119
|
+
configureBlazeoFromEffective(effective);
|
|
120
|
+
ensureBlazeoHttpReady({
|
|
121
|
+
baseUrl: effective.baseUrl,
|
|
122
|
+
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
setBusy(true);
|
|
126
|
+
try {
|
|
127
|
+
const res = await fetchLeadByEmail(em, ck, {
|
|
128
|
+
...connectionOpts,
|
|
129
|
+
baseUrl: effective.baseUrl,
|
|
130
|
+
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
131
|
+
});
|
|
132
|
+
if (!res.ok) {
|
|
133
|
+
setError(mapBlazeoDemoError(res.detail ?? ""));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setNote("LeadModel.getByEmail → GET /lead/getbyemail.");
|
|
137
|
+
setOutput(toDisplayJson({ lead: res.lead }));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
setError(explainFetchFailure(err, effective.baseUrl));
|
|
140
|
+
} finally {
|
|
141
|
+
setBusy(false);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function handleFetchByCompany(e) {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
setError("");
|
|
148
|
+
setNote("");
|
|
149
|
+
setOutput("");
|
|
150
|
+
const ck = listCompanyKey.trim();
|
|
151
|
+
if (!ck) {
|
|
152
|
+
setError("Enter company key for the list.");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (!ensureBaseConfigured()) return;
|
|
156
|
+
configureBlazeoFromEffective(effective);
|
|
157
|
+
ensureBlazeoHttpReady({
|
|
158
|
+
baseUrl: effective.baseUrl,
|
|
159
|
+
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const listOpts = {};
|
|
163
|
+
const s = skip.trim();
|
|
164
|
+
const t = take.trim();
|
|
165
|
+
if (s !== "") {
|
|
166
|
+
const n = Number(s);
|
|
167
|
+
if (!Number.isFinite(n)) {
|
|
168
|
+
setError("Skip must be a number.");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
listOpts.skip = n;
|
|
172
|
+
}
|
|
173
|
+
if (t !== "") {
|
|
174
|
+
const n = Number(t);
|
|
175
|
+
if (!Number.isFinite(n)) {
|
|
176
|
+
setError("Take must be a number.");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
listOpts.take = n;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
setBusy(true);
|
|
183
|
+
try {
|
|
184
|
+
const res = await fetchLeadsByCompany(ck, listOpts, {
|
|
185
|
+
...connectionOpts,
|
|
186
|
+
baseUrl: effective.baseUrl,
|
|
187
|
+
...(effective.consumer ? { consumer: effective.consumer } : {}),
|
|
188
|
+
});
|
|
189
|
+
if (!res.ok) {
|
|
190
|
+
setError(mapBlazeoDemoError(res.detail ?? ""));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
setNote(
|
|
194
|
+
`LeadModel.getByCompany → GET /lead/company/get (${res.leads.length} row(s), MST snapshots as plain objects).`
|
|
195
|
+
);
|
|
196
|
+
setOutput(toDisplayJson({ leads: res.leads }));
|
|
197
|
+
} catch (err) {
|
|
198
|
+
setError(explainFetchFailure(err, effective.baseUrl));
|
|
199
|
+
} finally {
|
|
200
|
+
setBusy(false);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<>
|
|
206
|
+
<div className="card">
|
|
207
|
+
<h2>Lead by id</h2>
|
|
208
|
+
<p className="muted small">
|
|
209
|
+
Uses <code>fetchLeadDetails(leadId)</code> from <code>appointment-client</code>, which wraps{" "}
|
|
210
|
+
<code>LeadModel.getRaw</code> + <code>LeadModel.get</code> (<code>GET /lead/get</code>). Output shows both the
|
|
211
|
+
mapped lead snapshot and the raw API envelope.
|
|
212
|
+
</p>
|
|
213
|
+
<form onSubmit={handleFetchById} className="form">
|
|
214
|
+
<label className="form__label">
|
|
215
|
+
<span>Lead id</span>
|
|
216
|
+
<input
|
|
217
|
+
type="text"
|
|
218
|
+
className="form__input"
|
|
219
|
+
placeholder="lead_id"
|
|
220
|
+
value={leadId}
|
|
221
|
+
onChange={(e) => setLeadId(e.target.value)}
|
|
222
|
+
autoComplete="off"
|
|
223
|
+
/>
|
|
224
|
+
</label>
|
|
225
|
+
<button type="submit" className="btn btn--primary" disabled={busy}>
|
|
226
|
+
{busy ? "Loading…" : "Fetch lead"}
|
|
227
|
+
</button>
|
|
228
|
+
</form>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div className="card">
|
|
232
|
+
<h2>Lead by email + company</h2>
|
|
233
|
+
<p className="muted small">
|
|
234
|
+
<code>fetchLeadByEmail</code> → <code>LeadModel.getByEmail</code> (<code>GET /lead/getbyemail</code>).
|
|
235
|
+
</p>
|
|
236
|
+
<form onSubmit={handleFetchByEmail} className="form">
|
|
237
|
+
<label className="form__label">
|
|
238
|
+
<span>Email</span>
|
|
239
|
+
<input
|
|
240
|
+
type="email"
|
|
241
|
+
className="form__input"
|
|
242
|
+
placeholder="user@example.com"
|
|
243
|
+
value={email}
|
|
244
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
245
|
+
autoComplete="off"
|
|
246
|
+
/>
|
|
247
|
+
</label>
|
|
248
|
+
<label className="form__label">
|
|
249
|
+
<span>Company key</span>
|
|
250
|
+
<input
|
|
251
|
+
type="text"
|
|
252
|
+
className="form__input"
|
|
253
|
+
placeholder="company_key"
|
|
254
|
+
value={companyKey}
|
|
255
|
+
onChange={(e) => setCompanyKey(e.target.value)}
|
|
256
|
+
autoComplete="off"
|
|
257
|
+
/>
|
|
258
|
+
</label>
|
|
259
|
+
<button type="submit" className="btn btn--secondary" disabled={busy}>
|
|
260
|
+
{busy ? "Loading…" : "Fetch lead"}
|
|
261
|
+
</button>
|
|
262
|
+
</form>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<div className="card">
|
|
266
|
+
<h2>Leads by company</h2>
|
|
267
|
+
<p className="muted small">
|
|
268
|
+
<code>fetchLeadsByCompany</code> → <code>LeadModel.getByCompany</code> (<code>GET /lead/company/get</code>).
|
|
269
|
+
Optional <code>skip</code> / <code>take</code> map to query params (see calendar-client).
|
|
270
|
+
</p>
|
|
271
|
+
<form onSubmit={handleFetchByCompany} className="form">
|
|
272
|
+
<label className="form__label">
|
|
273
|
+
<span>Company key</span>
|
|
274
|
+
<input
|
|
275
|
+
type="text"
|
|
276
|
+
className="form__input"
|
|
277
|
+
placeholder="company_key"
|
|
278
|
+
value={listCompanyKey}
|
|
279
|
+
onChange={(e) => setListCompanyKey(e.target.value)}
|
|
280
|
+
autoComplete="off"
|
|
281
|
+
/>
|
|
282
|
+
</label>
|
|
283
|
+
<label className="form__label">
|
|
284
|
+
<span>Skip (optional)</span>
|
|
285
|
+
<input
|
|
286
|
+
type="text"
|
|
287
|
+
className="form__input"
|
|
288
|
+
inputMode="numeric"
|
|
289
|
+
placeholder="e.g. 0"
|
|
290
|
+
value={skip}
|
|
291
|
+
onChange={(e) => setSkip(e.target.value)}
|
|
292
|
+
autoComplete="off"
|
|
293
|
+
/>
|
|
294
|
+
</label>
|
|
295
|
+
<label className="form__label">
|
|
296
|
+
<span>Take (optional)</span>
|
|
297
|
+
<input
|
|
298
|
+
type="text"
|
|
299
|
+
className="form__input"
|
|
300
|
+
inputMode="numeric"
|
|
301
|
+
placeholder="e.g. 50"
|
|
302
|
+
value={take}
|
|
303
|
+
onChange={(e) => setTake(e.target.value)}
|
|
304
|
+
autoComplete="off"
|
|
305
|
+
/>
|
|
306
|
+
</label>
|
|
307
|
+
<button type="submit" className="btn btn--secondary" disabled={busy}>
|
|
308
|
+
{busy ? "Loading…" : "Fetch leads"}
|
|
309
|
+
</button>
|
|
310
|
+
</form>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
{note ? (
|
|
314
|
+
<div className="card">
|
|
315
|
+
<h2>Note</h2>
|
|
316
|
+
<p className="muted small">{note}</p>
|
|
317
|
+
</div>
|
|
318
|
+
) : null}
|
|
319
|
+
|
|
320
|
+
{error ? (
|
|
321
|
+
<div className="card card--error" role="alert">
|
|
322
|
+
<h2>Error</h2>
|
|
323
|
+
<pre className="pre-block">{error}</pre>
|
|
324
|
+
</div>
|
|
325
|
+
) : null}
|
|
326
|
+
|
|
327
|
+
{output ? (
|
|
328
|
+
<div className="card">
|
|
329
|
+
<h2>JSON</h2>
|
|
330
|
+
<pre className="pre-block">{output}</pre>
|
|
331
|
+
</div>
|
|
332
|
+
) : null}
|
|
333
|
+
</>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSnapshot } from "mobx-state-tree";
|
|
2
|
-
import { addParticipantToCalendar, saveCalendarOpeningHour, saveCalendarOpeningHoursBatch } from "./blazeoCalendarRelationMethods.js";
|
|
2
|
+
import { addParticipantToCalendar, removeParticipantFromCalendar, saveCalendarOpeningHour, saveCalendarOpeningHoursBatch } from "./blazeoCalendarRelationMethods.js";
|
|
3
3
|
import { createCalendarAsync, updateCalendarAsync, deleteCalendarAsync } from "./createCalendar.js";
|
|
4
4
|
|
|
5
5
|
function isFailureStatus(res: any) {
|
|
@@ -8,7 +8,24 @@ function isFailureStatus(res: any) {
|
|
|
8
8
|
|
|
9
9
|
function normalizeParticipantGuid(id: any) {
|
|
10
10
|
if (id == null || !String(id).trim()) return undefined;
|
|
11
|
-
return String(id).trim().replace(/^\{|\}$/g, "");
|
|
11
|
+
return String(id).trim().replace(/^\{|\}$/g, "").toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Coerce MST / envelope shapes into a plain ID list for comparison. */
|
|
15
|
+
function unwrapParticipantIds(raw: any): string[] {
|
|
16
|
+
if (raw == null) return [];
|
|
17
|
+
let list: any[] = [];
|
|
18
|
+
if (Array.isArray(raw)) {
|
|
19
|
+
list = raw;
|
|
20
|
+
} else {
|
|
21
|
+
const d = raw.data ?? raw.Data ?? raw.items ?? raw.Items ?? raw;
|
|
22
|
+
list = Array.isArray(d) ? d : (d?.items ?? d?.Items ?? []);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return list.map(p => {
|
|
26
|
+
const id = p.participantId ?? p.ParticipantId ?? p.participant_id ?? p.id ?? p.Id;
|
|
27
|
+
return normalizeParticipantGuid(id);
|
|
28
|
+
}).filter(Boolean) as string[];
|
|
12
29
|
}
|
|
13
30
|
|
|
14
31
|
function newOpeningHourId() {
|
|
@@ -73,8 +90,34 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
|
|
|
73
90
|
};
|
|
74
91
|
}
|
|
75
92
|
|
|
93
|
+
// 1. Participant Reconciliation (Diff-based)
|
|
94
|
+
// Fetch current participants to see who needs to be removed
|
|
95
|
+
let currentParticipantIds: string[] = [];
|
|
96
|
+
try {
|
|
97
|
+
const currentRaw = await calendarNode.getParticipants();
|
|
98
|
+
currentParticipantIds = unwrapParticipantIds(currentRaw);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.warn("[calendarCreation] Failed to fetch current participants for reconciliation. Proceeding with additive mode.", err);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const desiredMembers = calendar.members ?? [];
|
|
104
|
+
const desiredIds = new Set(desiredMembers.map((m: any) => normalizeParticipantGuid(m.id)).filter(Boolean));
|
|
105
|
+
|
|
106
|
+
// A. Remove missing members
|
|
107
|
+
for (const existingId of currentParticipantIds) {
|
|
108
|
+
if (!desiredIds.has(existingId)) {
|
|
109
|
+
try {
|
|
110
|
+
await removeParticipantFromCalendar(calendarNode, existingId);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.warn(`[calendarCreation] Failed to remove participant ${existingId}:`, err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// B. Add new members
|
|
118
|
+
const existingIdSet = new Set(currentParticipantIds);
|
|
76
119
|
let membersAdded = 0;
|
|
77
|
-
for (const m of
|
|
120
|
+
for (const m of desiredMembers) {
|
|
78
121
|
const pid = normalizeParticipantGuid(m.id);
|
|
79
122
|
if (!pid) {
|
|
80
123
|
return {
|
|
@@ -82,19 +125,23 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
|
|
|
82
125
|
error: `Member id ${m.id}: thirdPartyMemberId is required to add a participant.`,
|
|
83
126
|
};
|
|
84
127
|
}
|
|
85
|
-
|
|
86
|
-
if
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
128
|
+
|
|
129
|
+
// Only add if not already there
|
|
130
|
+
if (!existingIdSet.has(pid)) {
|
|
131
|
+
const res = await addParticipantToCalendar(calendarNode, pid);
|
|
132
|
+
if (isFailureStatus(res)) {
|
|
133
|
+
const msg =
|
|
134
|
+
res.message ??
|
|
135
|
+
(typeof res.data === "string" ? res.data : undefined) ??
|
|
136
|
+
JSON.stringify(res);
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
error: `addParticipant failed for member ${m.id}: ${msg}`,
|
|
140
|
+
apiResponse: res,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
membersAdded += 1;
|
|
96
144
|
}
|
|
97
|
-
membersAdded += 1;
|
|
98
145
|
}
|
|
99
146
|
|
|
100
147
|
// 2. Save Opening Hours (Plan V2: Grouped by participant with explicit off-days per slot)
|
|
@@ -276,9 +276,17 @@ export async function fetchCalendarDetails(
|
|
|
276
276
|
|
|
277
277
|
if (!calendarView) return null as any;
|
|
278
278
|
|
|
279
|
+
// Use the mapper to normalize the final output, ensuring all fields like duration,
|
|
280
|
+
// bookingPageTitle, calendarId, etc. are correctly picked and named.
|
|
281
|
+
const finalView = mapToDesiredCalendarResponse(
|
|
282
|
+
payload,
|
|
283
|
+
calendarView.openingHours,
|
|
284
|
+
calendarView.members
|
|
285
|
+
) as any;
|
|
286
|
+
|
|
279
287
|
// Attach metadata as non-enumerable properties so they don't show up in JSON.stringify
|
|
280
288
|
// but are still accessible for debugging if needed.
|
|
281
|
-
Object.defineProperties(
|
|
289
|
+
Object.defineProperties(finalView, {
|
|
282
290
|
_cal: { value: cal, enumerable: false },
|
|
283
291
|
_participants: { value: participantList, enumerable: false },
|
|
284
292
|
_openingHours: { value: openingHours, enumerable: false },
|
|
@@ -294,7 +302,7 @@ export async function fetchCalendarDetails(
|
|
|
294
302
|
},
|
|
295
303
|
});
|
|
296
304
|
|
|
297
|
-
return
|
|
305
|
+
return finalView as any;
|
|
298
306
|
}
|
|
299
307
|
|
|
300
308
|
/**
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CalendarModel, CalendarParticipantModel } from "@blazeo.com/calendar-client";
|
|
2
2
|
import { ensureBlazeoHttpReady } from "../config/ensureBlazeoHttpReady.js";
|
|
3
|
+
import { mapToDesiredCalendarResponse } from "./mapToDesiredResponse.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Fetches all calendars for a company and populates each with its members (participants).
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Uses a highly optimized single-request approach per calendar to ensure speed in list views.
|
|
8
|
+
* Results are normalized via mapToDesiredCalendarResponse.
|
|
8
9
|
*/
|
|
9
10
|
export async function getCalendarsByCompany(
|
|
10
11
|
companyKey: string,
|
|
@@ -30,96 +31,48 @@ export async function getCalendarsByCompany(
|
|
|
30
31
|
if (!calendarId) return null;
|
|
31
32
|
|
|
32
33
|
try {
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
CalendarParticipantModel.getInfoByCalendar(calendarId)
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
const parts = Array.isArray(partsRaw) ? partsRaw : (partsRaw as any)?.participants ?? [];
|
|
34
|
+
// Optimization: Use only getInfoByCalendar to get names/emails in a single request.
|
|
35
|
+
// This is much faster for a list view than fetching both list and info records.
|
|
36
|
+
const infoRaw = await CalendarParticipantModel.getInfoByCalendar(calendarId);
|
|
40
37
|
const info = Array.isArray(infoRaw) ? infoRaw : (infoRaw as any)?.info ?? [];
|
|
41
38
|
|
|
42
39
|
// Merge logic to ensure names are matched to IDs
|
|
43
40
|
const membersMap = new Map<string, any>();
|
|
44
|
-
|
|
41
|
+
|
|
45
42
|
// Use participantId GUID as the primary key
|
|
46
43
|
const getAnyId = (obj: any) =>
|
|
47
44
|
obj.participantId ?? obj.ParticipantId ?? obj.participant_id ?? obj.id ?? obj.Id;
|
|
48
45
|
|
|
49
|
-
// 1.
|
|
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)
|
|
46
|
+
// 1. Process info list (Name, Email, Alias)
|
|
64
47
|
info.forEach((i: any) => {
|
|
65
48
|
const mid = getAnyId(i);
|
|
66
49
|
if (!mid) return;
|
|
67
50
|
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
51
|
|
|
52
|
+
const resolvedEmail = i.email ?? i.Email ?? i.userSsoEmail ?? i.UserSsoEmail;
|
|
72
53
|
const memberData = {
|
|
73
54
|
id: mid,
|
|
74
|
-
name: i.name ?? i.Name ?? i.alias ?? i.Alias ??
|
|
55
|
+
name: i.name ?? i.Name ?? i.alias ?? i.Alias ?? "Member",
|
|
75
56
|
email: resolvedEmail,
|
|
76
|
-
|
|
77
|
-
userSsoEmail: resolvedEmail,
|
|
78
|
-
uuId: mid,
|
|
79
|
-
status: i.status ?? i.Status ?? existing?.status ?? 1
|
|
57
|
+
status: i.status ?? i.Status ?? 1,
|
|
80
58
|
};
|
|
81
59
|
|
|
82
|
-
if (!
|
|
60
|
+
if (!membersMap.has(key)) {
|
|
83
61
|
membersMap.set(key, memberData);
|
|
84
|
-
} else {
|
|
85
|
-
Object.assign(existing, memberData);
|
|
86
62
|
}
|
|
87
63
|
});
|
|
88
64
|
|
|
89
65
|
const members = Array.from(membersMap.values());
|
|
90
66
|
|
|
91
|
-
//
|
|
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
|
-
};
|
|
67
|
+
// Use the unified mapper to ensure all properties (duration, calendarId, etc.) are included
|
|
68
|
+
return mapToDesiredCalendarResponse(cal, [], members);
|
|
105
69
|
} catch (err) {
|
|
106
70
|
console.error(`[getCalendarsByCompany] Error fetching members for ${calendarId}:`, err);
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
};
|
|
71
|
+
// Fallback to minimal mapping if enrichment fails
|
|
72
|
+
return mapToDesiredCalendarResponse(cal, [], []);
|
|
120
73
|
}
|
|
121
74
|
})
|
|
122
75
|
);
|
|
123
76
|
|
|
124
|
-
return enrichedCalendars.filter(
|
|
77
|
+
return enrichedCalendars.filter(Boolean);
|
|
125
78
|
}
|