@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.
Files changed (104) hide show
  1. package/blazeo.com-appointment-client-1.0.6.tgz +0 -0
  2. package/dist/calendar/blazeoCalendarRelationMethods.d.ts +4 -36
  3. package/dist/calendar/blazeoCalendarRelationMethods.js +0 -1
  4. package/dist/calendar/buildUnifiedCalendarView.d.ts +31 -0
  5. package/dist/calendar/buildUnifiedCalendarView.js +265 -0
  6. package/dist/calendar/calendarCreation.d.ts +27 -0
  7. package/dist/calendar/calendarCreation.js +167 -0
  8. package/dist/calendar/calendarCreationFacade.d.ts +4 -13
  9. package/dist/calendar/calendarCreationFacade.js +3 -5
  10. package/dist/calendar/createCalendar.d.ts +67 -37
  11. package/dist/calendar/createCalendar.js +1 -3
  12. package/dist/calendar/fetchCalendarDetails.d.ts +73 -0
  13. package/dist/calendar/fetchCalendarDetails.js +192 -0
  14. package/dist/calendar/fetchCalendarWithOpeningHours.d.ts +34 -21
  15. package/dist/calendar/fetchCalendarWithOpeningHours.js +95 -75
  16. package/dist/calendar/getAllParticipantOpeningHours.d.ts +19 -0
  17. package/dist/calendar/getAllParticipantOpeningHours.js +17 -0
  18. package/dist/calendar/getOpeningHours.d.ts +5 -0
  19. package/dist/calendar/getOpeningHours.js +9 -0
  20. package/dist/calendar/getParticipantOpeningHours.d.ts +37 -0
  21. package/dist/calendar/getParticipantOpeningHours.js +43 -0
  22. package/dist/calendar/getParticipants.d.ts +4 -0
  23. package/dist/calendar/getParticipants.js +8 -0
  24. package/dist/calendar/mapCalendarBoToBlazeoSnapshot.d.ts +9 -9
  25. package/dist/calendar/mapCalendarBoToBlazeoSnapshot.js +43 -43
  26. package/dist/calendar/mapCalendarToBlazeoSnapshot.d.ts +22 -3
  27. package/dist/calendar/mapCalendarToBlazeoSnapshot.js +0 -1
  28. package/dist/config/applyBlazeoClientConfig.d.ts +2 -2
  29. package/dist/config/applyBlazeoClientConfig.js +13 -13
  30. package/dist/config/applyBlazeoDefaults.d.ts +0 -1
  31. package/dist/config/applyBlazeoDefaults.js +0 -1
  32. package/dist/config/blazeo.config.d.ts +10 -10
  33. package/dist/config/blazeo.config.js +10 -10
  34. package/dist/config/blazeoClientDefaults.d.ts +1 -2
  35. package/dist/config/blazeoClientDefaults.js +0 -1
  36. package/dist/config/initializeAppointmentClient.d.ts +4 -28
  37. package/dist/config/initializeAppointmentClient.js +5 -24
  38. package/dist/events/appointmentEventFacade.d.ts +55 -32
  39. package/dist/events/appointmentEventFacade.js +5 -10
  40. package/dist/events/mapAppointmentToEventSnapshot.d.ts +1 -4
  41. package/dist/events/mapAppointmentToEventSnapshot.js +0 -1
  42. package/dist/exampleData.d.ts +114 -10
  43. package/dist/exampleData.js +4 -5
  44. package/dist/facade/calendarCreationFacade.d.ts +39 -39
  45. package/dist/facade/calendarCreationFacade.js +95 -95
  46. package/dist/facade/mapCalendarBOToSnapshot.d.ts +9 -9
  47. package/dist/facade/mapCalendarBOToSnapshot.js +43 -43
  48. package/dist/facades/index.d.ts +11 -11
  49. package/dist/facades/index.js +11 -11
  50. package/dist/index.d.ts +23 -81
  51. package/dist/index.js +21 -32
  52. package/dist/models/CalendarRootModel.d.ts +36 -11
  53. package/dist/models/CalendarRootModel.js +22 -5
  54. package/dist/models/CalendarSlotModel.d.ts +8 -8
  55. package/dist/models/CalendarSlotModel.js +7 -7
  56. package/dist/models/EventModel.d.ts +10 -10
  57. package/dist/models/EventModel.js +9 -9
  58. package/dist/models/ParticipantModel.d.ts +8 -8
  59. package/dist/models/ParticipantModel.js +7 -7
  60. package/dist/models/index.d.ts +4 -4
  61. package/dist/models/index.js +4 -4
  62. package/dist/types/appointment.d.ts +27 -27
  63. package/dist/types/appointment.js +5 -5
  64. package/dist/types/calendar.d.ts +51 -51
  65. package/dist/types/calendar.js +5 -5
  66. package/dist/types/calendarBo.d.ts +61 -61
  67. package/dist/types/calendarBo.js +5 -5
  68. package/package.json +9 -4
  69. package/sample/build_error.txt +0 -0
  70. package/sample/demo.js +70 -0
  71. package/sample/package-lock.json +53 -2
  72. package/sample/package.json +3 -1
  73. package/sample/scripts/getInfoByCalendar.mjs +36 -0
  74. package/sample/scripts/getParticipantOpeningHours.mjs +48 -0
  75. package/sample/src/AllParticipantOpeningHoursTab.jsx +73 -0
  76. package/sample/src/App2.jsx +39 -2
  77. package/sample/src/BlazeoConnectionSettings.jsx +1 -1
  78. package/sample/src/EventTab.jsx +128 -0
  79. package/sample/src/FetchCalendarTab.jsx +72 -43
  80. package/sample/src/OpeningHoursTab.jsx +78 -0
  81. package/sample/src/ParticipantInfoTab.jsx +72 -0
  82. package/sample/src/ParticipantOpeningHoursTab.jsx +88 -0
  83. package/sample/src/ParticipantTab.jsx +2 -2
  84. package/src/calendar/blazeoCalendarRelationMethods.ts +19 -0
  85. package/src/calendar/buildUnifiedCalendarView.ts +322 -0
  86. package/src/calendar/calendarCreation.ts +179 -0
  87. package/src/calendar/createCalendar.ts +243 -0
  88. package/src/calendar/fetchCalendarDetails.ts +226 -0
  89. package/src/calendar/fetchCalendarWithOpeningHours.ts +99 -0
  90. package/src/calendar/getAllParticipantOpeningHours.ts +22 -0
  91. package/src/calendar/getOpeningHours.ts +10 -0
  92. package/src/calendar/getParticipantOpeningHours.ts +46 -0
  93. package/src/calendar/getParticipants.ts +9 -0
  94. package/src/calendar/mapCalendarToBlazeoSnapshot.ts +46 -0
  95. package/src/config/applyBlazeoDefaults.ts +13 -0
  96. package/src/config/blazeoClientDefaults.ts +11 -0
  97. package/src/config/initializeAppointmentClient.ts +18 -0
  98. package/src/events/appointmentEventFacade.ts +148 -0
  99. package/src/events/mapAppointmentToEventSnapshot.ts +65 -0
  100. package/src/exampleData.ts +79 -0
  101. package/src/index.ts +45 -0
  102. package/src/models/CalendarRootModel.ts +60 -0
  103. package/tsconfig.json +16 -0
  104. package/blazeo.com-appointment-client-1.0.4.tgz +0 -0
@@ -2,6 +2,7 @@ import { useMemo, useState } from "react";
2
2
  import {
3
3
  cancelAppointmentEventAsync,
4
4
  createAppointmentEventAsync,
5
+ EventModel,
5
6
  rescheduleAppointmentEventAsync,
6
7
  } from "appointment-client";
7
8
  import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
@@ -65,6 +66,14 @@ function resultToJson(result) {
65
66
  return JSON.stringify(result, null, 2);
66
67
  }
67
68
 
69
+ function safeJsonParse(text, fallback) {
70
+ try {
71
+ return JSON.parse(text);
72
+ } catch {
73
+ return fallback;
74
+ }
75
+ }
76
+
68
77
  export function EventTab() {
69
78
  const { effective } = useBlazeoConnection();
70
79
  const [offsetMinutes, setOffsetMinutes] = useState(-new Date().getTimezoneOffset());
@@ -74,6 +83,35 @@ export function EventTab() {
74
83
  const [rescheduleJson, setRescheduleJson] = useState(() =>
75
84
  JSON.stringify(getExampleReschedulePayload(), null, 2)
76
85
  );
86
+ const [searchCompanyKey, setSearchCompanyKey] = useState("");
87
+ const [searchFrom, setSearchFrom] = useState(() => new Date().toISOString().slice(0, 10));
88
+ const [searchTo, setSearchTo] = useState(() => {
89
+ const d = new Date();
90
+ d.setDate(d.getDate() + 7);
91
+ return d.toISOString().slice(0, 10);
92
+ });
93
+ const [searchFiltersJson, setSearchFiltersJson] = useState(() =>
94
+ JSON.stringify(
95
+ {
96
+ calendarId: "",
97
+ participantId: "",
98
+ leadId: "",
99
+ visitorName: "",
100
+ visitorEmail: "",
101
+ visitorPhone: "",
102
+ title: "",
103
+ search: "",
104
+ attendeeStatus: "",
105
+ eventSource: "",
106
+ sort: "",
107
+ sortOrder: "desc",
108
+ page: 1,
109
+ page_size: 25,
110
+ },
111
+ null,
112
+ 2
113
+ )
114
+ );
77
115
  const [cancelEventId, setCancelEventId] = useState("");
78
116
  const [busy, setBusy] = useState(false);
79
117
  const [error, setError] = useState("");
@@ -153,6 +191,42 @@ export function EventTab() {
153
191
  }
154
192
  }
155
193
 
194
+ async function handleSearchByDateRange(e) {
195
+ e.preventDefault();
196
+ setError("");
197
+ setOutput("");
198
+ const companyKey = searchCompanyKey.trim();
199
+ if (!companyKey) return setError("Enter company key.");
200
+ if (!searchFrom) return setError("Pick start date.");
201
+ if (!searchTo) return setError("Pick end date.");
202
+ if (!ensureBase()) return;
203
+ configureBlazeoFromEffective(effective);
204
+
205
+ const optsFromJson = safeJsonParse(searchFiltersJson, {});
206
+ const startDateFrom = new Date(`${searchFrom}T00:00:00.000Z`).toISOString();
207
+ const startDateTo = new Date(`${searchTo}T23:59:59.999Z`).toISOString();
208
+
209
+ setBusy(true);
210
+ try {
211
+ const res = await EventModel.getByDateRangeWithFilters(
212
+ companyKey,
213
+ startDateFrom,
214
+ startDateTo,
215
+ optsFromJson
216
+ );
217
+
218
+ const events = (res?.events ?? []).map((e) =>
219
+ isStateTreeNode(e) ? getSnapshot(e) : (e?.toJSON?.() ?? e)
220
+ );
221
+ const totalCount = res?.totalCount ?? events.length;
222
+ setOutput(JSON.stringify({ totalCount, events }, null, 2));
223
+ } catch (err) {
224
+ setError(err instanceof Error ? err.message : String(err));
225
+ } finally {
226
+ setBusy(false);
227
+ }
228
+ }
229
+
156
230
  return (
157
231
  <>
158
232
  <div className="card">
@@ -171,6 +245,60 @@ export function EventTab() {
171
245
  </label>
172
246
  </div>
173
247
 
248
+ <div className="card">
249
+ <h2>Search events (date range + filters)</h2>
250
+ <p className="muted small">
251
+ Calls <code>EventModel.getByDateRangeWithFilters</code> →
252
+ <code> GET /event/search/daterange/get</code> (company scope). Offset header comes from the{" "}
253
+ <code>offset</code> field above.
254
+ </p>
255
+ <form onSubmit={handleSearchByDateRange} className="form">
256
+ <label className="form__label">
257
+ <span>Company key</span>
258
+ <input
259
+ className="form__input"
260
+ value={searchCompanyKey}
261
+ onChange={(e) => setSearchCompanyKey(e.target.value)}
262
+ placeholder="company_key"
263
+ autoComplete="off"
264
+ />
265
+ </label>
266
+ <div className="connection-card__row">
267
+ <label className="form__label">
268
+ <span>Start date (from)</span>
269
+ <input
270
+ type="date"
271
+ className="form__input"
272
+ value={searchFrom}
273
+ onChange={(e) => setSearchFrom(e.target.value)}
274
+ />
275
+ </label>
276
+ <label className="form__label">
277
+ <span>Start date (to)</span>
278
+ <input
279
+ type="date"
280
+ className="form__input"
281
+ value={searchTo}
282
+ onChange={(e) => setSearchTo(e.target.value)}
283
+ />
284
+ </label>
285
+ </div>
286
+ <label className="form__label">
287
+ <span>Filters (JSON)</span>
288
+ <textarea
289
+ className="form__textarea"
290
+ value={searchFiltersJson}
291
+ onChange={(e) => setSearchFiltersJson(e.target.value)}
292
+ spellCheck={false}
293
+ rows={10}
294
+ />
295
+ </label>
296
+ <button type="submit" className="btn btn--secondary" disabled={busy}>
297
+ {busy ? "Loading…" : "Search"}
298
+ </button>
299
+ </form>
300
+ </div>
301
+
174
302
  <div className="card">
175
303
  <h2>Create event</h2>
176
304
  <form onSubmit={handleCreate} className="form">
@@ -2,7 +2,7 @@ import { useMemo, useState } from "react";
2
2
  import {
3
3
  CalendarModel,
4
4
  deleteCalendarAsync,
5
- fetchCalendarWithOpeningHours,
5
+ fetchCalendarDetails,
6
6
  updateCalendarAsync,
7
7
  } from "appointment-client";
8
8
  import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
@@ -52,8 +52,10 @@ function explainFetchFailure(err, configuredBaseUrl) {
52
52
  return `${msg}\n\n${proxyHint}`;
53
53
  }
54
54
 
55
- /** Opening hours list from participant API wrapper or embedded `calendar.openingHours`. */
55
+ /** Opening hours list from `calendarView`, embedded `calendar.openingHours`, or legacy `openingHours`. */
56
56
  function pickOpeningHoursListFromBundle(parsed) {
57
+ const fromView = parsed?.calendarView?.openingHours;
58
+ if (Array.isArray(fromView) && fromView.length > 0) return fromView;
57
59
  const fromCal = parsed?.calendar?.openingHours;
58
60
  if (Array.isArray(fromCal) && fromCal.length > 0) return fromCal;
59
61
  const oh = parsed?.openingHours;
@@ -80,7 +82,7 @@ function OpeningHoursSummary({ outputJson }) {
80
82
  return (
81
83
  <p className="muted small" style={{ marginBottom: "0.75rem" }}>
82
84
  No opening-hours rows parsed for the table. Check{" "}
83
- <code>calendar.openingHours</code> or <code>openingHours</code> in the JSON below.
85
+ <code>calendar.openingHours</code>, <code>calendarView.openingHours</code>, or <code>openingHours</code> in the JSON below.
84
86
  </p>
85
87
  );
86
88
  }
@@ -217,48 +219,48 @@ export function FetchCalendarTab() {
217
219
 
218
220
  setBusy(true);
219
221
  try {
220
- const bundle = await fetchCalendarWithOpeningHours(id);
222
+ const details = await fetchCalendarDetails(id);
221
223
 
222
- if (bundle.cal == null) {
224
+ if (details.cal == null) {
223
225
  const raw = await CalendarModel.getRaw(id);
224
- setNote(
225
- "CalendarModel.get returned null. Showing CalendarModel.getRaw only (opening hours merge skipped)."
226
- );
226
+ setNote("CalendarModel.get returned null. Showing CalendarModel.getRaw only.");
227
227
  setOutput(toDisplayJson(raw));
228
228
  return;
229
229
  }
230
230
 
231
- const snap = getSnapshot(bundle.cal);
231
+ const snap = getSnapshot(details.cal);
232
232
  setLastFetchUpdatePayload(JSON.stringify(calendarSnapshotToUpdatePayload(snap), null, 2));
233
233
 
234
- const [participants, participantsInfo] = await Promise.all([
235
- bundle.cal.getParticipants(),
236
- bundle.cal.getParticipantsInfo(),
237
- ]);
238
-
239
234
  const payload = {
240
- calendar: bundle.calendar,
241
- openingHours: bundle.openingHours,
242
- ...(bundle.participantOpeningHoursResponse != null
243
- ? { participantOpeningHoursApiResponse: bundle.participantOpeningHoursResponse }
244
- : {}),
245
- participants,
246
- participantsInfo,
235
+ calendarView: details.calendarView,
236
+ calendar: details.calendar,
237
+ openingHours: details.openingHours,
238
+ allParticipantOpeningHours: details.allParticipantOpeningHours,
239
+ openingHoursApiResponse: details.participantOpeningHoursResponse ?? null,
240
+ participants: (details.participants ?? []).map((p) => (isStateTreeNode(p) ? getSnapshot(p) : p)),
241
+ participantsInfo: Array.isArray(details.participantsInfo)
242
+ ? details.participantsInfo.map((p) => (isStateTreeNode(p) ? getSnapshot(p) : p))
243
+ : details.participantsInfo ?? null,
247
244
  __openingHoursMeta: {
248
- fromCalendarGet: bundle.fromCalendarGet,
249
- fromParticipantApi: bundle.fromParticipantApi,
250
- embeddedCount: bundle.embeddedFromGet?.length ?? 0,
251
- resolvedCount: bundle.openingHours?.length ?? 0,
245
+ fromCalendarGet: details.fromCalendarGet,
246
+ fromParticipantApi: details.fromParticipantApi,
247
+ calendarViewUsedAllParticipantOpeningHours: details.meta?.calendarViewUsedAllParticipantOpeningHours,
248
+ embeddedCount: details.embeddedFromGet?.length ?? 0,
249
+ resolvedCount: details.openingHours?.length ?? 0,
252
250
  },
251
+ meta: details.meta,
253
252
  };
254
253
 
255
254
  setOutput(toDisplayJson(payload));
256
255
  setNote(
257
- bundle.fromCalendarGet
258
- ? "Opening hours: embedded on GET /Calendar/Get (`appointment-client` fetchCalendarWithOpeningHours)."
259
- : bundle.fromParticipantApi
260
- ? "Opening hours: from GET /Calendar/Participant/OpeningHours/Get (calendar GET had none)."
261
- : "Opening hours: none returned."
256
+ (details.calendarView
257
+ ? "Use `calendarView`: one object (calendar + nested participants with openingHours). "
258
+ : "") +
259
+ (details.fromCalendarGet
260
+ ? "Opening hours: embedded on GET /Calendar/Get; participants from GET /Calendar/Participant/All. Single bundle in output."
261
+ : details.fromParticipantApi
262
+ ? "Opening hours: GET /Calendar/Participant/OpeningHours/Get; participants from GET /Calendar/Participant/All. Single bundle in output."
263
+ : "Calendar loaded; opening hours empty from both embed + participant API; participants from GET /Calendar/Participant/All.")
262
264
  );
263
265
  } catch (err) {
264
266
  setError(explainFetchFailure(err, effective.baseUrl));
@@ -283,38 +285,46 @@ export function FetchCalendarTab() {
283
285
 
284
286
  setBusy(true);
285
287
  try {
286
- const list = await CalendarModel.getByCompany(key);
287
- if (list == null || list.length === 0) {
288
- setNote("getByCompany returned null or an empty list.");
289
- setOutput(toDisplayJson(list));
288
+ const byCompany = await CalendarModel.getByCompany(key);
289
+ const list = byCompany?.calendars ?? byCompany;
290
+ if (list == null || !Array.isArray(list) || list.length === 0) {
291
+ setNote(
292
+ "getByCompany returned null or an empty list. (calendar-client ≥1.0.17 returns { calendars, totalCount }.)"
293
+ );
294
+ setOutput(toDisplayJson(byCompany));
290
295
  } else {
291
296
  const enriched = await Promise.all(
292
297
  list.map(async (c) => {
293
298
  const id = c.calendarId ?? String(c.id ?? "");
294
299
  if (!id) return { calendar: getSnapshot(c), openingHours: [], meta: { error: "no id" } };
295
300
  try {
296
- const b = await fetchCalendarWithOpeningHours(id);
301
+ const b = await fetchCalendarDetails(id);
297
302
  return {
303
+ calendarView: b.calendarView,
298
304
  calendar: b.calendar ?? getSnapshot(c),
299
305
  openingHours: b.openingHours,
306
+ participants: (b.participants ?? []).map((p) => (isStateTreeNode(p) ? getSnapshot(p) : p)),
300
307
  __openingHoursMeta: {
301
308
  fromCalendarGet: b.fromCalendarGet,
302
309
  fromParticipantApi: b.fromParticipantApi,
310
+ calendarViewUsedAllParticipantOpeningHours: b.meta?.calendarViewUsedAllParticipantOpeningHours,
303
311
  },
312
+ meta: b.meta,
304
313
  };
305
314
  } catch (err) {
306
315
  return {
307
316
  calendar: getSnapshot(c),
308
317
  openingHours: [],
309
- __openingHoursMeta: {
318
+ meta: {
310
319
  error: err instanceof Error ? err.message : String(err),
311
320
  },
312
321
  };
313
322
  }
314
323
  })
315
324
  );
325
+ const total = byCompany?.totalCount ?? list.length;
316
326
  setNote(
317
- `Loaded ${list.length} calendar(s); opening hours merged per calendar (GET embed → participant API fallback).`
327
+ `Loaded ${list.length} calendar(s) (totalCount=${total}); opening hours (embed → participant API) + participants per calendar.`
318
328
  );
319
329
  setOutput(toDisplayJson(enriched));
320
330
  }
@@ -431,13 +441,32 @@ export function FetchCalendarTab() {
431
441
  return (
432
442
  <>
433
443
  <div className="card">
434
- <h2>Fetch calendar</h2>
444
+ <h2>Fetch calendar · calendarView</h2>
445
+ <p className="muted small">
446
+ Runs <code>fetchCalendarDetails(calendarId)</code>. JSON field <code>calendarView</code> is first: one object with
447
+ calendar snapshot fields + <code>members</code> + <code>openingHours</code>, and a **new** <code>participants</code> array
448
+ where each participant has their own <code>openingHours</code> nested inside.
449
+ </p>
450
+ <p className="muted small">
451
+ Uses <code>fetchCalendarDetails</code>: legacy <code>openingHours</code> prefers embed on{" "}
452
+ <code>CalendarModel.getRaw</code>, else <code>getParticipantOpeningHours</code>.{" "}
453
+ <code>calendarView.openingHours</code> prefers <code>getAllParticipantOpeningHours</code> (
454
+ <code>GET /Calendar/Participant/OpeningHours/All/Get</code>) when the API returns rows. Members combine{" "}
455
+ <code>CalendarModel.getParticipants</code> + <code>CalendarModel.getParticipantsInfo</code> (each member may
456
+ include <code>participantInfo</code>).
457
+ </p>
458
+ <p className="muted small">
459
+ <strong>Single object in code:</strong> <code>fetchCalendarBundle(calendarId)</code> after{" "}
460
+ <code>initializeAppointmentClient(&#123; baseUrl, consumer &#125;)</code> — same unified shape as{" "}
461
+ <code>calendarView</code> below. This tab runs <code>fetchCalendarDetails</code> so extra arrays stay visible.
462
+ </p>
435
463
  <p className="muted small">
436
- Uses <code>fetchCalendarWithOpeningHours</code> from <code>appointment-client</code>: merges
437
- embedded <code>openingHours</code> from <code>GET /Calendar/Get</code> when present (MST omits
438
- them); otherwise calls <code>calendar.getParticipantOpeningHours()</code> (
439
- <code>GET /Calendar/Participant/OpeningHours/Get</code>), matching{" "}
440
- <code>@blazeo.com/calendar-client</code>.
464
+ <strong>DevTools Network:</strong> Each fetch fires <code>/Calendar/Get</code> <strong>twice</strong> (
465
+ <code>CalendarModel.get</code> + <code>getRaw</code>). Other calls use different URLs — filter by{" "}
466
+ <code>Participant</code>, <code>OpeningHours</code>, or <code>GetInfo</code>. Those power{" "}
467
+ <code>calendarView</code>. If you only see <code>Calendar/Get</code> yet the UI JSON has members/hours,
468
+ widen the Network filter (&quot;All&quot;) or disable search; if <code>calendarView</code> is empty/missing fields,
469
+ check the <strong>Console</strong> for errors on the participant/opening-hours requests.
441
470
  </p>
442
471
  <p className="muted small">
443
472
  Effective: <code>{effective.baseUrl || "(set connection or blazeoClientDefaults.ts)"}</code>
@@ -0,0 +1,78 @@
1
+ import { useState } from "react";
2
+ import { fetchCalendarWithOpeningHours } from "appointment-client";
3
+ import {
4
+ configureBlazeoFromEffective,
5
+ useBlazeoConnection,
6
+ } from "./BlazeoConnectionSettings.jsx";
7
+
8
+ export function OpeningHoursTab() {
9
+ const { effective } = useBlazeoConnection();
10
+ const [calendarId, setCalendarId] = useState("");
11
+ const [busy, setBusy] = useState(false);
12
+ const [error, setError] = useState("");
13
+ const [output, setOutput] = useState("");
14
+
15
+ async function handleFetch(e) {
16
+ e.preventDefault();
17
+ setError("");
18
+ setOutput("");
19
+ const id = calendarId.trim();
20
+ if (!id) {
21
+ setError("Enter a calendar id.");
22
+ return;
23
+ }
24
+ if (!effective.baseUrl) {
25
+ setError("Set Base URL in the connection card above.");
26
+ return;
27
+ }
28
+ configureBlazeoFromEffective(effective);
29
+ setBusy(true);
30
+ try {
31
+ const res = await fetchCalendarWithOpeningHours(id);
32
+ setOutput(JSON.stringify(res, null, 2));
33
+ } catch (err) {
34
+ setError(err instanceof Error ? err.message : String(err));
35
+ } finally {
36
+ setBusy(false);
37
+ }
38
+ }
39
+
40
+ return (
41
+ <>
42
+ <div className="card">
43
+ <h2>Opening Hours (Detailed)</h2>
44
+ <p className="muted small">
45
+ Fetches business hours using <code>fetchCalendarWithOpeningHours</code>.
46
+ </p>
47
+ <form onSubmit={handleFetch} className="form">
48
+ <label className="form__label">
49
+ <span>Calendar id</span>
50
+ <input
51
+ type="text"
52
+ className="form__input"
53
+ value={calendarId}
54
+ onChange={(e) => setCalendarId(e.target.value)}
55
+ />
56
+ </label>
57
+ <button type="submit" className="btn btn--primary" disabled={busy}>
58
+ {busy ? "Loading…" : "Fetch Opening Hours"}
59
+ </button>
60
+ </form>
61
+ </div>
62
+
63
+ {error ? (
64
+ <div className="card card--error" role="alert">
65
+ <h2>Error</h2>
66
+ <pre className="pre-block">{error}</pre>
67
+ </div>
68
+ ) : null}
69
+
70
+ {output ? (
71
+ <div className="card card--success">
72
+ <h2>Result</h2>
73
+ <pre className="pre-block">{output}</pre>
74
+ </div>
75
+ ) : null}
76
+ </>
77
+ );
78
+ }
@@ -0,0 +1,72 @@
1
+ import { useState } from "react";
2
+ import { CalendarParticipantModel } from "appointment-client";
3
+ import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
4
+
5
+ export function ParticipantInfoTab() {
6
+ const { effective } = useBlazeoConnection();
7
+ const [calendarId, setCalendarId] = useState("");
8
+ const [busy, setBusy] = useState(false);
9
+ const [error, setError] = useState("");
10
+ const [output, setOutput] = useState("");
11
+
12
+ async function handleFetch(e) {
13
+ e.preventDefault();
14
+ setError("");
15
+ setOutput("");
16
+ const id = calendarId.trim();
17
+ if (!id) return setError("Enter a calendar id.");
18
+ if (!effective.baseUrl) return setError("Set Base URL in the connection card above.");
19
+
20
+ configureBlazeoFromEffective(effective);
21
+ setBusy(true);
22
+ try {
23
+ const info = await CalendarParticipantModel.getInfoByCalendar(id);
24
+ setOutput(JSON.stringify({ calendarId: id, count: Array.isArray(info) ? info.length : 0, info }, null, 2));
25
+ } catch (err) {
26
+ setError(err instanceof Error ? err.message : String(err));
27
+ } finally {
28
+ setBusy(false);
29
+ }
30
+ }
31
+
32
+ return (
33
+ <>
34
+ <div className="card">
35
+ <h2>Participant Info by Calendar</h2>
36
+ <p className="muted small">
37
+ Calls <code>CalendarParticipantModel.getInfoByCalendar(calendarId)</code>.
38
+ </p>
39
+ <form onSubmit={handleFetch} className="form">
40
+ <label className="form__label">
41
+ <span>Calendar id</span>
42
+ <input
43
+ type="text"
44
+ className="form__input"
45
+ value={calendarId}
46
+ onChange={(e) => setCalendarId(e.target.value)}
47
+ autoComplete="off"
48
+ />
49
+ </label>
50
+ <button type="submit" className="btn btn--secondary" disabled={busy}>
51
+ {busy ? "Loading…" : "Fetch participant info"}
52
+ </button>
53
+ </form>
54
+ </div>
55
+
56
+ {error ? (
57
+ <div className="card card--error" role="alert">
58
+ <h2>Error</h2>
59
+ <pre className="pre-block">{error}</pre>
60
+ </div>
61
+ ) : null}
62
+
63
+ {output ? (
64
+ <div className="card card--success">
65
+ <h2>Result</h2>
66
+ <pre className="pre-block">{output}</pre>
67
+ </div>
68
+ ) : null}
69
+ </>
70
+ );
71
+ }
72
+
@@ -0,0 +1,88 @@
1
+ import { useState } from "react";
2
+ import { getParticipantOpeningHours } from "appointment-client";
3
+ import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
4
+
5
+ export function ParticipantOpeningHoursTab() {
6
+ const { effective } = useBlazeoConnection();
7
+ const [calendarId, setCalendarId] = useState("");
8
+ const [busy, setBusy] = useState(false);
9
+ const [error, setError] = useState("");
10
+ const [output, setOutput] = useState("");
11
+
12
+ async function handleFetch(e) {
13
+ e.preventDefault();
14
+ setError("");
15
+ setOutput("");
16
+ const id = calendarId.trim();
17
+ if (!id) return setError("Enter a calendar id.");
18
+ if (!effective.baseUrl) return setError("Set Base URL in the connection card above.");
19
+
20
+ configureBlazeoFromEffective(effective);
21
+ setBusy(true);
22
+ try {
23
+ debugger;
24
+ const result = await getParticipantOpeningHours(id);
25
+
26
+ if (!result.meta.ok) {
27
+ setError(result.meta.error || `Failed to fetch: ${result.meta.reason}`);
28
+ if (result.raw) setOutput(JSON.stringify(result.raw, null, 2));
29
+ return;
30
+ }
31
+
32
+ setOutput(JSON.stringify({
33
+ calendarId: id,
34
+ count: result.openingHours.length,
35
+ openingHours: result.openingHours,
36
+ raw: result.raw,
37
+ meta: result.meta
38
+ }, null, 2));
39
+ } catch (err) {
40
+ setError(err instanceof Error ? err.message : String(err));
41
+ } finally {
42
+ setBusy(false);
43
+ }
44
+ }
45
+
46
+ return (
47
+ <>
48
+ <div className="card">
49
+ <h2>Participant Opening Hours (Direct)</h2>
50
+ <p className="muted small">
51
+ Calls <code>CalendarModel.get(calendarId)</code> then{" "}
52
+ <code>calendar.getParticipantOpeningHours(&#123;&#125;)</code> (API:{" "}
53
+ <code>GET /Calendar/Participant/OpeningHours/Get</code>).
54
+ </p>
55
+ <form onSubmit={handleFetch} className="form">
56
+ <label className="form__label">
57
+ <span>Calendar id</span>
58
+ <input
59
+ type="text"
60
+ className="form__input"
61
+ value={calendarId}
62
+ onChange={(e) => setCalendarId(e.target.value)}
63
+ autoComplete="off"
64
+ />
65
+ </label>
66
+ <button type="submit" className="btn btn--secondary" disabled={busy}>
67
+ {busy ? "Loading…" : "Fetch participant opening hours"}
68
+ </button>
69
+ </form>
70
+ </div>
71
+
72
+ {error ? (
73
+ <div className="card card--error" role="alert">
74
+ <h2>Error</h2>
75
+ <pre className="pre-block">{error}</pre>
76
+ </div>
77
+ ) : null}
78
+
79
+ {output ? (
80
+ <div className="card card--success">
81
+ <h2>Result</h2>
82
+ <pre className="pre-block">{output}</pre>
83
+ </div>
84
+ ) : null}
85
+ </>
86
+ );
87
+ }
88
+
@@ -1,5 +1,5 @@
1
1
  import { useMemo, useState } from "react";
2
- import { getExampleParticipants, ParticipantModel } from "appointment-client";
2
+ import { getExampleParticipants, getParticipants } from "appointment-client";
3
3
  import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
4
4
  import {
5
5
  configureBlazeoFromEffective,
@@ -43,7 +43,7 @@ export function ParticipantTab() {
43
43
  configureBlazeoFromEffective(effective);
44
44
  setBusy(true);
45
45
  try {
46
- const res = await ParticipantModel.getAllByCalendar(id);
46
+ const res = await getParticipants(id);
47
47
  setOutput(toDisplayJson(res));
48
48
  } catch (err) {
49
49
  setError(err instanceof Error ? err.message : String(err));
@@ -0,0 +1,19 @@
1
+ /** `GET /Calendar/Participant/Add` — attach `participantId` to this calendar. */
2
+ export function addParticipantToCalendar(calendar: any, participantId: string) {
3
+ return calendar.addParticipant(participantId);
4
+ }
5
+
6
+ /** `GET /Calendar/Participant/Remove` */
7
+ export function removeParticipantFromCalendar(calendar: any, participantId: string) {
8
+ return calendar.removeParticipant(participantId);
9
+ }
10
+
11
+ /** `POST /Calendar/Participant/Availability/OpeningHour/Save` — one day/slot. */
12
+ export function saveCalendarOpeningHour(calendar: any, payload: any) {
13
+ return calendar.saveOpeningHour(payload);
14
+ }
15
+
16
+ /** `POST /Calendar/Participant/Availability/OpeningHours/Save` — batch body (API-specific shape). */
17
+ export function saveCalendarOpeningHoursBatch(calendar: any, payload: any) {
18
+ return calendar.saveOpeningHours(payload);
19
+ }