@blazeo.com/appointment-client 1.0.5 → 1.0.7

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 (121) hide show
  1. package/blazeo.com-appointment-client-1.0.7.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 +39 -0
  5. package/dist/calendar/buildUnifiedCalendarView.js +280 -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 +41 -18
  13. package/dist/calendar/fetchCalendarDetails.js +261 -51
  14. package/dist/calendar/fetchCalendarWithOpeningHours.d.ts +25 -21
  15. package/dist/calendar/fetchCalendarWithOpeningHours.js +114 -75
  16. package/dist/calendar/getAllParticipantOpeningHours.d.ts +22 -0
  17. package/dist/calendar/getAllParticipantOpeningHours.js +22 -0
  18. package/dist/calendar/getOpeningHours.d.ts +8 -0
  19. package/dist/calendar/getOpeningHours.js +9 -0
  20. package/dist/calendar/getParticipantOpeningHours.d.ts +37 -0
  21. package/dist/calendar/getParticipantOpeningHours.js +48 -0
  22. package/dist/calendar/getParticipants.d.ts +7 -0
  23. package/dist/calendar/getParticipants.js +13 -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/calendar/mapToDesiredResponse.d.ts +70 -0
  29. package/dist/calendar/mapToDesiredResponse.js +99 -0
  30. package/dist/config/applyBlazeoClientConfig.d.ts +2 -2
  31. package/dist/config/applyBlazeoClientConfig.js +13 -13
  32. package/dist/config/applyBlazeoDefaults.d.ts +0 -1
  33. package/dist/config/applyBlazeoDefaults.js +3 -3
  34. package/dist/config/blazeo.config.d.ts +10 -10
  35. package/dist/config/blazeo.config.js +10 -10
  36. package/dist/config/blazeoClientDefaults.d.ts +1 -2
  37. package/dist/config/blazeoClientDefaults.js +2 -3
  38. package/dist/config/ensureBlazeoHttpReady.d.ts +17 -0
  39. package/dist/config/ensureBlazeoHttpReady.js +31 -0
  40. package/dist/config/initializeAppointmentClient.d.ts +8 -28
  41. package/dist/config/initializeAppointmentClient.js +11 -24
  42. package/dist/config/syncBlazeoConnection.d.ts +6 -0
  43. package/dist/config/syncBlazeoConnection.js +18 -0
  44. package/dist/events/appointmentEventFacade.d.ts +55 -32
  45. package/dist/events/appointmentEventFacade.js +5 -10
  46. package/dist/events/mapAppointmentToEventSnapshot.d.ts +1 -4
  47. package/dist/events/mapAppointmentToEventSnapshot.js +0 -1
  48. package/dist/exampleData.d.ts +114 -10
  49. package/dist/exampleData.js +4 -5
  50. package/dist/facade/calendarCreationFacade.d.ts +39 -39
  51. package/dist/facade/calendarCreationFacade.js +95 -95
  52. package/dist/facade/mapCalendarBOToSnapshot.d.ts +9 -9
  53. package/dist/facade/mapCalendarBOToSnapshot.js +43 -43
  54. package/dist/facades/index.d.ts +11 -11
  55. package/dist/facades/index.js +11 -11
  56. package/dist/index.d.ts +26 -82
  57. package/dist/index.js +23 -33
  58. package/dist/models/CalendarRootModel.d.ts +36 -11
  59. package/dist/models/CalendarRootModel.js +22 -5
  60. package/dist/models/CalendarSlotModel.d.ts +8 -8
  61. package/dist/models/CalendarSlotModel.js +7 -7
  62. package/dist/models/EventModel.d.ts +10 -10
  63. package/dist/models/EventModel.js +9 -9
  64. package/dist/models/ParticipantModel.d.ts +8 -8
  65. package/dist/models/ParticipantModel.js +7 -7
  66. package/dist/models/index.d.ts +4 -4
  67. package/dist/models/index.js +4 -4
  68. package/dist/types/appointment.d.ts +27 -27
  69. package/dist/types/appointment.js +5 -5
  70. package/dist/types/calendar.d.ts +51 -51
  71. package/dist/types/calendar.js +5 -5
  72. package/dist/types/calendarBo.d.ts +61 -61
  73. package/dist/types/calendarBo.js +5 -5
  74. package/package.json +8 -2
  75. package/sample/.env.example +5 -0
  76. package/sample/build_error.txt +0 -0
  77. package/sample/demo.js +70 -0
  78. package/sample/package-lock.json +5 -2
  79. package/sample/package.json +3 -1
  80. package/sample/scripts/getInfoByCalendar.mjs +36 -0
  81. package/sample/scripts/getParticipantOpeningHours.mjs +48 -0
  82. package/sample/src/AllParticipantOpeningHoursTab.jsx +82 -0
  83. package/sample/src/App2.jsx +60 -3
  84. package/sample/src/AvailabilityTab.jsx +8 -3
  85. package/sample/src/BlazeoConnectionSettings.jsx +17 -16
  86. package/sample/src/CreateCalendarTab.jsx +23 -6
  87. package/sample/src/EventTab.jsx +31 -8
  88. package/sample/src/FetchCalendarTab.jsx +114 -38
  89. package/sample/src/OpeningHoursTab.jsx +87 -0
  90. package/sample/src/ParticipantInfoTab.jsx +77 -0
  91. package/sample/src/ParticipantOpeningHoursTab.jsx +98 -0
  92. package/sample/src/ParticipantTab.jsx +13 -4
  93. package/sample/src/blazeoBootstrap.js +30 -0
  94. package/sample/src/blazeoDemoError.js +14 -0
  95. package/sample/src/blazeoPushConnection.js +23 -0
  96. package/sample/src/main.jsx +3 -3
  97. package/sample/vite.config.js +19 -5
  98. package/src/calendar/blazeoCalendarRelationMethods.ts +19 -0
  99. package/src/calendar/buildUnifiedCalendarView.ts +345 -0
  100. package/src/calendar/calendarCreation.ts +179 -0
  101. package/src/calendar/createCalendar.ts +243 -0
  102. package/src/calendar/fetchCalendarDetails.ts +316 -0
  103. package/src/calendar/fetchCalendarWithOpeningHours.ts +130 -0
  104. package/src/calendar/getAllParticipantOpeningHours.ts +30 -0
  105. package/src/calendar/getOpeningHours.ts +10 -0
  106. package/src/calendar/getParticipantOpeningHours.ts +55 -0
  107. package/src/calendar/getParticipants.ts +17 -0
  108. package/src/calendar/mapCalendarToBlazeoSnapshot.ts +46 -0
  109. package/src/calendar/mapToDesiredResponse.ts +104 -0
  110. package/src/config/applyBlazeoDefaults.ts +14 -0
  111. package/src/config/blazeoClientDefaults.ts +11 -0
  112. package/src/config/ensureBlazeoHttpReady.ts +41 -0
  113. package/src/config/initializeAppointmentClient.ts +24 -0
  114. package/src/config/syncBlazeoConnection.ts +19 -0
  115. package/src/events/appointmentEventFacade.ts +148 -0
  116. package/src/events/mapAppointmentToEventSnapshot.ts +65 -0
  117. package/src/exampleData.ts +79 -0
  118. package/src/index.ts +51 -0
  119. package/src/models/CalendarRootModel.ts +60 -0
  120. package/tsconfig.json +16 -0
  121. package/blazeo.com-appointment-client-1.0.5.tgz +0 -0
@@ -6,8 +6,8 @@ import {
6
6
  useMemo,
7
7
  useState,
8
8
  } from "react";
9
- import { blazeoClientConfig, initializeAppointmentClient } from "appointment-client";
10
- import { configure as configureCalendarClient } from "@blazeo.com/calendar-client";
9
+ import { blazeoClientConfig } from "appointment-client";
10
+ import { pushBlazeoConnection } from "./blazeoPushConnection.js";
11
11
 
12
12
  const STORAGE_BASE = "appointment-client-sample:blazeoBaseUrl";
13
13
  const STORAGE_CONSUMER = "appointment-client-sample:blazeoConsumer";
@@ -45,19 +45,11 @@ export function mergeBlazeoUiWithFile(uiBaseUrl, uiConsumer) {
45
45
  }
46
46
 
47
47
  /**
48
- * Re-apply global Blazeo `configure` from the merged connection card state.
49
- * Call at the start of any handler that uses `CalendarModel` / `EventModel` HTTP so
50
- * `getParticipantOpeningHours` (instance env) and static helpers always see the same
51
- * `baseUrl`.
48
+ * Re-apply global Blazeo `configure` from the effective connection card state.
49
+ * Pushes into both calendar-client entrypoints ({@link pushBlazeoConnection}).
52
50
  */
53
51
  export function configureBlazeoFromEffective(effective) {
54
- const baseUrl = normalizeBase(effective?.baseUrl ?? "");
55
- if (!baseUrl) return;
56
- const consumer = (effective?.consumer ?? "").trim() || undefined;
57
- // Configure appointment-client (which configures its internal calendar-client instance)
58
- initializeAppointmentClient({ baseUrl, ...(consumer ? { consumer } : {}) });
59
- // Also configure the calendar-client instance that the sample (and Vite alias) may resolve.
60
- configureCalendarClient({ baseUrl, ...(consumer ? { consumer } : {}) });
52
+ pushBlazeoConnection(effective);
61
53
  }
62
54
 
63
55
  const BlazeoConnectionContext = createContext(null);
@@ -71,6 +63,15 @@ export function BlazeoConnectionProvider({ children }) {
71
63
  [baseUrlInput, consumerInput]
72
64
  );
73
65
 
66
+ /** Pass into `fetchCalendarDetails`, `createCalendarAsync`, etc. (explicit `baseUrl` for `ensureBlazeoHttpReady`). */
67
+ const connectionOpts = useMemo(
68
+ () => ({
69
+ ...(effective.baseUrl ? { baseUrl: effective.baseUrl } : {}),
70
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
71
+ }),
72
+ [effective.baseUrl, effective.consumer]
73
+ );
74
+
74
75
  useEffect(() => {
75
76
  writeStored(STORAGE_BASE, baseUrlInput.trim());
76
77
  }, [baseUrlInput]);
@@ -87,8 +88,7 @@ export function BlazeoConnectionProvider({ children }) {
87
88
  useLayoutEffect(() => {
88
89
  const { baseUrl, consumer } = mergeBlazeoUiWithFile(baseUrlInput, consumerInput);
89
90
  if (!baseUrl) return;
90
- initializeAppointmentClient({ baseUrl, ...(consumer ? { consumer } : {}) });
91
- configureCalendarClient({ baseUrl, ...(consumer ? { consumer } : {}) });
91
+ pushBlazeoConnection({ baseUrl, consumer });
92
92
  }, [baseUrlInput, consumerInput]);
93
93
 
94
94
  const value = useMemo(
@@ -98,8 +98,9 @@ export function BlazeoConnectionProvider({ children }) {
98
98
  setBaseUrlInput,
99
99
  setConsumerInput,
100
100
  effective,
101
+ connectionOpts,
101
102
  }),
102
- [baseUrlInput, consumerInput, effective]
103
+ [baseUrlInput, consumerInput, effective, connectionOpts]
103
104
  );
104
105
 
105
106
  return (
@@ -1,7 +1,8 @@
1
1
  import { useMemo, useState } from "react";
2
- import { createCalendarAsync, createCalendarWithRelationsAsync } from "appointment-client";
2
+ import { createCalendarAsync, createCalendarWithRelationsAsync, ensureBlazeoHttpReady } from "appointment-client";
3
3
  import { getSnapshot } from "mobx-state-tree";
4
4
  import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
5
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
5
6
 
6
7
  /** Demo payload aligned with `CalendarBOInput` / server `CalendarBO`. */
7
8
  export function getExampleCalendarBOInput() {
@@ -38,7 +39,7 @@ export function getExampleCalendarBOInput() {
38
39
  }
39
40
 
40
41
  export function CreateCalendarTab() {
41
- const { effective } = useBlazeoConnection();
42
+ const { effective, connectionOpts } = useBlazeoConnection();
42
43
  const [localOnly, setLocalOnly] = useState(false);
43
44
  const [saveRelations, setSaveRelations] = useState(true);
44
45
  const [busy, setBusy] = useState(false);
@@ -70,7 +71,13 @@ export function CreateCalendarTab() {
70
71
  setError("Set Base URL in the connection card above.");
71
72
  return;
72
73
  }
73
- if (!localOnly) configureBlazeoFromEffective(effective);
74
+ if (!localOnly) {
75
+ configureBlazeoFromEffective(effective);
76
+ ensureBlazeoHttpReady({
77
+ baseUrl: effective.baseUrl,
78
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
79
+ });
80
+ }
74
81
 
75
82
  const hasRelations = (payload.members?.length ?? 0) > 0 || (payload.openingHours?.length ?? 0) > 0;
76
83
  const useRelations = saveRelations && hasRelations && !localOnly;
@@ -78,10 +85,20 @@ export function CreateCalendarTab() {
78
85
  setBusy(true);
79
86
  try {
80
87
  const result = useRelations
81
- ? await createCalendarWithRelationsAsync(payload, { localOnly })
82
- : await createCalendarAsync(payload, { localOnly });
88
+ ? await createCalendarWithRelationsAsync(payload, {
89
+ localOnly,
90
+ ...connectionOpts,
91
+ baseUrl: effective.baseUrl,
92
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
93
+ })
94
+ : await createCalendarAsync(payload, {
95
+ localOnly,
96
+ ...connectionOpts,
97
+ baseUrl: effective.baseUrl,
98
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
99
+ });
83
100
  setOutput(JSON.stringify(result.ok ? getSnapshot(result.calendar) : result, null, 2));
84
- if (!result.ok) setError(result.error);
101
+ if (!result.ok) setError(mapBlazeoDemoError(result.error));
85
102
  } finally {
86
103
  setBusy(false);
87
104
  }
@@ -2,6 +2,7 @@ import { useMemo, useState } from "react";
2
2
  import {
3
3
  cancelAppointmentEventAsync,
4
4
  createAppointmentEventAsync,
5
+ ensureBlazeoHttpReady,
5
6
  EventModel,
6
7
  rescheduleAppointmentEventAsync,
7
8
  } from "appointment-client";
@@ -10,6 +11,7 @@ import {
10
11
  configureBlazeoFromEffective,
11
12
  useBlazeoConnection,
12
13
  } from "./BlazeoConnectionSettings.jsx";
14
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
13
15
 
14
16
  function getExampleCreatePayload() {
15
17
  const start = new Date();
@@ -75,7 +77,7 @@ function safeJsonParse(text, fallback) {
75
77
  }
76
78
 
77
79
  export function EventTab() {
78
- const { effective } = useBlazeoConnection();
80
+ const { effective, connectionOpts } = useBlazeoConnection();
79
81
  const [offsetMinutes, setOffsetMinutes] = useState(-new Date().getTimezoneOffset());
80
82
  const [createJson, setCreateJson] = useState(() =>
81
83
  JSON.stringify(getExampleCreatePayload(), null, 2)
@@ -118,6 +120,7 @@ export function EventTab() {
118
120
  const [output, setOutput] = useState("");
119
121
 
120
122
  const opts = useMemo(() => ({ offsetMinutes: Number(offsetMinutes) || 0 }), [offsetMinutes]);
123
+ const eventOpts = useMemo(() => ({ ...opts, ...connectionOpts }), [opts, connectionOpts]);
121
124
 
122
125
  function ensureBase() {
123
126
  if (!effective.baseUrl) {
@@ -133,6 +136,10 @@ export function EventTab() {
133
136
  setOutput("");
134
137
  if (!ensureBase()) return;
135
138
  configureBlazeoFromEffective(effective);
139
+ ensureBlazeoHttpReady({
140
+ baseUrl: effective.baseUrl,
141
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
142
+ });
136
143
  let payload;
137
144
  try {
138
145
  payload = JSON.parse(createJson);
@@ -142,9 +149,9 @@ export function EventTab() {
142
149
  }
143
150
  setBusy(true);
144
151
  try {
145
- const result = await createAppointmentEventAsync(payload, opts);
152
+ const result = await createAppointmentEventAsync(payload, eventOpts);
146
153
  setOutput(resultToJson(result));
147
- if (!result.ok) setError(result.error);
154
+ if (!result.ok) setError(mapBlazeoDemoError(result.error));
148
155
  } finally {
149
156
  setBusy(false);
150
157
  }
@@ -156,6 +163,10 @@ export function EventTab() {
156
163
  setOutput("");
157
164
  if (!ensureBase()) return;
158
165
  configureBlazeoFromEffective(effective);
166
+ ensureBlazeoHttpReady({
167
+ baseUrl: effective.baseUrl,
168
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
169
+ });
159
170
  let payload;
160
171
  try {
161
172
  payload = JSON.parse(rescheduleJson);
@@ -165,9 +176,9 @@ export function EventTab() {
165
176
  }
166
177
  setBusy(true);
167
178
  try {
168
- const result = await rescheduleAppointmentEventAsync(payload, opts);
179
+ const result = await rescheduleAppointmentEventAsync(payload, eventOpts);
169
180
  setOutput(resultToJson(result));
170
- if (!result.ok) setError(result.error);
181
+ if (!result.ok) setError(mapBlazeoDemoError(result.error));
171
182
  } finally {
172
183
  setBusy(false);
173
184
  }
@@ -181,11 +192,19 @@ export function EventTab() {
181
192
  if (!id) return setError("Enter Blazeo event id to cancel.");
182
193
  if (!ensureBase()) return;
183
194
  configureBlazeoFromEffective(effective);
195
+ ensureBlazeoHttpReady({
196
+ baseUrl: effective.baseUrl,
197
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
198
+ });
184
199
  setBusy(true);
185
200
  try {
186
- const result = await cancelAppointmentEventAsync(id, {});
201
+ const result = await cancelAppointmentEventAsync(id, {
202
+ ...connectionOpts,
203
+ baseUrl: effective.baseUrl,
204
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
205
+ });
187
206
  setOutput(JSON.stringify(result, null, 2));
188
- if (!result.ok) setError(result.error);
207
+ if (!result.ok) setError(mapBlazeoDemoError(result.error));
189
208
  } finally {
190
209
  setBusy(false);
191
210
  }
@@ -201,6 +220,10 @@ export function EventTab() {
201
220
  if (!searchTo) return setError("Pick end date.");
202
221
  if (!ensureBase()) return;
203
222
  configureBlazeoFromEffective(effective);
223
+ ensureBlazeoHttpReady({
224
+ baseUrl: effective.baseUrl,
225
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
226
+ });
204
227
 
205
228
  const optsFromJson = safeJsonParse(searchFiltersJson, {});
206
229
  const startDateFrom = new Date(`${searchFrom}T00:00:00.000Z`).toISOString();
@@ -221,7 +244,7 @@ export function EventTab() {
221
244
  const totalCount = res?.totalCount ?? events.length;
222
245
  setOutput(JSON.stringify({ totalCount, events }, null, 2));
223
246
  } catch (err) {
224
- setError(err instanceof Error ? err.message : String(err));
247
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
225
248
  } finally {
226
249
  setBusy(false);
227
250
  }
@@ -2,12 +2,16 @@ import { useMemo, useState } from "react";
2
2
  import {
3
3
  CalendarModel,
4
4
  deleteCalendarAsync,
5
+ ensureBlazeoHttpReady,
5
6
  fetchCalendarDetails,
6
- fetchCalendarWithOpeningHours,
7
7
  updateCalendarAsync,
8
8
  } from "appointment-client";
9
9
  import { getSnapshot, isStateTreeNode } from "mobx-state-tree";
10
- import { configureBlazeoFromEffective, useBlazeoConnection } from "./BlazeoConnectionSettings.jsx";
10
+ import {
11
+ configureBlazeoFromEffective,
12
+ useBlazeoConnection,
13
+ } from "./BlazeoConnectionSettings.jsx";
14
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
11
15
  import { getExampleCalendarBOInput } from "./CreateCalendarTab.jsx";
12
16
 
13
17
  function pick(row, ...keys) {
@@ -33,7 +37,7 @@ function explainFetchFailure(err, configuredBaseUrl) {
33
37
  msg === "Failed to fetch" ||
34
38
  msg === "Load failed" ||
35
39
  (err instanceof TypeError && (/fetch/i.test(msg) || /network/i.test(msg)));
36
- if (!isNetwork) return msg;
40
+ if (!isNetwork) return mapBlazeoDemoError(msg);
37
41
 
38
42
  const isRemote =
39
43
  configuredBaseUrl &&
@@ -53,8 +57,11 @@ function explainFetchFailure(err, configuredBaseUrl) {
53
57
  return `${msg}\n\n${proxyHint}`;
54
58
  }
55
59
 
56
- /** Opening hours list from participant API wrapper or embedded `calendar.openingHours`. */
60
+ /** Opening hours list from `calendarView`, embedded `calendar.openingHours`, or legacy `openingHours`. */
57
61
  function pickOpeningHoursListFromBundle(parsed) {
62
+ if (Array.isArray(parsed?.openingHours)) return parsed.openingHours;
63
+ const fromView = parsed?.calendarView?.openingHours;
64
+ if (Array.isArray(fromView) && fromView.length > 0) return fromView;
58
65
  const fromCal = parsed?.calendar?.openingHours;
59
66
  if (Array.isArray(fromCal) && fromCal.length > 0) return fromCal;
60
67
  const oh = parsed?.openingHours;
@@ -81,7 +88,7 @@ function OpeningHoursSummary({ outputJson }) {
81
88
  return (
82
89
  <p className="muted small" style={{ marginBottom: "0.75rem" }}>
83
90
  No opening-hours rows parsed for the table. Check{" "}
84
- <code>calendar.openingHours</code> or <code>openingHours</code> in the JSON below.
91
+ <code>calendar.openingHours</code>, <code>calendarView.openingHours</code>, or <code>openingHours</code> in the JSON below.
85
92
  </p>
86
93
  );
87
94
  }
@@ -176,7 +183,7 @@ function calendarSnapshotToUpdatePayload(snap) {
176
183
  }
177
184
 
178
185
  export function FetchCalendarTab() {
179
- const { effective } = useBlazeoConnection();
186
+ const { effective, connectionOpts } = useBlazeoConnection();
180
187
  const [calendarId, setCalendarId] = useState("");
181
188
  const [companyKey, setCompanyKey] = useState("");
182
189
  const [busy, setBusy] = useState(false);
@@ -215,32 +222,48 @@ export function FetchCalendarTab() {
215
222
  }
216
223
  if (!ensureBaseConfigured()) return;
217
224
  configureBlazeoFromEffective(effective);
225
+ ensureBlazeoHttpReady({
226
+ baseUrl: effective.baseUrl,
227
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
228
+ });
218
229
 
219
230
  setBusy(true);
220
231
  try {
221
- const details = await fetchCalendarDetails(id, { includeParticipantsInfo: true });
232
+ const details = await fetchCalendarDetails(id, {
233
+ ...connectionOpts,
234
+ baseUrl: effective.baseUrl,
235
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
236
+ });
237
+
238
+ const meta = details?._meta ?? details?.meta;
239
+ if (meta && !meta.ok && meta.reason === "missing_base_url") {
240
+ setError(mapBlazeoDemoError(meta.detail ?? ""));
241
+ return;
242
+ }
222
243
 
223
- if (details.cal == null) {
244
+ if (!details) {
245
+ ensureBlazeoHttpReady({
246
+ baseUrl: effective.baseUrl,
247
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
248
+ });
224
249
  const raw = await CalendarModel.getRaw(id);
225
250
  setNote("CalendarModel.get returned null. Showing CalendarModel.getRaw only.");
226
251
  setOutput(toDisplayJson(raw));
227
252
  return;
228
253
  }
229
254
 
230
- const snap = getSnapshot(details.cal);
231
- setLastFetchUpdatePayload(JSON.stringify(calendarSnapshotToUpdatePayload(snap), null, 2));
232
-
233
- const payload = {
234
- calendar: details.calendar,
235
- openingHours: details.openingHours,
236
- openingHoursApiResponse: details.openingHoursApiResponse ?? null,
237
- participants: details.participants,
238
- participantsInfo: details.participantsInfo ?? null,
239
- meta: details.meta,
240
- };
255
+ const snap = details._cal ? getSnapshot(details._cal) : null;
256
+ if (snap) {
257
+ setLastFetchUpdatePayload(JSON.stringify(calendarSnapshotToUpdatePayload(snap), null, 2));
258
+ }
241
259
 
242
- setOutput(toDisplayJson(payload));
243
- setNote("Loaded calendar + opening hours + participants (3 calls) → single response object.");
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"}`);
266
+ }
244
267
  } catch (err) {
245
268
  setError(explainFetchFailure(err, effective.baseUrl));
246
269
  } finally {
@@ -261,24 +284,41 @@ export function FetchCalendarTab() {
261
284
  }
262
285
  if (!ensureBaseConfigured()) return;
263
286
  configureBlazeoFromEffective(effective);
287
+ ensureBlazeoHttpReady({
288
+ baseUrl: effective.baseUrl,
289
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
290
+ });
264
291
 
265
292
  setBusy(true);
266
293
  try {
267
- const list = await CalendarModel.getByCompany(key);
268
- if (list == null || list.length === 0) {
269
- setNote("getByCompany returned null or an empty list.");
270
- setOutput(toDisplayJson(list));
294
+ const byCompany = await CalendarModel.getByCompany(key);
295
+ const list = byCompany?.calendars ?? byCompany;
296
+ if (list == null || !Array.isArray(list) || list.length === 0) {
297
+ setNote(
298
+ "getByCompany returned null or an empty list. (calendar-client ≥1.0.17 returns { calendars, totalCount }.)"
299
+ );
300
+ setOutput(toDisplayJson(byCompany));
271
301
  } else {
272
302
  const enriched = await Promise.all(
273
303
  list.map(async (c) => {
274
304
  const id = c.calendarId ?? String(c.id ?? "");
275
305
  if (!id) return { calendar: getSnapshot(c), openingHours: [], meta: { error: "no id" } };
276
306
  try {
277
- const b = await fetchCalendarDetails(id);
307
+ const b = await fetchCalendarDetails(id, {
308
+ ...connectionOpts,
309
+ baseUrl: effective.baseUrl,
310
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
311
+ });
278
312
  return {
313
+ calendarView: b.calendarView,
279
314
  calendar: b.calendar ?? getSnapshot(c),
280
315
  openingHours: b.openingHours,
281
- participants: b.participants,
316
+ participants: (b.participants ?? []).map((p) => (isStateTreeNode(p) ? getSnapshot(p) : p)),
317
+ __openingHoursMeta: {
318
+ fromCalendarGet: b.fromCalendarGet,
319
+ fromParticipantApi: b.fromParticipantApi,
320
+ calendarViewUsedAllParticipantOpeningHours: b.meta?.calendarViewUsedAllParticipantOpeningHours,
321
+ },
282
322
  meta: b.meta,
283
323
  };
284
324
  } catch (err) {
@@ -292,8 +332,9 @@ export function FetchCalendarTab() {
292
332
  }
293
333
  })
294
334
  );
335
+ const total = byCompany?.totalCount ?? list.length;
295
336
  setNote(
296
- `Loaded ${list.length} calendar(s); opening hours + participants loaded per calendar (single details bundle).`
337
+ `Loaded ${list.length} calendar(s) (totalCount=${total}); opening hours (embed → participant API) + participants per calendar.`
297
338
  );
298
339
  setOutput(toDisplayJson(enriched));
299
340
  }
@@ -338,6 +379,10 @@ export function FetchCalendarTab() {
338
379
  setMutateOutput("");
339
380
  if (!ensureBaseConfigured()) return;
340
381
  configureBlazeoFromEffective(effective);
382
+ ensureBlazeoHttpReady({
383
+ baseUrl: effective.baseUrl,
384
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
385
+ });
341
386
  let payload;
342
387
  try {
343
388
  payload = JSON.parse(updateJson);
@@ -347,7 +392,11 @@ export function FetchCalendarTab() {
347
392
  }
348
393
  setBusy(true);
349
394
  try {
350
- const result = await updateCalendarAsync(payload, {});
395
+ const result = await updateCalendarAsync(payload, {
396
+ ...connectionOpts,
397
+ baseUrl: effective.baseUrl,
398
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
399
+ });
351
400
  if (result.ok) {
352
401
  setMutateNote("updateCalendarAsync → POST /Calendar/Event/Update");
353
402
  setMutateOutput(
@@ -361,7 +410,7 @@ export function FetchCalendarTab() {
361
410
  )
362
411
  );
363
412
  } else {
364
- setError(result.error);
413
+ setError(mapBlazeoDemoError(result.error));
365
414
  if (result.apiResponse != null) {
366
415
  setMutateOutput(JSON.stringify(result.apiResponse, null, 2));
367
416
  }
@@ -383,6 +432,10 @@ export function FetchCalendarTab() {
383
432
  }
384
433
  if (!ensureBaseConfigured()) return;
385
434
  configureBlazeoFromEffective(effective);
435
+ ensureBlazeoHttpReady({
436
+ baseUrl: effective.baseUrl,
437
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
438
+ });
386
439
  if (
387
440
  !window.confirm(
388
441
  `Delete calendar "${id}"?\n\nThis calls GET /Calendar/Remove (cannot be undone on the server).`
@@ -392,12 +445,16 @@ export function FetchCalendarTab() {
392
445
  }
393
446
  setBusy(true);
394
447
  try {
395
- const result = await deleteCalendarAsync(id, {});
448
+ const result = await deleteCalendarAsync(id, {
449
+ ...connectionOpts,
450
+ baseUrl: effective.baseUrl,
451
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
452
+ });
396
453
  if (result.ok) {
397
454
  setMutateNote("deleteCalendarAsync → GET /Calendar/Remove");
398
455
  setMutateOutput(JSON.stringify({ calendarId: id, apiResponse: result.apiResponse ?? null }, null, 2));
399
456
  } else {
400
- setError(result.error);
457
+ setError(mapBlazeoDemoError(result.error));
401
458
  if (result.apiResponse != null) {
402
459
  setMutateOutput(JSON.stringify(result.apiResponse, null, 2));
403
460
  }
@@ -410,13 +467,32 @@ export function FetchCalendarTab() {
410
467
  return (
411
468
  <>
412
469
  <div className="card">
413
- <h2>Fetch calendar</h2>
470
+ <h2>Fetch calendar · calendarView</h2>
471
+ <p className="muted small">
472
+ Runs <code>fetchCalendarDetails(calendarId)</code>. JSON field <code>calendarView</code> is first: one object with
473
+ calendar snapshot fields + <code>members</code> + <code>openingHours</code>, and a **new** <code>participants</code> array
474
+ where each participant has their own <code>openingHours</code> nested inside.
475
+ </p>
476
+ <p className="muted small">
477
+ Uses <code>fetchCalendarDetails</code>: legacy <code>openingHours</code> prefers embed on{" "}
478
+ <code>CalendarModel.getRaw</code>, else <code>getParticipantOpeningHours</code>.{" "}
479
+ <code>calendarView.openingHours</code> prefers <code>getAllParticipantOpeningHours</code> (
480
+ <code>GET /Calendar/Participant/OpeningHours/All/Get</code>) when the API returns rows. Members combine{" "}
481
+ <code>CalendarModel.getParticipants</code> + <code>CalendarModel.getParticipantsInfo</code> (each member may
482
+ include <code>participantInfo</code>).
483
+ </p>
484
+ <p className="muted small">
485
+ <strong>Single object in code:</strong> <code>fetchCalendarBundle(calendarId)</code> after{" "}
486
+ <code>initializeAppointmentClient(&#123; baseUrl, consumer &#125;)</code> — same unified shape as{" "}
487
+ <code>calendarView</code> below. This tab runs <code>fetchCalendarDetails</code> so extra arrays stay visible.
488
+ </p>
414
489
  <p className="muted small">
415
- Uses <code>fetchCalendarWithOpeningHours</code> from <code>appointment-client</code>: merges
416
- embedded <code>openingHours</code> from <code>GET /Calendar/Get</code> when present (MST omits
417
- them); otherwise calls <code>calendar.getParticipantOpeningHours()</code> (
418
- <code>GET /Calendar/Participant/OpeningHours/Get</code>), matching{" "}
419
- <code>@blazeo.com/calendar-client</code>.
490
+ <strong>DevTools Network:</strong> Each fetch fires <code>/Calendar/Get</code> <strong>twice</strong> (
491
+ <code>CalendarModel.get</code> + <code>getRaw</code>). Other calls use different URLs — filter by{" "}
492
+ <code>Participant</code>, <code>OpeningHours</code>, or <code>GetInfo</code>. Those power{" "}
493
+ <code>calendarView</code>. If you only see <code>Calendar/Get</code> yet the UI JSON has members/hours,
494
+ widen the Network filter (&quot;All&quot;) or disable search; if <code>calendarView</code> is empty/missing fields,
495
+ check the <strong>Console</strong> for errors on the participant/opening-hours requests.
420
496
  </p>
421
497
  <p className="muted small">
422
498
  Effective: <code>{effective.baseUrl || "(set connection or blazeoClientDefaults.ts)"}</code>
@@ -0,0 +1,87 @@
1
+ import { useState } from "react";
2
+ import { ensureBlazeoHttpReady, fetchCalendarWithOpeningHours } from "appointment-client";
3
+ import {
4
+ configureBlazeoFromEffective,
5
+ useBlazeoConnection,
6
+ } from "./BlazeoConnectionSettings.jsx";
7
+ import { mapBlazeoDemoError } from "./blazeoDemoError.js";
8
+
9
+ export function OpeningHoursTab() {
10
+ const { effective, connectionOpts } = useBlazeoConnection();
11
+ const [calendarId, setCalendarId] = useState("");
12
+ const [busy, setBusy] = useState(false);
13
+ const [error, setError] = useState("");
14
+ const [output, setOutput] = useState("");
15
+
16
+ async function handleFetch(e) {
17
+ e.preventDefault();
18
+ setError("");
19
+ setOutput("");
20
+ const id = calendarId.trim();
21
+ if (!id) {
22
+ setError("Enter a calendar id.");
23
+ return;
24
+ }
25
+ if (!effective.baseUrl) {
26
+ setError("Set Base URL in the connection card above.");
27
+ return;
28
+ }
29
+ configureBlazeoFromEffective(effective);
30
+ ensureBlazeoHttpReady({
31
+ baseUrl: effective.baseUrl,
32
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
33
+ });
34
+ setBusy(true);
35
+ try {
36
+ const res = await fetchCalendarWithOpeningHours(id, {
37
+ ...connectionOpts,
38
+ baseUrl: effective.baseUrl,
39
+ ...(effective.consumer ? { consumer: effective.consumer } : {}),
40
+ });
41
+ setOutput(JSON.stringify(res, null, 2));
42
+ } catch (err) {
43
+ setError(mapBlazeoDemoError(err instanceof Error ? err.message : String(err)));
44
+ } finally {
45
+ setBusy(false);
46
+ }
47
+ }
48
+
49
+ return (
50
+ <>
51
+ <div className="card">
52
+ <h2>Opening Hours (Detailed)</h2>
53
+ <p className="muted small">
54
+ Fetches business hours using <code>fetchCalendarWithOpeningHours</code>.
55
+ </p>
56
+ <form onSubmit={handleFetch} className="form">
57
+ <label className="form__label">
58
+ <span>Calendar id</span>
59
+ <input
60
+ type="text"
61
+ className="form__input"
62
+ value={calendarId}
63
+ onChange={(e) => setCalendarId(e.target.value)}
64
+ />
65
+ </label>
66
+ <button type="submit" className="btn btn--primary" disabled={busy}>
67
+ {busy ? "Loading…" : "Fetch 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
+ }