clinic-connect-widget 1.1.9 → 1.1.10
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 +19 -3
- package/dist/clinic-widget.es.js +62 -18
- package/dist/clinic-widget.umd.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,15 @@ You can pre-select a doctor or product context by passing additional attributes.
|
|
|
49
49
|
|
|
50
50
|
data-product-id="YOUR_PRODUCT_ID"
|
|
51
51
|
data-url-video="YOUR_VIDEO_URL"
|
|
52
|
+
|
|
53
|
+
data-appointment-mode="ONLINE"
|
|
54
|
+
data-schedule-mode="TELENOW"
|
|
55
|
+
data-resource-id="YOUR_RESOURCE_ID"
|
|
56
|
+
data-resource-type="DOCTOR"
|
|
57
|
+
data-subscription="YOUR_SUBSCRIPTION_ID"
|
|
58
|
+
data-service-id="YOUR_SERVICE_ID"
|
|
59
|
+
data-order-id="YOUR_ORDER_ID"
|
|
60
|
+
|
|
52
61
|
data-locale="vi">
|
|
53
62
|
</script>
|
|
54
63
|
```
|
|
@@ -64,14 +73,21 @@ The widget is highly configurable via `data-` attributes on the script tag.
|
|
|
64
73
|
| `data-service-operator` | The Service Operator ID (e.g., 'TRUE_DOC'). | **Yes** | - |
|
|
65
74
|
| `data-trigger-element-id`| The generic DOM ID of the element where you want to render the inline trigger card (instead of floating). | No | `null` |
|
|
66
75
|
| `data-locale` | Language code (e.g., 'vi', 'en'). | No | `vi` |
|
|
67
|
-
| `data-store-id` | The Store ID associated with the booking system. | No | `
|
|
68
|
-
| `data-org-id` | The Organization ID. | No | `
|
|
76
|
+
| `data-store-id` | The Store ID associated with the booking system. | No | `demo-store` |
|
|
77
|
+
| `data-org-id` | The Organization ID. | No | `demo-org` |
|
|
69
78
|
| `data-doctor-id` | Pre-select a specific doctor context. | No | `null` |
|
|
70
79
|
| `data-doctor-name` | Custom name for the doctor (Dynamic). | No | `null` |
|
|
71
80
|
| `data-doctor-specialty` | Custom specialty for the doctor (Dynamic). | No | `null` |
|
|
72
81
|
| `data-doctor-avatar` | Custom avatar URL for the doctor (Dynamic). | No | `null` |
|
|
73
82
|
| `data-product-id` | Pre-select a specific product or service package. | No | `null` |
|
|
74
83
|
| `data-url-video` | URL for the introductory video used in the permission flow. | No | `null` |
|
|
84
|
+
| `data-appointment-mode`| Mode of appointment (e.g., 'ONLINE', 'OFFLINE'). | No | `null` |
|
|
85
|
+
| `data-schedule-mode` | Mode of schedule (e.g., 'SCHEDULED', 'TELENOW'). | No | `null` |
|
|
86
|
+
| `data-resource-id` | ID of the specific resource to book. | No | `null` |
|
|
87
|
+
| `data-resource-type` | Type of the resource (e.g., 'DOCTOR'). | No | `null` |
|
|
88
|
+
| `data-subscription` | Subscription package ID. | No | `null` |
|
|
89
|
+
| `data-service-id` | Specific service ID for the booking. | No | `null` |
|
|
90
|
+
| `data-order-id` | External order ID to link with the booking. | No | `null` |
|
|
75
91
|
|
|
76
92
|
## Client Hooks (API)
|
|
77
93
|
|
|
@@ -145,7 +161,7 @@ To verify the final build behaves as expected:
|
|
|
145
161
|
|
|
146
162
|
## Project Structure
|
|
147
163
|
|
|
148
|
-
* `src/api/`: Handles API
|
|
164
|
+
* `src/api/`: Handles API integration, parsing customer data, calling ActionBooking API, etc.
|
|
149
165
|
* `src/core/`: Main widget logic (`ClinicWidget` class), lifecycle management, and event binding.
|
|
150
166
|
* `src/state/`: Centralized state management (Store pattern).
|
|
151
167
|
* `src/ui/`: Contains HTML Templates (`templates.js`) and CSS Styles (`styles.js`).
|
package/dist/clinic-widget.es.js
CHANGED
|
@@ -72,7 +72,7 @@ const CONFIG = {
|
|
|
72
72
|
LIVEKIT_URL: "wss://livekit.longvan.vn",
|
|
73
73
|
PARTICIPANTS_API_URL: "https://call.longvan.vn/api/livekit/room/participants",
|
|
74
74
|
API_BASE_URL: "https://portal.longvan.vn",
|
|
75
|
-
BOOKING_API_URL: "https://portal.longvan.vn/dynamic-collection/public/v2/schedule/
|
|
75
|
+
BOOKING_API_URL: "https://portal.longvan.vn/dynamic-collection/public/v2/schedule/actionBooking",
|
|
76
76
|
USER_GRAPHQL: "https://user.longvan.vn/user-gateway/graphql",
|
|
77
77
|
CRM_GRAPHQL: "https://crm.longvan.vn/authorization-gateway/graphql",
|
|
78
78
|
PRODUCT_GRAPHQL: "https://product-service.longvan.vn/product-service/graphql",
|
|
@@ -256,20 +256,41 @@ class ApiService {
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
async createBooking(bookingData) {
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
const url2 = this.config.bookingApiUrl || CONFIG.BOOKING_API_URL;
|
|
260
|
+
const {
|
|
261
|
+
partnerId,
|
|
262
|
+
ownerId,
|
|
263
|
+
employeeId,
|
|
264
|
+
startDateExpect,
|
|
265
|
+
endDateExpect,
|
|
266
|
+
appointmentMode = "ONLINE",
|
|
267
|
+
scheduleMode = "TELENOW",
|
|
268
|
+
resourceId,
|
|
269
|
+
resourceType = "DOCTOR",
|
|
270
|
+
serviceOperatorId,
|
|
271
|
+
note,
|
|
272
|
+
subscription,
|
|
273
|
+
serviceId,
|
|
274
|
+
orderId
|
|
275
|
+
} = bookingData;
|
|
263
276
|
const payload = {
|
|
264
277
|
partnerId,
|
|
265
278
|
ownerId,
|
|
266
279
|
employeeId,
|
|
267
280
|
startDateExpect,
|
|
268
|
-
endDateExpect
|
|
269
|
-
|
|
270
|
-
|
|
281
|
+
endDateExpect,
|
|
282
|
+
appointmentMode,
|
|
283
|
+
scheduleMode,
|
|
284
|
+
resourceId: resourceId || employeeId,
|
|
285
|
+
// Default to employeeId if not provided
|
|
286
|
+
resourceType,
|
|
287
|
+
serviceOperatorId,
|
|
288
|
+
note
|
|
271
289
|
};
|
|
272
|
-
|
|
290
|
+
if (subscription) payload.subscription = subscription;
|
|
291
|
+
if (serviceId) payload.serviceId = serviceId;
|
|
292
|
+
if (orderId) payload.orderId = orderId;
|
|
293
|
+
console.log("[ApiService] Creating Booking (ActionBooking):", { url: url2, payload });
|
|
273
294
|
try {
|
|
274
295
|
const res = await fetch(url2, {
|
|
275
296
|
method: "POST",
|
|
@@ -280,11 +301,11 @@ class ApiService {
|
|
|
280
301
|
body: JSON.stringify(payload)
|
|
281
302
|
});
|
|
282
303
|
if (!res.ok) {
|
|
283
|
-
console.warn("[ApiService]
|
|
304
|
+
console.warn("[ApiService] ActionBooking request failed:", res.status, res.statusText);
|
|
284
305
|
return null;
|
|
285
306
|
}
|
|
286
307
|
const json = await res.json();
|
|
287
|
-
console.log("[ApiService]
|
|
308
|
+
console.log("[ApiService] ActionBooking response:", json);
|
|
288
309
|
return json;
|
|
289
310
|
} catch (e2) {
|
|
290
311
|
console.error("[ApiService] Error initiating booking:", e2);
|
|
@@ -682,7 +703,7 @@ const consultationHtml = `<div class="td-consultation-modal">\r
|
|
|
682
703
|
</aside>\r
|
|
683
704
|
</div>\r
|
|
684
705
|
`;
|
|
685
|
-
const patientFormHtml = '<div class="td-overlay-wrapper">\r\n <div class="td-modal-centered" id="td-patient-form">\r\n <header class="td-header">\r\n <div class="td-header-title">\r\n <div class="td-icon-box">\r\n <span class="material-symbols-outlined">medical_services</span>\r\n </div>\r\n <span>Tư vấn trực tuyến</span>\r\n </div>\r\n <button class="td-close-btn" id="td-form-close">\r\n <span class="material-symbols-outlined">close</span>\r\n </button>\r\n </header>\r\n\r\n <div class="td-content">\r\n <div class="td-form-container">\r\n <div class="td-form-page-header">\r\n <h1 class="td-form-title">Kết nối với Bác sĩ</h1>\r\n <p class="td-form-desc">Vui lòng nhập thông tin để bắt đầu tư vấn trực tuyến.</p>\r\n </div>\r\n\r\n <form class="td-form" id="td-info-form">\r\n <div class="td-input-group">\r\n <label class="td-label">Họ và tên <span style="color: red;">*</span></label>\r\n <input type="text" class="td-input" name="name" placeholder="VD: Nguyễn Văn A" required />\r\n </div>\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Số điện thoại <span style="color: red;">*</span></label>\r\n <div style="position: relative;">\r\n <input type="tel" class="td-input" name="phone" placeholder="VD: 0912 345 678" required />\r\n <!-- <div\r\n style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: var(--td-primary);">\r\n <span class="material-symbols-outlined" style="font-size: 20px;">smartphone</span>\r\n </div> -->\r\n </div>\r\n </div>\r\n\r\n <!-- <div class="td-input-group">\r\n <label class="td-label">Triệu chứng của bạn?</label>\r\n <div class="td-chips-group">\r\n <button type="button" class="td-chip active">Sốt <span class="material-symbols-outlined"\r\n style="font-size: 16px;">check</span></button>\r\n <button type="button" class="td-chip">Ho / Cảm cúm</button>\r\n <button type="button" class="td-chip">Đau đầu</button>\r\n <button type="button" class="td-chip">Đau bụng</button>\r\n <button type="button" class="td-chip">Đau bụng</button>\r\n </div>\r\n </div> -->\r\n\r\n
|
|
706
|
+
const patientFormHtml = '<div class="td-overlay-wrapper">\r\n <div class="td-modal-centered" id="td-patient-form">\r\n <header class="td-header">\r\n <div class="td-header-title">\r\n <div class="td-icon-box">\r\n <span class="material-symbols-outlined">medical_services</span>\r\n </div>\r\n <span>Tư vấn trực tuyến</span>\r\n </div>\r\n <button class="td-close-btn" id="td-form-close">\r\n <span class="material-symbols-outlined">close</span>\r\n </button>\r\n </header>\r\n\r\n <div class="td-content">\r\n <div class="td-form-container">\r\n <div class="td-form-page-header">\r\n <h1 class="td-form-title">Kết nối với Bác sĩ</h1>\r\n <p class="td-form-desc">Vui lòng nhập thông tin để bắt đầu tư vấn trực tuyến.</p>\r\n </div>\r\n\r\n <form class="td-form" id="td-info-form">\r\n <div class="td-input-group">\r\n <label class="td-label">Họ và tên <span style="color: red;">*</span></label>\r\n <input type="text" class="td-input" name="name" placeholder="VD: Nguyễn Văn A" required />\r\n </div>\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Số điện thoại <span style="color: red;">*</span></label>\r\n <div style="position: relative;">\r\n <input type="tel" class="td-input" name="phone" placeholder="VD: 0912 345 678" required />\r\n <!-- <div\r\n style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: var(--td-primary);">\r\n <span class="material-symbols-outlined" style="font-size: 20px;">smartphone</span>\r\n </div> -->\r\n </div>\r\n </div>\r\n\r\n <!-- <div class="td-input-group">\r\n <label class="td-label">Triệu chứng của bạn?</label>\r\n <div class="td-chips-group">\r\n <button type="button" class="td-chip active">Sốt <span class="material-symbols-outlined"\r\n style="font-size: 16px;">check</span></button>\r\n <button type="button" class="td-chip">Ho / Cảm cúm</button>\r\n <button type="button" class="td-chip">Đau đầu</button>\r\n <button type="button" class="td-chip">Đau bụng</button>\r\n <button type="button" class="td-chip">Đau bụng</button>\r\n </div>\r\n </div> -->\r\n\r\n <div class="td-input-group">\r\n <label class="td-label">Mô tả thêm <span\r\n style="color: var(--td-text-sub); font-weight: 400;">(Tùy\r\n chọn)</span></label>\r\n <textarea class="td-textarea" name="note"\r\n placeholder="Bạn đang cảm thấy như thế nào?"></textarea>\r\n </div>\r\n\r\n <div style="margin-top: 8px;">\r\n <button type="submit" class="td-btn td-btn-primary" id="td-form-submit">\r\n Bắt đầu tư vấn\r\n <span class="material-symbols-outlined" style="margin-left: 8px;">arrow_forward</span>\r\n </button>\r\n </div>\r\n\r\n <div class="td-secure-note">\r\n <span class="material-symbols-outlined" style="font-size: 14px;">lock</span>\r\n Thông tin y tế được bảo mật 100%\r\n </div>\r\n </form>\r\n </div>\r\n </div>\r\n </div>\r\n</div>';
|
|
686
707
|
const postConsultationHtml = '<div class="td-overlay-wrapper">\r\n <div class="td-modal-centered" id="td-summary-view" style="max-width: 600px;">\r\n <header class="td-header">\r\n <h2 class="td-header-title" style="font-size: 16px;">Tổng kết tư vấn</h2>\r\n <button class="td-close-btn" id="td-summary-close">\r\n <span class="material-symbols-outlined">close</span>\r\n </button>\r\n </header>\r\n\r\n <div class="td-content">\r\n <div class="td-summary-container">\r\n <!-- Success Header -->\r\n <div class="td-success-header">\r\n <div class="td-success-icon">\r\n <span class="material-symbols-outlined" style="font-size: 32px;">check_circle</span>\r\n </div>\r\n <h3 class="td-summary-title">Tư vấn Hoàn tất</h3>\r\n <p class="td-summary-desc">Cuộc hẹn đã được ghi nhận vào hồ sơ sức khỏe của bạn.</p>\r\n </div>\r\n\r\n <!-- Info Grid -->\r\n <div class="td-info-grid">\r\n <div class="td-info-card">\r\n <div class="td-info-label">MÃ CUỘC HẸN</div>\r\n <div class="td-info-value">{{bookingCode}}</div>\r\n </div>\r\n <div class="td-info-card">\r\n <div class="td-info-label">THỜI GIAN</div>\r\n <div class="td-info-value">{{visitTime}}</div>\r\n </div>\r\n </div>\r\n\r\n <div id="td-prescription-container">\r\n {{prescriptionList}}\r\n </div>\r\n\r\n <!-- Footer Actions Removed as requested -->\r\n <!-- <div class="td-footer-actions"></div> -->\r\n </div>\r\n </div>\r\n </div>\r\n</div>';
|
|
687
708
|
const inlineTriggerHtml = `<style>\r
|
|
688
709
|
@keyframes td-pulse-animation {\r
|
|
@@ -29286,22 +29307,31 @@ class ClinicWidget {
|
|
|
29286
29307
|
const teleNowSlot = state.teleNowSlot || {};
|
|
29287
29308
|
const formatDate = (val) => {
|
|
29288
29309
|
if (!val) return null;
|
|
29289
|
-
const
|
|
29310
|
+
const numVal = !isNaN(Number(val)) && typeof val !== "boolean" && String(val).trim() !== "" ? Number(val) : val;
|
|
29311
|
+
const d = new Date(numVal);
|
|
29290
29312
|
if (isNaN(d.getTime())) return null;
|
|
29291
29313
|
const offsetMs = d.getTimezoneOffset() * 60 * 1e3;
|
|
29292
29314
|
const local = new Date(d.getTime() - offsetMs);
|
|
29293
|
-
return local.toISOString().slice(0,
|
|
29315
|
+
return local.toISOString().slice(0, 19);
|
|
29294
29316
|
};
|
|
29295
|
-
const startDateExpect = formatDate(teleNowSlot.startDateExpect)
|
|
29317
|
+
const startDateExpect = formatDate(teleNowSlot.startDateExpect);
|
|
29296
29318
|
const endDateExpect = formatDate(teleNowSlot.endDateExpect);
|
|
29319
|
+
const doctorId = ((_a = state.doctor) == null ? void 0 : _a.id) || this.config.doctorId;
|
|
29297
29320
|
const bookingRes = await this.api.createBooking({
|
|
29298
29321
|
partnerId: orgId,
|
|
29299
29322
|
ownerId: customerId,
|
|
29300
|
-
employeeId:
|
|
29323
|
+
employeeId: doctorId,
|
|
29301
29324
|
startDateExpect,
|
|
29302
29325
|
endDateExpect,
|
|
29303
|
-
appointmentMode: "ONLINE",
|
|
29304
|
-
|
|
29326
|
+
appointmentMode: this.config.appointmentMode || "ONLINE",
|
|
29327
|
+
scheduleMode: this.config.scheduleMode || "TELENOW",
|
|
29328
|
+
resourceId: this.config.resourceId || doctorId,
|
|
29329
|
+
resourceType: this.config.resourceType || "DOCTOR",
|
|
29330
|
+
serviceOperatorId: this.config.serviceOperator,
|
|
29331
|
+
subscription: this.config.subscription,
|
|
29332
|
+
serviceId: this.config.serviceId,
|
|
29333
|
+
orderId: this.config.orderId,
|
|
29334
|
+
note: formData.get("note") || ""
|
|
29305
29335
|
});
|
|
29306
29336
|
console.log("[Clinic-Connect] [API] Booking Response Received:", bookingRes);
|
|
29307
29337
|
if (bookingRes && bookingRes.id) {
|
|
@@ -30259,6 +30289,13 @@ class ClinicWidget {
|
|
|
30259
30289
|
const doctorName = currentScript ? currentScript.getAttribute("data-doctor-name") : null;
|
|
30260
30290
|
const doctorSpecialty = currentScript ? currentScript.getAttribute("data-doctor-specialty") : null;
|
|
30261
30291
|
const serviceOperator = currentScript ? currentScript.getAttribute("data-service-operator") : null;
|
|
30292
|
+
const appointmentMode = currentScript ? currentScript.getAttribute("data-appointment-mode") : null;
|
|
30293
|
+
const scheduleMode = currentScript ? currentScript.getAttribute("data-schedule-mode") : null;
|
|
30294
|
+
const resourceId = currentScript ? currentScript.getAttribute("data-resource-id") : null;
|
|
30295
|
+
const resourceType = currentScript ? currentScript.getAttribute("data-resource-type") : null;
|
|
30296
|
+
const subscription = currentScript ? currentScript.getAttribute("data-subscription") : null;
|
|
30297
|
+
const serviceId = currentScript ? currentScript.getAttribute("data-service-id") : null;
|
|
30298
|
+
const orderId = currentScript ? currentScript.getAttribute("data-order-id") : null;
|
|
30262
30299
|
const config = {
|
|
30263
30300
|
widgetId,
|
|
30264
30301
|
triggerElementId,
|
|
@@ -30272,6 +30309,13 @@ class ClinicWidget {
|
|
|
30272
30309
|
doctorName,
|
|
30273
30310
|
doctorSpecialty,
|
|
30274
30311
|
serviceOperator,
|
|
30312
|
+
appointmentMode,
|
|
30313
|
+
scheduleMode,
|
|
30314
|
+
resourceId,
|
|
30315
|
+
resourceType,
|
|
30316
|
+
subscription,
|
|
30317
|
+
serviceId,
|
|
30318
|
+
orderId,
|
|
30275
30319
|
scriptElement: currentScript
|
|
30276
30320
|
};
|
|
30277
30321
|
function initWidget() {
|