@edvak99/voice-receptionist-widget 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +3 -0
  2. package/fesm2022/{edvak99-voice-receptionist-widget-analytics-page.component-CW5OKmik.mjs → edvak99-voice-receptionist-widget-analytics-page.component-Dsujn5az.mjs} +4 -4
  3. package/fesm2022/{edvak99-voice-receptionist-widget-analytics-page.component-CW5OKmik.mjs.map → edvak99-voice-receptionist-widget-analytics-page.component-Dsujn5az.mjs.map} +1 -1
  4. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-xoLlHj8K.mjs +67 -0
  5. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-xoLlHj8K.mjs.map +1 -0
  6. package/fesm2022/{edvak99-voice-receptionist-widget-call-history-detail-page.component-CTwfUHUf.mjs → edvak99-voice-receptionist-widget-call-history-detail-page.component-2K9W77As.mjs} +11 -11
  7. package/fesm2022/{edvak99-voice-receptionist-widget-call-history-detail-page.component-CTwfUHUf.mjs.map → edvak99-voice-receptionist-widget-call-history-detail-page.component-2K9W77As.mjs.map} +1 -1
  8. package/fesm2022/{edvak99-voice-receptionist-widget-call-history-page.component-Cj4wxd4e.mjs → edvak99-voice-receptionist-widget-call-history-page.component-B5AnEjoh.mjs} +62 -26
  9. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-B5AnEjoh.mjs.map +1 -0
  10. package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-AN6GcSDq.mjs → edvak99-voice-receptionist-widget-call-recording-player.component-B8_qp7Fe.mjs} +4 -4
  11. package/fesm2022/{edvak99-voice-receptionist-widget-call-recording-player.component-AN6GcSDq.mjs.map → edvak99-voice-receptionist-widget-call-recording-player.component-B8_qp7Fe.mjs.map} +1 -1
  12. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Cy_Bf7AO.mjs → edvak99-voice-receptionist-widget-call-review.service-Cj2XTeK4.mjs} +2 -2
  13. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.service-Cy_Bf7AO.mjs.map → edvak99-voice-receptionist-widget-call-review.service-Cj2XTeK4.mjs.map} +1 -1
  14. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs → edvak99-voice-receptionist-widget-call-review.util-BgsBCKgZ.mjs} +2 -2
  15. package/fesm2022/{edvak99-voice-receptionist-widget-call-review.util-DQLIw7xb.mjs.map → edvak99-voice-receptionist-widget-call-review.util-BgsBCKgZ.mjs.map} +1 -1
  16. package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-page.component-DzdIt2A7.mjs → edvak99-voice-receptionist-widget-dashboard-page.component-BNdnbAVM.mjs} +10 -10
  17. package/fesm2022/{edvak99-voice-receptionist-widget-dashboard-page.component-DzdIt2A7.mjs.map → edvak99-voice-receptionist-widget-dashboard-page.component-BNdnbAVM.mjs.map} +1 -1
  18. package/fesm2022/{edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs → edvak99-voice-receptionist-widget-eds-avatar.component-CbphhP6o.mjs} +3 -3
  19. package/fesm2022/{edvak99-voice-receptionist-widget-eds-avatar.component-PCnIWeOA.mjs.map → edvak99-voice-receptionist-widget-eds-avatar.component-CbphhP6o.mjs.map} +1 -1
  20. package/fesm2022/{edvak99-voice-receptionist-widget-eds-elapsed.pipe-ouRlPpwT.mjs → edvak99-voice-receptionist-widget-eds-elapsed.pipe-BgLsnq6A.mjs} +2 -2
  21. package/fesm2022/{edvak99-voice-receptionist-widget-eds-elapsed.pipe-ouRlPpwT.mjs.map → edvak99-voice-receptionist-widget-eds-elapsed.pipe-BgLsnq6A.mjs.map} +1 -1
  22. package/fesm2022/{edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CMhknpGf.mjs → edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Dn0GrZ6_.mjs} +106 -48
  23. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Dn0GrZ6_.mjs.map +1 -0
  24. package/fesm2022/{edvak99-voice-receptionist-widget-knowledge-hub.page-cZqBuull.mjs → edvak99-voice-receptionist-widget-knowledge-hub.page-BngGx_Lc.mjs} +70 -36
  25. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-BngGx_Lc.mjs.map +1 -0
  26. package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-list.service-CDtFRW8w.mjs → edvak99-voice-receptionist-widget-live-calls-list.service-0kGWO6sh.mjs} +3 -3
  27. package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-list.service-CDtFRW8w.mjs.map → edvak99-voice-receptionist-widget-live-calls-list.service-0kGWO6sh.mjs.map} +1 -1
  28. package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-page.component-DrYzXJQW.mjs → edvak99-voice-receptionist-widget-live-calls-page.component-DfLmK89M.mjs} +7 -7
  29. package/fesm2022/{edvak99-voice-receptionist-widget-live-calls-page.component-DrYzXJQW.mjs.map → edvak99-voice-receptionist-widget-live-calls-page.component-DfLmK89M.mjs.map} +1 -1
  30. package/fesm2022/{edvak99-voice-receptionist-widget-live-monitor-page.component-D7dlk0y2.mjs → edvak99-voice-receptionist-widget-live-monitor-page.component-DNpLFqBw.mjs} +11 -11
  31. package/fesm2022/{edvak99-voice-receptionist-widget-live-monitor-page.component-D7dlk0y2.mjs.map → edvak99-voice-receptionist-widget-live-monitor-page.component-DNpLFqBw.mjs.map} +1 -1
  32. package/fesm2022/{edvak99-voice-receptionist-widget-outbound-purposes-page.component-CJ8NEjcj.mjs → edvak99-voice-receptionist-widget-outbound-purposes-page.component-CYSZ832p.mjs} +6 -6
  33. package/fesm2022/{edvak99-voice-receptionist-widget-outbound-purposes-page.component-CJ8NEjcj.mjs.map → edvak99-voice-receptionist-widget-outbound-purposes-page.component-CYSZ832p.mjs.map} +1 -1
  34. package/fesm2022/{edvak99-voice-receptionist-widget-settings-page.component-Bo3XNK3b.mjs → edvak99-voice-receptionist-widget-settings-page.component-B9nv6wtB.mjs} +4 -4
  35. package/fesm2022/{edvak99-voice-receptionist-widget-settings-page.component-Bo3XNK3b.mjs.map → edvak99-voice-receptionist-widget-settings-page.component-B9nv6wtB.mjs.map} +1 -1
  36. package/fesm2022/{edvak99-voice-receptionist-widget-tasks-page.component-CBwl15NP.mjs → edvak99-voice-receptionist-widget-tasks-page.component-DXxjMVUv.mjs} +40 -11
  37. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-DXxjMVUv.mjs.map +1 -0
  38. package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CJLa5pia.mjs → edvak99-voice-receptionist-widget-wizard-page.component-C1Z6yC0q.mjs} +4 -4
  39. package/fesm2022/{edvak99-voice-receptionist-widget-wizard-page.component-CJLa5pia.mjs.map → edvak99-voice-receptionist-widget-wizard-page.component-C1Z6yC0q.mjs.map} +1 -1
  40. package/fesm2022/edvak99-voice-receptionist-widget.mjs +1 -1
  41. package/lib/knowledge/models/practice-faq.model.d.ts +3 -0
  42. package/lib/knowledge/pages/knowledge-hub.page.d.ts +8 -1
  43. package/lib/knowledge/services/practice-faq-api.service.d.ts +6 -2
  44. package/lib/models/ai-receptionist.types.d.ts +1 -0
  45. package/lib/models/pagination.d.ts +11 -0
  46. package/lib/pages/audit-page/audit-page.component.d.ts +7 -0
  47. package/lib/pages/call-history-page/call-history-page.component.d.ts +7 -3
  48. package/lib/pages/tasks-page/tasks-page.component.d.ts +6 -0
  49. package/lib/services/ai-receptionist-api.mock.d.ts +10 -9
  50. package/lib/services/ai-receptionist-api.service.d.ts +9 -9
  51. package/package.json +1 -1
  52. package/styles/_index.scss +5 -40
  53. package/styles/chat-ui.scss +18 -18
  54. package/styles/eds.scss +135 -95
  55. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DL1GfQyF.mjs +0 -35
  56. package/fesm2022/edvak99-voice-receptionist-widget-audit-page.component-DL1GfQyF.mjs.map +0 -1
  57. package/fesm2022/edvak99-voice-receptionist-widget-call-history-page.component-Cj4wxd4e.mjs.map +0 -1
  58. package/fesm2022/edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CMhknpGf.mjs.map +0 -1
  59. package/fesm2022/edvak99-voice-receptionist-widget-knowledge-hub.page-cZqBuull.mjs.map +0 -1
  60. package/fesm2022/edvak99-voice-receptionist-widget-tasks-page.component-CBwl15NP.mjs.map +0 -1
@@ -5,7 +5,7 @@ import * as i1 from '@angular/forms';
5
5
  import { FormsModule } from '@angular/forms';
6
6
  import { HttpClient, HttpParams } from '@angular/common/http';
7
7
  import { of, delay, map } from 'rxjs';
8
- import { V as VOICE_RECEPTIONIST_CONFIG, e as createVoiceReceptionistPaths, E as EdsButtonComponent, M as ModalComponent, T as ToastService, b as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-CMhknpGf.mjs';
8
+ import { V as VOICE_RECEPTIONIST_CONFIG, e as createVoiceReceptionistPaths, D as DEFAULT_PAGE_SIZE, j as asPaginatedResult, E as EdsButtonComponent, M as ModalComponent, T as ToastService, f as totalPages, b as EdsIconComponent } from './edvak99-voice-receptionist-widget-edvak99-voice-receptionist-widget-Dn0GrZ6_.mjs';
9
9
 
10
10
  const FAQ_CATEGORY_PRESETS = [
11
11
  'hours',
@@ -197,13 +197,24 @@ class PracticeFaqApiService {
197
197
  get useMock() {
198
198
  return !!this.config.useMockApi;
199
199
  }
200
- list(activeOnly = false) {
201
- if (this.useMock)
202
- return this.mock.list(activeOnly);
203
- let params = new HttpParams();
204
- if (activeOnly)
200
+ list(opts) {
201
+ const page = opts?.page ?? 1;
202
+ const limit = opts?.limit ?? DEFAULT_PAGE_SIZE;
203
+ if (this.useMock) {
204
+ return this.mock.list(!!opts?.activeOnly).pipe(map((items) => {
205
+ const start = (page - 1) * limit;
206
+ return {
207
+ items: items.slice(start, start + limit),
208
+ total: items.length,
209
+ page,
210
+ limit,
211
+ };
212
+ }));
213
+ }
214
+ let params = new HttpParams().set('page', String(page)).set('limit', String(limit));
215
+ if (opts?.activeOnly)
205
216
  params = params.set('activeOnly', 'true');
206
- return this.http.get(this.base, { params }).pipe(map((body) => this.asList(body)));
217
+ return this.http.get(this.base, { params }).pipe(map((body) => asPaginatedResult(body, page, limit)));
207
218
  }
208
219
  get(faqId) {
209
220
  if (this.useMock)
@@ -234,11 +245,6 @@ class PracticeFaqApiService {
234
245
  const params = new HttpParams().set('q', q).set('limit', String(limit));
235
246
  return this.http.get(`${this.base}/search`, { params });
236
247
  }
237
- asList(body) {
238
- if (Array.isArray(body))
239
- return body;
240
- return body.items ?? [];
241
- }
242
248
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: PracticeFaqApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
243
249
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: PracticeFaqApiService, providedIn: 'root' }); }
244
250
  }
@@ -276,11 +282,11 @@ class FaqEmptyStateComponent {
276
282
  this.useTemplate.emit(t);
277
283
  }
278
284
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FaqEmptyStateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
279
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: FaqEmptyStateComponent, isStandalone: true, selector: "eds-faq-empty-state", outputs: { add: "add", useTemplate: "useTemplate" }, ngImport: i0, template: "<div class=\"faq-empty-state\">\n <div class=\"faq-empty-state__icon\" aria-hidden=\"true\">?</div>\n <h2 class=\"eds-h2\">No FAQs yet</h2>\n <p class=\"eds-body-sm\">Add answers your AI receptionist can use on live calls.</p>\n <eds-button variant=\"primary\" (click)=\"onAdd()\">Add your first FAQ</eds-button>\n\n <p class=\"eds-label faq-empty-state__suggest\">Suggested starters</p>\n <ul class=\"faq-empty-state__starters\">\n @for (t of starters; track t.label) {\n <li>\n <button type=\"button\" class=\"faq-empty-state__starter\" (click)=\"onTemplate(t)\">\n {{ t.label }}\n </button>\n </li>\n }\n </ul>\n</div>\n", styles: [".faq-empty-state{text-align:center;padding:48px 24px}.faq-empty-state__icon{width:56px;height:56px;margin:0 auto 12px;border-radius:50%;background:#0d94881f;color:#0d9488;font-size:28px;font-weight:700;display:flex;align-items:center;justify-content:center}.faq-empty-state__suggest{margin:24px 0 8px}.faq-empty-state__starters{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;justify-content:center;gap:8px}.faq-empty-state__starter{border:1px dashed var(--border-subtle, #d1d5db);background:var(--surface-l0, #fff);border-radius:999px;padding:6px 12px;font-size:13px;cursor:pointer}.faq-empty-state__starter:hover{border-color:var(--eds-brand, #0d9488);color:var(--eds-brand, #0d9488)}\n"], dependencies: [{ kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
285
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: FaqEmptyStateComponent, isStandalone: true, selector: "eds-faq-empty-state", outputs: { add: "add", useTemplate: "useTemplate" }, ngImport: i0, template: "<div class=\"faq-empty-state\">\n <div class=\"faq-empty-state__icon\" aria-hidden=\"true\">?</div>\n <h2 class=\"eds-h2\">No FAQs yet</h2>\n <p class=\"eds-body-sm\">Add answers your AI receptionist can use on live calls.</p>\n <eds-button variant=\"primary\" (click)=\"onAdd()\">Add your first FAQ</eds-button>\n\n <p class=\"eds-label faq-empty-state__suggest\">Suggested starters</p>\n <ul class=\"faq-empty-state__starters\">\n @for (t of starters; track t.label) {\n <li>\n <button type=\"button\" class=\"faq-empty-state__starter\" (click)=\"onTemplate(t)\">\n {{ t.label }}\n </button>\n </li>\n }\n </ul>\n</div>\n", styles: [".faq-empty-state{text-align:center;padding:48px 24px}.faq-empty-state__icon{width:56px;height:56px;margin:0 auto 12px;border-radius:50%;background:#0d94881f;color:#0d9488;font-size:28px;font-weight:700;display:flex;align-items:center;justify-content:center}.faq-empty-state__suggest{margin:24px 0 8px}.faq-empty-state__starters{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;justify-content:center;gap:8px}.faq-empty-state__starter{border:1px dashed var(--eds-border-subtle, #d1d5db);background:var(--eds-surface-l0, #fff);border-radius:999px;padding:6px 12px;font-size:13px;cursor:pointer}.faq-empty-state__starter:hover{border-color:var(--eds-brand, #0d9488);color:var(--eds-brand, #0d9488)}\n"], dependencies: [{ kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
280
286
  }
281
287
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FaqEmptyStateComponent, decorators: [{
282
288
  type: Component,
283
- args: [{ selector: 'eds-faq-empty-state', imports: [EdsButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"faq-empty-state\">\n <div class=\"faq-empty-state__icon\" aria-hidden=\"true\">?</div>\n <h2 class=\"eds-h2\">No FAQs yet</h2>\n <p class=\"eds-body-sm\">Add answers your AI receptionist can use on live calls.</p>\n <eds-button variant=\"primary\" (click)=\"onAdd()\">Add your first FAQ</eds-button>\n\n <p class=\"eds-label faq-empty-state__suggest\">Suggested starters</p>\n <ul class=\"faq-empty-state__starters\">\n @for (t of starters; track t.label) {\n <li>\n <button type=\"button\" class=\"faq-empty-state__starter\" (click)=\"onTemplate(t)\">\n {{ t.label }}\n </button>\n </li>\n }\n </ul>\n</div>\n", styles: [".faq-empty-state{text-align:center;padding:48px 24px}.faq-empty-state__icon{width:56px;height:56px;margin:0 auto 12px;border-radius:50%;background:#0d94881f;color:#0d9488;font-size:28px;font-weight:700;display:flex;align-items:center;justify-content:center}.faq-empty-state__suggest{margin:24px 0 8px}.faq-empty-state__starters{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;justify-content:center;gap:8px}.faq-empty-state__starter{border:1px dashed var(--border-subtle, #d1d5db);background:var(--surface-l0, #fff);border-radius:999px;padding:6px 12px;font-size:13px;cursor:pointer}.faq-empty-state__starter:hover{border-color:var(--eds-brand, #0d9488);color:var(--eds-brand, #0d9488)}\n"] }]
289
+ args: [{ selector: 'eds-faq-empty-state', imports: [EdsButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"faq-empty-state\">\n <div class=\"faq-empty-state__icon\" aria-hidden=\"true\">?</div>\n <h2 class=\"eds-h2\">No FAQs yet</h2>\n <p class=\"eds-body-sm\">Add answers your AI receptionist can use on live calls.</p>\n <eds-button variant=\"primary\" (click)=\"onAdd()\">Add your first FAQ</eds-button>\n\n <p class=\"eds-label faq-empty-state__suggest\">Suggested starters</p>\n <ul class=\"faq-empty-state__starters\">\n @for (t of starters; track t.label) {\n <li>\n <button type=\"button\" class=\"faq-empty-state__starter\" (click)=\"onTemplate(t)\">\n {{ t.label }}\n </button>\n </li>\n }\n </ul>\n</div>\n", styles: [".faq-empty-state{text-align:center;padding:48px 24px}.faq-empty-state__icon{width:56px;height:56px;margin:0 auto 12px;border-radius:50%;background:#0d94881f;color:#0d9488;font-size:28px;font-weight:700;display:flex;align-items:center;justify-content:center}.faq-empty-state__suggest{margin:24px 0 8px}.faq-empty-state__starters{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;justify-content:center;gap:8px}.faq-empty-state__starter{border:1px dashed var(--eds-border-subtle, #d1d5db);background:var(--eds-surface-l0, #fff);border-radius:999px;padding:6px 12px;font-size:13px;cursor:pointer}.faq-empty-state__starter:hover{border-color:var(--eds-brand, #0d9488);color:var(--eds-brand, #0d9488)}\n"] }]
284
290
  }], propDecorators: { add: [{
285
291
  type: Output
286
292
  }], useTemplate: [{
@@ -360,11 +366,11 @@ class FaqFormComponent {
360
366
  }
361
367
  }
362
368
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FaqFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
363
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: FaqFormComponent, isStandalone: true, selector: "eds-faq-form", inputs: { open: "open", faq: "faq", initialQuestion: "initialQuestion", initialAnswer: "initialAnswer", initialCategory: "initialCategory" }, outputs: { closed: "closed", saved: "saved" }, usesOnChanges: true, ngImport: i0, template: "@if (open) {\n <app-modal\n [title]=\"isEdit ? 'Edit FAQ' : 'Add FAQ'\"\n subtitle=\"Short, speakable answers work best on phone calls\"\n maxWidth=\"640px\"\n (closed)=\"close()\"\n >\n <form class=\"faq-form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Question <span class=\"faq-form__req\">*</span></span>\n <span class=\"faq-form__hint\">How a caller might ask</span>\n <textarea\n class=\"faq-form__textarea\"\n rows=\"2\"\n maxlength=\"500\"\n [(ngModel)]=\"question\"\n name=\"question\"\n required\n ></textarea>\n <span class=\"faq-form__count\">{{ question.length }}/500</span>\n </label>\n\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Answer <span class=\"faq-form__req\">*</span></span>\n <span class=\"faq-form__hint\">Write how you want the AI to speak on the phone \u2014 short, clear, no bullet points.</span>\n <textarea\n class=\"faq-form__textarea\"\n rows=\"4\"\n maxlength=\"2000\"\n [(ngModel)]=\"answer\"\n name=\"answer\"\n required\n ></textarea>\n <span class=\"faq-form__count\">{{ answer.length }}/2000</span>\n </label>\n\n <div class=\"faq-form__row\">\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Category</span>\n <select class=\"faq-form__input\" [(ngModel)]=\"category\" name=\"category\">\n <option value=\"\">\u2014 None \u2014</option>\n @for (c of categories; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </label>\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Sort order</span>\n <input type=\"number\" class=\"faq-form__input\" [(ngModel)]=\"sortOrder\" name=\"sortOrder\" min=\"0\" />\n </label>\n </div>\n\n <label class=\"faq-form__check\">\n <input type=\"checkbox\" [(ngModel)]=\"isActive\" name=\"isActive\" />\n Active (voice agent can use this answer)\n </label>\n\n <section class=\"faq-form__preview eds-card\">\n <h3 class=\"eds-label\">Live preview</h3>\n <p class=\"faq-form__preview-line\"><strong>Caller asks:</strong> \u201C{{ question || '\u2026' }}\u201D</p>\n <p class=\"faq-form__preview-line\"><strong>AI would say:</strong> \u201C{{ answer || '\u2026' }}\u201D</p>\n </section>\n\n @if (error()) {\n <p class=\"faq-form__error\" role=\"alert\">{{ error() }}</p>\n }\n\n <footer class=\"faq-form__footer\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"close()\">Cancel</eds-button>\n <eds-button variant=\"primary\" type=\"submit\">Save</eds-button>\n </footer>\n </form>\n </app-modal>\n}\n", styles: [".faq-form{display:flex;flex-direction:column;gap:14px}.faq-form__field{display:flex;flex-direction:column;gap:4px}.faq-form__hint{font-size:12px;color:var(--text-secondary)}.faq-form__textarea,.faq-form__input{padding:8px 10px;border:1px solid var(--border-subtle, #e4e4e7);border-radius:8px;font:inherit}.faq-form__textarea{resize:vertical}.faq-form__count{font-size:11px;color:var(--text-secondary);text-align:right}.faq-form__req{color:#dc2626}.faq-form__row{display:grid;grid-template-columns:1fr 120px;gap:12px}.faq-form__check{display:flex;align-items:center;gap:8px;font-size:13px}.faq-form__preview{padding:12px;background:var(--surface-muted, #f8fafc)}.faq-form__preview-line{margin:6px 0 0;font-size:14px;line-height:1.5}.faq-form__error{color:#b91c1c;font-size:13px;margin:0}.faq-form__footer{display:flex;justify-content:flex-end;gap:8px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ModalComponent, selector: "app-modal", inputs: ["title", "subtitle", "maxWidth"], outputs: ["closed"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
369
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: FaqFormComponent, isStandalone: true, selector: "eds-faq-form", inputs: { open: "open", faq: "faq", initialQuestion: "initialQuestion", initialAnswer: "initialAnswer", initialCategory: "initialCategory" }, outputs: { closed: "closed", saved: "saved" }, usesOnChanges: true, ngImport: i0, template: "@if (open) {\n <app-modal\n [title]=\"isEdit ? 'Edit FAQ' : 'Add FAQ'\"\n subtitle=\"Short, speakable answers work best on phone calls\"\n maxWidth=\"640px\"\n (closed)=\"close()\"\n >\n <form class=\"faq-form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Question <span class=\"faq-form__req\">*</span></span>\n <span class=\"faq-form__hint\">How a caller might ask</span>\n <textarea\n class=\"faq-form__textarea\"\n rows=\"2\"\n maxlength=\"500\"\n [(ngModel)]=\"question\"\n name=\"question\"\n required\n ></textarea>\n <span class=\"faq-form__count\">{{ question.length }}/500</span>\n </label>\n\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Answer <span class=\"faq-form__req\">*</span></span>\n <span class=\"faq-form__hint\">Write how you want the AI to speak on the phone \u2014 short, clear, no bullet points.</span>\n <textarea\n class=\"faq-form__textarea\"\n rows=\"4\"\n maxlength=\"2000\"\n [(ngModel)]=\"answer\"\n name=\"answer\"\n required\n ></textarea>\n <span class=\"faq-form__count\">{{ answer.length }}/2000</span>\n </label>\n\n <div class=\"faq-form__row\">\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Category</span>\n <select class=\"faq-form__input\" [(ngModel)]=\"category\" name=\"category\">\n <option value=\"\">\u2014 None \u2014</option>\n @for (c of categories; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </label>\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Sort order</span>\n <input type=\"number\" class=\"faq-form__input\" [(ngModel)]=\"sortOrder\" name=\"sortOrder\" min=\"0\" />\n </label>\n </div>\n\n <label class=\"faq-form__check\">\n <input type=\"checkbox\" [(ngModel)]=\"isActive\" name=\"isActive\" />\n Active (voice agent can use this answer)\n </label>\n\n <section class=\"faq-form__preview eds-card\">\n <h3 class=\"eds-label\">Live preview</h3>\n <p class=\"faq-form__preview-line\"><strong>Caller asks:</strong> \u201C{{ question || '\u2026' }}\u201D</p>\n <p class=\"faq-form__preview-line\"><strong>AI would say:</strong> \u201C{{ answer || '\u2026' }}\u201D</p>\n </section>\n\n @if (error()) {\n <p class=\"faq-form__error\" role=\"alert\">{{ error() }}</p>\n }\n\n <footer class=\"faq-form__footer\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"close()\">Cancel</eds-button>\n <eds-button variant=\"primary\" type=\"submit\">Save</eds-button>\n </footer>\n </form>\n </app-modal>\n}\n", styles: [".faq-form{display:flex;flex-direction:column;gap:14px}.faq-form__field{display:flex;flex-direction:column;gap:4px}.faq-form__hint{font-size:12px;color:var(--eds-text-secondary)}.faq-form__textarea,.faq-form__input{padding:8px 10px;border:1px solid var(--eds-border-subtle, #e4e4e7);border-radius:8px;font:inherit}.faq-form__textarea{resize:vertical}.faq-form__count{font-size:11px;color:var(--eds-text-secondary);text-align:right}.faq-form__req{color:#dc2626}.faq-form__row{display:grid;grid-template-columns:1fr 120px;gap:12px}.faq-form__check{display:flex;align-items:center;gap:8px;font-size:13px}.faq-form__preview{padding:12px;background:var(--eds-surface-muted, #f8fafc)}.faq-form__preview-line{margin:6px 0 0;font-size:14px;line-height:1.5}.faq-form__error{color:#b91c1c;font-size:13px;margin:0}.faq-form__footer{display:flex;justify-content:flex-end;gap:8px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ModalComponent, selector: "app-modal", inputs: ["title", "subtitle", "maxWidth"], outputs: ["closed"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
364
370
  }
365
371
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FaqFormComponent, decorators: [{
366
372
  type: Component,
367
- args: [{ selector: 'eds-faq-form', imports: [FormsModule, ModalComponent, EdsButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open) {\n <app-modal\n [title]=\"isEdit ? 'Edit FAQ' : 'Add FAQ'\"\n subtitle=\"Short, speakable answers work best on phone calls\"\n maxWidth=\"640px\"\n (closed)=\"close()\"\n >\n <form class=\"faq-form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Question <span class=\"faq-form__req\">*</span></span>\n <span class=\"faq-form__hint\">How a caller might ask</span>\n <textarea\n class=\"faq-form__textarea\"\n rows=\"2\"\n maxlength=\"500\"\n [(ngModel)]=\"question\"\n name=\"question\"\n required\n ></textarea>\n <span class=\"faq-form__count\">{{ question.length }}/500</span>\n </label>\n\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Answer <span class=\"faq-form__req\">*</span></span>\n <span class=\"faq-form__hint\">Write how you want the AI to speak on the phone \u2014 short, clear, no bullet points.</span>\n <textarea\n class=\"faq-form__textarea\"\n rows=\"4\"\n maxlength=\"2000\"\n [(ngModel)]=\"answer\"\n name=\"answer\"\n required\n ></textarea>\n <span class=\"faq-form__count\">{{ answer.length }}/2000</span>\n </label>\n\n <div class=\"faq-form__row\">\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Category</span>\n <select class=\"faq-form__input\" [(ngModel)]=\"category\" name=\"category\">\n <option value=\"\">\u2014 None \u2014</option>\n @for (c of categories; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </label>\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Sort order</span>\n <input type=\"number\" class=\"faq-form__input\" [(ngModel)]=\"sortOrder\" name=\"sortOrder\" min=\"0\" />\n </label>\n </div>\n\n <label class=\"faq-form__check\">\n <input type=\"checkbox\" [(ngModel)]=\"isActive\" name=\"isActive\" />\n Active (voice agent can use this answer)\n </label>\n\n <section class=\"faq-form__preview eds-card\">\n <h3 class=\"eds-label\">Live preview</h3>\n <p class=\"faq-form__preview-line\"><strong>Caller asks:</strong> \u201C{{ question || '\u2026' }}\u201D</p>\n <p class=\"faq-form__preview-line\"><strong>AI would say:</strong> \u201C{{ answer || '\u2026' }}\u201D</p>\n </section>\n\n @if (error()) {\n <p class=\"faq-form__error\" role=\"alert\">{{ error() }}</p>\n }\n\n <footer class=\"faq-form__footer\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"close()\">Cancel</eds-button>\n <eds-button variant=\"primary\" type=\"submit\">Save</eds-button>\n </footer>\n </form>\n </app-modal>\n}\n", styles: [".faq-form{display:flex;flex-direction:column;gap:14px}.faq-form__field{display:flex;flex-direction:column;gap:4px}.faq-form__hint{font-size:12px;color:var(--text-secondary)}.faq-form__textarea,.faq-form__input{padding:8px 10px;border:1px solid var(--border-subtle, #e4e4e7);border-radius:8px;font:inherit}.faq-form__textarea{resize:vertical}.faq-form__count{font-size:11px;color:var(--text-secondary);text-align:right}.faq-form__req{color:#dc2626}.faq-form__row{display:grid;grid-template-columns:1fr 120px;gap:12px}.faq-form__check{display:flex;align-items:center;gap:8px;font-size:13px}.faq-form__preview{padding:12px;background:var(--surface-muted, #f8fafc)}.faq-form__preview-line{margin:6px 0 0;font-size:14px;line-height:1.5}.faq-form__error{color:#b91c1c;font-size:13px;margin:0}.faq-form__footer{display:flex;justify-content:flex-end;gap:8px}\n"] }]
373
+ args: [{ selector: 'eds-faq-form', imports: [FormsModule, ModalComponent, EdsButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open) {\n <app-modal\n [title]=\"isEdit ? 'Edit FAQ' : 'Add FAQ'\"\n subtitle=\"Short, speakable answers work best on phone calls\"\n maxWidth=\"640px\"\n (closed)=\"close()\"\n >\n <form class=\"faq-form\" (ngSubmit)=\"submit()\" novalidate>\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Question <span class=\"faq-form__req\">*</span></span>\n <span class=\"faq-form__hint\">How a caller might ask</span>\n <textarea\n class=\"faq-form__textarea\"\n rows=\"2\"\n maxlength=\"500\"\n [(ngModel)]=\"question\"\n name=\"question\"\n required\n ></textarea>\n <span class=\"faq-form__count\">{{ question.length }}/500</span>\n </label>\n\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Answer <span class=\"faq-form__req\">*</span></span>\n <span class=\"faq-form__hint\">Write how you want the AI to speak on the phone \u2014 short, clear, no bullet points.</span>\n <textarea\n class=\"faq-form__textarea\"\n rows=\"4\"\n maxlength=\"2000\"\n [(ngModel)]=\"answer\"\n name=\"answer\"\n required\n ></textarea>\n <span class=\"faq-form__count\">{{ answer.length }}/2000</span>\n </label>\n\n <div class=\"faq-form__row\">\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Category</span>\n <select class=\"faq-form__input\" [(ngModel)]=\"category\" name=\"category\">\n <option value=\"\">\u2014 None \u2014</option>\n @for (c of categories; track c) {\n <option [value]=\"c\">{{ c }}</option>\n }\n </select>\n </label>\n <label class=\"faq-form__field\">\n <span class=\"eds-label\">Sort order</span>\n <input type=\"number\" class=\"faq-form__input\" [(ngModel)]=\"sortOrder\" name=\"sortOrder\" min=\"0\" />\n </label>\n </div>\n\n <label class=\"faq-form__check\">\n <input type=\"checkbox\" [(ngModel)]=\"isActive\" name=\"isActive\" />\n Active (voice agent can use this answer)\n </label>\n\n <section class=\"faq-form__preview eds-card\">\n <h3 class=\"eds-label\">Live preview</h3>\n <p class=\"faq-form__preview-line\"><strong>Caller asks:</strong> \u201C{{ question || '\u2026' }}\u201D</p>\n <p class=\"faq-form__preview-line\"><strong>AI would say:</strong> \u201C{{ answer || '\u2026' }}\u201D</p>\n </section>\n\n @if (error()) {\n <p class=\"faq-form__error\" role=\"alert\">{{ error() }}</p>\n }\n\n <footer class=\"faq-form__footer\">\n <eds-button variant=\"secondary\" type=\"button\" (click)=\"close()\">Cancel</eds-button>\n <eds-button variant=\"primary\" type=\"submit\">Save</eds-button>\n </footer>\n </form>\n </app-modal>\n}\n", styles: [".faq-form{display:flex;flex-direction:column;gap:14px}.faq-form__field{display:flex;flex-direction:column;gap:4px}.faq-form__hint{font-size:12px;color:var(--eds-text-secondary)}.faq-form__textarea,.faq-form__input{padding:8px 10px;border:1px solid var(--eds-border-subtle, #e4e4e7);border-radius:8px;font:inherit}.faq-form__textarea{resize:vertical}.faq-form__count{font-size:11px;color:var(--eds-text-secondary);text-align:right}.faq-form__req{color:#dc2626}.faq-form__row{display:grid;grid-template-columns:1fr 120px;gap:12px}.faq-form__check{display:flex;align-items:center;gap:8px;font-size:13px}.faq-form__preview{padding:12px;background:var(--eds-surface-muted, #f8fafc)}.faq-form__preview-line{margin:6px 0 0;font-size:14px;line-height:1.5}.faq-form__error{color:#b91c1c;font-size:13px;margin:0}.faq-form__footer{display:flex;justify-content:flex-end;gap:8px}\n"] }]
368
374
  }], propDecorators: { open: [{
369
375
  type: Input
370
376
  }], faq: [{
@@ -414,18 +420,21 @@ class FaqSearchTesterComponent {
414
420
  });
415
421
  }
416
422
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FaqSearchTesterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
417
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: FaqSearchTesterComponent, isStandalone: true, selector: "eds-faq-search-tester", ngImport: i0, template: "<aside class=\"eds-card faq-search-tester\">\n <h2 class=\"eds-h2\">Test voice search</h2>\n <p class=\"eds-body-sm faq-search-tester__help\">This uses the same search as live calls.</p>\n\n <label class=\"faq-search-tester__field\">\n <span class=\"eds-label\">What a caller might ask\u2026</span>\n <input\n type=\"search\"\n class=\"faq-search-tester__input\"\n placeholder=\"Do you have Saturday hours?\"\n [(ngModel)]=\"query\"\n (keyup.enter)=\"search()\"\n />\n </label>\n\n <eds-button variant=\"primary\" [disabled]=\"loading() || !query.trim()\" (click)=\"search()\">\n {{ loading() ? 'Searching\u2026' : 'Test search' }}\n </eds-button>\n\n @if (error()) {\n <p class=\"faq-search-tester__error\" role=\"alert\">{{ error() }}</p>\n }\n\n @if (searched()) {\n @if (found()) {\n <ul class=\"faq-search-tester__results\">\n @for (item of results(); track item.faqId) {\n <li class=\"faq-search-tester__result\">\n <div class=\"faq-search-tester__score-row\">\n <span class=\"eds-label\">Relevance</span>\n <div class=\"faq-search-tester__score-bar\">\n <span class=\"faq-search-tester__score-fill\" [style.width.%]=\"scorePercent(item.score)\"></span>\n </div>\n <span class=\"eds-mono-sm\">{{ scorePercent(item.score) }}%</span>\n </div>\n @if (item.category) {\n <span class=\"faq-chip\" [class]=\"categoryClass(item.category)\">{{ item.category }}</span>\n }\n <p class=\"faq-search-tester__question\">{{ item.question }}</p>\n <p class=\"faq-search-tester__answer\">{{ item.answer }}</p>\n </li>\n }\n </ul>\n } @else {\n <p class=\"faq-search-tester__no-match\" role=\"status\">\n No match \u2014 voice agent will offer to transfer to reception\n </p>\n }\n }\n</aside>\n", styles: [".faq-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize}.faq-chip--hours{background:#3b82f61f;color:#2563eb}.faq-chip--location{background:#14b8a61f;color:#0d9488}.faq-chip--parking{background:#6b72801f;color:#4b5563}.faq-chip--insurance{background:#8b5cf61f;color:#7c3aed}.faq-chip--services{background:#22c55e1f;color:#16a34a}.faq-chip--policies{background:#f973161f;color:#ea580c}.faq-chip--general{background:#6b72801f;color:#6b7280}.faq-search-tester{padding:16px;position:sticky;top:calc(var(--eds-topbar-height, 56px) + 16px)}.faq-search-tester__help{color:var(--text-secondary);margin:4px 0 12px}.faq-search-tester__field{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.faq-search-tester__input{padding:8px 10px;border:1px solid var(--border-subtle, #e4e4e7);border-radius:8px;font:inherit}.faq-search-tester__error{color:#b91c1c;font-size:13px;margin-top:10px}.faq-search-tester__no-match{margin-top:14px;padding:12px;border-radius:8px;background:#f59e0b1f;color:#b45309;font-size:13px}.faq-search-tester__results{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:12px}.faq-search-tester__result{padding:12px;border:1px solid var(--border-subtle, #e4e4e7);border-radius:8px}.faq-search-tester__score-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:8px;margin-bottom:8px}.faq-search-tester__score-bar{height:6px;border-radius:999px;background:var(--surface-muted, #e5e7eb);overflow:hidden}.faq-search-tester__score-fill{display:block;height:100%;background:var(--eds-brand, #0d9488);border-radius:999px}.faq-search-tester__question{font-weight:600;font-size:13px;margin:6px 0 4px}.faq-search-tester__answer{font-size:13px;color:var(--text-secondary);margin:0;line-height:1.5}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
423
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: FaqSearchTesterComponent, isStandalone: true, selector: "eds-faq-search-tester", ngImport: i0, template: "<aside class=\"eds-card faq-search-tester\">\n <h2 class=\"eds-h2\">Test voice search</h2>\n <p class=\"eds-body-sm faq-search-tester__help\">This uses the same search as live calls.</p>\n\n <label class=\"faq-search-tester__field\">\n <span class=\"eds-label\">What a caller might ask\u2026</span>\n <input\n type=\"search\"\n class=\"faq-search-tester__input\"\n placeholder=\"Do you have Saturday hours?\"\n [(ngModel)]=\"query\"\n (keyup.enter)=\"search()\"\n />\n </label>\n\n <eds-button variant=\"primary\" [disabled]=\"loading() || !query.trim()\" (click)=\"search()\">\n {{ loading() ? 'Searching\u2026' : 'Test search' }}\n </eds-button>\n\n @if (error()) {\n <p class=\"faq-search-tester__error\" role=\"alert\">{{ error() }}</p>\n }\n\n @if (searched()) {\n @if (found()) {\n <ul class=\"faq-search-tester__results\">\n @for (item of results(); track item.faqId) {\n <li class=\"faq-search-tester__result\">\n <div class=\"faq-search-tester__score-row\">\n <span class=\"eds-label\">Relevance</span>\n <div class=\"faq-search-tester__score-bar\">\n <span class=\"faq-search-tester__score-fill\" [style.width.%]=\"scorePercent(item.score)\"></span>\n </div>\n <span class=\"eds-mono-sm\">{{ scorePercent(item.score) }}%</span>\n </div>\n @if (item.category) {\n <span class=\"faq-chip\" [class]=\"categoryClass(item.category)\">{{ item.category }}</span>\n }\n <p class=\"faq-search-tester__question\">{{ item.question }}</p>\n <p class=\"faq-search-tester__answer\">{{ item.answer }}</p>\n </li>\n }\n </ul>\n } @else {\n <p class=\"faq-search-tester__no-match\" role=\"status\">\n No match \u2014 voice agent will offer to transfer to reception\n </p>\n }\n }\n</aside>\n", styles: [".faq-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize}.faq-chip--hours{background:#3b82f61f;color:#2563eb}.faq-chip--location{background:#14b8a61f;color:#0d9488}.faq-chip--parking{background:#6b72801f;color:#4b5563}.faq-chip--insurance{background:#8b5cf61f;color:#7c3aed}.faq-chip--services{background:#22c55e1f;color:#16a34a}.faq-chip--policies{background:#f973161f;color:#ea580c}.faq-chip--general{background:#6b72801f;color:#6b7280}.faq-search-tester{padding:16px;position:sticky;top:calc(var(--eds-topbar-height, 56px) + 16px)}.faq-search-tester__help{color:var(--eds-text-secondary);margin:4px 0 12px}.faq-search-tester__field{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.faq-search-tester__input{padding:8px 10px;border:1px solid var(--eds-border-subtle, #e4e4e7);border-radius:8px;font:inherit}.faq-search-tester__error{color:#b91c1c;font-size:13px;margin-top:10px}.faq-search-tester__no-match{margin-top:14px;padding:12px;border-radius:8px;background:#f59e0b1f;color:#b45309;font-size:13px}.faq-search-tester__results{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:12px}.faq-search-tester__result{padding:12px;border:1px solid var(--eds-border-subtle, #e4e4e7);border-radius:8px}.faq-search-tester__score-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:8px;margin-bottom:8px}.faq-search-tester__score-bar{height:6px;border-radius:999px;background:var(--eds-surface-muted, #e5e7eb);overflow:hidden}.faq-search-tester__score-fill{display:block;height:100%;background:var(--eds-brand, #0d9488);border-radius:999px}.faq-search-tester__question{font-weight:600;font-size:13px;margin:6px 0 4px}.faq-search-tester__answer{font-size:13px;color:var(--eds-text-secondary);margin:0;line-height:1.5}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
418
424
  }
419
425
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: FaqSearchTesterComponent, decorators: [{
420
426
  type: Component,
421
- args: [{ selector: 'eds-faq-search-tester', imports: [FormsModule, EdsButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<aside class=\"eds-card faq-search-tester\">\n <h2 class=\"eds-h2\">Test voice search</h2>\n <p class=\"eds-body-sm faq-search-tester__help\">This uses the same search as live calls.</p>\n\n <label class=\"faq-search-tester__field\">\n <span class=\"eds-label\">What a caller might ask\u2026</span>\n <input\n type=\"search\"\n class=\"faq-search-tester__input\"\n placeholder=\"Do you have Saturday hours?\"\n [(ngModel)]=\"query\"\n (keyup.enter)=\"search()\"\n />\n </label>\n\n <eds-button variant=\"primary\" [disabled]=\"loading() || !query.trim()\" (click)=\"search()\">\n {{ loading() ? 'Searching\u2026' : 'Test search' }}\n </eds-button>\n\n @if (error()) {\n <p class=\"faq-search-tester__error\" role=\"alert\">{{ error() }}</p>\n }\n\n @if (searched()) {\n @if (found()) {\n <ul class=\"faq-search-tester__results\">\n @for (item of results(); track item.faqId) {\n <li class=\"faq-search-tester__result\">\n <div class=\"faq-search-tester__score-row\">\n <span class=\"eds-label\">Relevance</span>\n <div class=\"faq-search-tester__score-bar\">\n <span class=\"faq-search-tester__score-fill\" [style.width.%]=\"scorePercent(item.score)\"></span>\n </div>\n <span class=\"eds-mono-sm\">{{ scorePercent(item.score) }}%</span>\n </div>\n @if (item.category) {\n <span class=\"faq-chip\" [class]=\"categoryClass(item.category)\">{{ item.category }}</span>\n }\n <p class=\"faq-search-tester__question\">{{ item.question }}</p>\n <p class=\"faq-search-tester__answer\">{{ item.answer }}</p>\n </li>\n }\n </ul>\n } @else {\n <p class=\"faq-search-tester__no-match\" role=\"status\">\n No match \u2014 voice agent will offer to transfer to reception\n </p>\n }\n }\n</aside>\n", styles: [".faq-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize}.faq-chip--hours{background:#3b82f61f;color:#2563eb}.faq-chip--location{background:#14b8a61f;color:#0d9488}.faq-chip--parking{background:#6b72801f;color:#4b5563}.faq-chip--insurance{background:#8b5cf61f;color:#7c3aed}.faq-chip--services{background:#22c55e1f;color:#16a34a}.faq-chip--policies{background:#f973161f;color:#ea580c}.faq-chip--general{background:#6b72801f;color:#6b7280}.faq-search-tester{padding:16px;position:sticky;top:calc(var(--eds-topbar-height, 56px) + 16px)}.faq-search-tester__help{color:var(--text-secondary);margin:4px 0 12px}.faq-search-tester__field{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.faq-search-tester__input{padding:8px 10px;border:1px solid var(--border-subtle, #e4e4e7);border-radius:8px;font:inherit}.faq-search-tester__error{color:#b91c1c;font-size:13px;margin-top:10px}.faq-search-tester__no-match{margin-top:14px;padding:12px;border-radius:8px;background:#f59e0b1f;color:#b45309;font-size:13px}.faq-search-tester__results{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:12px}.faq-search-tester__result{padding:12px;border:1px solid var(--border-subtle, #e4e4e7);border-radius:8px}.faq-search-tester__score-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:8px;margin-bottom:8px}.faq-search-tester__score-bar{height:6px;border-radius:999px;background:var(--surface-muted, #e5e7eb);overflow:hidden}.faq-search-tester__score-fill{display:block;height:100%;background:var(--eds-brand, #0d9488);border-radius:999px}.faq-search-tester__question{font-weight:600;font-size:13px;margin:6px 0 4px}.faq-search-tester__answer{font-size:13px;color:var(--text-secondary);margin:0;line-height:1.5}\n"] }]
427
+ args: [{ selector: 'eds-faq-search-tester', imports: [FormsModule, EdsButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<aside class=\"eds-card faq-search-tester\">\n <h2 class=\"eds-h2\">Test voice search</h2>\n <p class=\"eds-body-sm faq-search-tester__help\">This uses the same search as live calls.</p>\n\n <label class=\"faq-search-tester__field\">\n <span class=\"eds-label\">What a caller might ask\u2026</span>\n <input\n type=\"search\"\n class=\"faq-search-tester__input\"\n placeholder=\"Do you have Saturday hours?\"\n [(ngModel)]=\"query\"\n (keyup.enter)=\"search()\"\n />\n </label>\n\n <eds-button variant=\"primary\" [disabled]=\"loading() || !query.trim()\" (click)=\"search()\">\n {{ loading() ? 'Searching\u2026' : 'Test search' }}\n </eds-button>\n\n @if (error()) {\n <p class=\"faq-search-tester__error\" role=\"alert\">{{ error() }}</p>\n }\n\n @if (searched()) {\n @if (found()) {\n <ul class=\"faq-search-tester__results\">\n @for (item of results(); track item.faqId) {\n <li class=\"faq-search-tester__result\">\n <div class=\"faq-search-tester__score-row\">\n <span class=\"eds-label\">Relevance</span>\n <div class=\"faq-search-tester__score-bar\">\n <span class=\"faq-search-tester__score-fill\" [style.width.%]=\"scorePercent(item.score)\"></span>\n </div>\n <span class=\"eds-mono-sm\">{{ scorePercent(item.score) }}%</span>\n </div>\n @if (item.category) {\n <span class=\"faq-chip\" [class]=\"categoryClass(item.category)\">{{ item.category }}</span>\n }\n <p class=\"faq-search-tester__question\">{{ item.question }}</p>\n <p class=\"faq-search-tester__answer\">{{ item.answer }}</p>\n </li>\n }\n </ul>\n } @else {\n <p class=\"faq-search-tester__no-match\" role=\"status\">\n No match \u2014 voice agent will offer to transfer to reception\n </p>\n }\n }\n</aside>\n", styles: [".faq-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize}.faq-chip--hours{background:#3b82f61f;color:#2563eb}.faq-chip--location{background:#14b8a61f;color:#0d9488}.faq-chip--parking{background:#6b72801f;color:#4b5563}.faq-chip--insurance{background:#8b5cf61f;color:#7c3aed}.faq-chip--services{background:#22c55e1f;color:#16a34a}.faq-chip--policies{background:#f973161f;color:#ea580c}.faq-chip--general{background:#6b72801f;color:#6b7280}.faq-search-tester{padding:16px;position:sticky;top:calc(var(--eds-topbar-height, 56px) + 16px)}.faq-search-tester__help{color:var(--eds-text-secondary);margin:4px 0 12px}.faq-search-tester__field{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.faq-search-tester__input{padding:8px 10px;border:1px solid var(--eds-border-subtle, #e4e4e7);border-radius:8px;font:inherit}.faq-search-tester__error{color:#b91c1c;font-size:13px;margin-top:10px}.faq-search-tester__no-match{margin-top:14px;padding:12px;border-radius:8px;background:#f59e0b1f;color:#b45309;font-size:13px}.faq-search-tester__results{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:12px}.faq-search-tester__result{padding:12px;border:1px solid var(--eds-border-subtle, #e4e4e7);border-radius:8px}.faq-search-tester__score-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:8px;margin-bottom:8px}.faq-search-tester__score-bar{height:6px;border-radius:999px;background:var(--eds-surface-muted, #e5e7eb);overflow:hidden}.faq-search-tester__score-fill{display:block;height:100%;background:var(--eds-brand, #0d9488);border-radius:999px}.faq-search-tester__question{font-weight:600;font-size:13px;margin:6px 0 4px}.faq-search-tester__answer{font-size:13px;color:var(--eds-text-secondary);margin:0;line-height:1.5}\n"] }]
422
428
  }] });
423
429
 
424
430
  class KnowledgeHubPageComponent {
425
431
  constructor() {
426
432
  this.api = inject(PracticeFaqApiService);
427
433
  this.toast = inject(ToastService);
428
- this.allFaqs = signal([]);
434
+ this.pageSize = DEFAULT_PAGE_SIZE;
435
+ this.pageFaqs = signal([]);
436
+ this.total = signal(0);
437
+ this.page = signal(1);
429
438
  this.loading = signal(true);
430
439
  this.error = signal(null);
431
440
  this.activeFilter = signal('all');
@@ -444,11 +453,10 @@ class KnowledgeHubPageComponent {
444
453
  { id: 'active', label: 'Active only' },
445
454
  { id: 'inactive', label: 'Inactive' },
446
455
  ];
456
+ /** Client filters still applied on the current page (category / local search / inactive). */
447
457
  this.faqs = computed(() => {
448
- let rows = [...this.allFaqs()];
458
+ let rows = [...this.pageFaqs()];
449
459
  const active = this.activeFilter();
450
- if (active === 'active')
451
- rows = rows.filter((f) => f.isActive);
452
460
  if (active === 'inactive')
453
461
  rows = rows.filter((f) => !f.isActive);
454
462
  const cat = this.categoryFilter();
@@ -471,9 +479,17 @@ class KnowledgeHubPageComponent {
471
479
  load() {
472
480
  this.loading.set(true);
473
481
  this.error.set(null);
474
- this.api.list().subscribe({
475
- next: (items) => {
476
- this.allFaqs.set(items);
482
+ this.api
483
+ .list({
484
+ activeOnly: this.activeFilter() === 'active',
485
+ page: this.page(),
486
+ limit: DEFAULT_PAGE_SIZE,
487
+ })
488
+ .subscribe({
489
+ next: ({ items, total, page }) => {
490
+ this.pageFaqs.set(items);
491
+ this.total.set(total);
492
+ this.page.set(page);
477
493
  this.loading.set(false);
478
494
  },
479
495
  error: () => {
@@ -484,6 +500,8 @@ class KnowledgeHubPageComponent {
484
500
  }
485
501
  setActiveFilter(id) {
486
502
  this.activeFilter.set(id);
503
+ this.page.set(1);
504
+ this.load();
487
505
  }
488
506
  toggleCategory(cat) {
489
507
  this.categoryFilter.update((c) => (c === cat ? '' : cat));
@@ -491,6 +509,21 @@ class KnowledgeHubPageComponent {
491
509
  isCategorySelected(cat) {
492
510
  return this.categoryFilter() === cat;
493
511
  }
512
+ totalPages() {
513
+ return totalPages(this.total(), DEFAULT_PAGE_SIZE);
514
+ }
515
+ prevPage() {
516
+ if (this.page() <= 1)
517
+ return;
518
+ this.page.update((p) => p - 1);
519
+ this.load();
520
+ }
521
+ nextPage() {
522
+ if (this.page() >= this.totalPages())
523
+ return;
524
+ this.page.update((p) => p + 1);
525
+ this.load();
526
+ }
494
527
  openCreate() {
495
528
  this.editing.set(null);
496
529
  this.formSeed.set({ question: '', answer: '', category: '' });
@@ -513,32 +546,33 @@ class KnowledgeHubPageComponent {
513
546
  const edit = this.editing();
514
547
  if (edit) {
515
548
  this.api.update(edit.faqId, payload).subscribe({
516
- next: (updated) => {
517
- this.allFaqs.update((list) => list.map((f) => (f.faqId === updated.faqId ? updated : f)));
549
+ next: () => {
518
550
  this.closeForm();
519
551
  this.toast.show('FAQ saved — voice agent updated', 'success');
552
+ this.load();
520
553
  },
521
554
  error: () => this.toast.show('Failed to save FAQ', 'error'),
522
555
  });
523
556
  return;
524
557
  }
525
558
  this.api.create(payload).subscribe({
526
- next: (created) => {
527
- this.allFaqs.update((list) => [...list, created]);
559
+ next: () => {
528
560
  this.closeForm();
529
561
  this.toast.show('Your AI receptionist can now answer this on live calls.', 'success');
562
+ this.page.set(1);
563
+ this.load();
530
564
  },
531
565
  error: () => this.toast.show('Failed to create FAQ', 'error'),
532
566
  });
533
567
  }
534
568
  toggleActive(faq, active) {
535
- this.allFaqs.update((list) => list.map((f) => (f.faqId === faq.faqId ? { ...f, isActive: active } : f)));
569
+ this.pageFaqs.update((list) => list.map((f) => (f.faqId === faq.faqId ? { ...f, isActive: active } : f)));
536
570
  this.api.update(faq.faqId, { isActive: active }).subscribe({
537
571
  next: (updated) => {
538
- this.allFaqs.update((list) => list.map((f) => (f.faqId === updated.faqId ? updated : f)));
572
+ this.pageFaqs.update((list) => list.map((f) => (f.faqId === updated.faqId ? updated : f)));
539
573
  },
540
574
  error: () => {
541
- this.allFaqs.update((list) => list.map((f) => (f.faqId === faq.faqId ? { ...f, isActive: !active } : f)));
575
+ this.pageFaqs.update((list) => list.map((f) => (f.faqId === faq.faqId ? { ...f, isActive: !active } : f)));
542
576
  this.toast.show('Failed to update FAQ status', 'error');
543
577
  },
544
578
  });
@@ -549,14 +583,14 @@ class KnowledgeHubPageComponent {
549
583
  return;
550
584
  this.api.delete(faq.faqId).subscribe({
551
585
  next: () => {
552
- this.allFaqs.update((list) => list.filter((f) => f.faqId !== faq.faqId));
553
586
  this.toast.show('FAQ removed', 'success');
587
+ this.load();
554
588
  },
555
589
  error: () => this.toast.show('Failed to delete FAQ', 'error'),
556
590
  });
557
591
  }
558
592
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: KnowledgeHubPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
559
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: KnowledgeHubPageComponent, isStandalone: true, selector: "eds-knowledge-hub-page", ngImport: i0, template: "<div class=\"eds-page knowledge-hub\">\n <header class=\"knowledge-hub__header\">\n <div>\n <h1 class=\"eds-h1\">Practice knowledge base</h1>\n <p class=\"eds-body-sm\">\n Answers here are used by your AI receptionist on phone calls. Keep replies short and factual.\n </p>\n </div>\n <eds-button variant=\"primary\" class=\"knowledge-hub__add-desktop\" (click)=\"openCreate()\">\n <eds-icon name=\"add\" [size]=\"18\" /> Add FAQ\n </eds-button>\n </header>\n\n <div class=\"knowledge-hub__banner eds-card\" role=\"note\">\n <eds-icon name=\"info\" [size]=\"18\" />\n <p class=\"eds-body-sm\">\n Do not put patient-specific or clinical advice here. Use general practice information only.\n </p>\n </div>\n\n <div class=\"knowledge-hub__layout\">\n <main class=\"knowledge-hub__main\">\n <nav class=\"knowledge-hub__tabs\" aria-label=\"FAQ status\">\n @for (t of activeTabs; track t.id) {\n <button\n type=\"button\"\n class=\"knowledge-hub__tab\"\n [class.knowledge-hub__tab--active]=\"activeFilter() === t.id\"\n (click)=\"setActiveFilter(t.id)\"\n >\n {{ t.label }}\n </button>\n }\n </nav>\n\n <div class=\"knowledge-hub__filters eds-card\">\n <input\n type=\"search\"\n class=\"knowledge-hub__search\"\n placeholder=\"Filter by question or answer\u2026\"\n [ngModel]=\"listSearch()\"\n (ngModelChange)=\"listSearch.set($event)\"\n />\n <div class=\"knowledge-hub__chips\">\n @for (cat of categories; track cat) {\n <button\n type=\"button\"\n class=\"faq-chip knowledge-hub__chip\"\n [class]=\"'faq-chip--' + cat\"\n [class.knowledge-hub__chip--on]=\"isCategorySelected(cat)\"\n (click)=\"toggleCategory(cat)\"\n >\n {{ cat }}\n </button>\n }\n </div>\n </div>\n\n @if (loading()) {\n <div class=\"knowledge-hub__skeleton eds-card\">\n @for (i of [1, 2, 3]; track i) {\n <div class=\"knowledge-hub__skel-row\"></div>\n }\n </div>\n } @else if (error()) {\n <p class=\"knowledge-hub__error eds-body-sm\" role=\"alert\">{{ error() }}</p>\n <eds-button variant=\"secondary\" (click)=\"load()\">Retry</eds-button>\n } @else if (allFaqs().length === 0) {\n <div class=\"eds-card\">\n <eds-faq-empty-state (add)=\"openCreate()\" (useTemplate)=\"onStarterTemplate($event)\" />\n </div>\n } @else if (faqs().length === 0) {\n <p class=\"eds-body-sm knowledge-hub__empty-filter\">No FAQs match your filters.</p>\n } @else {\n <div class=\"eds-card knowledge-hub__table-wrap\">\n <table class=\"eds-table knowledge-hub__table\">\n <thead>\n <tr>\n <th>Question</th>\n <th>Answer</th>\n <th>Category</th>\n <th>Active</th>\n <th>Updated</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (faq of faqs(); track faq.faqId) {\n <tr [class.knowledge-hub__row--inactive]=\"!faq.isActive\">\n <td class=\"knowledge-hub__question\" [title]=\"faq.question\">{{ faq.question }}</td>\n <td class=\"knowledge-hub__answer\" [title]=\"faq.answer\">{{ truncate(faq.answer, 72) }}</td>\n <td>\n @if (faq.category) {\n <span class=\"faq-chip\" [class]=\"categoryClass(faq.category)\">{{ faq.category }}</span>\n } @else {\n <span class=\"eds-body-sm\">\u2014</span>\n }\n </td>\n <td>\n <label class=\"knowledge-hub__toggle\">\n <input\n type=\"checkbox\"\n [checked]=\"faq.isActive\"\n (change)=\"toggleActive(faq, $any($event.target).checked)\"\n [attr.aria-label]=\"'Toggle active for ' + faq.question\"\n />\n <span class=\"knowledge-hub__status\" [class.knowledge-hub__status--on]=\"faq.isActive\">\n {{ faq.isActive ? 'Active' : 'Inactive' }}\n </span>\n </label>\n </td>\n <td class=\"eds-mono-sm\">{{ faq.updatedAt | date: 'mediumDate' }}</td>\n <td class=\"knowledge-hub__actions\">\n <button type=\"button\" class=\"knowledge-hub__link\" (click)=\"openEdit(faq)\">Edit</button>\n <button type=\"button\" class=\"knowledge-hub__link knowledge-hub__link--danger\" (click)=\"deleteFaq(faq)\">\n Delete\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </main>\n\n <eds-faq-search-tester />\n </div>\n\n <button type=\"button\" class=\"knowledge-hub__fab\" aria-label=\"Add FAQ\" (click)=\"openCreate()\">\n <eds-icon name=\"add\" [size]=\"24\" />\n </button>\n\n <eds-faq-form\n [open]=\"formOpen()\"\n [faq]=\"editing()\"\n [initialQuestion]=\"formSeed().question\"\n [initialAnswer]=\"formSeed().answer\"\n [initialCategory]=\"formSeed().category\"\n (closed)=\"closeForm()\"\n (saved)=\"onSave($event)\"\n />\n</div>\n", styles: [".faq-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize}.faq-chip--hours{background:#3b82f61f;color:#2563eb}.faq-chip--location{background:#14b8a61f;color:#0d9488}.faq-chip--parking{background:#6b72801f;color:#4b5563}.faq-chip--insurance{background:#8b5cf61f;color:#7c3aed}.faq-chip--services{background:#22c55e1f;color:#16a34a}.faq-chip--policies{background:#f973161f;color:#ea580c}.faq-chip--general{background:#6b72801f;color:#6b7280}.knowledge-hub__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px}.knowledge-hub__banner{display:flex;align-items:flex-start;gap:10px;padding:12px 16px;margin-bottom:20px;background:#3b82f60f;border-color:#3b82f633}.knowledge-hub__banner p{margin:0}.knowledge-hub__layout{display:grid;grid-template-columns:1fr 320px;gap:20px;align-items:start}.knowledge-hub__tabs{display:flex;gap:4px;margin-bottom:12px;padding:4px;width:fit-content;background:var(--surface-l1);border-radius:var(--eds-radius-md)}.knowledge-hub__tab{border:none;background:transparent;padding:8px 14px;border-radius:var(--eds-radius-sm);font-size:13px;cursor:pointer;color:var(--text-secondary)}.knowledge-hub__tab--active{background:var(--surface-l0);color:var(--text-primary);font-weight:600;box-shadow:0 1px 2px #0000000f}.knowledge-hub__filters{padding:12px;margin-bottom:12px}.knowledge-hub__search{width:100%;padding:8px 12px;border:1px solid var(--border-default);border-radius:var(--eds-radius-md);font-size:14px;margin-bottom:10px}.knowledge-hub__chips{display:flex;flex-wrap:wrap;gap:6px}.knowledge-hub__chip{border:1px solid transparent;cursor:pointer;opacity:.6}.knowledge-hub__chip--on{opacity:1;border-color:currentColor}.knowledge-hub__table-wrap{overflow-x:auto;padding:0}.knowledge-hub__question{font-weight:600;max-width:220px}.knowledge-hub__answer{color:var(--text-secondary);max-width:260px}.knowledge-hub__row--inactive{opacity:.65}.knowledge-hub__toggle{display:flex;align-items:center;gap:6px;cursor:pointer}.knowledge-hub__status{font-size:12px;color:var(--text-secondary)}.knowledge-hub__status--on{color:#16a34a}.knowledge-hub__status:before{content:\"\";display:inline-block;width:6px;height:6px;border-radius:50%;background:#9ca3af;margin-right:4px;vertical-align:middle}.knowledge-hub__status--on:before{background:#22c55e}.knowledge-hub__actions{white-space:nowrap;display:flex;gap:8px}.knowledge-hub__link{border:none;background:none;color:var(--eds-brand, #0d9488);font-size:13px;cursor:pointer;padding:0}.knowledge-hub__link--danger{color:#dc2626}.knowledge-hub__error{color:#b91c1c}.knowledge-hub__empty-filter{padding:24px;text-align:center;color:var(--text-secondary)}.knowledge-hub__skeleton{padding:16px}.knowledge-hub__skel-row{height:48px;border-radius:8px;background:linear-gradient(90deg,#f3f4f6 25%,#e5e7eb,#f3f4f6 75%);background-size:200% 100%;animation:faq-shimmer 1.2s infinite;margin-bottom:8px}@keyframes faq-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.knowledge-hub__fab{display:none;position:fixed;right:24px;bottom:24px;width:56px;height:56px;border-radius:50%;border:none;background:var(--eds-brand, #0d9488);color:#fff;box-shadow:0 4px 14px #0003;cursor:pointer;align-items:center;justify-content:center}@media(max-width:960px){.knowledge-hub__layout{grid-template-columns:1fr}.knowledge-hub__add-desktop{display:none}.knowledge-hub__fab{display:inline-flex}}@media(min-width:961px){.knowledge-hub__fab{display:none}}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: FaqEmptyStateComponent, selector: "eds-faq-empty-state", outputs: ["add", "useTemplate"] }, { kind: "component", type: FaqFormComponent, selector: "eds-faq-form", inputs: ["open", "faq", "initialQuestion", "initialAnswer", "initialCategory"], outputs: ["closed", "saved"] }, { kind: "component", type: FaqSearchTesterComponent, selector: "eds-faq-search-tester" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
593
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: KnowledgeHubPageComponent, isStandalone: true, selector: "eds-knowledge-hub-page", ngImport: i0, template: "<div class=\"eds-page knowledge-hub\">\n <header class=\"knowledge-hub__header\">\n <div>\n <h1 class=\"eds-h1\">Practice knowledge base</h1>\n <p class=\"eds-body-sm\">\n Answers here are used by your AI receptionist on phone calls. Keep replies short and factual.\n </p>\n </div>\n <eds-button variant=\"primary\" class=\"knowledge-hub__add-desktop\" (click)=\"openCreate()\">\n <eds-icon name=\"add\" [size]=\"18\" /> Add FAQ\n </eds-button>\n </header>\n\n <div class=\"knowledge-hub__banner eds-card\" role=\"note\">\n <eds-icon name=\"info\" [size]=\"18\" />\n <p class=\"eds-body-sm\">\n Do not put patient-specific or clinical advice here. Use general practice information only.\n </p>\n </div>\n\n <div class=\"knowledge-hub__layout\">\n <main class=\"knowledge-hub__main\">\n <nav class=\"knowledge-hub__tabs\" aria-label=\"FAQ status\">\n @for (t of activeTabs; track t.id) {\n <button\n type=\"button\"\n class=\"knowledge-hub__tab\"\n [class.knowledge-hub__tab--active]=\"activeFilter() === t.id\"\n (click)=\"setActiveFilter(t.id)\"\n >\n {{ t.label }}\n </button>\n }\n </nav>\n\n <div class=\"knowledge-hub__filters eds-card\">\n <input\n type=\"search\"\n class=\"knowledge-hub__search\"\n placeholder=\"Filter by question or answer\u2026\"\n [ngModel]=\"listSearch()\"\n (ngModelChange)=\"listSearch.set($event)\"\n />\n <div class=\"knowledge-hub__chips\">\n @for (cat of categories; track cat) {\n <button\n type=\"button\"\n class=\"faq-chip knowledge-hub__chip\"\n [class]=\"'faq-chip--' + cat\"\n [class.knowledge-hub__chip--on]=\"isCategorySelected(cat)\"\n (click)=\"toggleCategory(cat)\"\n >\n {{ cat }}\n </button>\n }\n </div>\n </div>\n\n @if (loading()) {\n <div class=\"knowledge-hub__skeleton eds-card\">\n @for (i of [1, 2, 3]; track i) {\n <div class=\"knowledge-hub__skel-row\"></div>\n }\n </div>\n } @else if (error()) {\n <p class=\"knowledge-hub__error eds-body-sm\" role=\"alert\">{{ error() }}</p>\n <eds-button variant=\"secondary\" (click)=\"load()\">Retry</eds-button>\n } @else if (total() === 0 && !categoryFilter() && !listSearch() && activeFilter() === 'all') {\n <div class=\"eds-card\">\n <eds-faq-empty-state (add)=\"openCreate()\" (useTemplate)=\"onStarterTemplate($event)\" />\n </div>\n } @else if (faqs().length === 0) {\n <p class=\"eds-body-sm knowledge-hub__empty-filter\">No FAQs match your filters.</p>\n } @else {\n <div class=\"eds-card knowledge-hub__table-wrap\">\n <table class=\"eds-table knowledge-hub__table\">\n <thead>\n <tr>\n <th>Question</th>\n <th>Answer</th>\n <th>Category</th>\n <th>Active</th>\n <th>Updated</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (faq of faqs(); track faq.faqId) {\n <tr [class.knowledge-hub__row--inactive]=\"!faq.isActive\">\n <td class=\"knowledge-hub__question\" [title]=\"faq.question\">{{ faq.question }}</td>\n <td class=\"knowledge-hub__answer\" [title]=\"faq.answer\">{{ truncate(faq.answer, 72) }}</td>\n <td>\n @if (faq.category) {\n <span class=\"faq-chip\" [class]=\"categoryClass(faq.category)\">{{ faq.category }}</span>\n } @else {\n <span class=\"eds-body-sm\">\u2014</span>\n }\n </td>\n <td>\n <label class=\"knowledge-hub__toggle\">\n <input\n type=\"checkbox\"\n [checked]=\"faq.isActive\"\n (change)=\"toggleActive(faq, $any($event.target).checked)\"\n [attr.aria-label]=\"'Toggle active for ' + faq.question\"\n />\n <span class=\"knowledge-hub__status\" [class.knowledge-hub__status--on]=\"faq.isActive\">\n {{ faq.isActive ? 'Active' : 'Inactive' }}\n </span>\n </label>\n </td>\n <td class=\"eds-mono-sm\">{{ faq.updatedAt | date: 'mediumDate' }}</td>\n <td class=\"knowledge-hub__actions\">\n <button type=\"button\" class=\"knowledge-hub__link\" (click)=\"openEdit(faq)\">Edit</button>\n <button type=\"button\" class=\"knowledge-hub__link knowledge-hub__link--danger\" (click)=\"deleteFaq(faq)\">\n Delete\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <footer class=\"knowledge-hub__pager\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"knowledge-hub__pager-actions\">\n <eds-button variant=\"secondary\" [disabled]=\"page() <= 1\" (click)=\"prevPage()\">Previous</eds-button>\n <eds-button variant=\"secondary\" [disabled]=\"page() >= totalPages()\" (click)=\"nextPage()\">Next</eds-button>\n </div>\n </footer>\n }\n </main>\n\n <eds-faq-search-tester />\n </div>\n\n <button type=\"button\" class=\"knowledge-hub__fab\" aria-label=\"Add FAQ\" (click)=\"openCreate()\">\n <eds-icon name=\"add\" [size]=\"24\" />\n </button>\n\n <eds-faq-form\n [open]=\"formOpen()\"\n [faq]=\"editing()\"\n [initialQuestion]=\"formSeed().question\"\n [initialAnswer]=\"formSeed().answer\"\n [initialCategory]=\"formSeed().category\"\n (closed)=\"closeForm()\"\n (saved)=\"onSave($event)\"\n />\n</div>\n", styles: [".faq-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize}.faq-chip--hours{background:#3b82f61f;color:#2563eb}.faq-chip--location{background:#14b8a61f;color:#0d9488}.faq-chip--parking{background:#6b72801f;color:#4b5563}.faq-chip--insurance{background:#8b5cf61f;color:#7c3aed}.faq-chip--services{background:#22c55e1f;color:#16a34a}.faq-chip--policies{background:#f973161f;color:#ea580c}.faq-chip--general{background:#6b72801f;color:#6b7280}.knowledge-hub__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px}.knowledge-hub__banner{display:flex;align-items:flex-start;gap:10px;padding:12px 16px;margin-bottom:20px;background:#3b82f60f;border-color:#3b82f633}.knowledge-hub__banner p{margin:0}.knowledge-hub__layout{display:grid;grid-template-columns:1fr 320px;gap:20px;align-items:start}.knowledge-hub__tabs{display:flex;gap:4px;margin-bottom:12px;padding:4px;width:fit-content;background:var(--eds-surface-l1);border-radius:var(--eds-radius-md)}.knowledge-hub__tab{border:none;background:transparent;padding:8px 14px;border-radius:var(--eds-radius-sm);font-size:13px;cursor:pointer;color:var(--eds-text-secondary)}.knowledge-hub__tab--active{background:var(--eds-surface-l0);color:var(--eds-text-primary);font-weight:600;box-shadow:0 1px 2px #0000000f}.knowledge-hub__filters{padding:12px;margin-bottom:12px}.knowledge-hub__search{width:100%;padding:8px 12px;border:1px solid var(--eds-border-default);border-radius:var(--eds-radius-md);font-size:14px;margin-bottom:10px}.knowledge-hub__chips{display:flex;flex-wrap:wrap;gap:6px}.knowledge-hub__chip{border:1px solid transparent;cursor:pointer;opacity:.6}.knowledge-hub__chip--on{opacity:1;border-color:currentColor}.knowledge-hub__table-wrap{overflow-x:auto;padding:0}.knowledge-hub__question{font-weight:600;max-width:220px}.knowledge-hub__answer{color:var(--eds-text-secondary);max-width:260px}.knowledge-hub__row--inactive{opacity:.65}.knowledge-hub__toggle{display:flex;align-items:center;gap:6px;cursor:pointer}.knowledge-hub__status{font-size:12px;color:var(--eds-text-secondary)}.knowledge-hub__status--on{color:#16a34a}.knowledge-hub__status:before{content:\"\";display:inline-block;width:6px;height:6px;border-radius:50%;background:#9ca3af;margin-right:4px;vertical-align:middle}.knowledge-hub__status--on:before{background:#22c55e}.knowledge-hub__actions{white-space:nowrap;display:flex;gap:8px}.knowledge-hub__link{border:none;background:none;color:var(--eds-brand, #0d9488);font-size:13px;cursor:pointer;padding:0}.knowledge-hub__link--danger{color:#dc2626}.knowledge-hub__error{color:#b91c1c}.knowledge-hub__empty-filter{padding:24px;text-align:center;color:var(--eds-text-secondary)}.knowledge-hub__skeleton{padding:16px}.knowledge-hub__skel-row{height:48px;border-radius:8px;background:linear-gradient(90deg,#f3f4f6 25%,#e5e7eb,#f3f4f6 75%);background-size:200% 100%;animation:faq-shimmer 1.2s infinite;margin-bottom:8px}@keyframes faq-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.knowledge-hub__fab{display:none;position:fixed;right:24px;bottom:24px;width:56px;height:56px;border-radius:50%;border:none;background:var(--eds-brand, #0d9488);color:#fff;box-shadow:0 4px 14px #0003;cursor:pointer;align-items:center;justify-content:center}@media(max-width:960px){.knowledge-hub__layout{grid-template-columns:1fr}.knowledge-hub__add-desktop{display:none}.knowledge-hub__fab{display:inline-flex}}@media(min-width:961px){.knowledge-hub__fab{display:none}}.knowledge-hub__pager{display:flex;align-items:center;justify-content:space-between;margin-top:16px;padding-top:12px;border-top:1px solid var(--eds-border-subtle, #e4e4e7)}.knowledge-hub__pager-actions{display:flex;gap:8px}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: EdsButtonComponent, selector: "eds-button", inputs: ["variant", "type", "disabled"] }, { kind: "component", type: EdsIconComponent, selector: "eds-icon", inputs: ["name", "size", "fill", "weight"] }, { kind: "component", type: FaqEmptyStateComponent, selector: "eds-faq-empty-state", outputs: ["add", "useTemplate"] }, { kind: "component", type: FaqFormComponent, selector: "eds-faq-form", inputs: ["open", "faq", "initialQuestion", "initialAnswer", "initialCategory"], outputs: ["closed", "saved"] }, { kind: "component", type: FaqSearchTesterComponent, selector: "eds-faq-search-tester" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
560
594
  }
561
595
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: KnowledgeHubPageComponent, decorators: [{
562
596
  type: Component,
@@ -568,8 +602,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
568
602
  FaqEmptyStateComponent,
569
603
  FaqFormComponent,
570
604
  FaqSearchTesterComponent,
571
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page knowledge-hub\">\n <header class=\"knowledge-hub__header\">\n <div>\n <h1 class=\"eds-h1\">Practice knowledge base</h1>\n <p class=\"eds-body-sm\">\n Answers here are used by your AI receptionist on phone calls. Keep replies short and factual.\n </p>\n </div>\n <eds-button variant=\"primary\" class=\"knowledge-hub__add-desktop\" (click)=\"openCreate()\">\n <eds-icon name=\"add\" [size]=\"18\" /> Add FAQ\n </eds-button>\n </header>\n\n <div class=\"knowledge-hub__banner eds-card\" role=\"note\">\n <eds-icon name=\"info\" [size]=\"18\" />\n <p class=\"eds-body-sm\">\n Do not put patient-specific or clinical advice here. Use general practice information only.\n </p>\n </div>\n\n <div class=\"knowledge-hub__layout\">\n <main class=\"knowledge-hub__main\">\n <nav class=\"knowledge-hub__tabs\" aria-label=\"FAQ status\">\n @for (t of activeTabs; track t.id) {\n <button\n type=\"button\"\n class=\"knowledge-hub__tab\"\n [class.knowledge-hub__tab--active]=\"activeFilter() === t.id\"\n (click)=\"setActiveFilter(t.id)\"\n >\n {{ t.label }}\n </button>\n }\n </nav>\n\n <div class=\"knowledge-hub__filters eds-card\">\n <input\n type=\"search\"\n class=\"knowledge-hub__search\"\n placeholder=\"Filter by question or answer\u2026\"\n [ngModel]=\"listSearch()\"\n (ngModelChange)=\"listSearch.set($event)\"\n />\n <div class=\"knowledge-hub__chips\">\n @for (cat of categories; track cat) {\n <button\n type=\"button\"\n class=\"faq-chip knowledge-hub__chip\"\n [class]=\"'faq-chip--' + cat\"\n [class.knowledge-hub__chip--on]=\"isCategorySelected(cat)\"\n (click)=\"toggleCategory(cat)\"\n >\n {{ cat }}\n </button>\n }\n </div>\n </div>\n\n @if (loading()) {\n <div class=\"knowledge-hub__skeleton eds-card\">\n @for (i of [1, 2, 3]; track i) {\n <div class=\"knowledge-hub__skel-row\"></div>\n }\n </div>\n } @else if (error()) {\n <p class=\"knowledge-hub__error eds-body-sm\" role=\"alert\">{{ error() }}</p>\n <eds-button variant=\"secondary\" (click)=\"load()\">Retry</eds-button>\n } @else if (allFaqs().length === 0) {\n <div class=\"eds-card\">\n <eds-faq-empty-state (add)=\"openCreate()\" (useTemplate)=\"onStarterTemplate($event)\" />\n </div>\n } @else if (faqs().length === 0) {\n <p class=\"eds-body-sm knowledge-hub__empty-filter\">No FAQs match your filters.</p>\n } @else {\n <div class=\"eds-card knowledge-hub__table-wrap\">\n <table class=\"eds-table knowledge-hub__table\">\n <thead>\n <tr>\n <th>Question</th>\n <th>Answer</th>\n <th>Category</th>\n <th>Active</th>\n <th>Updated</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (faq of faqs(); track faq.faqId) {\n <tr [class.knowledge-hub__row--inactive]=\"!faq.isActive\">\n <td class=\"knowledge-hub__question\" [title]=\"faq.question\">{{ faq.question }}</td>\n <td class=\"knowledge-hub__answer\" [title]=\"faq.answer\">{{ truncate(faq.answer, 72) }}</td>\n <td>\n @if (faq.category) {\n <span class=\"faq-chip\" [class]=\"categoryClass(faq.category)\">{{ faq.category }}</span>\n } @else {\n <span class=\"eds-body-sm\">\u2014</span>\n }\n </td>\n <td>\n <label class=\"knowledge-hub__toggle\">\n <input\n type=\"checkbox\"\n [checked]=\"faq.isActive\"\n (change)=\"toggleActive(faq, $any($event.target).checked)\"\n [attr.aria-label]=\"'Toggle active for ' + faq.question\"\n />\n <span class=\"knowledge-hub__status\" [class.knowledge-hub__status--on]=\"faq.isActive\">\n {{ faq.isActive ? 'Active' : 'Inactive' }}\n </span>\n </label>\n </td>\n <td class=\"eds-mono-sm\">{{ faq.updatedAt | date: 'mediumDate' }}</td>\n <td class=\"knowledge-hub__actions\">\n <button type=\"button\" class=\"knowledge-hub__link\" (click)=\"openEdit(faq)\">Edit</button>\n <button type=\"button\" class=\"knowledge-hub__link knowledge-hub__link--danger\" (click)=\"deleteFaq(faq)\">\n Delete\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </main>\n\n <eds-faq-search-tester />\n </div>\n\n <button type=\"button\" class=\"knowledge-hub__fab\" aria-label=\"Add FAQ\" (click)=\"openCreate()\">\n <eds-icon name=\"add\" [size]=\"24\" />\n </button>\n\n <eds-faq-form\n [open]=\"formOpen()\"\n [faq]=\"editing()\"\n [initialQuestion]=\"formSeed().question\"\n [initialAnswer]=\"formSeed().answer\"\n [initialCategory]=\"formSeed().category\"\n (closed)=\"closeForm()\"\n (saved)=\"onSave($event)\"\n />\n</div>\n", styles: [".faq-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize}.faq-chip--hours{background:#3b82f61f;color:#2563eb}.faq-chip--location{background:#14b8a61f;color:#0d9488}.faq-chip--parking{background:#6b72801f;color:#4b5563}.faq-chip--insurance{background:#8b5cf61f;color:#7c3aed}.faq-chip--services{background:#22c55e1f;color:#16a34a}.faq-chip--policies{background:#f973161f;color:#ea580c}.faq-chip--general{background:#6b72801f;color:#6b7280}.knowledge-hub__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px}.knowledge-hub__banner{display:flex;align-items:flex-start;gap:10px;padding:12px 16px;margin-bottom:20px;background:#3b82f60f;border-color:#3b82f633}.knowledge-hub__banner p{margin:0}.knowledge-hub__layout{display:grid;grid-template-columns:1fr 320px;gap:20px;align-items:start}.knowledge-hub__tabs{display:flex;gap:4px;margin-bottom:12px;padding:4px;width:fit-content;background:var(--surface-l1);border-radius:var(--eds-radius-md)}.knowledge-hub__tab{border:none;background:transparent;padding:8px 14px;border-radius:var(--eds-radius-sm);font-size:13px;cursor:pointer;color:var(--text-secondary)}.knowledge-hub__tab--active{background:var(--surface-l0);color:var(--text-primary);font-weight:600;box-shadow:0 1px 2px #0000000f}.knowledge-hub__filters{padding:12px;margin-bottom:12px}.knowledge-hub__search{width:100%;padding:8px 12px;border:1px solid var(--border-default);border-radius:var(--eds-radius-md);font-size:14px;margin-bottom:10px}.knowledge-hub__chips{display:flex;flex-wrap:wrap;gap:6px}.knowledge-hub__chip{border:1px solid transparent;cursor:pointer;opacity:.6}.knowledge-hub__chip--on{opacity:1;border-color:currentColor}.knowledge-hub__table-wrap{overflow-x:auto;padding:0}.knowledge-hub__question{font-weight:600;max-width:220px}.knowledge-hub__answer{color:var(--text-secondary);max-width:260px}.knowledge-hub__row--inactive{opacity:.65}.knowledge-hub__toggle{display:flex;align-items:center;gap:6px;cursor:pointer}.knowledge-hub__status{font-size:12px;color:var(--text-secondary)}.knowledge-hub__status--on{color:#16a34a}.knowledge-hub__status:before{content:\"\";display:inline-block;width:6px;height:6px;border-radius:50%;background:#9ca3af;margin-right:4px;vertical-align:middle}.knowledge-hub__status--on:before{background:#22c55e}.knowledge-hub__actions{white-space:nowrap;display:flex;gap:8px}.knowledge-hub__link{border:none;background:none;color:var(--eds-brand, #0d9488);font-size:13px;cursor:pointer;padding:0}.knowledge-hub__link--danger{color:#dc2626}.knowledge-hub__error{color:#b91c1c}.knowledge-hub__empty-filter{padding:24px;text-align:center;color:var(--text-secondary)}.knowledge-hub__skeleton{padding:16px}.knowledge-hub__skel-row{height:48px;border-radius:8px;background:linear-gradient(90deg,#f3f4f6 25%,#e5e7eb,#f3f4f6 75%);background-size:200% 100%;animation:faq-shimmer 1.2s infinite;margin-bottom:8px}@keyframes faq-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.knowledge-hub__fab{display:none;position:fixed;right:24px;bottom:24px;width:56px;height:56px;border-radius:50%;border:none;background:var(--eds-brand, #0d9488);color:#fff;box-shadow:0 4px 14px #0003;cursor:pointer;align-items:center;justify-content:center}@media(max-width:960px){.knowledge-hub__layout{grid-template-columns:1fr}.knowledge-hub__add-desktop{display:none}.knowledge-hub__fab{display:inline-flex}}@media(min-width:961px){.knowledge-hub__fab{display:none}}\n"] }]
605
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"eds-page knowledge-hub\">\n <header class=\"knowledge-hub__header\">\n <div>\n <h1 class=\"eds-h1\">Practice knowledge base</h1>\n <p class=\"eds-body-sm\">\n Answers here are used by your AI receptionist on phone calls. Keep replies short and factual.\n </p>\n </div>\n <eds-button variant=\"primary\" class=\"knowledge-hub__add-desktop\" (click)=\"openCreate()\">\n <eds-icon name=\"add\" [size]=\"18\" /> Add FAQ\n </eds-button>\n </header>\n\n <div class=\"knowledge-hub__banner eds-card\" role=\"note\">\n <eds-icon name=\"info\" [size]=\"18\" />\n <p class=\"eds-body-sm\">\n Do not put patient-specific or clinical advice here. Use general practice information only.\n </p>\n </div>\n\n <div class=\"knowledge-hub__layout\">\n <main class=\"knowledge-hub__main\">\n <nav class=\"knowledge-hub__tabs\" aria-label=\"FAQ status\">\n @for (t of activeTabs; track t.id) {\n <button\n type=\"button\"\n class=\"knowledge-hub__tab\"\n [class.knowledge-hub__tab--active]=\"activeFilter() === t.id\"\n (click)=\"setActiveFilter(t.id)\"\n >\n {{ t.label }}\n </button>\n }\n </nav>\n\n <div class=\"knowledge-hub__filters eds-card\">\n <input\n type=\"search\"\n class=\"knowledge-hub__search\"\n placeholder=\"Filter by question or answer\u2026\"\n [ngModel]=\"listSearch()\"\n (ngModelChange)=\"listSearch.set($event)\"\n />\n <div class=\"knowledge-hub__chips\">\n @for (cat of categories; track cat) {\n <button\n type=\"button\"\n class=\"faq-chip knowledge-hub__chip\"\n [class]=\"'faq-chip--' + cat\"\n [class.knowledge-hub__chip--on]=\"isCategorySelected(cat)\"\n (click)=\"toggleCategory(cat)\"\n >\n {{ cat }}\n </button>\n }\n </div>\n </div>\n\n @if (loading()) {\n <div class=\"knowledge-hub__skeleton eds-card\">\n @for (i of [1, 2, 3]; track i) {\n <div class=\"knowledge-hub__skel-row\"></div>\n }\n </div>\n } @else if (error()) {\n <p class=\"knowledge-hub__error eds-body-sm\" role=\"alert\">{{ error() }}</p>\n <eds-button variant=\"secondary\" (click)=\"load()\">Retry</eds-button>\n } @else if (total() === 0 && !categoryFilter() && !listSearch() && activeFilter() === 'all') {\n <div class=\"eds-card\">\n <eds-faq-empty-state (add)=\"openCreate()\" (useTemplate)=\"onStarterTemplate($event)\" />\n </div>\n } @else if (faqs().length === 0) {\n <p class=\"eds-body-sm knowledge-hub__empty-filter\">No FAQs match your filters.</p>\n } @else {\n <div class=\"eds-card knowledge-hub__table-wrap\">\n <table class=\"eds-table knowledge-hub__table\">\n <thead>\n <tr>\n <th>Question</th>\n <th>Answer</th>\n <th>Category</th>\n <th>Active</th>\n <th>Updated</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (faq of faqs(); track faq.faqId) {\n <tr [class.knowledge-hub__row--inactive]=\"!faq.isActive\">\n <td class=\"knowledge-hub__question\" [title]=\"faq.question\">{{ faq.question }}</td>\n <td class=\"knowledge-hub__answer\" [title]=\"faq.answer\">{{ truncate(faq.answer, 72) }}</td>\n <td>\n @if (faq.category) {\n <span class=\"faq-chip\" [class]=\"categoryClass(faq.category)\">{{ faq.category }}</span>\n } @else {\n <span class=\"eds-body-sm\">\u2014</span>\n }\n </td>\n <td>\n <label class=\"knowledge-hub__toggle\">\n <input\n type=\"checkbox\"\n [checked]=\"faq.isActive\"\n (change)=\"toggleActive(faq, $any($event.target).checked)\"\n [attr.aria-label]=\"'Toggle active for ' + faq.question\"\n />\n <span class=\"knowledge-hub__status\" [class.knowledge-hub__status--on]=\"faq.isActive\">\n {{ faq.isActive ? 'Active' : 'Inactive' }}\n </span>\n </label>\n </td>\n <td class=\"eds-mono-sm\">{{ faq.updatedAt | date: 'mediumDate' }}</td>\n <td class=\"knowledge-hub__actions\">\n <button type=\"button\" class=\"knowledge-hub__link\" (click)=\"openEdit(faq)\">Edit</button>\n <button type=\"button\" class=\"knowledge-hub__link knowledge-hub__link--danger\" (click)=\"deleteFaq(faq)\">\n Delete\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <footer class=\"knowledge-hub__pager\">\n <span class=\"eds-body-sm\">\n Page {{ page() }} of {{ totalPages() }} \u00B7 {{ total() }} total\n </span>\n <div class=\"knowledge-hub__pager-actions\">\n <eds-button variant=\"secondary\" [disabled]=\"page() <= 1\" (click)=\"prevPage()\">Previous</eds-button>\n <eds-button variant=\"secondary\" [disabled]=\"page() >= totalPages()\" (click)=\"nextPage()\">Next</eds-button>\n </div>\n </footer>\n }\n </main>\n\n <eds-faq-search-tester />\n </div>\n\n <button type=\"button\" class=\"knowledge-hub__fab\" aria-label=\"Add FAQ\" (click)=\"openCreate()\">\n <eds-icon name=\"add\" [size]=\"24\" />\n </button>\n\n <eds-faq-form\n [open]=\"formOpen()\"\n [faq]=\"editing()\"\n [initialQuestion]=\"formSeed().question\"\n [initialAnswer]=\"formSeed().answer\"\n [initialCategory]=\"formSeed().category\"\n (closed)=\"closeForm()\"\n (saved)=\"onSave($event)\"\n />\n</div>\n", styles: [".faq-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize}.faq-chip--hours{background:#3b82f61f;color:#2563eb}.faq-chip--location{background:#14b8a61f;color:#0d9488}.faq-chip--parking{background:#6b72801f;color:#4b5563}.faq-chip--insurance{background:#8b5cf61f;color:#7c3aed}.faq-chip--services{background:#22c55e1f;color:#16a34a}.faq-chip--policies{background:#f973161f;color:#ea580c}.faq-chip--general{background:#6b72801f;color:#6b7280}.knowledge-hub__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px}.knowledge-hub__banner{display:flex;align-items:flex-start;gap:10px;padding:12px 16px;margin-bottom:20px;background:#3b82f60f;border-color:#3b82f633}.knowledge-hub__banner p{margin:0}.knowledge-hub__layout{display:grid;grid-template-columns:1fr 320px;gap:20px;align-items:start}.knowledge-hub__tabs{display:flex;gap:4px;margin-bottom:12px;padding:4px;width:fit-content;background:var(--eds-surface-l1);border-radius:var(--eds-radius-md)}.knowledge-hub__tab{border:none;background:transparent;padding:8px 14px;border-radius:var(--eds-radius-sm);font-size:13px;cursor:pointer;color:var(--eds-text-secondary)}.knowledge-hub__tab--active{background:var(--eds-surface-l0);color:var(--eds-text-primary);font-weight:600;box-shadow:0 1px 2px #0000000f}.knowledge-hub__filters{padding:12px;margin-bottom:12px}.knowledge-hub__search{width:100%;padding:8px 12px;border:1px solid var(--eds-border-default);border-radius:var(--eds-radius-md);font-size:14px;margin-bottom:10px}.knowledge-hub__chips{display:flex;flex-wrap:wrap;gap:6px}.knowledge-hub__chip{border:1px solid transparent;cursor:pointer;opacity:.6}.knowledge-hub__chip--on{opacity:1;border-color:currentColor}.knowledge-hub__table-wrap{overflow-x:auto;padding:0}.knowledge-hub__question{font-weight:600;max-width:220px}.knowledge-hub__answer{color:var(--eds-text-secondary);max-width:260px}.knowledge-hub__row--inactive{opacity:.65}.knowledge-hub__toggle{display:flex;align-items:center;gap:6px;cursor:pointer}.knowledge-hub__status{font-size:12px;color:var(--eds-text-secondary)}.knowledge-hub__status--on{color:#16a34a}.knowledge-hub__status:before{content:\"\";display:inline-block;width:6px;height:6px;border-radius:50%;background:#9ca3af;margin-right:4px;vertical-align:middle}.knowledge-hub__status--on:before{background:#22c55e}.knowledge-hub__actions{white-space:nowrap;display:flex;gap:8px}.knowledge-hub__link{border:none;background:none;color:var(--eds-brand, #0d9488);font-size:13px;cursor:pointer;padding:0}.knowledge-hub__link--danger{color:#dc2626}.knowledge-hub__error{color:#b91c1c}.knowledge-hub__empty-filter{padding:24px;text-align:center;color:var(--eds-text-secondary)}.knowledge-hub__skeleton{padding:16px}.knowledge-hub__skel-row{height:48px;border-radius:8px;background:linear-gradient(90deg,#f3f4f6 25%,#e5e7eb,#f3f4f6 75%);background-size:200% 100%;animation:faq-shimmer 1.2s infinite;margin-bottom:8px}@keyframes faq-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.knowledge-hub__fab{display:none;position:fixed;right:24px;bottom:24px;width:56px;height:56px;border-radius:50%;border:none;background:var(--eds-brand, #0d9488);color:#fff;box-shadow:0 4px 14px #0003;cursor:pointer;align-items:center;justify-content:center}@media(max-width:960px){.knowledge-hub__layout{grid-template-columns:1fr}.knowledge-hub__add-desktop{display:none}.knowledge-hub__fab{display:inline-flex}}@media(min-width:961px){.knowledge-hub__fab{display:none}}.knowledge-hub__pager{display:flex;align-items:center;justify-content:space-between;margin-top:16px;padding-top:12px;border-top:1px solid var(--eds-border-subtle, #e4e4e7)}.knowledge-hub__pager-actions{display:flex;gap:8px}\n"] }]
572
606
  }] });
573
607
 
574
608
  export { KnowledgeHubPageComponent };
575
- //# sourceMappingURL=edvak99-voice-receptionist-widget-knowledge-hub.page-cZqBuull.mjs.map
609
+ //# sourceMappingURL=edvak99-voice-receptionist-widget-knowledge-hub.page-BngGx_Lc.mjs.map