@claudebernard/therapeutic-alternative-element 2.2.0 → 2.4.0

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.
@@ -17,6 +17,12 @@ export declare class TherapeuticAlternative extends LitElement {
17
17
  patientProfile: Bundle | null;
18
18
  apiUrl: string;
19
19
  apiKey: string | undefined;
20
+ /**
21
+ * Contexte d'intégration (application consommatrice + chaîne de web components BCB parents)
22
+ * transmis par l'intégrateur, propagé sur chaque appel API sortant via des en-têtes dédiés -
23
+ * voir `@shared/utils/tracing`. N'inclut pas ce composant lui-même dans `ancestry`.
24
+ */
25
+ context: WebComponentContext | undefined;
20
26
  private sourceProduct;
21
27
  private results;
22
28
  private totalElements;
@@ -26,6 +32,7 @@ export declare class TherapeuticAlternative extends LitElement {
26
32
  private include;
27
33
  private exclude;
28
34
  private quickFilters;
35
+ private quickFilterFacets;
29
36
  private scopeFacets;
30
37
  private includeFacets;
31
38
  private excludeFacets;
@@ -45,13 +52,15 @@ export declare class TherapeuticAlternative extends LitElement {
45
52
  private apiReplaceError;
46
53
  private apiReplaceLoading;
47
54
  private missingPrescriptionError;
48
- private hasAppliedInitialScope;
49
55
  private refetchTimer;
50
56
  private requestSeq;
51
57
  private get currentMedication();
52
58
  private get currentMedicationRequest();
53
59
  private get sourceProductId();
60
+ private get sourceProductLabel();
61
+ private get isParaSource();
54
62
  updated(changedProperties: Map<string | number | symbol, unknown>): void;
63
+ private resetAllCriteria;
55
64
  disconnectedCallback(): void;
56
65
  private clearScheduledRefetch;
57
66
  private scheduleRefetch;
@@ -61,7 +70,10 @@ export declare class TherapeuticAlternative extends LitElement {
61
70
  private static buildScopeFacets;
62
71
  private static buildIncludeFacets;
63
72
  private static buildExcludeFacets;
64
- private static deriveInitialScope;
73
+ private static codesOf;
74
+ private scopePossessedIds;
75
+ private includePossessedIds;
76
+ private excludePossessedIds;
65
77
  private toggleScope;
66
78
  private toggleExclude;
67
79
  private toggleInclude;
@@ -84,13 +96,36 @@ export declare class TherapeuticAlternative extends LitElement {
84
96
  private renderExclusionSection;
85
97
  private renderIncludeSection;
86
98
  private renderQuickFiltersSection;
99
+ private renderQuickFilterCount;
87
100
  private renderSectionHeader;
88
101
  private matchReasonLabels;
89
102
  private renderResults;
103
+ private renderTitle;
90
104
  render(): TemplateResult<1>;
91
105
  static styles: CSSResult;
92
106
  }
93
107
 
108
+ /**
109
+ * Contexte d'intégration porté par une instance du composant : quelle application consommatrice
110
+ * (Equasens, Maiia officine, ...) et quelle chaîne de web components BCB imbriqués ont mené jusqu'à
111
+ * cet appel. Les deux champs sont optionnels et indépendants - un intégrateur peut renseigner l'un
112
+ * sans l'autre.
113
+ */
114
+ declare interface WebComponentContext {
115
+ /** Application consommatrice, ex: "equasens" ou "equasens@1.3.1". Envoyé tel quel. */
116
+ consumer?: string;
117
+ /**
118
+ * Chaîne des web components BCB parents, du plus extérieur au plus proche - N'INCLUT PAS le
119
+ * composant courant. Ex: pour Equasens -> shared-medication-review -> medication-request-editor
120
+ * -> ce composant, `ancestry` vaut [{name: "shared-medication-review", ...}, {name:
121
+ * "medication-request-editor", ...}].
122
+ */
123
+ ancestry?: Array<{
124
+ name: string;
125
+ version: string;
126
+ }>;
127
+ }
128
+
94
129
 
95
130
  export * from "@claudebernard/web-component-fhir-utils";
96
131
 
@@ -775,6 +775,67 @@ const getFhirPosologies = (options) => (options?.client ?? client$1).post({
775
775
  ...options?.headers
776
776
  }
777
777
  });
778
+ function random$1(len) {
779
+ return crypto.getRandomValues(new Uint8Array(len));
780
+ }
781
+ const W3C_TRACEPARENT_VERSION = "00";
782
+ const FLAG_SAMPLE = 1;
783
+ const FLAG_RANDOM = 2;
784
+ function traceparent(version, trace_id, parent_id, flags) {
785
+ return {
786
+ version,
787
+ trace_id,
788
+ parent_id,
789
+ flags,
790
+ child() {
791
+ return traceparent(this.version, this.trace_id, random(8), this.flags);
792
+ },
793
+ toString() {
794
+ let flags2 = this.flags.toString(16).padStart(2, "0");
795
+ return `${this.version}-${this.trace_id}-${this.parent_id}-${flags2}`;
796
+ }
797
+ };
798
+ }
799
+ function make() {
800
+ let id = random(24);
801
+ return traceparent(W3C_TRACEPARENT_VERSION, id.slice(0, 32), id.slice(32), FLAG_SAMPLE | FLAG_RANDOM);
802
+ }
803
+ let IDX = 256, HEX = [];
804
+ while (IDX--) HEX[IDX] = (IDX + 256).toString(16).substring(1);
805
+ function random(size) {
806
+ let a2 = random$1(size), i3 = 0, o2 = "";
807
+ for (; i3 < a2.length; i3++) o2 += HEX[a2[i3]];
808
+ return o2;
809
+ }
810
+ const COMPONENT_HEADERS = {
811
+ "Bcb-Web-Component-Package": "@claudebernard/therapeutic-alternative-element",
812
+ "Bcb-Web-Component-Version": "2.4"
813
+ };
814
+ let activeContext;
815
+ function setActiveContext(context) {
816
+ activeContext = context;
817
+ }
818
+ function makeTraceparent() {
819
+ return make().child().toString();
820
+ }
821
+ function buildTracingHeaders() {
822
+ return {
823
+ traceparent: makeTraceparent(),
824
+ ...COMPONENT_HEADERS,
825
+ ...activeContext?.consumer ? { "Bcb-Consumer-Product": activeContext.consumer } : {},
826
+ ...activeContext?.ancestry?.length ? { "Bcb-Web-Component-Chain": activeContext.ancestry.map(({ name, version }) => `${name}@${version}`).join(",") } : {}
827
+ };
828
+ }
829
+ function attachTracingHeaders(request) {
830
+ const defaults = buildTracingHeaders();
831
+ for (const [key, value] of Object.entries(defaults)) {
832
+ if (!request.headers.has(key)) {
833
+ request.headers.set(key, value);
834
+ }
835
+ }
836
+ return request;
837
+ }
838
+ client$1.interceptors.request.use(attachTracingHeaders);
778
839
  const prescriptionApi = {
779
840
  getFhirPosologies
780
841
  };
@@ -1559,6 +1620,7 @@ const searchProductsById7 = (options) => (options.client ?? client).post({
1559
1620
  ...options.headers
1560
1621
  }
1561
1622
  });
1623
+ client.interceptors.request.use(attachTracingHeaders);
1562
1624
  const searchApi = {
1563
1625
  searchProductsByIndication: searchProductsById7,
1564
1626
  searchAlternative
@@ -1642,7 +1704,7 @@ function findMostAdequateMedicationRequest(entries, threshold = DEFAULT_ADEQUATI
1642
1704
  const medicationRequestEntry = entries.find((entry) => entry.resource?.resourceType === "MedicationRequest" && getAdequationScore(entry) <= threshold);
1643
1705
  return medicationRequestEntry?.resource;
1644
1706
  }
1645
- const t$3 = globalThis, e$4 = t$3.ShadowRoot && (void 0 === t$3.ShadyCSS || t$3.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$2 = /* @__PURE__ */ Symbol(), o$5 = /* @__PURE__ */ new WeakMap();
1707
+ const t$3 = globalThis, e$4 = t$3.ShadowRoot && (void 0 === t$3.ShadyCSS || t$3.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$2 = /* @__PURE__ */ Symbol(), o$6 = /* @__PURE__ */ new WeakMap();
1646
1708
  let n$3 = class n {
1647
1709
  constructor(t2, e2, o2) {
1648
1710
  if (this._$cssResult$ = true, o2 !== s$2) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");
@@ -1653,7 +1715,7 @@ let n$3 = class n {
1653
1715
  const s2 = this.t;
1654
1716
  if (e$4 && void 0 === t2) {
1655
1717
  const e2 = void 0 !== s2 && 1 === s2.length;
1656
- e2 && (t2 = o$5.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$5.set(s2, t2));
1718
+ e2 && (t2 = o$6.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$6.set(s2, t2));
1657
1719
  }
1658
1720
  return t2;
1659
1721
  }
@@ -1679,7 +1741,7 @@ const r$4 = (t2) => new n$3("string" == typeof t2 ? t2 : t2 + "", void 0, s$2),
1679
1741
  for (const s2 of t3.cssRules) e2 += s2.cssText;
1680
1742
  return r$4(e2);
1681
1743
  })(t2) : t2;
1682
- const { is: i$3, defineProperty: e$3, getOwnPropertyDescriptor: h$1, getOwnPropertyNames: r$3, getOwnPropertySymbols: o$4, getPrototypeOf: n$2 } = Object, a$1 = globalThis, c$1 = a$1.trustedTypes, l$1 = c$1 ? c$1.emptyScript : "", p$1 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$1 = { toAttribute(t2, s2) {
1744
+ const { is: i$3, defineProperty: e$3, getOwnPropertyDescriptor: h$1, getOwnPropertyNames: r$3, getOwnPropertySymbols: o$5, getPrototypeOf: n$2 } = Object, a$1 = globalThis, c$1 = a$1.trustedTypes, l$1 = c$1 ? c$1.emptyScript : "", p$1 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$1 = { toAttribute(t2, s2) {
1683
1745
  switch (s2) {
1684
1746
  case Boolean:
1685
1747
  t2 = t2 ? l$1 : null;
@@ -1744,7 +1806,7 @@ let y$1 = class y extends HTMLElement {
1744
1806
  static finalize() {
1745
1807
  if (this.hasOwnProperty(d$1("finalized"))) return;
1746
1808
  if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d$1("properties"))) {
1747
- const t3 = this.properties, s2 = [...r$3(t3), ...o$4(t3)];
1809
+ const t3 = this.properties, s2 = [...r$3(t3), ...o$5(t3)];
1748
1810
  for (const i3 of s2) this.createProperty(i3, t3[i3]);
1749
1811
  }
1750
1812
  const t2 = this[Symbol.metadata];
@@ -1891,7 +1953,7 @@ let y$1 = class y extends HTMLElement {
1891
1953
  }
1892
1954
  };
1893
1955
  y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$1("elementProperties")] = /* @__PURE__ */ new Map(), y$1[d$1("finalized")] = /* @__PURE__ */ new Map(), p$1?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.2");
1894
- const t$2 = globalThis, i$2 = (t2) => t2, s$1 = t$2.trustedTypes, e$2 = s$1 ? s$1.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, h = "$lit$", o$3 = `lit$${Math.random().toFixed(9).slice(2)}$`, n$1 = "?" + o$3, r$2 = `<${n$1}>`, l = document, c = () => l.createComment(""), a = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, u = Array.isArray, d = (t2) => u(t2) || "function" == typeof t2?.[Symbol.iterator], f = "[ \n\f\r]", v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m = />/g, p = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^
1956
+ const t$2 = globalThis, i$2 = (t2) => t2, s$1 = t$2.trustedTypes, e$2 = s$1 ? s$1.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, h = "$lit$", o$4 = `lit$${Math.random().toFixed(9).slice(2)}$`, n$1 = "?" + o$4, r$2 = `<${n$1}>`, l = document, c = () => l.createComment(""), a = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, u = Array.isArray, d = (t2) => u(t2) || "function" == typeof t2?.[Symbol.iterator], f = "[ \n\f\r]", v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m = />/g, p = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^
1895
1957
  \f\r"'\`<>=]|("|')|))|$)`, "g"), g = /'/g, $ = /"/g, y2 = /^(?:script|style|textarea|title)$/i, x = (t2) => (i3, ...s2) => ({ _$litType$: t2, strings: i3, values: s2 }), b = x(1), E = /* @__PURE__ */ Symbol.for("lit-noChange"), A = /* @__PURE__ */ Symbol.for("lit-nothing"), C = /* @__PURE__ */ new WeakMap(), P = l.createTreeWalker(l, 129);
1896
1958
  function V(t2, i3) {
1897
1959
  if (!u(t2) || !t2.hasOwnProperty("raw")) throw Error("invalid template strings array");
@@ -1905,7 +1967,7 @@ const N = (t2, i3) => {
1905
1967
  let a2, u2, d2 = -1, f2 = 0;
1906
1968
  for (; f2 < s3.length && (c2.lastIndex = f2, u2 = c2.exec(s3), null !== u2); ) f2 = c2.lastIndex, c2 === v ? "!--" === u2[1] ? c2 = _ : void 0 !== u2[1] ? c2 = m : void 0 !== u2[2] ? (y2.test(u2[2]) && (n3 = RegExp("</" + u2[2], "g")), c2 = p) : void 0 !== u2[3] && (c2 = p) : c2 === p ? ">" === u2[0] ? (c2 = n3 ?? v, d2 = -1) : void 0 === u2[1] ? d2 = -2 : (d2 = c2.lastIndex - u2[2].length, a2 = u2[1], c2 = void 0 === u2[3] ? p : '"' === u2[3] ? $ : g) : c2 === $ || c2 === g ? c2 = p : c2 === _ || c2 === m ? c2 = v : (c2 = p, n3 = void 0);
1907
1969
  const x2 = c2 === p && t2[i4 + 1].startsWith("/>") ? " " : "";
1908
- l2 += c2 === v ? s3 + r$2 : d2 >= 0 ? (e2.push(a2), s3.slice(0, d2) + h + s3.slice(d2) + o$3 + x2) : s3 + o$3 + (-2 === d2 ? i4 : x2);
1970
+ l2 += c2 === v ? s3 + r$2 : d2 >= 0 ? (e2.push(a2), s3.slice(0, d2) + h + s3.slice(d2) + o$4 + x2) : s3 + o$4 + (-2 === d2 ? i4 : x2);
1909
1971
  }
1910
1972
  return [V(t2, l2 + (t2[s2] || "<?>") + (2 === i3 ? "</svg>" : 3 === i3 ? "</math>" : "")), e2];
1911
1973
  };
@@ -1922,11 +1984,11 @@ class S {
1922
1984
  for (; null !== (r2 = P.nextNode()) && d2.length < u2; ) {
1923
1985
  if (1 === r2.nodeType) {
1924
1986
  if (r2.hasAttributes()) for (const t3 of r2.getAttributeNames()) if (t3.endsWith(h)) {
1925
- const i4 = v2[a2++], s2 = r2.getAttribute(t3).split(o$3), e3 = /([.?@])?(.*)/.exec(i4);
1987
+ const i4 = v2[a2++], s2 = r2.getAttribute(t3).split(o$4), e3 = /([.?@])?(.*)/.exec(i4);
1926
1988
  d2.push({ type: 1, index: l2, name: e3[2], strings: s2, ctor: "." === e3[1] ? I : "?" === e3[1] ? L : "@" === e3[1] ? z : H }), r2.removeAttribute(t3);
1927
- } else t3.startsWith(o$3) && (d2.push({ type: 6, index: l2 }), r2.removeAttribute(t3));
1989
+ } else t3.startsWith(o$4) && (d2.push({ type: 6, index: l2 }), r2.removeAttribute(t3));
1928
1990
  if (y2.test(r2.tagName)) {
1929
- const t3 = r2.textContent.split(o$3), i4 = t3.length - 1;
1991
+ const t3 = r2.textContent.split(o$4), i4 = t3.length - 1;
1930
1992
  if (i4 > 0) {
1931
1993
  r2.textContent = s$1 ? s$1.emptyScript : "";
1932
1994
  for (let s2 = 0; s2 < i4; s2++) r2.append(t3[s2], c()), P.nextNode(), d2.push({ type: 2, index: ++l2 });
@@ -1936,7 +1998,7 @@ class S {
1936
1998
  } else if (8 === r2.nodeType) if (r2.data === n$1) d2.push({ type: 2, index: l2 });
1937
1999
  else {
1938
2000
  let t3 = -1;
1939
- for (; -1 !== (t3 = r2.data.indexOf(o$3, t3 + 1)); ) d2.push({ type: 7, index: l2 }), t3 += o$3.length - 1;
2001
+ for (; -1 !== (t3 = r2.data.indexOf(o$4, t3 + 1)); ) d2.push({ type: 7, index: l2 }), t3 += o$4.length - 1;
1940
2002
  }
1941
2003
  l2++;
1942
2004
  }
@@ -2139,15 +2201,15 @@ let i$1 = class i extends y$1 {
2139
2201
  }
2140
2202
  };
2141
2203
  i$1._$litElement$ = true, i$1["finalized"] = true, s.litElementHydrateSupport?.({ LitElement: i$1 });
2142
- const o$2 = s.litElementPolyfillSupport;
2143
- o$2?.({ LitElement: i$1 });
2204
+ const o$3 = s.litElementPolyfillSupport;
2205
+ o$3?.({ LitElement: i$1 });
2144
2206
  (s.litElementVersions ??= []).push("4.2.2");
2145
2207
  const t$1 = (t2) => (e2, o2) => {
2146
2208
  void 0 !== o2 ? o2.addInitializer(() => {
2147
2209
  customElements.define(t2, e2);
2148
2210
  }) : customElements.define(t2, e2);
2149
2211
  };
2150
- const o$1 = { attribute: true, type: String, converter: u$1, reflect: false, hasChanged: f$1 }, r$1 = (t2 = o$1, e2, r2) => {
2212
+ const o$2 = { attribute: true, type: String, converter: u$1, reflect: false, hasChanged: f$1 }, r$1 = (t2 = o$2, e2, r2) => {
2151
2213
  const { kind: n3, metadata: i3 } = r2;
2152
2214
  let s2 = globalThis.litPropertyMetadata.get(i3);
2153
2215
  if (void 0 === s2 && globalThis.litPropertyMetadata.set(i3, s2 = /* @__PURE__ */ new Map()), "setter" === n3 && ((t2 = Object.create(t2)).wrapped = true), s2.set(r2.name, t2), "accessor" === n3) {
@@ -2177,6 +2239,7 @@ function n2(t2) {
2177
2239
  function r(r2) {
2178
2240
  return n2({ ...r2, state: true, attribute: false });
2179
2241
  }
2242
+ const o$1 = (o2) => o2 ?? A;
2180
2243
  const t = { CHILD: 2 }, e$1 = (t2) => (...e2) => ({ _$litDirective$: t2, values: e2 });
2181
2244
  class i2 {
2182
2245
  constructor(t2) {
@@ -2306,8 +2369,9 @@ const EXCLUSION_GROUPS = [
2306
2369
  { key: "notableExcipients", idsField: "notableExcipientIds", label: "Excipients à effet notoire" },
2307
2370
  { key: "excipients", idsField: "excipientIds", label: "Excipients" },
2308
2371
  { key: "contraindications", idsField: "contraindicationIds", label: "Utilisation contre-indiquée" },
2309
- { key: "cautions", idsField: "cautionIds", label: "Mises en garde" },
2310
- { key: "precautions", idsField: "precautionIds", label: "Précaution d'emploi" }
2372
+ { key: "cautions", idsField: "cautionIds", label: "Utilisation déconseillée" },
2373
+ { key: "precautions", idsField: "precautionIds", label: "Précaution d'emploi" },
2374
+ { key: "sideEffects", idsField: "sideEffectIds", label: "Effets indésirables" }
2311
2375
  ];
2312
2376
  const QUICK_FILTERS = [
2313
2377
  { key: "sameGalenicForm", label: "Même forme galénique" },
@@ -2326,7 +2390,7 @@ const MATCH_REASON_VERB_BY_DIMENSION = {
2326
2390
  indication: "indiqué pour",
2327
2391
  activeSubstance: "contient"
2328
2392
  };
2329
- const therapeuticAlternativeCSS = '.claudebernard-therapeutic-alternative {\n font-family: var(--custom-font-family, "Ubuntu"), sans-serif;\n padding: 1rem;\n}\n.claudebernard-therapeutic-alternative-without-title {\n padding-top: 0;\n max-width: 70vw;\n width: 60vw;\n max-height: 70vh;\n}\n.claudebernard-therapeutic-alternative-title {\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--bcbd-gray-500-color, #64748B);\n margin-bottom: 0.375rem;\n display: block;\n}\n.claudebernard-therapeutic-alternative__source {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__disclaimer {\n margin-bottom: 1rem;\n}\n.claudebernard-therapeutic-alternative__loading {\n width: 100%;\n height: 30%;\n padding-top: 2rem;\n display: flex;\n justify-content: center;\n}\n.claudebernard-therapeutic-alternative__section {\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n border-radius: 0.625rem;\n padding: 0.75rem 0.875rem;\n margin-bottom: 1rem;\n}\n.claudebernard-therapeutic-alternative__section-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n cursor: pointer;\n background: none;\n border: none;\n padding: 0;\n margin-bottom: 0.625rem;\n font-family: inherit;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__section-header span:first-child {\n font-size: 0.75rem;\n font-weight: 700;\n color: var(--bcbd-gray-500-color, #64748B);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n.claudebernard-therapeutic-alternative__chevron {\n font-size: 0.75rem;\n color: var(--bcbd-gray-400-color, #94A3B8);\n transition: transform 0.15s ease-out;\n display: inline-flex;\n}\n.claudebernard-therapeutic-alternative__chevron--expanded {\n transform: rotate(180deg);\n}\n.claudebernard-therapeutic-alternative__pills {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__pill {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n background: #fff;\n color: var(--bcbd-gray-600-color, #475569);\n border-radius: 6.25rem;\n padding: 0.375rem 0.75rem;\n font-size: 0.8125rem;\n font-family: inherit;\n font-weight: 400;\n white-space: nowrap;\n cursor: pointer;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__pill--active {\n border-color: var(--bcbd-primary-color, #0C75AD);\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n font-weight: 700;\n}\n.claudebernard-therapeutic-alternative__panel {\n border-top: 1px solid var(--bcbd-border-color-200, #E2E8F0);\n margin-top: 0.75rem;\n padding-top: 0.75rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__panel-rows {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n max-height: 16.25rem;\n overflow-y: auto;\n padding: 0.25rem 0.125rem;\n}\n.claudebernard-therapeutic-alternative__panel-empty {\n display: block;\n margin-top: 0.75rem;\n font-size: 0.875rem;\n color: var(--bcbd-gray-500-color, #64748B);\n}\n.claudebernard-therapeutic-alternative__panel-expand {\n align-self: flex-start;\n background: none;\n border: none;\n padding: 0.25rem 0 0;\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: 700;\n color: var(--bcbd-primary-color, #0C75AD);\n cursor: pointer;\n}\n.claudebernard-therapeutic-alternative__panel-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n border-radius: 0.375rem;\n --bcbd-checkbox-font-size: 0.875rem;\n --bcbd-checkbox-size: 0.9375rem;\n}\n.claudebernard-therapeutic-alternative__panel-row--pending {\n background: var(--bcbd-gray-100-color, #F1F5F9);\n padding: 0.25rem 0.375rem;\n margin: -0.25rem -0.375rem;\n}\n.claudebernard-therapeutic-alternative__count-badge {\n font-size: 0.75rem;\n color: var(--bcbd-white-color, #fff);\n background: var(--bcbd-primary-color, #0C75AD);\n border-radius: 0.25rem;\n padding: 0.125rem 0.375rem;\n flex-shrink: 0;\n}\n.claudebernard-therapeutic-alternative__selected-chips {\n display: flex;\n gap: 0.375rem;\n flex-wrap: wrap;\n margin-top: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__chips {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__chip {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n background: #fff;\n color: var(--bcbd-gray-500-color, #64748B);\n border-radius: 6.25rem;\n padding: 0.375rem 0.75rem;\n font-size: 0.8125rem;\n font-family: inherit;\n font-weight: 400;\n white-space: nowrap;\n cursor: pointer;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__chip--active {\n border-color: var(--bcbd-primary-color, #0C75AD);\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n font-weight: 700;\n}\n.claudebernard-therapeutic-alternative__results {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__results-header {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n margin-bottom: 0.625rem;\n}\n.claudebernard-therapeutic-alternative__results-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n border-radius: 6.25rem;\n padding: 0.3125rem 0.75rem;\n font-size: 0.8125rem;\n font-weight: 700;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__results-list {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__no-results {\n text-align: center;\n padding: 1.5rem 1rem;\n color: var(--bcbd-gray-500-color, #64748B);\n font-size: 0.875rem;\n}\n.claudebernard-therapeutic-alternative__no-medication {\n padding: 1.25rem;\n}';
2393
+ const therapeuticAlternativeCSS = '.claudebernard-therapeutic-alternative {\n font-family: var(--custom-font-family, "Ubuntu"), sans-serif;\n padding: 1rem;\n}\n.claudebernard-therapeutic-alternative-without-title {\n padding-top: 0;\n max-width: 70vw;\n width: 60vw;\n max-height: 70vh;\n}\n.claudebernard-therapeutic-alternative-title {\n font-size: 0.75rem;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--bcbd-gray-500-color, #64748B);\n margin-bottom: 0.375rem;\n display: block;\n}\n.claudebernard-therapeutic-alternative__source {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__source .claudebernard-therapeutic-alternative-title {\n margin-bottom: 0;\n}\n.claudebernard-therapeutic-alternative__source-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__disclaimer {\n margin-bottom: 1rem;\n}\n.claudebernard-therapeutic-alternative__loading {\n width: 100%;\n height: 30%;\n padding-top: 2rem;\n display: flex;\n justify-content: center;\n}\n.claudebernard-therapeutic-alternative__section {\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n border-radius: 0.625rem;\n padding: 0.75rem 0.875rem;\n margin-bottom: 1rem;\n}\n.claudebernard-therapeutic-alternative__section-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n cursor: pointer;\n background: none;\n border: none;\n padding: 0;\n margin-bottom: 0.625rem;\n font-family: inherit;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__section-header span:first-child {\n font-size: 0.75rem;\n font-weight: 700;\n color: var(--bcbd-gray-500-color, #64748B);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n}\n.claudebernard-therapeutic-alternative__chevron {\n font-size: 0.75rem;\n color: var(--bcbd-gray-400-color, #94A3B8);\n transition: transform 0.15s ease-out;\n display: inline-flex;\n}\n.claudebernard-therapeutic-alternative__chevron--expanded {\n transform: rotate(180deg);\n}\n.claudebernard-therapeutic-alternative__pills {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n}\n.claudebernard-therapeutic-alternative__pill {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n border: 1px solid var(--bcbd-border-gray-color, #CBD5E1);\n background: #fff;\n color: var(--bcbd-gray-600-color, #475569);\n border-radius: 6.25rem;\n padding: 0.375rem 0.75rem;\n font-size: 0.8125rem;\n font-family: inherit;\n font-weight: 400;\n white-space: nowrap;\n cursor: pointer;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__pill--active {\n border-color: var(--bcbd-primary-color, #0C75AD);\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n font-weight: 700;\n}\n.claudebernard-therapeutic-alternative__panel {\n border-top: 1px solid var(--bcbd-border-color-200, #E2E8F0);\n margin-top: 0.75rem;\n padding-top: 0.75rem;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__panel-rows {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n max-height: 16.25rem;\n overflow-y: auto;\n padding: 0.25rem 0.125rem;\n}\n.claudebernard-therapeutic-alternative__panel-empty {\n display: block;\n margin-top: 0.75rem;\n font-size: 0.875rem;\n color: var(--bcbd-gray-500-color, #64748B);\n}\n.claudebernard-therapeutic-alternative__panel-expand {\n align-self: flex-start;\n background: none;\n border: none;\n padding: 0.25rem 0 0;\n font-family: inherit;\n font-size: 0.8125rem;\n font-weight: 700;\n color: var(--bcbd-primary-color, #0C75AD);\n cursor: pointer;\n}\n.claudebernard-therapeutic-alternative__panel-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n border-radius: 0.375rem;\n --bcbd-checkbox-font-size: 0.875rem;\n --bcbd-checkbox-size: 0.9375rem;\n}\n.claudebernard-therapeutic-alternative__panel-row--pending {\n background: var(--bcbd-gray-100-color, #F1F5F9);\n padding: 0.25rem 0.375rem;\n margin: -0.25rem -0.375rem;\n}\n.claudebernard-therapeutic-alternative__panel-row--possessed {\n --bcbd-gray-900-color: var(--bcbd-primary-color, #0C75AD);\n}\n.claudebernard-therapeutic-alternative__count-badge {\n font-size: 0.75rem;\n color: var(--bcbd-white-color, #fff);\n background: var(--bcbd-primary-color, #0C75AD);\n border-radius: 0.25rem;\n padding: 0.125rem 0.375rem;\n flex-shrink: 0;\n}\n.claudebernard-therapeutic-alternative__selected-chips {\n display: flex;\n gap: 0.375rem;\n flex-wrap: wrap;\n margin-top: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__results {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__results-header {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n margin-bottom: 0.625rem;\n}\n.claudebernard-therapeutic-alternative__results-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n background: color-mix(in srgb, var(--bcbd-primary-color, #0C75AD) 10%, transparent);\n color: var(--bcbd-primary-color, #0C75AD);\n border-radius: 6.25rem;\n padding: 0.3125rem 0.75rem;\n font-size: 0.8125rem;\n font-weight: 700;\n line-height: 1;\n}\n.claudebernard-therapeutic-alternative__results-list {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n.claudebernard-therapeutic-alternative__no-results {\n text-align: center;\n padding: 1.5rem 1rem;\n color: var(--bcbd-gray-500-color, #64748B);\n font-size: 0.875rem;\n}\n.claudebernard-therapeutic-alternative__no-medication {\n padding: 1.25rem;\n}';
2330
2394
  function emptyScopeCriteria() {
2331
2395
  return { indicationIds: [], atcClassIds: [] };
2332
2396
  }
@@ -2340,7 +2404,8 @@ function emptyExcludeCriteria() {
2340
2404
  excipientIds: [],
2341
2405
  contraindicationIds: [],
2342
2406
  cautionIds: [],
2343
- precautionIds: []
2407
+ precautionIds: [],
2408
+ sideEffectIds: []
2344
2409
  };
2345
2410
  }
2346
2411
  function emptyQuickFilters() {
@@ -2359,7 +2424,8 @@ function emptyExcludeFacets() {
2359
2424
  excipients: [],
2360
2425
  contraindications: [],
2361
2426
  cautions: [],
2362
- precautions: []
2427
+ precautions: [],
2428
+ sideEffects: []
2363
2429
  };
2364
2430
  }
2365
2431
  function emptyScopeLabels() {
@@ -2368,17 +2434,6 @@ function emptyScopeLabels() {
2368
2434
  function emptyIncludeLabels() {
2369
2435
  return { activeSubstances: {}, galenicForms: {}, dosages: {}, routesOfAdministration: {} };
2370
2436
  }
2371
- function labelsForIds(ids, values) {
2372
- const labelById = new Map(values.filter((v2) => v2.id).map((v2) => [v2.id, v2.label ?? v2.id]));
2373
- const result = {};
2374
- for (const id of ids) {
2375
- const label = labelById.get(id);
2376
- if (label) {
2377
- result[id] = label;
2378
- }
2379
- }
2380
- return result;
2381
- }
2382
2437
  function emptyExcludeLabels() {
2383
2438
  return {
2384
2439
  activeSubstances: {},
@@ -2386,7 +2441,8 @@ function emptyExcludeLabels() {
2386
2441
  excipients: {},
2387
2442
  contraindications: {},
2388
2443
  cautions: {},
2389
- precautions: {}
2444
+ precautions: {},
2445
+ sideEffects: {}
2390
2446
  };
2391
2447
  }
2392
2448
  var __defProp = Object.defineProperty;
@@ -2415,6 +2471,7 @@ let TherapeuticAlternative = class extends i$1 {
2415
2471
  this.include = emptyIncludeCriteria();
2416
2472
  this.exclude = emptyExcludeCriteria();
2417
2473
  this.quickFilters = emptyQuickFilters();
2474
+ this.quickFilterFacets = {};
2418
2475
  this.scopeFacets = emptyScopeFacets();
2419
2476
  this.includeFacets = emptyIncludeFacets();
2420
2477
  this.excludeFacets = emptyExcludeFacets();
@@ -2434,7 +2491,6 @@ let TherapeuticAlternative = class extends i$1 {
2434
2491
  this.apiReplaceError = null;
2435
2492
  this.apiReplaceLoading = false;
2436
2493
  this.missingPrescriptionError = null;
2437
- this.hasAppliedInitialScope = false;
2438
2494
  this.requestSeq = 0;
2439
2495
  }
2440
2496
  get currentMedication() {
@@ -2447,6 +2503,16 @@ let TherapeuticAlternative = class extends i$1 {
2447
2503
  const bcbCoding = this.currentMedication?.code?.coding?.find((coding) => coding.system === "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code");
2448
2504
  return bcbCoding?.code ?? null;
2449
2505
  }
2506
+ get sourceProductLabel() {
2507
+ const title = getMedicationTitle(this.currentMedication);
2508
+ return title !== "" ? decodeHtml(title) : "ce produit";
2509
+ }
2510
+ // Same rule as bcb-cmp-ui's product sheet (isPara): product types 3 to 8 are parapharmacie, for
2511
+ // which there is no therapeutic alternative. Only known once the first response brings sourceProduct.
2512
+ get isParaSource() {
2513
+ const type = this.sourceProduct?.type;
2514
+ return type !== void 0 && type >= 3 && type <= 8;
2515
+ }
2450
2516
  updated(changedProperties) {
2451
2517
  if (changedProperties.has("apiUrl")) {
2452
2518
  client.setConfig({
@@ -2457,23 +2523,27 @@ let TherapeuticAlternative = class extends i$1 {
2457
2523
  });
2458
2524
  }
2459
2525
  if (changedProperties.has("medication") && this.currentMedication) {
2460
- this.clearScheduledRefetch();
2461
- this.scope = emptyScopeCriteria();
2462
- this.include = emptyIncludeCriteria();
2463
- this.exclude = emptyExcludeCriteria();
2464
- this.scopeLabels = emptyScopeLabels();
2465
- this.includeLabels = emptyIncludeLabels();
2466
- this.excludeLabels = emptyExcludeLabels();
2467
- this.quickFilters = emptyQuickFilters();
2468
- this.openScopeGroup = null;
2469
- this.openExclusionGroup = null;
2470
- this.openIncludeGroup = null;
2471
- this.expandFacetGroupKey = null;
2472
- this.currentPage = 1;
2473
- this.hasAppliedInitialScope = false;
2474
- this.fetchAlternatives();
2526
+ this.resetAllCriteria();
2475
2527
  }
2476
2528
  }
2529
+ // Shared by a medication change (updated() above) and the user-facing "Réinitialiser" button:
2530
+ // both mean "start over from the initial state for this medication" (nothing selected).
2531
+ resetAllCriteria() {
2532
+ this.clearScheduledRefetch();
2533
+ this.scope = emptyScopeCriteria();
2534
+ this.include = emptyIncludeCriteria();
2535
+ this.exclude = emptyExcludeCriteria();
2536
+ this.scopeLabels = emptyScopeLabels();
2537
+ this.includeLabels = emptyIncludeLabels();
2538
+ this.excludeLabels = emptyExcludeLabels();
2539
+ this.quickFilters = emptyQuickFilters();
2540
+ this.openScopeGroup = null;
2541
+ this.openExclusionGroup = null;
2542
+ this.openIncludeGroup = null;
2543
+ this.expandFacetGroupKey = null;
2544
+ this.currentPage = 1;
2545
+ this.fetchAlternatives();
2546
+ }
2477
2547
  disconnectedCallback() {
2478
2548
  super.disconnectedCallback();
2479
2549
  this.clearScheduledRefetch();
@@ -2516,6 +2586,7 @@ let TherapeuticAlternative = class extends i$1 {
2516
2586
  pageSize: numberOfResults,
2517
2587
  ...this.expandFacetGroupKey ? { expandFacetGroup: this.expandFacetGroupKey } : {}
2518
2588
  };
2589
+ setActiveContext(this.context);
2519
2590
  searchApi.searchAlternative({ body, path: { productId } }).then((response) => {
2520
2591
  if (seq !== this.requestSeq) {
2521
2592
  return;
@@ -2541,15 +2612,8 @@ let TherapeuticAlternative = class extends i$1 {
2541
2612
  this.scopeFacets = TherapeuticAlternative.buildScopeFacets(data);
2542
2613
  this.includeFacets = TherapeuticAlternative.buildIncludeFacets(data);
2543
2614
  this.excludeFacets = TherapeuticAlternative.buildExcludeFacets(data);
2615
+ this.quickFilterFacets = data.facets?.quickFilters ?? {};
2544
2616
  this.truncatedGroups = data.facets?.truncatedGroups ?? [];
2545
- if (!this.hasAppliedInitialScope) {
2546
- this.hasAppliedInitialScope = true;
2547
- this.scope = TherapeuticAlternative.deriveInitialScope(this.sourceProduct);
2548
- this.scopeLabels = {
2549
- indications: labelsForIds(this.scope.indicationIds, this.scopeFacets.indications),
2550
- atcClasses: labelsForIds(this.scope.atcClassIds, this.scopeFacets.atcClasses)
2551
- };
2552
- }
2553
2617
  }
2554
2618
  static buildScopeFacets(data) {
2555
2619
  return {
@@ -2566,29 +2630,74 @@ let TherapeuticAlternative = class extends i$1 {
2566
2630
  };
2567
2631
  }
2568
2632
  static buildExcludeFacets(data) {
2569
- return {
2570
- activeSubstances: data.facets?.exclude?.activeSubstances ?? [],
2571
- notableExcipients: data.facets?.exclude?.notableExcipients ?? [],
2572
- excipients: data.facets?.exclude?.excipients ?? [],
2573
- contraindications: data.facets?.exclude?.contraindications ?? [],
2574
- cautions: data.facets?.exclude?.cautions ?? [],
2575
- precautions: data.facets?.exclude?.precautions ?? []
2576
- };
2577
- }
2578
- static deriveInitialScope(sourceProduct) {
2579
- const codesOf = (list) => (list ?? []).map((item) => item.code).filter((code) => !!code);
2580
- return {
2581
- indicationIds: codesOf(sourceProduct?.extendedProperties?.keywords?.indicationsKeywords),
2582
- // Contrairement à indications (une liste plate de valeurs propres au produit),
2583
- // sourceProduct.properties.atcClasses est un fil d'Ariane hiérarchique (classe > sous-classe
2584
- // > ... > code le plus spécifique, ex: M, M01, M01A, M01AX, M01AX02) — les mapper tous comme
2585
- // ids cochés n'a pas de sens (5 "sélections" pour UNE seule classification), et envoie en
2586
- // plus des codes ancêtres bien trop larges (ex: "M" = tout le système musculo-squelettique)
2587
- // dans la requête, invisibles dans l'UI puisque sans label correspondant dans
2588
- // scopeFacets.atcClasses. Rien n'est pré-coché pour ce groupe : le backend applique déjà son
2589
- // propre défaut sur atcClassIds vide (voir le commentaire plus haut sur fetchAlternatives).
2590
- atcClassIds: []
2591
- };
2633
+ const facets = emptyExcludeFacets();
2634
+ for (const group of EXCLUSION_GROUPS) {
2635
+ facets[group.key] = data.facets?.exclude?.[group.key] ?? [];
2636
+ }
2637
+ return facets;
2638
+ }
2639
+ static codesOf(list) {
2640
+ return (list ?? []).map((item) => item.code).filter((code) => !!code);
2641
+ }
2642
+ // The ids to tint in a scope checklist panel (same highlight as the include/exclude panels, see
2643
+ // renderChecklistPanel's possessedIds param) -- the source product's own indications. They are
2644
+ // only highlighted, never pre-checked: an empty scope.indicationIds sends no indication
2645
+ // constraint, so ticking them would claim a filter that is not applied. The ATC group has none:
2646
+ // sourceProduct.properties.atcClasses is a whole breadcrumb (M > M01 > M01A > ...), not a flat
2647
+ // list of values the source "has".
2648
+ scopePossessedIds(key) {
2649
+ if (key !== "indications") {
2650
+ return /* @__PURE__ */ new Set();
2651
+ }
2652
+ return new Set(TherapeuticAlternative.codesOf(this.sourceProduct?.extendedProperties?.keywords?.indicationsKeywords));
2653
+ }
2654
+ // The ids to tint in an include checklist panel (see renderChecklistPanel's possessedIds param)
2655
+ // -- what the SOURCE product itself already has for that dimension, not what's currently
2656
+ // selected. galenicForms/dosages are single-valued on the source (a product has exactly one), so
2657
+ // each contributes at most one id; activeSubstances/routesOfAdministration can be multi-valued.
2658
+ // dosages is a best-effort string match (source's raw productDosage against the facet's own
2659
+ // dosage-string ids) -- it can occasionally miss on formatting, unlike the others' exact codes.
2660
+ includePossessedIds(key) {
2661
+ const properties = this.sourceProduct?.properties;
2662
+ const composition = this.sourceProduct?.extendedProperties?.composition;
2663
+ switch (key) {
2664
+ case "activeSubstances":
2665
+ return new Set(TherapeuticAlternative.codesOf(composition?.activeSubstances));
2666
+ case "galenicForms":
2667
+ return properties?.galenicFormCode ? /* @__PURE__ */ new Set([properties.galenicFormCode]) : /* @__PURE__ */ new Set();
2668
+ case "dosages":
2669
+ return properties?.productDosage ? /* @__PURE__ */ new Set([properties.productDosage]) : /* @__PURE__ */ new Set();
2670
+ case "routesOfAdministration":
2671
+ return new Set(TherapeuticAlternative.codesOf(this.sourceProduct?.extendedProperties?.routes));
2672
+ default:
2673
+ return /* @__PURE__ */ new Set();
2674
+ }
2675
+ }
2676
+ // Same idea as includePossessedIds, for the exclude checklist panels: the exclude groups' own
2677
+ // values come from the whole candidate pool (see SearchCustomService.addExcludeMustNotQueries),
2678
+ // not just the source product, so "does the source itself already have this excipient/
2679
+ // contraindication/..." is a real, non-trivial question worth answering here too.
2680
+ excludePossessedIds(key) {
2681
+ const composition = this.sourceProduct?.extendedProperties?.composition;
2682
+ const keywords = this.sourceProduct?.extendedProperties?.keywords;
2683
+ switch (key) {
2684
+ case "activeSubstances":
2685
+ return new Set(TherapeuticAlternative.codesOf(composition?.activeSubstances));
2686
+ case "notableExcipients":
2687
+ return new Set(TherapeuticAlternative.codesOf(composition?.excipientsWithKnownEffects));
2688
+ case "excipients":
2689
+ return new Set(TherapeuticAlternative.codesOf(composition?.excipients));
2690
+ case "contraindications":
2691
+ return new Set(TherapeuticAlternative.codesOf(keywords?.contraindicationsKeywords));
2692
+ case "cautions":
2693
+ return new Set(TherapeuticAlternative.codesOf(keywords?.contraindicationsNotRecommendedKeywords));
2694
+ case "precautions":
2695
+ return new Set(TherapeuticAlternative.codesOf(keywords?.precautionsForUseKeywords));
2696
+ case "sideEffects":
2697
+ return new Set(TherapeuticAlternative.codesOf(keywords?.sideEffectsKeywords));
2698
+ default:
2699
+ return /* @__PURE__ */ new Set();
2700
+ }
2592
2701
  }
2593
2702
  // label n'est utilisé que sur la branche "coche" (pour alimenter scopeLabels/includeLabels/
2594
2703
  // excludeLabels — voir emptyScopeLabels) ; sur la branche "décoche" l'id est simplement retiré
@@ -2658,6 +2767,7 @@ let TherapeuticAlternative = class extends i$1 {
2658
2767
  this.includeLabels = { ...this.includeLabels, [group.key]: {} };
2659
2768
  }
2660
2769
  }
2770
+ this.pendingToggleIds = new Set(this.pendingToggleIds).add(key);
2661
2771
  this.scheduleRefetch();
2662
2772
  }
2663
2773
  // The exclude "activeSubstances" group's own backend/truncatedGroups key is
@@ -2715,6 +2825,7 @@ let TherapeuticAlternative = class extends i$1 {
2715
2825
  this.apiReplaceLoading = true;
2716
2826
  let newMedicationRequest;
2717
2827
  try {
2828
+ setActiveContext(this.context);
2718
2829
  const response = await prescriptionApi.getFhirPosologies({
2719
2830
  body: {
2720
2831
  patient: this.patientProfile ?? {},
@@ -2761,7 +2872,7 @@ let TherapeuticAlternative = class extends i$1 {
2761
2872
  this.apiReplaceLoading = false;
2762
2873
  }
2763
2874
  }
2764
- renderChecklistPanel(items, selectedIds, showCount, countTooltip, onToggle, testIdPrefix, showCode = false, truncated = false, onExpand) {
2875
+ renderChecklistPanel(items, selectedIds, showCount, countTooltip, onToggle, testIdPrefix, showCode = false, truncated = false, onExpand, possessedIds = /* @__PURE__ */ new Set()) {
2765
2876
  const visibleItems = items.filter((item) => {
2766
2877
  if (item.id !== void 0 && this.pendingToggleIds.has(item.id)) {
2767
2878
  return true;
@@ -2788,10 +2899,12 @@ let TherapeuticAlternative = class extends i$1 {
2788
2899
  const pending = this.pendingToggleIds.has(item.id);
2789
2900
  const rowClasses = [
2790
2901
  `${PREFIX}-therapeutic-alternative__panel-row`,
2791
- pending ? `${PREFIX}-therapeutic-alternative__panel-row--pending` : ""
2902
+ pending ? `${PREFIX}-therapeutic-alternative__panel-row--pending` : "",
2903
+ possessedIds.has(item.id) ? `${PREFIX}-therapeutic-alternative__panel-row--possessed` : ""
2792
2904
  ].join(" ");
2905
+ const possessedTitle = possessedIds.has(item.id) ? `Valeur présente dans le produit d'origine (${this.sourceProductLabel})` : void 0;
2793
2906
  return b`
2794
- <div class=${rowClasses}>
2907
+ <div class=${rowClasses} title=${o$1(possessedTitle)}>
2795
2908
  <bcbd-checkbox
2796
2909
  id=${`${testIdPrefix}-${item.id}`}
2797
2910
  label=${showCode ? `${item.id} : ${item.label ?? ""}` : item.label ?? ""}
@@ -2936,7 +3049,8 @@ let TherapeuticAlternative = class extends i$1 {
2936
3049
  // distinct values) response back -- there's no third, even-larger tier to escalate to, so
2937
3050
  // showing the button again would just be a no-op click.
2938
3051
  openGroup.key === "atcClasses" && this.truncatedGroups.includes("atcClasses") && this.expandFacetGroupKey !== "atcClasses",
2939
- () => this.requestMoreFacetValues("atcClasses")
3052
+ () => this.requestMoreFacetValues("atcClasses"),
3053
+ this.scopePossessedIds(openGroup.key)
2940
3054
  ) : A}
2941
3055
  `;
2942
3056
  }
@@ -2974,7 +3088,8 @@ let TherapeuticAlternative = class extends i$1 {
2974
3088
  `exclusion-${openGroup.key}`,
2975
3089
  false,
2976
3090
  canExpand,
2977
- () => this.requestMoreFacetValues(truncatedGroupKey)
3091
+ () => this.requestMoreFacetValues(truncatedGroupKey),
3092
+ this.excludePossessedIds(openGroup.key)
2978
3093
  ) : A}
2979
3094
  `;
2980
3095
  }
@@ -3012,7 +3127,8 @@ let TherapeuticAlternative = class extends i$1 {
3012
3127
  `include-${openGroup.key}`,
3013
3128
  false,
3014
3129
  canExpand,
3015
- () => this.requestMoreFacetValues(truncatedGroupKey)
3130
+ () => this.requestMoreFacetValues(truncatedGroupKey),
3131
+ this.includePossessedIds(openGroup.key)
3016
3132
  ) : A}
3017
3133
  `;
3018
3134
  }
@@ -3020,25 +3136,56 @@ let TherapeuticAlternative = class extends i$1 {
3020
3136
  if (this.sectionCollapsed.quickFilters) {
3021
3137
  return A;
3022
3138
  }
3023
- const chips = QUICK_FILTERS.map((filter) => {
3024
- const active = this.quickFilters[filter.key];
3025
- const chipClasses = [
3026
- `${PREFIX}-therapeutic-alternative__chip`,
3027
- active ? `${PREFIX}-therapeutic-alternative__chip--active` : ""
3139
+ const visibleFilters = QUICK_FILTERS.filter((filter) => {
3140
+ if (this.quickFilters[filter.key] || this.pendingToggleIds.has(filter.key)) {
3141
+ return true;
3142
+ }
3143
+ return this.quickFilterFacets[filter.key] !== 0;
3144
+ });
3145
+ if (visibleFilters.length === 0) {
3146
+ return b`<span class="${PREFIX}-therapeutic-alternative__panel-empty">Aucune valeur disponible.</span>`;
3147
+ }
3148
+ const rows = visibleFilters.map((filter) => {
3149
+ const count = this.quickFilterFacets[filter.key];
3150
+ const pending = this.pendingToggleIds.has(filter.key);
3151
+ const rowClasses = [
3152
+ `${PREFIX}-therapeutic-alternative__panel-row`,
3153
+ pending ? `${PREFIX}-therapeutic-alternative__panel-row--pending` : ""
3028
3154
  ].join(" ");
3029
3155
  return b`
3030
- <button type="button" class=${chipClasses} @click=${() => this.toggleQuickFilter(filter.key)} data-test-id=${`quick-filter-${filter.key}`}>
3031
- ${active ? b`<bcbd-icon icon="check" size="extra-small"></bcbd-icon>` : A}
3032
- ${filter.label}
3033
- </button>
3156
+ <div class=${rowClasses}>
3157
+ <bcbd-checkbox
3158
+ id=${`quick-filter-${filter.key}`}
3159
+ label=${filter.label}
3160
+ ?checked=${this.quickFilters[filter.key]}
3161
+ color="primary"
3162
+ @change=${() => this.toggleQuickFilter(filter.key)}
3163
+ data-test-id=${`quick-filter-${filter.key}`}>
3164
+ </bcbd-checkbox>
3165
+ ${pending ? b`
3166
+ <bcbd-loader theme="standard" size="18" data-test-id=${`quick-filter-loader-${filter.key}`}></bcbd-loader>
3167
+ ` : this.renderQuickFilterCount(filter.key, count)}
3168
+ </div>
3034
3169
  `;
3035
3170
  });
3036
3171
  return b`
3037
- <div class="${PREFIX}-therapeutic-alternative__chips" data-test-id="quick-filters-chips">
3038
- ${chips}
3172
+ <div class="${PREFIX}-therapeutic-alternative__panel" data-test-id="quick-filters-panel">
3173
+ <div class="${PREFIX}-therapeutic-alternative__panel-rows">
3174
+ ${rows}
3175
+ </div>
3039
3176
  </div>
3040
3177
  `;
3041
3178
  }
3179
+ renderQuickFilterCount(key, count) {
3180
+ if (count === void 0) {
3181
+ return A;
3182
+ }
3183
+ return b`
3184
+ <bcbd-tooltip placement="left" content=${INCLUDE_COUNT_TOOLTIP} data-test-id=${`quick-filter-count-tooltip-${key}`}>
3185
+ <span class="${PREFIX}-therapeutic-alternative__count-badge" data-test-id=${`quick-filter-count-${key}`}>${count}</span>
3186
+ </bcbd-tooltip>
3187
+ `;
3188
+ }
3042
3189
  renderSectionHeader(title, section) {
3043
3190
  const collapsed = this.sectionCollapsed[section];
3044
3191
  const chevronClasses = [
@@ -3130,24 +3277,49 @@ let TherapeuticAlternative = class extends i$1 {
3130
3277
  </bcbd-pagination>`}
3131
3278
  `;
3132
3279
  }
3280
+ // The product name in the title replaces the "Produit sélectionné" line when has-title is on; without
3281
+ // a title, that line (see render) is the only place the product name shows.
3282
+ renderTitle(medicationTitle) {
3283
+ return b`<span class="${PREFIX}-therapeutic-alternative-title" data-test-id="title">Alternative Thérapeutique pour ${medicationTitle !== "" ? decodeHtml(medicationTitle) : "ce produit"}</span>`;
3284
+ }
3133
3285
  render() {
3134
3286
  const containerClasses = [
3135
3287
  `${PREFIX}-therapeutic-alternative`,
3136
3288
  this.hasTitle ? "" : `${PREFIX}-therapeutic-alternative-without-title`
3137
3289
  ].join(" ");
3138
3290
  const medicationTitle = getMedicationTitle(this.currentMedication);
3291
+ if (this.currentMedication && this.isParaSource) {
3292
+ return b`
3293
+ <div class=${containerClasses} data-test-id="therapeutic-alternative-container">
3294
+ ${this.hasTitle ? this.renderTitle(medicationTitle) : ""}
3295
+ <div class="${PREFIX}-therapeutic-alternative__disclaimer">
3296
+ <bcbd-alert title-message="Alternative thérapeutique indisponible"
3297
+ description-message="La recherche d'alternative n'est pas disponible pour les produits de parapharmacie."
3298
+ type="info" data-test-id="para-unavailable-alert">
3299
+ </bcbd-alert>
3300
+ </div>
3301
+ </div>
3302
+ `;
3303
+ }
3139
3304
  return b`
3140
3305
  <div class=${containerClasses} data-test-id="therapeutic-alternative-container">
3141
- ${this.hasTitle ? b`<span class="${PREFIX}-therapeutic-alternative-title">Alternative thérapeutique</span>` : ""}
3142
3306
  ${this.currentMedication ? b`
3143
3307
  <div class="${PREFIX}-therapeutic-alternative__source mb-3" data-test-id="source-product">
3144
- <span data-test-id="source-product-label">
3145
- Produit sélectionné : <span class="text-primary">${medicationTitle !== "" ? decodeHtml(medicationTitle) : "Inconnu"}</span>
3146
- </span>
3147
- <bcbd-button size="small" background-color="primary" outlined
3148
- @click=${this.cancelAlternative} data-test-id="cancel-alternative-button">
3149
- Retour au contrôle d'ordonnance
3150
- </bcbd-button>
3308
+ ${this.hasTitle ? this.renderTitle(medicationTitle) : b`
3309
+ <span data-test-id="source-product-label">
3310
+ Produit sélectionné : <span class="text-primary">${medicationTitle !== "" ? decodeHtml(medicationTitle) : "Inconnu"}</span>
3311
+ </span>`}
3312
+ <div class="${PREFIX}-therapeutic-alternative__source-actions">
3313
+ <bcbd-button size="small" background-color="secondary" outlined
3314
+ ?disabled=${this.hasNoSelectedCriteria()}
3315
+ @click=${this.resetAllCriteria} data-test-id="reset-criteria-button">
3316
+ Réinitialiser
3317
+ </bcbd-button>
3318
+ <bcbd-button size="small" background-color="primary" outlined
3319
+ @click=${this.cancelAlternative} data-test-id="cancel-alternative-button">
3320
+ Annuler
3321
+ </bcbd-button>
3322
+ </div>
3151
3323
  </div>
3152
3324
 
3153
3325
  <div class="${PREFIX}-therapeutic-alternative__disclaimer">
@@ -3190,6 +3362,7 @@ let TherapeuticAlternative = class extends i$1 {
3190
3362
  <div class="${PREFIX}-therapeutic-alternative__results" data-test-id="results-container">
3191
3363
  ${this.renderResults()}
3192
3364
  </div>` : b`
3365
+ ${this.hasTitle ? this.renderTitle(medicationTitle) : ""}
3193
3366
  <div class="${PREFIX}-therapeutic-alternative__no-medication" data-test-id="no-medication-container">
3194
3367
  <bcbd-alert title-message="Pas de médicament sélectionné"
3195
3368
  description-message="Un problème est survenu au moment de sélectionner le produit"
@@ -3216,6 +3389,9 @@ __decorateClass([
3216
3389
  __decorateClass([
3217
3390
  n2({ type: String, attribute: "api-key" })
3218
3391
  ], TherapeuticAlternative.prototype, "apiKey", 2);
3392
+ __decorateClass([
3393
+ n2({ type: Object })
3394
+ ], TherapeuticAlternative.prototype, "context", 2);
3219
3395
  __decorateClass([
3220
3396
  r()
3221
3397
  ], TherapeuticAlternative.prototype, "sourceProduct", 2);
@@ -3243,6 +3419,9 @@ __decorateClass([
3243
3419
  __decorateClass([
3244
3420
  r()
3245
3421
  ], TherapeuticAlternative.prototype, "quickFilters", 2);
3422
+ __decorateClass([
3423
+ r()
3424
+ ], TherapeuticAlternative.prototype, "quickFilterFacets", 2);
3246
3425
  __decorateClass([
3247
3426
  r()
3248
3427
  ], TherapeuticAlternative.prototype, "scopeFacets", 2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudebernard/therapeutic-alternative-element",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "main": "dist/therapeutic-alternative.es.js",
5
5
  "module": "dist/therapeutic-alternative.es.js",
6
6
  "types": "dist/therapeutic-alternative.d.ts",