pih-appointment-widget 0.0.38 → 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/components/AppointmentPage.js +87 -62
- package/dist/components/ICD10Assistant.js +43 -46
- 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 +185 -191
- 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/icdIcons.js +23 -23
- package/src/components/AppointmentPage.js +2502 -2498
- package/src/components/ICD10Assistant.jsx +923 -923
- 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;
|
|
@@ -2660,10 +2658,10 @@
|
|
|
2660
2658
|
copy: "⎘"
|
|
2661
2659
|
};
|
|
2662
2660
|
|
|
2663
|
-
/**
|
|
2664
|
-
* ICD10Assistant.jsx
|
|
2665
|
-
* AI-powered ICD-10 coding assistant panel.
|
|
2666
|
-
* Floats as a pill button; expands into a full panel on click.
|
|
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.
|
|
2667
2665
|
*/
|
|
2668
2666
|
|
|
2669
2667
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
@@ -2712,12 +2710,12 @@
|
|
|
2712
2710
|
}, children);
|
|
2713
2711
|
}
|
|
2714
2712
|
function Spinner() {
|
|
2715
|
-
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", {
|
|
2716
2714
|
style: {
|
|
2717
2715
|
width: 20,
|
|
2718
2716
|
height: 20,
|
|
2719
2717
|
border: "2.5px solid #d1d5db",
|
|
2720
|
-
borderTop:
|
|
2718
|
+
borderTop: `2.5px solid ${C.primary}`,
|
|
2721
2719
|
borderRadius: "50%",
|
|
2722
2720
|
animation: "icd-spin 0.8s linear infinite",
|
|
2723
2721
|
flexShrink: 0
|
|
@@ -2735,7 +2733,7 @@
|
|
|
2735
2733
|
const copied = copiedKey === copyKey;
|
|
2736
2734
|
return /*#__PURE__*/React__default["default"].createElement("button", {
|
|
2737
2735
|
onClick: () => copy(text, copyKey),
|
|
2738
|
-
title: copied ? "Copied!" :
|
|
2736
|
+
title: copied ? "Copied!" : `Copy ${label}`,
|
|
2739
2737
|
style: {
|
|
2740
2738
|
display: "inline-flex",
|
|
2741
2739
|
alignItems: "center",
|
|
@@ -2744,7 +2742,7 @@
|
|
|
2744
2742
|
fontSize: 11,
|
|
2745
2743
|
fontWeight: 600,
|
|
2746
2744
|
fontFamily: FONT,
|
|
2747
|
-
border:
|
|
2745
|
+
border: `1px solid ${copied ? C.success : C.border}`,
|
|
2748
2746
|
borderRadius: 5,
|
|
2749
2747
|
background: copied ? C.successLight : C.white,
|
|
2750
2748
|
color: copied ? C.success : C.muted,
|
|
@@ -2752,7 +2750,7 @@
|
|
|
2752
2750
|
transition: "all 0.15s ease",
|
|
2753
2751
|
whiteSpace: "nowrap"
|
|
2754
2752
|
}
|
|
2755
|
-
}, copied ? "✓ Copied" :
|
|
2753
|
+
}, copied ? "✓ Copied" : `⎘ ${label}`);
|
|
2756
2754
|
}
|
|
2757
2755
|
|
|
2758
2756
|
// ─── Result Card ──────────────────────────────────────────────────────────────
|
|
@@ -2763,11 +2761,11 @@
|
|
|
2763
2761
|
copy,
|
|
2764
2762
|
copiedKey
|
|
2765
2763
|
} = _ref3;
|
|
2766
|
-
const cardCopyKey =
|
|
2767
|
-
const fullText =
|
|
2764
|
+
const cardCopyKey = `card-${index}`;
|
|
2765
|
+
const fullText = `${match.code} — ${match.description}${match.reason ? `\nNote: ${match.reason}` : ""}`;
|
|
2768
2766
|
return /*#__PURE__*/React__default["default"].createElement("div", {
|
|
2769
2767
|
style: {
|
|
2770
|
-
border:
|
|
2768
|
+
border: `1px solid ${C.border}`,
|
|
2771
2769
|
borderRadius: 10,
|
|
2772
2770
|
padding: "14px 16px",
|
|
2773
2771
|
background: C.white,
|
|
@@ -2825,10 +2823,10 @@
|
|
|
2825
2823
|
} = _ref4;
|
|
2826
2824
|
return /*#__PURE__*/React__default["default"].createElement("button", {
|
|
2827
2825
|
onClick: () => onClick(item.query),
|
|
2828
|
-
title:
|
|
2826
|
+
title: `Re-run: ${item.query}`,
|
|
2829
2827
|
style: {
|
|
2830
2828
|
padding: "4px 10px",
|
|
2831
|
-
border:
|
|
2829
|
+
border: `1px solid ${C.border}`,
|
|
2832
2830
|
borderRadius: 999,
|
|
2833
2831
|
fontSize: 11,
|
|
2834
2832
|
fontFamily: FONT,
|
|
@@ -2901,10 +2899,7 @@
|
|
|
2901
2899
|
const debounceRef = React.useRef(null);
|
|
2902
2900
|
const inputRef = React.useRef(null);
|
|
2903
2901
|
React.useEffect(() => {
|
|
2904
|
-
setTimeout(() =>
|
|
2905
|
-
var _inputRef$current;
|
|
2906
|
-
return (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
|
|
2907
|
-
}, 80);
|
|
2902
|
+
setTimeout(() => inputRef.current?.focus(), 80);
|
|
2908
2903
|
}, []);
|
|
2909
2904
|
const search = React.useCallback(async term => {
|
|
2910
2905
|
if (term.length < 2) {
|
|
@@ -2917,7 +2912,7 @@
|
|
|
2917
2912
|
const data = await getICDSuggestions(term, ICD_MODE.ICD_ONLY);
|
|
2918
2913
|
const codes = (data.matches || []).map(m => [m.code, m.description]);
|
|
2919
2914
|
setResults(codes);
|
|
2920
|
-
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" : ""}`);
|
|
2921
2916
|
} catch (err) {
|
|
2922
2917
|
setStatus(err.message || "Could not fetch results. Check your connection.");
|
|
2923
2918
|
setResults([]);
|
|
@@ -2953,9 +2948,9 @@
|
|
|
2953
2948
|
overflow: "hidden",
|
|
2954
2949
|
animation: "icd-slide-in 0.2s ease"
|
|
2955
2950
|
}
|
|
2956
|
-
}, /*#__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", {
|
|
2957
2952
|
style: {
|
|
2958
|
-
background:
|
|
2953
|
+
background: `linear-gradient(135deg, ${C.primary} 0%, ${C.teal} 100%)`,
|
|
2959
2954
|
padding: "14px 18px",
|
|
2960
2955
|
flexShrink: 0
|
|
2961
2956
|
}
|
|
@@ -3030,7 +3025,7 @@
|
|
|
3030
3025
|
padding: "10px 14px",
|
|
3031
3026
|
fontSize: 14,
|
|
3032
3027
|
fontFamily: FONT,
|
|
3033
|
-
border:
|
|
3028
|
+
border: `1.5px solid ${C.border}`,
|
|
3034
3029
|
borderRadius: 8,
|
|
3035
3030
|
outline: "none",
|
|
3036
3031
|
boxSizing: "border-box",
|
|
@@ -3044,7 +3039,7 @@
|
|
|
3044
3039
|
fontSize: 11,
|
|
3045
3040
|
color: "#aaa",
|
|
3046
3041
|
lineHeight: 1.6,
|
|
3047
|
-
borderLeft:
|
|
3042
|
+
borderLeft: `3px solid ${C.border}`,
|
|
3048
3043
|
paddingLeft: 8
|
|
3049
3044
|
}
|
|
3050
3045
|
}, /*#__PURE__*/React__default["default"].createElement("strong", null, "Note:"), " Use short, specific keywords for better results, or try", " ", /*#__PURE__*/React__default["default"].createElement("button", {
|
|
@@ -3069,7 +3064,7 @@
|
|
|
3069
3064
|
}, status), status === "No matching codes found." && results.length === 0 && /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3070
3065
|
style: {
|
|
3071
3066
|
background: C.warnLight,
|
|
3072
|
-
border:
|
|
3067
|
+
border: `1px solid #fcd34d`,
|
|
3073
3068
|
borderRadius: 10,
|
|
3074
3069
|
padding: "14px 16px",
|
|
3075
3070
|
display: "flex",
|
|
@@ -3124,7 +3119,7 @@
|
|
|
3124
3119
|
alignItems: "center",
|
|
3125
3120
|
gap: 12,
|
|
3126
3121
|
padding: "10px 14px",
|
|
3127
|
-
border:
|
|
3122
|
+
border: `1px solid ${C.border}`,
|
|
3128
3123
|
borderRadius: 8,
|
|
3129
3124
|
cursor: "pointer",
|
|
3130
3125
|
background: C.white,
|
|
@@ -3160,7 +3155,7 @@
|
|
|
3160
3155
|
style: {
|
|
3161
3156
|
flexShrink: 0,
|
|
3162
3157
|
padding: "10px 18px",
|
|
3163
|
-
borderTop:
|
|
3158
|
+
borderTop: `1px solid ${C.border}`,
|
|
3164
3159
|
background: C.bg,
|
|
3165
3160
|
fontSize: 11,
|
|
3166
3161
|
color: "#aaa",
|
|
@@ -3189,7 +3184,7 @@
|
|
|
3189
3184
|
padding: "10px 18px",
|
|
3190
3185
|
borderRadius: 999,
|
|
3191
3186
|
border: "none",
|
|
3192
|
-
background:
|
|
3187
|
+
background: `linear-gradient(135deg, ${C.primary} 0%, ${C.teal} 100%)`,
|
|
3193
3188
|
color: C.white,
|
|
3194
3189
|
fontFamily: FONT,
|
|
3195
3190
|
fontWeight: 700,
|
|
@@ -3220,7 +3215,6 @@
|
|
|
3220
3215
|
// ─── Panel ────────────────────────────────────────────────────────────────────
|
|
3221
3216
|
// Defined OUTSIDE the main component so its DOM is never torn down on re-render.
|
|
3222
3217
|
function Panel(_ref9) {
|
|
3223
|
-
var _result$matches;
|
|
3224
3218
|
let {
|
|
3225
3219
|
panelRef,
|
|
3226
3220
|
textareaRef,
|
|
@@ -3240,7 +3234,7 @@
|
|
|
3240
3234
|
mode,
|
|
3241
3235
|
onModeChange
|
|
3242
3236
|
} = _ref9;
|
|
3243
|
-
const allCodesText =
|
|
3237
|
+
const allCodesText = result?.matches?.map(m => `${m.code} — ${m.description}`).join("\n") || "";
|
|
3244
3238
|
return /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3245
3239
|
style: {
|
|
3246
3240
|
position: "fixed",
|
|
@@ -3259,9 +3253,14 @@
|
|
|
3259
3253
|
animation: "icd-slide-in 0.2s ease"
|
|
3260
3254
|
},
|
|
3261
3255
|
ref: panelRef
|
|
3262
|
-
}, /*#__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", {
|
|
3263
3262
|
style: {
|
|
3264
|
-
background:
|
|
3263
|
+
background: `linear-gradient(135deg, ${C.primary} 0%, ${C.teal} 100%)`,
|
|
3265
3264
|
padding: "14px 18px",
|
|
3266
3265
|
flexShrink: 0
|
|
3267
3266
|
}
|
|
@@ -3357,7 +3356,7 @@
|
|
|
3357
3356
|
style: {
|
|
3358
3357
|
width: "100%",
|
|
3359
3358
|
padding: "10px 12px",
|
|
3360
|
-
border:
|
|
3359
|
+
border: `1.5px solid ${C.border}`,
|
|
3361
3360
|
borderRadius: 8,
|
|
3362
3361
|
fontSize: 13,
|
|
3363
3362
|
fontFamily: FONT,
|
|
@@ -3405,7 +3404,7 @@
|
|
|
3405
3404
|
justifyContent: "center",
|
|
3406
3405
|
gap: 8,
|
|
3407
3406
|
padding: "10px 16px",
|
|
3408
|
-
background: !query.trim() || loading ? "#d1d5db" :
|
|
3407
|
+
background: !query.trim() || loading ? "#d1d5db" : `linear-gradient(135deg, ${C.primary} 0%, ${C.primaryDark} 100%)`,
|
|
3409
3408
|
color: C.white,
|
|
3410
3409
|
border: "none",
|
|
3411
3410
|
borderRadius: 8,
|
|
@@ -3420,7 +3419,7 @@
|
|
|
3420
3419
|
style: {
|
|
3421
3420
|
padding: "10px 14px",
|
|
3422
3421
|
background: C.bg,
|
|
3423
|
-
border:
|
|
3422
|
+
border: `1px solid ${C.border}`,
|
|
3424
3423
|
borderRadius: 8,
|
|
3425
3424
|
fontSize: 13,
|
|
3426
3425
|
fontWeight: 600,
|
|
@@ -3431,7 +3430,7 @@
|
|
|
3431
3430
|
}, "Clear")), error && /*#__PURE__*/React__default["default"].createElement("div", {
|
|
3432
3431
|
style: {
|
|
3433
3432
|
background: C.errorLight,
|
|
3434
|
-
border:
|
|
3433
|
+
border: `1px solid #fca5a5`,
|
|
3435
3434
|
borderRadius: 8,
|
|
3436
3435
|
padding: "10px 14px",
|
|
3437
3436
|
fontSize: 13,
|
|
@@ -3515,7 +3514,7 @@
|
|
|
3515
3514
|
style: {
|
|
3516
3515
|
flexShrink: 0,
|
|
3517
3516
|
padding: "12px 18px",
|
|
3518
|
-
borderTop:
|
|
3517
|
+
borderTop: `1px solid ${C.border}`,
|
|
3519
3518
|
background: C.bg,
|
|
3520
3519
|
fontSize: 11,
|
|
3521
3520
|
color: "#aaa",
|
|
@@ -3544,10 +3543,7 @@
|
|
|
3544
3543
|
// Focus textarea when panel opens
|
|
3545
3544
|
React.useEffect(() => {
|
|
3546
3545
|
if (open && textareaRef.current) {
|
|
3547
|
-
setTimeout(() =>
|
|
3548
|
-
var _textareaRef$current;
|
|
3549
|
-
return (_textareaRef$current = textareaRef.current) === null || _textareaRef$current === void 0 ? void 0 : _textareaRef$current.focus();
|
|
3550
|
-
}, 80);
|
|
3546
|
+
setTimeout(() => textareaRef.current?.focus(), 80);
|
|
3551
3547
|
}
|
|
3552
3548
|
}, [open]);
|
|
3553
3549
|
|
|
@@ -3590,18 +3586,16 @@
|
|
|
3590
3586
|
}
|
|
3591
3587
|
}, [handleSubmit]);
|
|
3592
3588
|
const handleClear = React.useCallback(() => {
|
|
3593
|
-
var _textareaRef$current2;
|
|
3594
3589
|
setQuery("");
|
|
3595
3590
|
setResult(null);
|
|
3596
3591
|
setError(null);
|
|
3597
|
-
|
|
3592
|
+
textareaRef.current?.focus();
|
|
3598
3593
|
}, []);
|
|
3599
3594
|
const handleHistoryClick = React.useCallback(q => {
|
|
3600
|
-
var _textareaRef$current3;
|
|
3601
3595
|
setQuery(q);
|
|
3602
3596
|
setResult(null);
|
|
3603
3597
|
setError(null);
|
|
3604
|
-
|
|
3598
|
+
textareaRef.current?.focus();
|
|
3605
3599
|
}, []);
|
|
3606
3600
|
const handleQueryChange = React.useCallback(e => setQuery(e.target.value), []);
|
|
3607
3601
|
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, !open && /*#__PURE__*/React__default["default"].createElement(FloatingButton, {
|
|
@@ -3636,14 +3630,14 @@
|
|
|
3636
3630
|
|
|
3637
3631
|
// SDK for embedding appointment widget
|
|
3638
3632
|
const BookingSDK = {
|
|
3639
|
-
/**
|
|
3640
|
-
* Show appointment widget with configuration
|
|
3641
|
-
* @param {object} config - Configuration object
|
|
3642
|
-
* @param {string} config.apiBaseUrl - Base URL for API
|
|
3643
|
-
* @param {string} config.hospitalId - Hospital ID
|
|
3644
|
-
* @param {number} config.doctorId - Doctor ID
|
|
3645
|
-
* @param {string} config.joinCallUrl - Video call URL
|
|
3646
|
-
* @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
|
|
3647
3641
|
*/
|
|
3648
3642
|
showWidget: (config, onAction) => {
|
|
3649
3643
|
if (!bookingWidgetInstance) {
|