pih-appointment-widget 0.0.37 → 0.0.39
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/README.md +70 -70
- package/babel.config.js +5 -5
- package/dist/App.js +10 -9
- package/dist/assets/icons/icdIcons.js +30 -0
- package/dist/components/AppointmentPage.js +87 -62
- package/dist/components/ICD10Assistant.js +144 -63
- package/dist/doctor-appointments-widget.umd.js +115 -119
- package/dist/doctor-appointments-widget.umd.min.js +1 -1
- package/dist/hooks/useClipboard.js +3 -3
- package/dist/pih-appointment-widget.umd.js +309 -208
- package/dist/pih-appointment-widget.umd.min.js +1 -1
- package/dist/services/appointmentService.js +20 -20
- package/dist/services/httpService.js +14 -18
- package/dist/services/icdService.js +21 -23
- package/package.json +67 -67
- package/public/index.html +43 -43
- package/public/manifest.json +25 -25
- package/public/robots.txt +3 -3
- package/rollup.config.js +43 -43
- package/src/App.js +50 -50
- package/src/Example.js +14 -14
- package/src/assets/icons/icdIcon.png +0 -0
- package/src/assets/icons/icdIcons.js +23 -0
- package/src/components/AppointmentPage.js +2502 -2498
- package/src/components/ICD10Assistant.jsx +923 -855
- package/src/constants/apiConfig.js +29 -29
- package/src/hooks/useClipboard.js +35 -35
- package/src/index.js +6 -6
- package/src/services/appointmentService.js +92 -92
- package/src/services/httpService.js +103 -103
- package/src/services/icdService.js +76 -76
|
@@ -9,29 +9,6 @@
|
|
|
9
9
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
10
10
|
var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
|
|
11
11
|
|
|
12
|
-
function _defineProperty(e, r, t) {
|
|
13
|
-
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
14
|
-
value: t,
|
|
15
|
-
enumerable: !0,
|
|
16
|
-
configurable: !0,
|
|
17
|
-
writable: !0
|
|
18
|
-
}) : e[r] = t, e;
|
|
19
|
-
}
|
|
20
|
-
function _toPrimitive(t, r) {
|
|
21
|
-
if ("object" != typeof t || !t) return t;
|
|
22
|
-
var e = t[Symbol.toPrimitive];
|
|
23
|
-
if (void 0 !== e) {
|
|
24
|
-
var i = e.call(t, r || "default");
|
|
25
|
-
if ("object" != typeof i) return i;
|
|
26
|
-
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
27
|
-
}
|
|
28
|
-
return ("string" === r ? String : Number)(t);
|
|
29
|
-
}
|
|
30
|
-
function _toPropertyKey(t) {
|
|
31
|
-
var i = _toPrimitive(t, "string");
|
|
32
|
-
return "symbol" == typeof i ? i : i + "";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
12
|
const getApi = async function (url) {
|
|
36
13
|
let params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
37
14
|
let token = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
|
|
@@ -41,16 +18,15 @@
|
|
|
41
18
|
method: "GET",
|
|
42
19
|
headers: {
|
|
43
20
|
"Content-Type": "application/json",
|
|
44
|
-
"Authorization":
|
|
21
|
+
"Authorization": `${token}`
|
|
45
22
|
// "X-CLIENT-APP": header,
|
|
46
23
|
}
|
|
47
24
|
};
|
|
48
25
|
return fetch(urlWithParams, options).then(async response => {
|
|
49
26
|
const data = await response.json().catch(() => ({}));
|
|
50
27
|
if (!response.ok) {
|
|
51
|
-
var _data$resultInfo;
|
|
52
28
|
return {
|
|
53
|
-
err:
|
|
29
|
+
err: data?.resultInfo?.message || "Something went wrong!",
|
|
54
30
|
status: response.status
|
|
55
31
|
};
|
|
56
32
|
}
|
|
@@ -58,7 +34,7 @@
|
|
|
58
34
|
}).catch(error => {
|
|
59
35
|
console.error("Fetch error:", error);
|
|
60
36
|
return {
|
|
61
|
-
err:
|
|
37
|
+
err: error?.message || String(error) || "Network error"
|
|
62
38
|
};
|
|
63
39
|
});
|
|
64
40
|
};
|
|
@@ -82,9 +58,8 @@
|
|
|
82
58
|
return fetch(urlWithParams.toString(), options).then(async response => {
|
|
83
59
|
const data = await response.json().catch(() => ({}));
|
|
84
60
|
if (!response.ok) {
|
|
85
|
-
var _data$resultInfo2;
|
|
86
61
|
return {
|
|
87
|
-
err:
|
|
62
|
+
err: data?.resultInfo?.message || "Something went wrong!",
|
|
88
63
|
status: response.status
|
|
89
64
|
};
|
|
90
65
|
}
|
|
@@ -97,17 +72,17 @@
|
|
|
97
72
|
});
|
|
98
73
|
};
|
|
99
74
|
|
|
100
|
-
/**
|
|
101
|
-
* SSO login: exchange idToken + email for app token.
|
|
102
|
-
* @param {string} apiBaseUrl - e.g. https://afiyaapiqa.powermindinc.com
|
|
103
|
-
* @param {string} hospitalId - e.g. dMtEGhak
|
|
104
|
-
* @param {string} idToken - JWT id token from auth provider
|
|
105
|
-
* @param {string} email - User email
|
|
106
|
-
* @returns {Promise<{ token?: string, err?: string, ... }>} - Response with token on success, or { err } on failure
|
|
75
|
+
/**
|
|
76
|
+
* SSO login: exchange idToken + email for app token.
|
|
77
|
+
* @param {string} apiBaseUrl - e.g. https://afiyaapiqa.powermindinc.com
|
|
78
|
+
* @param {string} hospitalId - e.g. dMtEGhak
|
|
79
|
+
* @param {string} idToken - JWT id token from auth provider
|
|
80
|
+
* @param {string} email - User email
|
|
81
|
+
* @returns {Promise<{ token?: string, err?: string, ... }>} - Response with token on success, or { err } on failure
|
|
107
82
|
*/
|
|
108
83
|
const getTokenFromSso = async (apiBaseUrl, hospitalId, idToken, email) => {
|
|
109
84
|
const base = apiBaseUrl.replace(/\/$/, "");
|
|
110
|
-
const url =
|
|
85
|
+
const url = `${base}/um/user/V1/sso/login?hospitalId=${encodeURIComponent(hospitalId)}`;
|
|
111
86
|
const options = {
|
|
112
87
|
method: "POST",
|
|
113
88
|
headers: {
|
|
@@ -129,8 +104,7 @@
|
|
|
129
104
|
console.log("[getTokenFromSso] response status", response.status, response.statusText);
|
|
130
105
|
if (!response.ok) {
|
|
131
106
|
return response.json().then(errorData => {
|
|
132
|
-
|
|
133
|
-
const errorMessage = (errorData === null || errorData === void 0 || (_errorData$resultInfo = errorData.resultInfo) === null || _errorData$resultInfo === void 0 ? void 0 : _errorData$resultInfo.message) || "SSO login failed";
|
|
107
|
+
const errorMessage = errorData?.resultInfo?.message || "SSO login failed";
|
|
134
108
|
console.log("[getTokenFromSso] error body", errorData);
|
|
135
109
|
return {
|
|
136
110
|
err: errorMessage,
|
|
@@ -142,11 +116,10 @@
|
|
|
142
116
|
}));
|
|
143
117
|
}
|
|
144
118
|
return response.json().then(data => {
|
|
145
|
-
var _data$data;
|
|
146
119
|
console.log("[getTokenFromSso] success", {
|
|
147
120
|
hasData: !!data,
|
|
148
121
|
dataKeys: data ? Object.keys(data) : [],
|
|
149
|
-
hasAccessToken: !!
|
|
122
|
+
hasAccessToken: !!data?.data?.access_token
|
|
150
123
|
});
|
|
151
124
|
return data;
|
|
152
125
|
});
|
|
@@ -179,13 +152,13 @@
|
|
|
179
152
|
const JOIN_CALL_URL = "https://ittisalqa.powermindinc.com/call?token=";
|
|
180
153
|
const WEB_URL = "https://afiyaproqa.powermindinc.com/";
|
|
181
154
|
|
|
182
|
-
/**
|
|
183
|
-
* Fetch appointments by status
|
|
184
|
-
* @param {string} status - Appointment status (inprogress, completed, cancelled, upcoming)
|
|
185
|
-
* @param {string} fromDate - Start date in YYYY-MM-DD format
|
|
186
|
-
* @param {string} toDate - End date in YYYY-MM-DD format
|
|
187
|
-
* @param {object} config - Optional configuration { apiBaseUrl, hospitalId, doctorId }
|
|
188
|
-
* @returns {Promise} Appointments data
|
|
155
|
+
/**
|
|
156
|
+
* Fetch appointments by status
|
|
157
|
+
* @param {string} status - Appointment status (inprogress, completed, cancelled, upcoming)
|
|
158
|
+
* @param {string} fromDate - Start date in YYYY-MM-DD format
|
|
159
|
+
* @param {string} toDate - End date in YYYY-MM-DD format
|
|
160
|
+
* @param {object} config - Optional configuration { apiBaseUrl, hospitalId, doctorId }
|
|
161
|
+
* @returns {Promise} Appointments data
|
|
189
162
|
*/
|
|
190
163
|
const getAppointmentsByStatus = async function (status, fromDate, toDate) {
|
|
191
164
|
let config = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
|
|
@@ -212,24 +185,24 @@
|
|
|
212
185
|
// params.appointmentType = String(type).toUpperCase();
|
|
213
186
|
// }
|
|
214
187
|
params.appointmentType = 'ONLINE';
|
|
215
|
-
const url =
|
|
188
|
+
const url = `${baseURL}${API_PATHS.APPOINTMENTS}`;
|
|
216
189
|
|
|
217
190
|
// Return raw response (including status) so caller can detect 401 and re-login
|
|
218
191
|
const response = await getApi(url, params, "PIH-Appointment-Widget", token);
|
|
219
192
|
return response;
|
|
220
193
|
};
|
|
221
194
|
|
|
222
|
-
/**
|
|
223
|
-
* Initiate an online consultation — returns LiveKit room token + wss URL.
|
|
224
|
-
* @param {object} appointment - Selected appointment object
|
|
225
|
-
* @param {object} config - { apiBaseUrl, hospitalId, doctorId, doctorName, appToken }
|
|
226
|
-
* @returns {Promise<{ data: { token, url, roomName, participantName }, err?, status? }>}
|
|
195
|
+
/**
|
|
196
|
+
* Initiate an online consultation — returns LiveKit room token + wss URL.
|
|
197
|
+
* @param {object} appointment - Selected appointment object
|
|
198
|
+
* @param {object} config - { apiBaseUrl, hospitalId, doctorId, doctorName, appToken }
|
|
199
|
+
* @returns {Promise<{ data: { token, url, roomName, participantName }, err?, status? }>}
|
|
227
200
|
*/
|
|
228
201
|
const initiateConsultation = async function (appointment) {
|
|
229
202
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
230
203
|
console.log("initiateConsultation -> config", config);
|
|
231
204
|
const baseURL = config.apiBaseUrl.replace(/\/$/, "");
|
|
232
|
-
const url =
|
|
205
|
+
const url = `${baseURL}${API_PATHS.INITIATE_CALL}`;
|
|
233
206
|
const appToken = config.appToken || "";
|
|
234
207
|
const body = {
|
|
235
208
|
patientId: String(appointment.patientId || ""),
|
|
@@ -252,7 +225,7 @@
|
|
|
252
225
|
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
253
226
|
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
|
|
254
227
|
return JSON.parse(atob(padded));
|
|
255
|
-
} catch
|
|
228
|
+
} catch {
|
|
256
229
|
return {};
|
|
257
230
|
}
|
|
258
231
|
}
|
|
@@ -272,32 +245,26 @@
|
|
|
272
245
|
|
|
273
246
|
// Extract from SSO login response (not from appToken JWT — these are only in login response)
|
|
274
247
|
function extractAppToken(response) {
|
|
275
|
-
var _ref, _ref2, _ref3, _response$data$access, _response$data, _response$data2;
|
|
276
248
|
if (!response || response.err) return null;
|
|
277
|
-
return
|
|
249
|
+
return response.data?.access_token ?? response.data?.token ?? response.token ?? response.accessToken ?? null;
|
|
278
250
|
}
|
|
279
251
|
function extractDoctorIdFromLoginResponse(response) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const id = (_ref4 = (_response$data$doctor = response.data.doctor_id) !== null && _response$data$doctor !== void 0 ? _response$data$doctor : response.data.doctorId) !== null && _ref4 !== void 0 ? _ref4 : null;
|
|
252
|
+
if (!response?.data) return null;
|
|
253
|
+
const id = response.data.doctor_id ?? response.data.doctorId ?? null;
|
|
283
254
|
console.log(id, 'extractDoctorIdFromLoginResponse -> id');
|
|
284
255
|
return id != null ? String(id) : null;
|
|
285
256
|
}
|
|
286
257
|
function extractUserNameFromLoginResponse(response) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const name = (_ref5 = (_ref6 = (_response$data$name = response.data.name) !== null && _response$data$name !== void 0 ? _response$data$name : response.data.doctor_name) !== null && _ref6 !== void 0 ? _ref6 : response.data.userName) !== null && _ref5 !== void 0 ? _ref5 : null;
|
|
258
|
+
if (!response?.data) return null;
|
|
259
|
+
const name = response.data.name ?? response.data.doctor_name ?? response.data.userName ?? null;
|
|
290
260
|
return name != null ? String(name) : null;
|
|
291
261
|
}
|
|
292
262
|
|
|
293
263
|
// Error boundary so a render error never shows a blank screen
|
|
294
264
|
class AppointmentErrorBoundary extends React.Component {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
hasError: false
|
|
299
|
-
});
|
|
300
|
-
}
|
|
265
|
+
state = {
|
|
266
|
+
hasError: false
|
|
267
|
+
};
|
|
301
268
|
static getDerivedStateFromError() {
|
|
302
269
|
return {
|
|
303
270
|
hasError: true
|
|
@@ -354,21 +321,21 @@
|
|
|
354
321
|
}
|
|
355
322
|
|
|
356
323
|
// SDK Component - accepts configuration from parent app
|
|
357
|
-
const AppointmentPage =
|
|
324
|
+
const AppointmentPage = _ref => {
|
|
358
325
|
let {
|
|
359
326
|
config = {}
|
|
360
|
-
} =
|
|
327
|
+
} = _ref;
|
|
361
328
|
const [storedApiBaseUrl, setStoredApiBaseUrl] = React.useState(() => {
|
|
362
329
|
try {
|
|
363
330
|
return typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY_API_BASE_URL) : null;
|
|
364
|
-
} catch
|
|
331
|
+
} catch {
|
|
365
332
|
return null;
|
|
366
333
|
}
|
|
367
334
|
});
|
|
368
335
|
const [storedHospitalId, setStoredHospitalId] = React.useState(() => {
|
|
369
336
|
try {
|
|
370
337
|
return typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY_HOSPITAL_ID) : null;
|
|
371
|
-
} catch
|
|
338
|
+
} catch {
|
|
372
339
|
return null;
|
|
373
340
|
}
|
|
374
341
|
});
|
|
@@ -379,14 +346,14 @@
|
|
|
379
346
|
const [storedIdToken, setStoredIdToken] = React.useState(() => {
|
|
380
347
|
try {
|
|
381
348
|
return typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY_ID_TOKEN) : null;
|
|
382
|
-
} catch
|
|
349
|
+
} catch {
|
|
383
350
|
return null;
|
|
384
351
|
}
|
|
385
352
|
});
|
|
386
353
|
const [storedEmail, setStoredEmail] = React.useState(() => {
|
|
387
354
|
try {
|
|
388
355
|
return typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY_EMAIL) : null;
|
|
389
|
-
} catch
|
|
356
|
+
} catch {
|
|
390
357
|
return null;
|
|
391
358
|
}
|
|
392
359
|
});
|
|
@@ -401,12 +368,14 @@
|
|
|
401
368
|
if (token && String(token).trim()) {
|
|
402
369
|
const prevToken = localStorage.getItem(STORAGE_KEY_ID_TOKEN);
|
|
403
370
|
if (prevToken !== token) {
|
|
404
|
-
// New token from parent (e.g. Flutter re-login) — clear
|
|
405
|
-
//
|
|
371
|
+
// New token from parent (e.g. Flutter re-login) — clear stored credentials.
|
|
372
|
+
// Only increment refreshLoginTrigger when there was an existing appToken to evict;
|
|
373
|
+
// if appToken is already absent, the SSO effect will fire naturally via !appToken.
|
|
374
|
+
const hadAppToken = !!localStorage.getItem(STORAGE_KEY_APP_TOKEN);
|
|
406
375
|
setAppToken(null);
|
|
407
376
|
setDoctorIdFromLogin(null);
|
|
408
377
|
setUserName(null);
|
|
409
|
-
setRefreshLoginTrigger(t => t + 1);
|
|
378
|
+
if (hadAppToken) setRefreshLoginTrigger(t => t + 1);
|
|
410
379
|
localStorage.removeItem(STORAGE_KEY_APP_TOKEN);
|
|
411
380
|
localStorage.removeItem(STORAGE_KEY_DOCTOR_ID);
|
|
412
381
|
localStorage.removeItem(STORAGE_KEY_USER_NAME);
|
|
@@ -431,21 +400,21 @@
|
|
|
431
400
|
const [appToken, setAppToken] = React.useState(() => {
|
|
432
401
|
try {
|
|
433
402
|
return typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY_APP_TOKEN) : null;
|
|
434
|
-
} catch
|
|
403
|
+
} catch {
|
|
435
404
|
return null;
|
|
436
405
|
}
|
|
437
406
|
});
|
|
438
407
|
const [doctorIdFromLogin, setDoctorIdFromLogin] = React.useState(() => {
|
|
439
408
|
try {
|
|
440
409
|
return typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY_DOCTOR_ID) : null;
|
|
441
|
-
} catch
|
|
410
|
+
} catch {
|
|
442
411
|
return null;
|
|
443
412
|
}
|
|
444
413
|
});
|
|
445
414
|
const [userName, setUserName] = React.useState(() => {
|
|
446
415
|
try {
|
|
447
416
|
return typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE_KEY_USER_NAME) : null;
|
|
448
|
-
} catch
|
|
417
|
+
} catch {
|
|
449
418
|
return null;
|
|
450
419
|
}
|
|
451
420
|
});
|
|
@@ -456,7 +425,7 @@
|
|
|
456
425
|
const hasId = localStorage.getItem(STORAGE_KEY_ID_TOKEN);
|
|
457
426
|
const hasEmail = localStorage.getItem(STORAGE_KEY_EMAIL);
|
|
458
427
|
return !hasApp && !!(hasId && hasEmail);
|
|
459
|
-
} catch
|
|
428
|
+
} catch {
|
|
460
429
|
return false;
|
|
461
430
|
}
|
|
462
431
|
});
|
|
@@ -518,7 +487,7 @@
|
|
|
518
487
|
const y = date.getFullYear();
|
|
519
488
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
520
489
|
const d = String(date.getDate()).padStart(2, "0");
|
|
521
|
-
return
|
|
490
|
+
return `${y}-${m}-${d}`;
|
|
522
491
|
};
|
|
523
492
|
const getDateRange = option => {
|
|
524
493
|
const today = new Date();
|
|
@@ -549,8 +518,8 @@
|
|
|
549
518
|
to = formatLocalDate(lastOfMonth);
|
|
550
519
|
break;
|
|
551
520
|
case "currentYear":
|
|
552
|
-
from =
|
|
553
|
-
to =
|
|
521
|
+
from = `${today.getFullYear()}-01-01`;
|
|
522
|
+
to = `${today.getFullYear()}-12-31`;
|
|
554
523
|
break;
|
|
555
524
|
default:
|
|
556
525
|
from = to = getTodayDate();
|
|
@@ -575,7 +544,7 @@
|
|
|
575
544
|
case "currentYear":
|
|
576
545
|
return "Current Year";
|
|
577
546
|
case "custom":
|
|
578
|
-
return isMobile ? "Custom" :
|
|
547
|
+
return isMobile ? "Custom" : `${fromDate} to ${toDate}`;
|
|
579
548
|
default:
|
|
580
549
|
return "Today";
|
|
581
550
|
}
|
|
@@ -660,12 +629,12 @@
|
|
|
660
629
|
|
|
661
630
|
// Helper to get unique identifier from appointment
|
|
662
631
|
const getAppointmentId = appointment => {
|
|
663
|
-
return
|
|
632
|
+
return appointment?.id || appointment?._id || appointment?.appointmentId || appointment?.patientId || JSON.stringify(appointment);
|
|
664
633
|
};
|
|
665
634
|
|
|
666
635
|
// Generate avatar with first letter if no image
|
|
667
636
|
const getPatientAvatar = appointment => {
|
|
668
|
-
if (appointment
|
|
637
|
+
if (appointment?.image) {
|
|
669
638
|
return appointment.image;
|
|
670
639
|
}
|
|
671
640
|
// Return null to use the letter avatar component
|
|
@@ -766,8 +735,8 @@
|
|
|
766
735
|
// Returns true if the current time is within 2 hours after the appointment's scheduled slot time.
|
|
767
736
|
// Handles date format: "Sun, 15 Mar 2026" and time format: "12:15 PM"
|
|
768
737
|
const isWithinJoinWindow = appointment => {
|
|
769
|
-
const dateStr =
|
|
770
|
-
const timeStr =
|
|
738
|
+
const dateStr = appointment?.date || appointment?.appointmentDate;
|
|
739
|
+
const timeStr = appointment?.time || appointment?.appointmentTime;
|
|
771
740
|
if (!dateStr || !timeStr) return false;
|
|
772
741
|
try {
|
|
773
742
|
// Parse "12:15 PM" or "9:30 AM" into 24-hour h/m values
|
|
@@ -787,11 +756,11 @@
|
|
|
787
756
|
}
|
|
788
757
|
if (isNaN(h) || isNaN(m)) return false;
|
|
789
758
|
// "Sun, 15 Mar 2026 12:15:00" — space-separated; JS parses RFC-like date strings correctly
|
|
790
|
-
const appointmentDate = new Date(
|
|
759
|
+
const appointmentDate = new Date(`${dateStr} ${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:00`);
|
|
791
760
|
if (isNaN(appointmentDate.getTime())) return false;
|
|
792
761
|
const windowEnd = new Date(appointmentDate.getTime() + 2 * 60 * 60 * 1000);
|
|
793
762
|
return new Date() <= windowEnd;
|
|
794
|
-
} catch
|
|
763
|
+
} catch {
|
|
795
764
|
return false;
|
|
796
765
|
}
|
|
797
766
|
};
|
|
@@ -802,7 +771,6 @@
|
|
|
802
771
|
setCallLoading(true);
|
|
803
772
|
setCallError(null);
|
|
804
773
|
try {
|
|
805
|
-
var _response$data3;
|
|
806
774
|
const callConfig = {
|
|
807
775
|
apiBaseUrl,
|
|
808
776
|
hospitalId,
|
|
@@ -830,7 +798,7 @@
|
|
|
830
798
|
setCallError("Session expired. Re-authenticating...");
|
|
831
799
|
return;
|
|
832
800
|
}
|
|
833
|
-
if (response.err || !
|
|
801
|
+
if (response.err || !response.data?.token) {
|
|
834
802
|
setCallError(String(response.err || "Failed to initiate call"));
|
|
835
803
|
return;
|
|
836
804
|
}
|
|
@@ -1053,6 +1021,7 @@
|
|
|
1053
1021
|
setAppToken(null);
|
|
1054
1022
|
setDoctorIdFromLogin(null);
|
|
1055
1023
|
setUserName(null);
|
|
1024
|
+
setRefreshLoginTrigger(0); // reset trigger even on failure to prevent infinite retry loops
|
|
1056
1025
|
try {
|
|
1057
1026
|
if (typeof localStorage !== "undefined") {
|
|
1058
1027
|
localStorage.removeItem(STORAGE_KEY_APP_TOKEN);
|
|
@@ -1069,6 +1038,7 @@
|
|
|
1069
1038
|
setUserName(name);
|
|
1070
1039
|
setTokenError(null);
|
|
1071
1040
|
setRedirectToHome(false);
|
|
1041
|
+
setRefreshLoginTrigger(0); // reset one-shot trigger so SSO doesn't re-fire on next dep change
|
|
1072
1042
|
try {
|
|
1073
1043
|
if (typeof localStorage !== "undefined") {
|
|
1074
1044
|
localStorage.setItem(STORAGE_KEY_APP_TOKEN, token);
|
|
@@ -1079,7 +1049,7 @@
|
|
|
1079
1049
|
}
|
|
1080
1050
|
}).catch(err => {
|
|
1081
1051
|
if (!cancelled) {
|
|
1082
|
-
setTokenError(
|
|
1052
|
+
setTokenError(err?.message || "Authentication failed. Please try again.");
|
|
1083
1053
|
setAppToken(null);
|
|
1084
1054
|
}
|
|
1085
1055
|
}).finally(() => {
|
|
@@ -1111,7 +1081,32 @@
|
|
|
1111
1081
|
|
|
1112
1082
|
// Add responsive styles and animations
|
|
1113
1083
|
const style = document.createElement("style");
|
|
1114
|
-
style.innerHTML =
|
|
1084
|
+
style.innerHTML = `
|
|
1085
|
+
@keyframes spin {
|
|
1086
|
+
0% { transform: rotate(0deg); }
|
|
1087
|
+
100% { transform: rotate(360deg); }
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
@media (max-width: 768px) {
|
|
1091
|
+
.appointments-grid {
|
|
1092
|
+
grid-template-columns: 1.5fr 1fr 0.8fr !important;
|
|
1093
|
+
}
|
|
1094
|
+
.appointments-header-grid {
|
|
1095
|
+
grid-template-columns: 1.5fr 1fr 0.8fr !important;
|
|
1096
|
+
}
|
|
1097
|
+
.hide-on-mobile {
|
|
1098
|
+
display: none !important;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
@media (max-width: 480px) {
|
|
1102
|
+
.appointments-header-grid {
|
|
1103
|
+
font-size: 10px !important;
|
|
1104
|
+
}
|
|
1105
|
+
.appointments-grid {
|
|
1106
|
+
font-size: 11px !important;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
`;
|
|
1115
1110
|
document.head.appendChild(style);
|
|
1116
1111
|
|
|
1117
1112
|
// Handle window resize
|
|
@@ -1163,7 +1158,7 @@
|
|
|
1163
1158
|
justifyContent: "center",
|
|
1164
1159
|
padding: "24px"
|
|
1165
1160
|
}
|
|
1166
|
-
}, /*#__PURE__*/React__default["default"].createElement("style", null,
|
|
1161
|
+
}, /*#__PURE__*/React__default["default"].createElement("style", null, `@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
1167
1162
|
style: {
|
|
1168
1163
|
width: "40px",
|
|
1169
1164
|
height: "40px",
|
|
@@ -1776,7 +1771,7 @@
|
|
|
1776
1771
|
style: {
|
|
1777
1772
|
fontSize: "13px"
|
|
1778
1773
|
}
|
|
1779
|
-
}, searchQuery ?
|
|
1774
|
+
}, searchQuery ? `No appointments found for "${searchQuery}"` : "No appointments found"), searchQuery && /*#__PURE__*/React__default["default"].createElement("button", {
|
|
1780
1775
|
onClick: () => setSearchQuery(""),
|
|
1781
1776
|
style: {
|
|
1782
1777
|
marginTop: "12px",
|
|
@@ -2055,7 +2050,7 @@
|
|
|
2055
2050
|
fontWeight: "700",
|
|
2056
2051
|
fontSize: isMobile ? "12px" : "13px"
|
|
2057
2052
|
}
|
|
2058
|
-
},
|
|
2053
|
+
}, selectedAppointment?.specialisation || selectedAppointment?.speciality || "N/A")), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2059
2054
|
style: {
|
|
2060
2055
|
textAlign: "right"
|
|
2061
2056
|
}
|
|
@@ -2070,7 +2065,7 @@
|
|
|
2070
2065
|
fontWeight: "700",
|
|
2071
2066
|
fontSize: isMobile ? "12px" : "13px"
|
|
2072
2067
|
}
|
|
2073
|
-
},
|
|
2068
|
+
}, selectedAppointment?.type || selectedAppointment?.appointmentType || "Online"))), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2074
2069
|
style: {
|
|
2075
2070
|
display: "flex",
|
|
2076
2071
|
justifyContent: "space-between"
|
|
@@ -2090,7 +2085,7 @@
|
|
|
2090
2085
|
fontWeight: "700",
|
|
2091
2086
|
fontSize: isMobile ? "12px" : "13px"
|
|
2092
2087
|
}
|
|
2093
|
-
},
|
|
2088
|
+
}, selectedAppointment?.date || selectedAppointment?.appointmentDate || "N/A")), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2094
2089
|
style: {
|
|
2095
2090
|
textAlign: "right"
|
|
2096
2091
|
}
|
|
@@ -2105,7 +2100,7 @@
|
|
|
2105
2100
|
fontWeight: "700",
|
|
2106
2101
|
fontSize: isMobile ? "12px" : "13px"
|
|
2107
2102
|
}
|
|
2108
|
-
},
|
|
2103
|
+
}, selectedAppointment?.time || selectedAppointment?.appointmentTime || "N/A"))), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2109
2104
|
style: {
|
|
2110
2105
|
display: "flex",
|
|
2111
2106
|
justifyContent: "space-between"
|
|
@@ -2125,7 +2120,7 @@
|
|
|
2125
2120
|
fontWeight: "700",
|
|
2126
2121
|
fontSize: isMobile ? "12px" : "13px"
|
|
2127
2122
|
}
|
|
2128
|
-
},
|
|
2123
|
+
}, selectedAppointment?.doctor || selectedAppointment?.doctorName || "N/A"))), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2129
2124
|
style: {
|
|
2130
2125
|
display: "flex",
|
|
2131
2126
|
justifyContent: "space-between"
|
|
@@ -2145,7 +2140,7 @@
|
|
|
2145
2140
|
fontWeight: "700",
|
|
2146
2141
|
fontSize: isMobile ? "12px" : "13px"
|
|
2147
2142
|
}
|
|
2148
|
-
},
|
|
2143
|
+
}, selectedAppointment?.hospital || selectedAppointment?.hospitalName || "N/A"))), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2149
2144
|
style: {
|
|
2150
2145
|
display: "flex",
|
|
2151
2146
|
justifyContent: "space-between"
|
|
@@ -2166,7 +2161,7 @@
|
|
|
2166
2161
|
fontSize: isMobile ? "11px" : "12px",
|
|
2167
2162
|
lineHeight: "1.4"
|
|
2168
2163
|
}
|
|
2169
|
-
},
|
|
2164
|
+
}, selectedAppointment?.reason || selectedAppointment?.reasonForAppointment || "No reason provided"))), (activeTab === "upcoming" || activeTab === "completed" && isWithinJoinWindow(selectedAppointment)) && /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2170
2165
|
style: {
|
|
2171
2166
|
display: "flex",
|
|
2172
2167
|
flexDirection: isMobile ? "column" : "row",
|
|
@@ -2219,12 +2214,12 @@
|
|
|
2219
2214
|
}, "Select an appointment to view details"))))))), showPipVideo && /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2220
2215
|
style: {
|
|
2221
2216
|
position: "fixed",
|
|
2222
|
-
left: isPipFullscreen ? "0" : isMobile ? "10px" :
|
|
2223
|
-
top: isPipFullscreen ? "0" : isMobile ? "70px" :
|
|
2217
|
+
left: isPipFullscreen ? "0" : isMobile ? "10px" : `${pipPosition.x}px`,
|
|
2218
|
+
top: isPipFullscreen ? "0" : isMobile ? "70px" : `${pipPosition.y}px`,
|
|
2224
2219
|
right: isPipFullscreen ? "0" : isMobile ? "10px" : "auto",
|
|
2225
2220
|
bottom: isPipFullscreen ? "0" : "auto",
|
|
2226
|
-
width: isPipFullscreen ? "100vw" : isMobile ? "calc(100vw - 20px)" : isPipMinimized ? "350px" :
|
|
2227
|
-
height: isPipFullscreen ? "100vh" : isPipMinimized ? "auto" : isMobile ? "300px" :
|
|
2221
|
+
width: isPipFullscreen ? "100vw" : isMobile ? "calc(100vw - 20px)" : isPipMinimized ? "350px" : `${pipSize.width}px`,
|
|
2222
|
+
height: isPipFullscreen ? "100vh" : isPipMinimized ? "auto" : isMobile ? "300px" : `${pipSize.height}px`,
|
|
2228
2223
|
background: "#FFFFFF",
|
|
2229
2224
|
borderRadius: isPipFullscreen ? "0" : "8px",
|
|
2230
2225
|
boxShadow: "0 8px 24px rgba(0, 0, 0, 0.3)",
|
|
@@ -2275,7 +2270,7 @@
|
|
|
2275
2270
|
textOverflow: "ellipsis",
|
|
2276
2271
|
whiteSpace: "nowrap"
|
|
2277
2272
|
}
|
|
2278
|
-
}, "Video Call - ",
|
|
2273
|
+
}, "Video Call - ", selectedAppointment?.patientName || "Patient")), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2279
2274
|
style: {
|
|
2280
2275
|
display: "flex",
|
|
2281
2276
|
gap: isMobile ? "4px" : "6px",
|
|
@@ -2404,7 +2399,7 @@
|
|
|
2404
2399
|
src: (() => {
|
|
2405
2400
|
if (!callToken) return "";
|
|
2406
2401
|
const base = String(joinCallUrlBase || "");
|
|
2407
|
-
return
|
|
2402
|
+
return `${base}${callToken}`;
|
|
2408
2403
|
})(),
|
|
2409
2404
|
style: {
|
|
2410
2405
|
width: "100%",
|
|
@@ -2475,7 +2470,12 @@
|
|
|
2475
2470
|
background: "transparent",
|
|
2476
2471
|
zIndex: 10001
|
|
2477
2472
|
}
|
|
2478
|
-
})), /*#__PURE__*/React__default["default"].createElement("style", null,
|
|
2473
|
+
})), /*#__PURE__*/React__default["default"].createElement("style", null, `
|
|
2474
|
+
@keyframes pulse {
|
|
2475
|
+
0%, 100% { opacity: 1; }
|
|
2476
|
+
50% { opacity: 0.5; }
|
|
2477
|
+
}
|
|
2478
|
+
`))), showAuthError && /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2479
2479
|
style: {
|
|
2480
2480
|
position: "fixed",
|
|
2481
2481
|
inset: 0,
|
|
@@ -2522,20 +2522,20 @@
|
|
|
2522
2522
|
return /*#__PURE__*/React__default["default"].createElement(AppointmentErrorBoundary, null, content);
|
|
2523
2523
|
};
|
|
2524
2524
|
|
|
2525
|
-
/**
|
|
2526
|
-
* icdService.js
|
|
2527
|
-
* Calls the Afiya ICD suggestion API.
|
|
2528
|
-
*
|
|
2529
|
-
* POST /insurance/api/icd/suggest
|
|
2530
|
-
* Body: { query: string, mode: ICD_MODE }
|
|
2525
|
+
/**
|
|
2526
|
+
* icdService.js
|
|
2527
|
+
* Calls the Afiya ICD suggestion API.
|
|
2528
|
+
*
|
|
2529
|
+
* POST /insurance/api/icd/suggest
|
|
2530
|
+
* Body: { query: string, mode: ICD_MODE }
|
|
2531
2531
|
*/
|
|
2532
2532
|
|
|
2533
|
-
/**
|
|
2534
|
-
* Enum for ICD suggest API modes.
|
|
2535
|
-
* Use these constants everywhere — never hardcode the string values.
|
|
2536
|
-
*
|
|
2537
|
-
* @readonly
|
|
2538
|
-
* @enum {string}
|
|
2533
|
+
/**
|
|
2534
|
+
* Enum for ICD suggest API modes.
|
|
2535
|
+
* Use these constants everywhere — never hardcode the string values.
|
|
2536
|
+
*
|
|
2537
|
+
* @readonly
|
|
2538
|
+
* @enum {string}
|
|
2539
2539
|
*/
|
|
2540
2540
|
const ICD_MODE = Object.freeze({
|
|
2541
2541
|
/** Real-time ICD-10 code lookup (fast, no AI) */
|
|
@@ -2545,15 +2545,14 @@
|
|
|
2545
2545
|
/** Backend decides best strategy automatically */
|
|
2546
2546
|
AUTO: "AUTO"
|
|
2547
2547
|
});
|
|
2548
|
-
const SUGGEST_URL =
|
|
2548
|
+
const SUGGEST_URL = `${API_BASE_URL}/insurance/api/icd/suggest`;
|
|
2549
2549
|
|
|
2550
|
-
/**
|
|
2551
|
-
* @param {string} query - Plain-language diagnosis / procedure
|
|
2552
|
-
* @param {"ICD_ONLY"|"CLAUDE_ONLY"|"AUTO"} mode
|
|
2553
|
-
* @returns {Promise<{ matches: Array<{ code, description, reason }>, note: string }>}
|
|
2550
|
+
/**
|
|
2551
|
+
* @param {string} query - Plain-language diagnosis / procedure
|
|
2552
|
+
* @param {"ICD_ONLY"|"CLAUDE_ONLY"|"AUTO"} mode
|
|
2553
|
+
* @returns {Promise<{ matches: Array<{ code, description, reason }>, note: string }>}
|
|
2554
2554
|
*/
|
|
2555
2555
|
async function getICDSuggestions(query, mode) {
|
|
2556
|
-
var _data$data;
|
|
2557
2556
|
if (!query || !query.trim()) throw new Error("Query cannot be empty");
|
|
2558
2557
|
const response = await fetch(SUGGEST_URL, {
|
|
2559
2558
|
method: "POST",
|
|
@@ -2566,18 +2565,17 @@
|
|
|
2566
2565
|
})
|
|
2567
2566
|
});
|
|
2568
2567
|
if (!response.ok) {
|
|
2569
|
-
var _errBody$error;
|
|
2570
2568
|
const errBody = await response.json().catch(() => ({}));
|
|
2571
|
-
const msg =
|
|
2569
|
+
const msg = errBody?.message || errBody?.error?.message || `API error: ${response.status} ${response.statusText}`;
|
|
2572
2570
|
throw new Error(msg);
|
|
2573
2571
|
}
|
|
2574
2572
|
const data = await response.json();
|
|
2575
2573
|
|
|
2576
2574
|
// API wraps response in { data: { matches, note, ... }, resultInfo }
|
|
2577
|
-
const payload =
|
|
2575
|
+
const payload = data?.data ?? data;
|
|
2578
2576
|
|
|
2579
2577
|
// Normalise response → always return { matches, note }
|
|
2580
|
-
if (Array.isArray(payload
|
|
2578
|
+
if (Array.isArray(payload?.matches)) {
|
|
2581
2579
|
return {
|
|
2582
2580
|
matches: payload.matches,
|
|
2583
2581
|
note: payload.note || ""
|
|
@@ -2589,7 +2587,7 @@
|
|
|
2589
2587
|
note: ""
|
|
2590
2588
|
};
|
|
2591
2589
|
}
|
|
2592
|
-
if (Array.isArray(payload
|
|
2590
|
+
if (Array.isArray(payload?.results)) {
|
|
2593
2591
|
return {
|
|
2594
2592
|
matches: payload.results.map(r => ({
|
|
2595
2593
|
code: r.code,
|
|
@@ -2602,9 +2600,9 @@
|
|
|
2602
2600
|
throw new Error("Unexpected response format from ICD suggest API");
|
|
2603
2601
|
}
|
|
2604
2602
|
|
|
2605
|
-
/**
|
|
2606
|
-
* useClipboard.js
|
|
2607
|
-
* Tiny hook for clipboard copy with transient "Copied!" feedback.
|
|
2603
|
+
/**
|
|
2604
|
+
* useClipboard.js
|
|
2605
|
+
* Tiny hook for clipboard copy with transient "Copied!" feedback.
|
|
2608
2606
|
*/
|
|
2609
2607
|
function useClipboard() {
|
|
2610
2608
|
let resetMs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1800;
|
|
@@ -2636,10 +2634,34 @@
|
|
|
2636
2634
|
};
|
|
2637
2635
|
}
|
|
2638
2636
|
|
|
2639
|
-
/**
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2637
|
+
var img = "data:image/png;base64,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";
|
|
2638
|
+
|
|
2639
|
+
// ─── Image assets ─────────────────────────────────────────────────────────────
|
|
2640
|
+
const IMG = {
|
|
2641
|
+
/** Main ICD-10 Assistant branding icon (floating button + panel header) */
|
|
2642
|
+
icdIcon: img
|
|
2643
|
+
};
|
|
2644
|
+
|
|
2645
|
+
// ─── Emoji / text icons ───────────────────────────────────────────────────────
|
|
2646
|
+
const ICON = {
|
|
2647
|
+
/** Reason / tip indicator on result cards */
|
|
2648
|
+
reason: "💡",
|
|
2649
|
+
/** Search history chip prefix */
|
|
2650
|
+
history: "🕐",
|
|
2651
|
+
/** Footer medical disclaimer */
|
|
2652
|
+
medical: "⚕️",
|
|
2653
|
+
/** Coding note (currently commented out) */
|
|
2654
|
+
codingNote: "📋",
|
|
2655
|
+
/** Copy success checkmark */
|
|
2656
|
+
copied: "✓",
|
|
2657
|
+
/** Copy action */
|
|
2658
|
+
copy: "⎘"
|
|
2659
|
+
};
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* ICD10Assistant.jsx
|
|
2663
|
+
* AI-powered ICD-10 coding assistant panel.
|
|
2664
|
+
* Floats as a pill button; expands into a full panel on click.
|
|
2643
2665
|
*/
|
|
2644
2666
|
|
|
2645
2667
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
@@ -2688,12 +2710,12 @@
|
|
|
2688
2710
|
}, children);
|
|
2689
2711
|
}
|
|
2690
2712
|
function Spinner() {
|
|
2691
|
-
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("style", null,
|
|
2713
|
+
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("style", null, `@keyframes icd-spin{to{transform:rotate(360deg)}}`), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2692
2714
|
style: {
|
|
2693
2715
|
width: 20,
|
|
2694
2716
|
height: 20,
|
|
2695
2717
|
border: "2.5px solid #d1d5db",
|
|
2696
|
-
borderTop:
|
|
2718
|
+
borderTop: `2.5px solid ${C.primary}`,
|
|
2697
2719
|
borderRadius: "50%",
|
|
2698
2720
|
animation: "icd-spin 0.8s linear infinite",
|
|
2699
2721
|
flexShrink: 0
|
|
@@ -2711,7 +2733,7 @@
|
|
|
2711
2733
|
const copied = copiedKey === copyKey;
|
|
2712
2734
|
return /*#__PURE__*/React__default["default"].createElement("button", {
|
|
2713
2735
|
onClick: () => copy(text, copyKey),
|
|
2714
|
-
title: copied ? "Copied!" :
|
|
2736
|
+
title: copied ? "Copied!" : `Copy ${label}`,
|
|
2715
2737
|
style: {
|
|
2716
2738
|
display: "inline-flex",
|
|
2717
2739
|
alignItems: "center",
|
|
@@ -2720,7 +2742,7 @@
|
|
|
2720
2742
|
fontSize: 11,
|
|
2721
2743
|
fontWeight: 600,
|
|
2722
2744
|
fontFamily: FONT,
|
|
2723
|
-
border:
|
|
2745
|
+
border: `1px solid ${copied ? C.success : C.border}`,
|
|
2724
2746
|
borderRadius: 5,
|
|
2725
2747
|
background: copied ? C.successLight : C.white,
|
|
2726
2748
|
color: copied ? C.success : C.muted,
|
|
@@ -2728,7 +2750,7 @@
|
|
|
2728
2750
|
transition: "all 0.15s ease",
|
|
2729
2751
|
whiteSpace: "nowrap"
|
|
2730
2752
|
}
|
|
2731
|
-
}, copied ? "✓ Copied" :
|
|
2753
|
+
}, copied ? "✓ Copied" : `⎘ ${label}`);
|
|
2732
2754
|
}
|
|
2733
2755
|
|
|
2734
2756
|
// ─── Result Card ──────────────────────────────────────────────────────────────
|
|
@@ -2739,11 +2761,11 @@
|
|
|
2739
2761
|
copy,
|
|
2740
2762
|
copiedKey
|
|
2741
2763
|
} = _ref3;
|
|
2742
|
-
const cardCopyKey =
|
|
2743
|
-
const fullText =
|
|
2764
|
+
const cardCopyKey = `card-${index}`;
|
|
2765
|
+
const fullText = `${match.code} — ${match.description}${match.reason ? `\nNote: ${match.reason}` : ""}`;
|
|
2744
2766
|
return /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2745
2767
|
style: {
|
|
2746
|
-
border:
|
|
2768
|
+
border: `1px solid ${C.border}`,
|
|
2747
2769
|
borderRadius: 10,
|
|
2748
2770
|
padding: "14px 16px",
|
|
2749
2771
|
background: C.white,
|
|
@@ -2790,7 +2812,7 @@
|
|
|
2790
2812
|
lineHeight: 1.5,
|
|
2791
2813
|
paddingLeft: 2
|
|
2792
2814
|
}
|
|
2793
|
-
}, "
|
|
2815
|
+
}, ICON.reason, " ", match.reason));
|
|
2794
2816
|
}
|
|
2795
2817
|
|
|
2796
2818
|
// ─── History Chip ─────────────────────────────────────────────────────────────
|
|
@@ -2801,10 +2823,10 @@
|
|
|
2801
2823
|
} = _ref4;
|
|
2802
2824
|
return /*#__PURE__*/React__default["default"].createElement("button", {
|
|
2803
2825
|
onClick: () => onClick(item.query),
|
|
2804
|
-
title:
|
|
2826
|
+
title: `Re-run: ${item.query}`,
|
|
2805
2827
|
style: {
|
|
2806
2828
|
padding: "4px 10px",
|
|
2807
|
-
border:
|
|
2829
|
+
border: `1px solid ${C.border}`,
|
|
2808
2830
|
borderRadius: 999,
|
|
2809
2831
|
fontSize: 11,
|
|
2810
2832
|
fontFamily: FONT,
|
|
@@ -2819,7 +2841,7 @@
|
|
|
2819
2841
|
},
|
|
2820
2842
|
onMouseEnter: e => e.currentTarget.style.borderColor = C.primary,
|
|
2821
2843
|
onMouseLeave: e => e.currentTarget.style.borderColor = C.border
|
|
2822
|
-
}, "
|
|
2844
|
+
}, ICON.history, " ", item.query);
|
|
2823
2845
|
}
|
|
2824
2846
|
|
|
2825
2847
|
// ─── Mode Toggle (tab bar) ──────────────────────────────────────────────────
|
|
@@ -2858,7 +2880,7 @@
|
|
|
2858
2880
|
gap: 2,
|
|
2859
2881
|
marginTop: 10
|
|
2860
2882
|
}
|
|
2861
|
-
}, tab("nlm", "Quick Lookup", "
|
|
2883
|
+
}, tab("nlm", "Quick Lookup", ""), tab("ai", "AI Suggest", ""));
|
|
2862
2884
|
}
|
|
2863
2885
|
|
|
2864
2886
|
// ─── NLM Lookup Panel ────────────────────────────────────────────────────────
|
|
@@ -2877,10 +2899,7 @@
|
|
|
2877
2899
|
const debounceRef = React.useRef(null);
|
|
2878
2900
|
const inputRef = React.useRef(null);
|
|
2879
2901
|
React.useEffect(() => {
|
|
2880
|
-
setTimeout(() =>
|
|
2881
|
-
var _inputRef$current;
|
|
2882
|
-
return (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
|
|
2883
|
-
}, 80);
|
|
2902
|
+
setTimeout(() => inputRef.current?.focus(), 80);
|
|
2884
2903
|
}, []);
|
|
2885
2904
|
const search = React.useCallback(async term => {
|
|
2886
2905
|
if (term.length < 2) {
|
|
@@ -2893,7 +2912,7 @@
|
|
|
2893
2912
|
const data = await getICDSuggestions(term, ICD_MODE.ICD_ONLY);
|
|
2894
2913
|
const codes = (data.matches || []).map(m => [m.code, m.description]);
|
|
2895
2914
|
setResults(codes);
|
|
2896
|
-
setStatus(codes.length === 0 ? "No matching codes found." :
|
|
2915
|
+
setStatus(codes.length === 0 ? "No matching codes found." : `Showing ${codes.length} result${codes.length !== 1 ? "s" : ""}`);
|
|
2897
2916
|
} catch (err) {
|
|
2898
2917
|
setStatus(err.message || "Could not fetch results. Check your connection.");
|
|
2899
2918
|
setResults([]);
|
|
@@ -2929,9 +2948,9 @@
|
|
|
2929
2948
|
overflow: "hidden",
|
|
2930
2949
|
animation: "icd-slide-in 0.2s ease"
|
|
2931
2950
|
}
|
|
2932
|
-
}, /*#__PURE__*/React__default["default"].createElement("style", null,
|
|
2951
|
+
}, /*#__PURE__*/React__default["default"].createElement("style", null, `@keyframes icd-slide-in{from{opacity:0;transform:translateY(20px) scale(0.97)}to{opacity:1;transform:translateY(0) scale(1)}}`), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2933
2952
|
style: {
|
|
2934
|
-
background:
|
|
2953
|
+
background: `linear-gradient(135deg, ${C.primary} 0%, ${C.teal} 100%)`,
|
|
2935
2954
|
padding: "14px 18px",
|
|
2936
2955
|
flexShrink: 0
|
|
2937
2956
|
}
|
|
@@ -2947,11 +2966,15 @@
|
|
|
2947
2966
|
alignItems: "center",
|
|
2948
2967
|
gap: 10
|
|
2949
2968
|
}
|
|
2950
|
-
}, /*#__PURE__*/React__default["default"].createElement("
|
|
2969
|
+
}, /*#__PURE__*/React__default["default"].createElement("img", {
|
|
2970
|
+
src: IMG.icdIcon,
|
|
2971
|
+
alt: "ICD-10",
|
|
2951
2972
|
style: {
|
|
2952
|
-
|
|
2973
|
+
width: 32,
|
|
2974
|
+
height: 32,
|
|
2975
|
+
objectFit: "contain"
|
|
2953
2976
|
}
|
|
2954
|
-
}
|
|
2977
|
+
}), /*#__PURE__*/React__default["default"].createElement("div", null, /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2955
2978
|
style: {
|
|
2956
2979
|
color: C.white,
|
|
2957
2980
|
fontWeight: 700,
|
|
@@ -3002,7 +3025,7 @@
|
|
|
3002
3025
|
padding: "10px 14px",
|
|
3003
3026
|
fontSize: 14,
|
|
3004
3027
|
fontFamily: FONT,
|
|
3005
|
-
border:
|
|
3028
|
+
border: `1.5px solid ${C.border}`,
|
|
3006
3029
|
borderRadius: 8,
|
|
3007
3030
|
outline: "none",
|
|
3008
3031
|
boxSizing: "border-box",
|
|
@@ -3010,13 +3033,77 @@
|
|
|
3010
3033
|
},
|
|
3011
3034
|
onFocus: e => e.target.style.borderColor = C.primary,
|
|
3012
3035
|
onBlur: e => e.target.style.borderColor = C.border
|
|
3013
|
-
}),
|
|
3036
|
+
}), /*#__PURE__*/React__default["default"].createElement("p", {
|
|
3037
|
+
style: {
|
|
3038
|
+
margin: 0,
|
|
3039
|
+
fontSize: 11,
|
|
3040
|
+
color: "#aaa",
|
|
3041
|
+
lineHeight: 1.6,
|
|
3042
|
+
borderLeft: `3px solid ${C.border}`,
|
|
3043
|
+
paddingLeft: 8
|
|
3044
|
+
}
|
|
3045
|
+
}, /*#__PURE__*/React__default["default"].createElement("strong", null, "Note:"), " Use short, specific keywords for better results, or try", " ", /*#__PURE__*/React__default["default"].createElement("button", {
|
|
3046
|
+
onClick: () => onModeChange("ai"),
|
|
3047
|
+
style: {
|
|
3048
|
+
background: "none",
|
|
3049
|
+
border: "none",
|
|
3050
|
+
padding: 0,
|
|
3051
|
+
color: C.primary,
|
|
3052
|
+
fontWeight: 700,
|
|
3053
|
+
fontSize: 11,
|
|
3054
|
+
cursor: "pointer",
|
|
3055
|
+
textDecoration: "underline",
|
|
3056
|
+
fontFamily: FONT
|
|
3057
|
+
}
|
|
3058
|
+
}, "AI Suggest"), " ", "for broader symptom-based recommendations."), status && results.length > 0 && /*#__PURE__*/React__default["default"].createElement("p", {
|
|
3014
3059
|
style: {
|
|
3015
3060
|
margin: 0,
|
|
3016
3061
|
fontSize: 12,
|
|
3017
3062
|
color: C.muted
|
|
3018
3063
|
}
|
|
3019
|
-
}, status), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3064
|
+
}, status), status === "No matching codes found." && results.length === 0 && /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3065
|
+
style: {
|
|
3066
|
+
background: C.warnLight,
|
|
3067
|
+
border: `1px solid #fcd34d`,
|
|
3068
|
+
borderRadius: 10,
|
|
3069
|
+
padding: "14px 16px",
|
|
3070
|
+
display: "flex",
|
|
3071
|
+
gap: 10,
|
|
3072
|
+
alignItems: "flex-start"
|
|
3073
|
+
}
|
|
3074
|
+
}, /*#__PURE__*/React__default["default"].createElement("span", {
|
|
3075
|
+
style: {
|
|
3076
|
+
fontSize: 18,
|
|
3077
|
+
flexShrink: 0
|
|
3078
|
+
}
|
|
3079
|
+
}, "\uD83D\uDD0D"), /*#__PURE__*/React__default["default"].createElement("div", null, /*#__PURE__*/React__default["default"].createElement("p", {
|
|
3080
|
+
style: {
|
|
3081
|
+
margin: "0 0 4px",
|
|
3082
|
+
fontSize: 13,
|
|
3083
|
+
fontWeight: 700,
|
|
3084
|
+
color: C.warn
|
|
3085
|
+
}
|
|
3086
|
+
}, "No results found"), /*#__PURE__*/React__default["default"].createElement("p", {
|
|
3087
|
+
style: {
|
|
3088
|
+
margin: 0,
|
|
3089
|
+
fontSize: 12,
|
|
3090
|
+
color: C.warn,
|
|
3091
|
+
lineHeight: 1.6
|
|
3092
|
+
}
|
|
3093
|
+
}, "We couldn't find a match. Try using more accurate keywords or switch to", " ", /*#__PURE__*/React__default["default"].createElement("button", {
|
|
3094
|
+
onClick: () => onModeChange("ai"),
|
|
3095
|
+
style: {
|
|
3096
|
+
background: "none",
|
|
3097
|
+
border: "none",
|
|
3098
|
+
padding: 0,
|
|
3099
|
+
color: C.primary,
|
|
3100
|
+
fontWeight: 700,
|
|
3101
|
+
fontSize: 12,
|
|
3102
|
+
cursor: "pointer",
|
|
3103
|
+
textDecoration: "underline",
|
|
3104
|
+
fontFamily: FONT
|
|
3105
|
+
}
|
|
3106
|
+
}, "AI Suggest"), " ", "for better results."))), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3020
3107
|
style: {
|
|
3021
3108
|
display: "flex",
|
|
3022
3109
|
flexDirection: "column",
|
|
@@ -3032,7 +3119,7 @@
|
|
|
3032
3119
|
alignItems: "center",
|
|
3033
3120
|
gap: 12,
|
|
3034
3121
|
padding: "10px 14px",
|
|
3035
|
-
border:
|
|
3122
|
+
border: `1px solid ${C.border}`,
|
|
3036
3123
|
borderRadius: 8,
|
|
3037
3124
|
cursor: "pointer",
|
|
3038
3125
|
background: C.white,
|
|
@@ -3068,13 +3155,13 @@
|
|
|
3068
3155
|
style: {
|
|
3069
3156
|
flexShrink: 0,
|
|
3070
3157
|
padding: "10px 18px",
|
|
3071
|
-
borderTop:
|
|
3158
|
+
borderTop: `1px solid ${C.border}`,
|
|
3072
3159
|
background: C.bg,
|
|
3073
3160
|
fontSize: 11,
|
|
3074
3161
|
color: "#aaa",
|
|
3075
3162
|
textAlign: "center"
|
|
3076
3163
|
}
|
|
3077
|
-
}, "
|
|
3164
|
+
}, ICON.medical, " For reference only. Always verify codes with a certified medical coder."));
|
|
3078
3165
|
}
|
|
3079
3166
|
|
|
3080
3167
|
// ─── FloatingButton ───────────────────────────────────────────────────────────
|
|
@@ -3097,7 +3184,7 @@
|
|
|
3097
3184
|
padding: "10px 18px",
|
|
3098
3185
|
borderRadius: 999,
|
|
3099
3186
|
border: "none",
|
|
3100
|
-
background:
|
|
3187
|
+
background: `linear-gradient(135deg, ${C.primary} 0%, ${C.teal} 100%)`,
|
|
3101
3188
|
color: C.white,
|
|
3102
3189
|
fontFamily: FONT,
|
|
3103
3190
|
fontWeight: 700,
|
|
@@ -3114,17 +3201,20 @@
|
|
|
3114
3201
|
e.currentTarget.style.transform = "translateY(0)";
|
|
3115
3202
|
e.currentTarget.style.boxShadow = "0 4px 16px rgba(76,77,220,0.35)";
|
|
3116
3203
|
}
|
|
3117
|
-
}, /*#__PURE__*/React__default["default"].createElement("
|
|
3204
|
+
}, /*#__PURE__*/React__default["default"].createElement("img", {
|
|
3205
|
+
src: IMG.icdIcon,
|
|
3206
|
+
alt: "ICD-10",
|
|
3118
3207
|
style: {
|
|
3119
|
-
|
|
3208
|
+
width: 22,
|
|
3209
|
+
height: 22,
|
|
3210
|
+
objectFit: "contain"
|
|
3120
3211
|
}
|
|
3121
|
-
}
|
|
3212
|
+
}), "ICD-10 Assistant");
|
|
3122
3213
|
}
|
|
3123
3214
|
|
|
3124
3215
|
// ─── Panel ────────────────────────────────────────────────────────────────────
|
|
3125
3216
|
// Defined OUTSIDE the main component so its DOM is never torn down on re-render.
|
|
3126
3217
|
function Panel(_ref9) {
|
|
3127
|
-
var _result$matches;
|
|
3128
3218
|
let {
|
|
3129
3219
|
panelRef,
|
|
3130
3220
|
textareaRef,
|
|
@@ -3144,7 +3234,7 @@
|
|
|
3144
3234
|
mode,
|
|
3145
3235
|
onModeChange
|
|
3146
3236
|
} = _ref9;
|
|
3147
|
-
const allCodesText =
|
|
3237
|
+
const allCodesText = result?.matches?.map(m => `${m.code} — ${m.description}`).join("\n") || "";
|
|
3148
3238
|
return /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3149
3239
|
style: {
|
|
3150
3240
|
position: "fixed",
|
|
@@ -3163,9 +3253,14 @@
|
|
|
3163
3253
|
animation: "icd-slide-in 0.2s ease"
|
|
3164
3254
|
},
|
|
3165
3255
|
ref: panelRef
|
|
3166
|
-
}, /*#__PURE__*/React__default["default"].createElement("style", null,
|
|
3256
|
+
}, /*#__PURE__*/React__default["default"].createElement("style", null, `
|
|
3257
|
+
@keyframes icd-slide-in {
|
|
3258
|
+
from { opacity: 0; transform: translateY(20px) scale(0.97); }
|
|
3259
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
3260
|
+
}
|
|
3261
|
+
`), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3167
3262
|
style: {
|
|
3168
|
-
background:
|
|
3263
|
+
background: `linear-gradient(135deg, ${C.primary} 0%, ${C.teal} 100%)`,
|
|
3169
3264
|
padding: "14px 18px",
|
|
3170
3265
|
flexShrink: 0
|
|
3171
3266
|
}
|
|
@@ -3181,11 +3276,15 @@
|
|
|
3181
3276
|
alignItems: "center",
|
|
3182
3277
|
gap: 10
|
|
3183
3278
|
}
|
|
3184
|
-
}, /*#__PURE__*/React__default["default"].createElement("
|
|
3279
|
+
}, /*#__PURE__*/React__default["default"].createElement("img", {
|
|
3280
|
+
src: IMG.icdIcon,
|
|
3281
|
+
alt: "ICD-10",
|
|
3185
3282
|
style: {
|
|
3186
|
-
|
|
3283
|
+
width: 32,
|
|
3284
|
+
height: 32,
|
|
3285
|
+
objectFit: "contain"
|
|
3187
3286
|
}
|
|
3188
|
-
}
|
|
3287
|
+
}), /*#__PURE__*/React__default["default"].createElement("div", null, /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3189
3288
|
style: {
|
|
3190
3289
|
color: C.white,
|
|
3191
3290
|
fontWeight: 700,
|
|
@@ -3257,7 +3356,7 @@
|
|
|
3257
3356
|
style: {
|
|
3258
3357
|
width: "100%",
|
|
3259
3358
|
padding: "10px 12px",
|
|
3260
|
-
border:
|
|
3359
|
+
border: `1.5px solid ${C.border}`,
|
|
3261
3360
|
borderRadius: 8,
|
|
3262
3361
|
fontSize: 13,
|
|
3263
3362
|
fontFamily: FONT,
|
|
@@ -3283,7 +3382,14 @@
|
|
|
3283
3382
|
borderRadius: 3,
|
|
3284
3383
|
fontFamily: "monospace"
|
|
3285
3384
|
}
|
|
3286
|
-
}, "
|
|
3385
|
+
}, "Enter"), " to submit \xB7 ", /*#__PURE__*/React__default["default"].createElement("kbd", {
|
|
3386
|
+
style: {
|
|
3387
|
+
background: "#f3f4f6",
|
|
3388
|
+
padding: "1px 5px",
|
|
3389
|
+
borderRadius: 3,
|
|
3390
|
+
fontFamily: "monospace"
|
|
3391
|
+
}
|
|
3392
|
+
}, "Shift+Enter"), " for new line")), /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3287
3393
|
style: {
|
|
3288
3394
|
display: "flex",
|
|
3289
3395
|
gap: 8
|
|
@@ -3298,7 +3404,7 @@
|
|
|
3298
3404
|
justifyContent: "center",
|
|
3299
3405
|
gap: 8,
|
|
3300
3406
|
padding: "10px 16px",
|
|
3301
|
-
background: !query.trim() || loading ? "#d1d5db" :
|
|
3407
|
+
background: !query.trim() || loading ? "#d1d5db" : `linear-gradient(135deg, ${C.primary} 0%, ${C.primaryDark} 100%)`,
|
|
3302
3408
|
color: C.white,
|
|
3303
3409
|
border: "none",
|
|
3304
3410
|
borderRadius: 8,
|
|
@@ -3308,12 +3414,12 @@
|
|
|
3308
3414
|
cursor: !query.trim() || loading ? "not-allowed" : "pointer",
|
|
3309
3415
|
transition: "background 0.15s"
|
|
3310
3416
|
}
|
|
3311
|
-
}, loading ? /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Spinner, null), "Searching codes\u2026") : "
|
|
3417
|
+
}, loading ? /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Spinner, null), "Searching codes\u2026") : "Find ICD-10 Codes"), (query || result) && /*#__PURE__*/React__default["default"].createElement("button", {
|
|
3312
3418
|
onClick: onClear,
|
|
3313
3419
|
style: {
|
|
3314
3420
|
padding: "10px 14px",
|
|
3315
3421
|
background: C.bg,
|
|
3316
|
-
border:
|
|
3422
|
+
border: `1px solid ${C.border}`,
|
|
3317
3423
|
borderRadius: 8,
|
|
3318
3424
|
fontSize: 13,
|
|
3319
3425
|
fontWeight: 600,
|
|
@@ -3324,7 +3430,7 @@
|
|
|
3324
3430
|
}, "Clear")), error && /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3325
3431
|
style: {
|
|
3326
3432
|
background: C.errorLight,
|
|
3327
|
-
border:
|
|
3433
|
+
border: `1px solid #fca5a5`,
|
|
3328
3434
|
borderRadius: 8,
|
|
3329
3435
|
padding: "10px 14px",
|
|
3330
3436
|
fontSize: 13,
|
|
@@ -3408,14 +3514,14 @@
|
|
|
3408
3514
|
style: {
|
|
3409
3515
|
flexShrink: 0,
|
|
3410
3516
|
padding: "12px 18px",
|
|
3411
|
-
borderTop:
|
|
3517
|
+
borderTop: `1px solid ${C.border}`,
|
|
3412
3518
|
background: C.bg,
|
|
3413
3519
|
fontSize: 11,
|
|
3414
3520
|
color: "#aaa",
|
|
3415
3521
|
lineHeight: 1.5,
|
|
3416
3522
|
textAlign: "center"
|
|
3417
3523
|
}
|
|
3418
|
-
}, "
|
|
3524
|
+
}, ICON.medical, " For reference only. Always verify codes with a certified medical coder. Not a substitute for clinical judgment or official coding guidelines."));
|
|
3419
3525
|
}
|
|
3420
3526
|
|
|
3421
3527
|
// ─── Main Component ────────────────────────────────────────────────────────────
|
|
@@ -3437,10 +3543,7 @@
|
|
|
3437
3543
|
// Focus textarea when panel opens
|
|
3438
3544
|
React.useEffect(() => {
|
|
3439
3545
|
if (open && textareaRef.current) {
|
|
3440
|
-
setTimeout(() =>
|
|
3441
|
-
var _textareaRef$current;
|
|
3442
|
-
return (_textareaRef$current = textareaRef.current) === null || _textareaRef$current === void 0 ? void 0 : _textareaRef$current.focus();
|
|
3443
|
-
}, 80);
|
|
3546
|
+
setTimeout(() => textareaRef.current?.focus(), 80);
|
|
3444
3547
|
}
|
|
3445
3548
|
}, [open]);
|
|
3446
3549
|
|
|
@@ -3477,24 +3580,22 @@
|
|
|
3477
3580
|
}
|
|
3478
3581
|
}, [query, loading]);
|
|
3479
3582
|
const handleKeyDown = React.useCallback(e => {
|
|
3480
|
-
if (
|
|
3583
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
3481
3584
|
e.preventDefault();
|
|
3482
3585
|
handleSubmit();
|
|
3483
3586
|
}
|
|
3484
3587
|
}, [handleSubmit]);
|
|
3485
3588
|
const handleClear = React.useCallback(() => {
|
|
3486
|
-
var _textareaRef$current2;
|
|
3487
3589
|
setQuery("");
|
|
3488
3590
|
setResult(null);
|
|
3489
3591
|
setError(null);
|
|
3490
|
-
|
|
3592
|
+
textareaRef.current?.focus();
|
|
3491
3593
|
}, []);
|
|
3492
3594
|
const handleHistoryClick = React.useCallback(q => {
|
|
3493
|
-
var _textareaRef$current3;
|
|
3494
3595
|
setQuery(q);
|
|
3495
3596
|
setResult(null);
|
|
3496
3597
|
setError(null);
|
|
3497
|
-
|
|
3598
|
+
textareaRef.current?.focus();
|
|
3498
3599
|
}, []);
|
|
3499
3600
|
const handleQueryChange = React.useCallback(e => setQuery(e.target.value), []);
|
|
3500
3601
|
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, !open && /*#__PURE__*/React__default["default"].createElement(FloatingButton, {
|
|
@@ -3529,14 +3630,14 @@
|
|
|
3529
3630
|
|
|
3530
3631
|
// SDK for embedding appointment widget
|
|
3531
3632
|
const BookingSDK = {
|
|
3532
|
-
/**
|
|
3533
|
-
* Show appointment widget with configuration
|
|
3534
|
-
* @param {object} config - Configuration object
|
|
3535
|
-
* @param {string} config.apiBaseUrl - Base URL for API
|
|
3536
|
-
* @param {string} config.hospitalId - Hospital ID
|
|
3537
|
-
* @param {number} config.doctorId - Doctor ID
|
|
3538
|
-
* @param {string} config.joinCallUrl - Video call URL
|
|
3539
|
-
* @param {function} onAction - Optional callback for actions
|
|
3633
|
+
/**
|
|
3634
|
+
* Show appointment widget with configuration
|
|
3635
|
+
* @param {object} config - Configuration object
|
|
3636
|
+
* @param {string} config.apiBaseUrl - Base URL for API
|
|
3637
|
+
* @param {string} config.hospitalId - Hospital ID
|
|
3638
|
+
* @param {number} config.doctorId - Doctor ID
|
|
3639
|
+
* @param {string} config.joinCallUrl - Video call URL
|
|
3640
|
+
* @param {function} onAction - Optional callback for actions
|
|
3540
3641
|
*/
|
|
3541
3642
|
showWidget: (config, onAction) => {
|
|
3542
3643
|
if (!bookingWidgetInstance) {
|