@claudebernard/therapeutic-alternative-element 2.1.2 → 2.2.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.
|
@@ -5,9 +5,10 @@ import { TemplateResult } from 'lit-html';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Composant pour afficher les alternatives thérapeutiques d'un médicament donné.
|
|
8
|
-
* Permet à l'utilisateur d'affiner sa recherche via
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Permet à l'utilisateur d'affiner sa recherche via un pool de base (scope), des critères
|
|
9
|
+
* complémentaires (include), des critères d'exclusion et des filtres rapides, et affiche les
|
|
10
|
+
* résultats correspondants — toute la recherche/agrégation reste côté serveur, ce composant ne
|
|
11
|
+
* fait qu'envoyer les critères et afficher ce qui revient.
|
|
11
12
|
* @element bcb-therapeutic-alternative
|
|
12
13
|
*/
|
|
13
14
|
export declare class TherapeuticAlternative extends LitElement {
|
|
@@ -21,17 +22,21 @@ export declare class TherapeuticAlternative extends LitElement {
|
|
|
21
22
|
private totalElements;
|
|
22
23
|
private lastRequestHadNoSelectedCriteria;
|
|
23
24
|
private currentPage;
|
|
25
|
+
private scope;
|
|
24
26
|
private include;
|
|
25
27
|
private exclude;
|
|
26
28
|
private quickFilters;
|
|
29
|
+
private scopeFacets;
|
|
27
30
|
private includeFacets;
|
|
28
31
|
private excludeFacets;
|
|
29
32
|
private truncatedGroups;
|
|
30
33
|
private expandFacetGroupKey;
|
|
34
|
+
private scopeLabels;
|
|
31
35
|
private includeLabels;
|
|
32
36
|
private excludeLabels;
|
|
33
|
-
private
|
|
37
|
+
private openScopeGroup;
|
|
34
38
|
private openExclusionGroup;
|
|
39
|
+
private openIncludeGroup;
|
|
35
40
|
private checklistSearchTerm;
|
|
36
41
|
private sectionCollapsed;
|
|
37
42
|
private apiSearchError;
|
|
@@ -40,7 +45,7 @@ export declare class TherapeuticAlternative extends LitElement {
|
|
|
40
45
|
private apiReplaceError;
|
|
41
46
|
private apiReplaceLoading;
|
|
42
47
|
private missingPrescriptionError;
|
|
43
|
-
private
|
|
48
|
+
private hasAppliedInitialScope;
|
|
44
49
|
private refetchTimer;
|
|
45
50
|
private requestSeq;
|
|
46
51
|
private get currentMedication();
|
|
@@ -53,15 +58,19 @@ export declare class TherapeuticAlternative extends LitElement {
|
|
|
53
58
|
private hasNoSelectedCriteria;
|
|
54
59
|
private fetchAlternatives;
|
|
55
60
|
private applyResponse;
|
|
61
|
+
private static buildScopeFacets;
|
|
56
62
|
private static buildIncludeFacets;
|
|
57
63
|
private static buildExcludeFacets;
|
|
58
|
-
private static
|
|
59
|
-
private
|
|
64
|
+
private static deriveInitialScope;
|
|
65
|
+
private toggleScope;
|
|
60
66
|
private toggleExclude;
|
|
67
|
+
private toggleInclude;
|
|
61
68
|
private toggleQuickFilter;
|
|
62
69
|
private exclusionTruncatedGroupKey;
|
|
63
|
-
private
|
|
70
|
+
private includeTruncatedGroupKey;
|
|
71
|
+
private toggleScopePanel;
|
|
64
72
|
private toggleExclusionPanel;
|
|
73
|
+
private toggleIncludePanel;
|
|
65
74
|
private requestMoreFacetValues;
|
|
66
75
|
private toggleSection;
|
|
67
76
|
private cancelAlternative;
|
|
@@ -71,8 +80,9 @@ export declare class TherapeuticAlternative extends LitElement {
|
|
|
71
80
|
private renderChecklistSearch;
|
|
72
81
|
private renderSelectedChips;
|
|
73
82
|
private renderPill;
|
|
74
|
-
private
|
|
83
|
+
private renderScopeSection;
|
|
75
84
|
private renderExclusionSection;
|
|
85
|
+
private renderIncludeSection;
|
|
76
86
|
private renderQuickFiltersSection;
|
|
77
87
|
private renderSectionHeader;
|
|
78
88
|
private matchReasonLabels;
|
|
@@ -2297,10 +2297,9 @@ ProductCard = __decorateClass$1([
|
|
|
2297
2297
|
], ProductCard);
|
|
2298
2298
|
const REFETCH_DEBOUNCE_MS = 300;
|
|
2299
2299
|
const CHECKLIST_SEARCH_THRESHOLD = 30;
|
|
2300
|
-
const
|
|
2300
|
+
const SCOPE_GROUPS = [
|
|
2301
2301
|
{ key: "indications", idsField: "indicationIds", label: "Indications" },
|
|
2302
|
-
{ key: "
|
|
2303
|
-
{ key: "atcClasses", idsField: "atcClassIds", label: "Classe thérapeutique" }
|
|
2302
|
+
{ key: "atcClasses", idsField: "atcClassIds", label: "Classe ATC" }
|
|
2304
2303
|
];
|
|
2305
2304
|
const EXCLUSION_GROUPS = [
|
|
2306
2305
|
{ key: "activeSubstances", idsField: "activeSubstanceIds", label: "Principes actifs" },
|
|
@@ -2315,6 +2314,12 @@ const QUICK_FILTERS = [
|
|
|
2315
2314
|
{ key: "sameDosage", label: "Même dosage" },
|
|
2316
2315
|
{ key: "sameRouteOfAdministration", label: "Même voie d'administration" }
|
|
2317
2316
|
];
|
|
2317
|
+
const INCLUDE_GROUPS = [
|
|
2318
|
+
{ key: "activeSubstances", idsField: "activeSubstanceIds", label: "Principes actifs" },
|
|
2319
|
+
{ key: "galenicForms", idsField: "galenicFormIds", label: "Forme galénique", quickFilterKey: "sameGalenicForm" },
|
|
2320
|
+
{ key: "dosages", idsField: "dosageIds", label: "Dosage", quickFilterKey: "sameDosage" },
|
|
2321
|
+
{ key: "routesOfAdministration", idsField: "routeOfAdministrationIds", label: "Voie d'administration", quickFilterKey: "sameRouteOfAdministration" }
|
|
2322
|
+
];
|
|
2318
2323
|
const INCLUDE_COUNT_TOOLTIP = "Nombre de résultats restants avec cette valeur";
|
|
2319
2324
|
const EXCLUDE_COUNT_TOOLTIP = "Nombre de résultats retirés avec cette valeur";
|
|
2320
2325
|
const MATCH_REASON_VERB_BY_DIMENSION = {
|
|
@@ -2322,8 +2327,11 @@ const MATCH_REASON_VERB_BY_DIMENSION = {
|
|
|
2322
2327
|
activeSubstance: "contient"
|
|
2323
2328
|
};
|
|
2324
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}';
|
|
2330
|
+
function emptyScopeCriteria() {
|
|
2331
|
+
return { indicationIds: [], atcClassIds: [] };
|
|
2332
|
+
}
|
|
2325
2333
|
function emptyIncludeCriteria() {
|
|
2326
|
-
return {
|
|
2334
|
+
return { activeSubstanceIds: [], galenicFormIds: [], dosageIds: [], routeOfAdministrationIds: [] };
|
|
2327
2335
|
}
|
|
2328
2336
|
function emptyExcludeCriteria() {
|
|
2329
2337
|
return {
|
|
@@ -2338,8 +2346,11 @@ function emptyExcludeCriteria() {
|
|
|
2338
2346
|
function emptyQuickFilters() {
|
|
2339
2347
|
return { sameGalenicForm: false, sameDosage: false, sameRouteOfAdministration: false };
|
|
2340
2348
|
}
|
|
2349
|
+
function emptyScopeFacets() {
|
|
2350
|
+
return { indications: [], atcClasses: [] };
|
|
2351
|
+
}
|
|
2341
2352
|
function emptyIncludeFacets() {
|
|
2342
|
-
return {
|
|
2353
|
+
return { activeSubstances: [], galenicForms: [], dosages: [], routesOfAdministration: [] };
|
|
2343
2354
|
}
|
|
2344
2355
|
function emptyExcludeFacets() {
|
|
2345
2356
|
return {
|
|
@@ -2351,8 +2362,11 @@ function emptyExcludeFacets() {
|
|
|
2351
2362
|
precautions: []
|
|
2352
2363
|
};
|
|
2353
2364
|
}
|
|
2365
|
+
function emptyScopeLabels() {
|
|
2366
|
+
return { indications: {}, atcClasses: {} };
|
|
2367
|
+
}
|
|
2354
2368
|
function emptyIncludeLabels() {
|
|
2355
|
-
return {
|
|
2369
|
+
return { activeSubstances: {}, galenicForms: {}, dosages: {}, routesOfAdministration: {} };
|
|
2356
2370
|
}
|
|
2357
2371
|
function labelsForIds(ids, values) {
|
|
2358
2372
|
const labelById = new Map(values.filter((v2) => v2.id).map((v2) => [v2.id, v2.label ?? v2.id]));
|
|
@@ -2397,26 +2411,30 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2397
2411
|
this.totalElements = 0;
|
|
2398
2412
|
this.lastRequestHadNoSelectedCriteria = true;
|
|
2399
2413
|
this.currentPage = 1;
|
|
2414
|
+
this.scope = emptyScopeCriteria();
|
|
2400
2415
|
this.include = emptyIncludeCriteria();
|
|
2401
2416
|
this.exclude = emptyExcludeCriteria();
|
|
2402
2417
|
this.quickFilters = emptyQuickFilters();
|
|
2418
|
+
this.scopeFacets = emptyScopeFacets();
|
|
2403
2419
|
this.includeFacets = emptyIncludeFacets();
|
|
2404
2420
|
this.excludeFacets = emptyExcludeFacets();
|
|
2405
2421
|
this.truncatedGroups = [];
|
|
2406
2422
|
this.expandFacetGroupKey = null;
|
|
2423
|
+
this.scopeLabels = emptyScopeLabels();
|
|
2407
2424
|
this.includeLabels = emptyIncludeLabels();
|
|
2408
2425
|
this.excludeLabels = emptyExcludeLabels();
|
|
2409
|
-
this.
|
|
2426
|
+
this.openScopeGroup = null;
|
|
2410
2427
|
this.openExclusionGroup = null;
|
|
2428
|
+
this.openIncludeGroup = null;
|
|
2411
2429
|
this.checklistSearchTerm = "";
|
|
2412
|
-
this.sectionCollapsed = {
|
|
2430
|
+
this.sectionCollapsed = { scope: false, include: false, exclusion: false, quickFilters: false };
|
|
2413
2431
|
this.apiSearchError = null;
|
|
2414
2432
|
this.apiSearchLoading = false;
|
|
2415
2433
|
this.pendingToggleIds = /* @__PURE__ */ new Set();
|
|
2416
2434
|
this.apiReplaceError = null;
|
|
2417
2435
|
this.apiReplaceLoading = false;
|
|
2418
2436
|
this.missingPrescriptionError = null;
|
|
2419
|
-
this.
|
|
2437
|
+
this.hasAppliedInitialScope = false;
|
|
2420
2438
|
this.requestSeq = 0;
|
|
2421
2439
|
}
|
|
2422
2440
|
get currentMedication() {
|
|
@@ -2440,16 +2458,19 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2440
2458
|
}
|
|
2441
2459
|
if (changedProperties.has("medication") && this.currentMedication) {
|
|
2442
2460
|
this.clearScheduledRefetch();
|
|
2461
|
+
this.scope = emptyScopeCriteria();
|
|
2443
2462
|
this.include = emptyIncludeCriteria();
|
|
2444
2463
|
this.exclude = emptyExcludeCriteria();
|
|
2464
|
+
this.scopeLabels = emptyScopeLabels();
|
|
2445
2465
|
this.includeLabels = emptyIncludeLabels();
|
|
2446
2466
|
this.excludeLabels = emptyExcludeLabels();
|
|
2447
2467
|
this.quickFilters = emptyQuickFilters();
|
|
2448
|
-
this.
|
|
2468
|
+
this.openScopeGroup = null;
|
|
2449
2469
|
this.openExclusionGroup = null;
|
|
2470
|
+
this.openIncludeGroup = null;
|
|
2450
2471
|
this.expandFacetGroupKey = null;
|
|
2451
2472
|
this.currentPage = 1;
|
|
2452
|
-
this.
|
|
2473
|
+
this.hasAppliedInitialScope = false;
|
|
2453
2474
|
this.fetchAlternatives();
|
|
2454
2475
|
}
|
|
2455
2476
|
}
|
|
@@ -2471,10 +2492,11 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2471
2492
|
// Mirrors the backend's own hasNoSelectedCriteria(request) -- must be evaluated against
|
|
2472
2493
|
// whatever's about to be SENT, not read back later, see lastRequestHadNoSelectedCriteria.
|
|
2473
2494
|
hasNoSelectedCriteria() {
|
|
2474
|
-
const
|
|
2495
|
+
const anyScope = this.scope.indicationIds.length > 0 || this.scope.atcClassIds.length > 0;
|
|
2496
|
+
const anyInclude = Object.values(this.include).some((ids) => ids.length > 0);
|
|
2475
2497
|
const anyExclude = Object.values(this.exclude).some((ids) => ids.length > 0);
|
|
2476
2498
|
const anyQuickFilter = this.quickFilters.sameGalenicForm || this.quickFilters.sameDosage || this.quickFilters.sameRouteOfAdministration;
|
|
2477
|
-
return !anyInclude && !anyExclude && !anyQuickFilter;
|
|
2499
|
+
return !anyScope && !anyInclude && !anyExclude && !anyQuickFilter;
|
|
2478
2500
|
}
|
|
2479
2501
|
fetchAlternatives() {
|
|
2480
2502
|
const productId = this.sourceProductId;
|
|
@@ -2486,6 +2508,7 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2486
2508
|
this.apiSearchError = null;
|
|
2487
2509
|
this.lastRequestHadNoSelectedCriteria = this.hasNoSelectedCriteria();
|
|
2488
2510
|
const body = {
|
|
2511
|
+
scope: this.scope,
|
|
2489
2512
|
include: this.include,
|
|
2490
2513
|
exclude: this.exclude,
|
|
2491
2514
|
quickFilters: this.quickFilters,
|
|
@@ -2515,24 +2538,31 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2515
2538
|
this.sourceProduct = data.sourceProduct ?? null;
|
|
2516
2539
|
this.results = data.results?.content ?? [];
|
|
2517
2540
|
this.totalElements = data.results?.totalElements ?? 0;
|
|
2541
|
+
this.scopeFacets = TherapeuticAlternative.buildScopeFacets(data);
|
|
2518
2542
|
this.includeFacets = TherapeuticAlternative.buildIncludeFacets(data);
|
|
2519
2543
|
this.excludeFacets = TherapeuticAlternative.buildExcludeFacets(data);
|
|
2520
2544
|
this.truncatedGroups = data.facets?.truncatedGroups ?? [];
|
|
2521
|
-
if (!this.
|
|
2522
|
-
this.
|
|
2523
|
-
this.
|
|
2524
|
-
this.
|
|
2525
|
-
indications: labelsForIds(this.
|
|
2526
|
-
|
|
2527
|
-
atcClasses: labelsForIds(this.include.atcClassIds, this.includeFacets.atcClasses)
|
|
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)
|
|
2528
2551
|
};
|
|
2529
2552
|
}
|
|
2530
2553
|
}
|
|
2554
|
+
static buildScopeFacets(data) {
|
|
2555
|
+
return {
|
|
2556
|
+
indications: data.facets?.scope?.indications ?? [],
|
|
2557
|
+
atcClasses: data.facets?.scope?.atcClasses ?? []
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2531
2560
|
static buildIncludeFacets(data) {
|
|
2532
2561
|
return {
|
|
2533
|
-
indications: data.facets?.include?.indications ?? [],
|
|
2534
2562
|
activeSubstances: data.facets?.include?.activeSubstances ?? [],
|
|
2535
|
-
|
|
2563
|
+
galenicForms: data.facets?.include?.galenicForms ?? [],
|
|
2564
|
+
dosages: data.facets?.include?.dosages ?? [],
|
|
2565
|
+
routesOfAdministration: data.facets?.include?.routesOfAdministration ?? []
|
|
2536
2566
|
};
|
|
2537
2567
|
}
|
|
2538
2568
|
static buildExcludeFacets(data) {
|
|
@@ -2545,38 +2575,37 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2545
2575
|
precautions: data.facets?.exclude?.precautions ?? []
|
|
2546
2576
|
};
|
|
2547
2577
|
}
|
|
2548
|
-
static
|
|
2578
|
+
static deriveInitialScope(sourceProduct) {
|
|
2549
2579
|
const codesOf = (list) => (list ?? []).map((item) => item.code).filter((code) => !!code);
|
|
2550
2580
|
return {
|
|
2551
2581
|
indicationIds: codesOf(sourceProduct?.extendedProperties?.keywords?.indicationsKeywords),
|
|
2552
|
-
|
|
2553
|
-
//
|
|
2554
|
-
//
|
|
2555
|
-
//
|
|
2556
|
-
//
|
|
2557
|
-
//
|
|
2558
|
-
//
|
|
2559
|
-
//
|
|
2560
|
-
// son propre défaut sur atcClassIds vide (voir le commentaire plus haut sur fetchAlternatives).
|
|
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).
|
|
2561
2590
|
atcClassIds: []
|
|
2562
2591
|
};
|
|
2563
2592
|
}
|
|
2564
|
-
// label n'est utilisé que sur la branche "coche" (pour alimenter includeLabels/
|
|
2565
|
-
// voir
|
|
2566
|
-
// que l'appelant soit une checkbox du panel (qui a le label sous la main) ou un
|
|
2567
|
-
// (qui décoche un id dont le panel courant n'a peut-être même plus trace).
|
|
2568
|
-
|
|
2569
|
-
const current = this.
|
|
2593
|
+
// label n'est utilisé que sur la branche "coche" (pour alimenter scopeLabels/includeLabels/
|
|
2594
|
+
// excludeLabels — voir emptyScopeLabels) ; sur la branche "décoche" l'id est simplement retiré
|
|
2595
|
+
// des deux côtés, que l'appelant soit une checkbox du panel (qui a le label sous la main) ou un
|
|
2596
|
+
// chip de rappel (qui décoche un id dont le panel courant n'a peut-être même plus trace).
|
|
2597
|
+
toggleScope(group, id, label) {
|
|
2598
|
+
const current = this.scope[group.idsField] ?? [];
|
|
2570
2599
|
const checking = !current.includes(id);
|
|
2571
2600
|
const next = checking ? [...current, id] : current.filter((existing) => existing !== id);
|
|
2572
|
-
this.
|
|
2573
|
-
const nextLabels = { ...this.
|
|
2601
|
+
this.scope = { ...this.scope, [group.idsField]: next };
|
|
2602
|
+
const nextLabels = { ...this.scopeLabels[group.key] };
|
|
2574
2603
|
if (checking) {
|
|
2575
2604
|
nextLabels[id] = label;
|
|
2576
2605
|
} else {
|
|
2577
2606
|
delete nextLabels[id];
|
|
2578
2607
|
}
|
|
2579
|
-
this.
|
|
2608
|
+
this.scopeLabels = { ...this.scopeLabels, [group.key]: nextLabels };
|
|
2580
2609
|
this.pendingToggleIds = new Set(this.pendingToggleIds).add(id);
|
|
2581
2610
|
this.scheduleRefetch();
|
|
2582
2611
|
}
|
|
@@ -2595,25 +2624,66 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2595
2624
|
this.pendingToggleIds = new Set(this.pendingToggleIds).add(id);
|
|
2596
2625
|
this.scheduleRefetch();
|
|
2597
2626
|
}
|
|
2627
|
+
// Checking a specific value in an include group takes over from its corresponding quick filter,
|
|
2628
|
+
// when it has one (see IncludeGroupConfig.quickFilterKey -- activeSubstances has none) --
|
|
2629
|
+
// leaving both on could silently AND into an impossible/contradictory combination (e.g. quick
|
|
2630
|
+
// filter pinned to the source's own "Comprimé" while the checklist asks for "Solution
|
|
2631
|
+
// injectable") rather than the refinement the user asked for. Same rationale as
|
|
2632
|
+
// toggleQuickFilter below, the other direction.
|
|
2633
|
+
toggleInclude(group, id, label) {
|
|
2634
|
+
const current = this.include[group.idsField] ?? [];
|
|
2635
|
+
const checking = !current.includes(id);
|
|
2636
|
+
const next = checking ? [...current, id] : current.filter((existing) => existing !== id);
|
|
2637
|
+
this.include = { ...this.include, [group.idsField]: next };
|
|
2638
|
+
const nextLabels = { ...this.includeLabels[group.key] };
|
|
2639
|
+
if (checking) {
|
|
2640
|
+
nextLabels[id] = label;
|
|
2641
|
+
} else {
|
|
2642
|
+
delete nextLabels[id];
|
|
2643
|
+
}
|
|
2644
|
+
this.includeLabels = { ...this.includeLabels, [group.key]: nextLabels };
|
|
2645
|
+
if (checking && group.quickFilterKey && this.quickFilters[group.quickFilterKey]) {
|
|
2646
|
+
this.quickFilters = { ...this.quickFilters, [group.quickFilterKey]: false };
|
|
2647
|
+
}
|
|
2648
|
+
this.pendingToggleIds = new Set(this.pendingToggleIds).add(id);
|
|
2649
|
+
this.scheduleRefetch();
|
|
2650
|
+
}
|
|
2598
2651
|
toggleQuickFilter(key) {
|
|
2599
|
-
|
|
2652
|
+
const turningOn = !this.quickFilters[key];
|
|
2653
|
+
this.quickFilters = { ...this.quickFilters, [key]: turningOn };
|
|
2654
|
+
if (turningOn) {
|
|
2655
|
+
const group = INCLUDE_GROUPS.find((g2) => g2.quickFilterKey === key);
|
|
2656
|
+
if (group && this.include[group.idsField].length > 0) {
|
|
2657
|
+
this.include = { ...this.include, [group.idsField]: [] };
|
|
2658
|
+
this.includeLabels = { ...this.includeLabels, [group.key]: {} };
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2600
2661
|
this.scheduleRefetch();
|
|
2601
2662
|
}
|
|
2602
2663
|
// The exclude "activeSubstances" group's own backend/truncatedGroups key is
|
|
2603
2664
|
// "excludeActiveSubstances" (see SearchCustomService.assignFacetValues) -- distinct from the
|
|
2604
|
-
//
|
|
2665
|
+
// include dimension of the same frontend name, so it can't reuse ExcludeGroupKey as-is.
|
|
2605
2666
|
// Every other exclude group's backend key matches its frontend key 1:1.
|
|
2606
2667
|
exclusionTruncatedGroupKey(key) {
|
|
2607
2668
|
return key === "activeSubstances" ? "excludeActiveSubstances" : key;
|
|
2608
2669
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2670
|
+
// Same rationale as exclusionTruncatedGroupKey, the include side: "activeSubstances"' own
|
|
2671
|
+
// backend key is "includeActiveSubstances".
|
|
2672
|
+
includeTruncatedGroupKey(key) {
|
|
2673
|
+
return key === "activeSubstances" ? "includeActiveSubstances" : key;
|
|
2674
|
+
}
|
|
2675
|
+
toggleScopePanel(key) {
|
|
2676
|
+
this.openScopeGroup = this.openScopeGroup === key ? null : key;
|
|
2611
2677
|
this.checklistSearchTerm = "";
|
|
2612
2678
|
}
|
|
2613
2679
|
toggleExclusionPanel(key) {
|
|
2614
2680
|
this.openExclusionGroup = this.openExclusionGroup === key ? null : key;
|
|
2615
2681
|
this.checklistSearchTerm = "";
|
|
2616
2682
|
}
|
|
2683
|
+
toggleIncludePanel(key) {
|
|
2684
|
+
this.openIncludeGroup = this.openIncludeGroup === key ? null : key;
|
|
2685
|
+
this.checklistSearchTerm = "";
|
|
2686
|
+
}
|
|
2617
2687
|
// "Afficher plus" for one truncated group's checklist -- see expandFacetGroupKey. Triggers an
|
|
2618
2688
|
// immediate refetch (unlike opening/closing a panel) since this is the one action that actually
|
|
2619
2689
|
// needs a new response to have an effect.
|
|
@@ -2764,8 +2834,8 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2764
2834
|
}
|
|
2765
2835
|
// Only rendered past CHECKLIST_SEARCH_THRESHOLD (see renderChecklistPanel) -- below that, typing
|
|
2766
2836
|
// is slower than just scanning the list by eye. checklistSearchTerm is reset whenever a different
|
|
2767
|
-
// group/section opens (
|
|
2768
|
-
// previous panel can't silently hide everything in the next one.
|
|
2837
|
+
// group/section opens (toggleScopePanel/toggleExclusionPanel/toggleIncludePanel) so a leftover
|
|
2838
|
+
// term from a previous panel can't silently hide everything in the next one.
|
|
2769
2839
|
renderChecklistSearch(testIdPrefix) {
|
|
2770
2840
|
return b`
|
|
2771
2841
|
<bcbd-input
|
|
@@ -2781,9 +2851,9 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2781
2851
|
}
|
|
2782
2852
|
// Rappel des valeurs actuellement cochées pour TOUTE la section (tous ses groupes confondus),
|
|
2783
2853
|
// toujours affiché dès qu'il y en a — indépendamment de quel panel est ouvert, voire qu'aucun ne
|
|
2784
|
-
// le soit. C'est la vue "source de vérité" (includeLabels/excludeLabels) plutôt que
|
|
2785
|
-
// ouvert : une valeur cochée reste visible et décochable ici même si un refetch a fait
|
|
2786
|
-
// sa ligne du panel courant (voir
|
|
2854
|
+
// le soit. C'est la vue "source de vérité" (scopeLabels/includeLabels/excludeLabels) plutôt que
|
|
2855
|
+
// le panel ouvert : une valeur cochée reste visible et décochable ici même si un refetch a fait
|
|
2856
|
+
// disparaître sa ligne du panel courant (voir emptyScopeLabels).
|
|
2787
2857
|
renderSelectedChips(labelsByGroup, onRemove, testIdPrefix) {
|
|
2788
2858
|
const entries = [];
|
|
2789
2859
|
for (const [groupKey, labels] of Object.entries(labelsByGroup)) {
|
|
@@ -2830,36 +2900,36 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2830
2900
|
</button>
|
|
2831
2901
|
`;
|
|
2832
2902
|
}
|
|
2833
|
-
|
|
2834
|
-
const chips = this.renderSelectedChips(this.
|
|
2835
|
-
const group =
|
|
2903
|
+
renderScopeSection() {
|
|
2904
|
+
const chips = this.renderSelectedChips(this.scopeLabels, (groupKey, id) => {
|
|
2905
|
+
const group = SCOPE_GROUPS.find((g2) => g2.key === groupKey);
|
|
2836
2906
|
if (group) {
|
|
2837
|
-
this.
|
|
2907
|
+
this.toggleScope(group, id, "");
|
|
2838
2908
|
}
|
|
2839
|
-
}, "
|
|
2840
|
-
if (this.sectionCollapsed.
|
|
2909
|
+
}, "scope");
|
|
2910
|
+
if (this.sectionCollapsed.scope) {
|
|
2841
2911
|
return chips;
|
|
2842
2912
|
}
|
|
2843
|
-
const openGroup =
|
|
2844
|
-
const pills =
|
|
2913
|
+
const openGroup = SCOPE_GROUPS.find((group) => group.key === this.openScopeGroup);
|
|
2914
|
+
const pills = SCOPE_GROUPS.map((group) => this.renderPill(
|
|
2845
2915
|
group.label,
|
|
2846
|
-
this.
|
|
2847
|
-
group.key === this.
|
|
2848
|
-
() => this.
|
|
2849
|
-
`
|
|
2916
|
+
this.scope[group.idsField].length > 0,
|
|
2917
|
+
group.key === this.openScopeGroup,
|
|
2918
|
+
() => this.toggleScopePanel(group.key),
|
|
2919
|
+
`scope-pill-${group.key}`
|
|
2850
2920
|
));
|
|
2851
2921
|
return b`
|
|
2852
|
-
<div class="${PREFIX}-therapeutic-alternative__pills" data-test-id="
|
|
2922
|
+
<div class="${PREFIX}-therapeutic-alternative__pills" data-test-id="scope-pills">
|
|
2853
2923
|
${pills}
|
|
2854
2924
|
</div>
|
|
2855
2925
|
${chips}
|
|
2856
2926
|
${openGroup ? this.renderChecklistPanel(
|
|
2857
|
-
this.
|
|
2858
|
-
this.
|
|
2927
|
+
this.scopeFacets[openGroup.key],
|
|
2928
|
+
this.scope[openGroup.idsField],
|
|
2859
2929
|
true,
|
|
2860
2930
|
INCLUDE_COUNT_TOOLTIP,
|
|
2861
|
-
(id, label) => this.
|
|
2862
|
-
`
|
|
2931
|
+
(id, label) => this.toggleScope(openGroup, id, label),
|
|
2932
|
+
`scope-${openGroup.key}`,
|
|
2863
2933
|
openGroup.key === "atcClasses",
|
|
2864
2934
|
// Already expanded == clicking again would send the exact same expandFacetGroup and get
|
|
2865
2935
|
// the exact same (still truncated, if it's a group with more than FACET_VALUES_EXPANDED_SIZE
|
|
@@ -2908,6 +2978,44 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2908
2978
|
) : A}
|
|
2909
2979
|
`;
|
|
2910
2980
|
}
|
|
2981
|
+
renderIncludeSection() {
|
|
2982
|
+
const chips = this.renderSelectedChips(this.includeLabels, (groupKey, id) => {
|
|
2983
|
+
const group = INCLUDE_GROUPS.find((g2) => g2.key === groupKey);
|
|
2984
|
+
if (group) {
|
|
2985
|
+
this.toggleInclude(group, id, "");
|
|
2986
|
+
}
|
|
2987
|
+
}, "include");
|
|
2988
|
+
if (this.sectionCollapsed.include) {
|
|
2989
|
+
return chips;
|
|
2990
|
+
}
|
|
2991
|
+
const openGroup = INCLUDE_GROUPS.find((group) => group.key === this.openIncludeGroup);
|
|
2992
|
+
const pills = INCLUDE_GROUPS.map((group) => this.renderPill(
|
|
2993
|
+
group.label,
|
|
2994
|
+
this.include[group.idsField].length > 0,
|
|
2995
|
+
group.key === this.openIncludeGroup,
|
|
2996
|
+
() => this.toggleIncludePanel(group.key),
|
|
2997
|
+
`include-pill-${group.key}`
|
|
2998
|
+
));
|
|
2999
|
+
const truncatedGroupKey = openGroup ? this.includeTruncatedGroupKey(openGroup.key) : null;
|
|
3000
|
+
const canExpand = truncatedGroupKey !== null && this.truncatedGroups.includes(truncatedGroupKey) && this.expandFacetGroupKey !== truncatedGroupKey;
|
|
3001
|
+
return b`
|
|
3002
|
+
<div class="${PREFIX}-therapeutic-alternative__pills" data-test-id="include-pills">
|
|
3003
|
+
${pills}
|
|
3004
|
+
</div>
|
|
3005
|
+
${chips}
|
|
3006
|
+
${openGroup ? this.renderChecklistPanel(
|
|
3007
|
+
this.includeFacets[openGroup.key],
|
|
3008
|
+
this.include[openGroup.idsField],
|
|
3009
|
+
true,
|
|
3010
|
+
INCLUDE_COUNT_TOOLTIP,
|
|
3011
|
+
(id, label) => this.toggleInclude(openGroup, id, label),
|
|
3012
|
+
`include-${openGroup.key}`,
|
|
3013
|
+
false,
|
|
3014
|
+
canExpand,
|
|
3015
|
+
() => this.requestMoreFacetValues(truncatedGroupKey)
|
|
3016
|
+
) : A}
|
|
3017
|
+
`;
|
|
3018
|
+
}
|
|
2911
3019
|
renderQuickFiltersSection() {
|
|
2912
3020
|
if (this.sectionCollapsed.quickFilters) {
|
|
2913
3021
|
return A;
|
|
@@ -2952,8 +3060,8 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
2952
3060
|
// Turns a result's raw matchReasons into short bullet lines, e.g. "- indiqué pour Arthrose" or
|
|
2953
3061
|
// "- contient Bisoprolol (synonyme de Métoprolol)" -- label/synonymLabel come straight from the
|
|
2954
3062
|
// backend (resolved from the source/matching product's own documents), not looked up in
|
|
2955
|
-
// includeFacets:
|
|
2956
|
-
// is a code found on this specific result product, not a criterion at all.
|
|
3063
|
+
// scopeFacets/includeFacets: those only ever cover the request's criterion codes, never
|
|
3064
|
+
// synonymCode, which is a code found on this specific result product, not a criterion at all.
|
|
2957
3065
|
matchReasonLabels(result) {
|
|
2958
3066
|
const labels = (result.matchReasons ?? []).map((reason) => {
|
|
2959
3067
|
const dimension = reason.dimension ?? "";
|
|
@@ -3059,13 +3167,18 @@ let TherapeuticAlternative = class extends i$1 {
|
|
|
3059
3167
|
</bcbd-alert>
|
|
3060
3168
|
</div>` : ""}
|
|
3061
3169
|
|
|
3062
|
-
<div class="${PREFIX}-therapeutic-alternative__section" data-test-id="
|
|
3063
|
-
${this.renderSectionHeader("Rechercher une alternative", "
|
|
3064
|
-
${this.
|
|
3170
|
+
<div class="${PREFIX}-therapeutic-alternative__section" data-test-id="scope-section">
|
|
3171
|
+
${this.renderSectionHeader("Rechercher une alternative", "scope")}
|
|
3172
|
+
${this.renderScopeSection()}
|
|
3173
|
+
</div>
|
|
3174
|
+
|
|
3175
|
+
<div class="${PREFIX}-therapeutic-alternative__section" data-test-id="include-section">
|
|
3176
|
+
${this.renderSectionHeader("Critères d'inclusion", "include")}
|
|
3177
|
+
${this.renderIncludeSection()}
|
|
3065
3178
|
</div>
|
|
3066
3179
|
|
|
3067
3180
|
<div class="${PREFIX}-therapeutic-alternative__section" data-test-id="exclusion-section">
|
|
3068
|
-
${this.renderSectionHeader("
|
|
3181
|
+
${this.renderSectionHeader("Critères d'exclusion", "exclusion")}
|
|
3069
3182
|
${this.renderExclusionSection()}
|
|
3070
3183
|
</div>
|
|
3071
3184
|
|
|
@@ -3118,6 +3231,9 @@ __decorateClass([
|
|
|
3118
3231
|
__decorateClass([
|
|
3119
3232
|
r()
|
|
3120
3233
|
], TherapeuticAlternative.prototype, "currentPage", 2);
|
|
3234
|
+
__decorateClass([
|
|
3235
|
+
r()
|
|
3236
|
+
], TherapeuticAlternative.prototype, "scope", 2);
|
|
3121
3237
|
__decorateClass([
|
|
3122
3238
|
r()
|
|
3123
3239
|
], TherapeuticAlternative.prototype, "include", 2);
|
|
@@ -3127,6 +3243,9 @@ __decorateClass([
|
|
|
3127
3243
|
__decorateClass([
|
|
3128
3244
|
r()
|
|
3129
3245
|
], TherapeuticAlternative.prototype, "quickFilters", 2);
|
|
3246
|
+
__decorateClass([
|
|
3247
|
+
r()
|
|
3248
|
+
], TherapeuticAlternative.prototype, "scopeFacets", 2);
|
|
3130
3249
|
__decorateClass([
|
|
3131
3250
|
r()
|
|
3132
3251
|
], TherapeuticAlternative.prototype, "includeFacets", 2);
|
|
@@ -3139,6 +3258,9 @@ __decorateClass([
|
|
|
3139
3258
|
__decorateClass([
|
|
3140
3259
|
r()
|
|
3141
3260
|
], TherapeuticAlternative.prototype, "expandFacetGroupKey", 2);
|
|
3261
|
+
__decorateClass([
|
|
3262
|
+
r()
|
|
3263
|
+
], TherapeuticAlternative.prototype, "scopeLabels", 2);
|
|
3142
3264
|
__decorateClass([
|
|
3143
3265
|
r()
|
|
3144
3266
|
], TherapeuticAlternative.prototype, "includeLabels", 2);
|
|
@@ -3147,10 +3269,13 @@ __decorateClass([
|
|
|
3147
3269
|
], TherapeuticAlternative.prototype, "excludeLabels", 2);
|
|
3148
3270
|
__decorateClass([
|
|
3149
3271
|
r()
|
|
3150
|
-
], TherapeuticAlternative.prototype, "
|
|
3272
|
+
], TherapeuticAlternative.prototype, "openScopeGroup", 2);
|
|
3151
3273
|
__decorateClass([
|
|
3152
3274
|
r()
|
|
3153
3275
|
], TherapeuticAlternative.prototype, "openExclusionGroup", 2);
|
|
3276
|
+
__decorateClass([
|
|
3277
|
+
r()
|
|
3278
|
+
], TherapeuticAlternative.prototype, "openIncludeGroup", 2);
|
|
3154
3279
|
__decorateClass([
|
|
3155
3280
|
r()
|
|
3156
3281
|
], TherapeuticAlternative.prototype, "checklistSearchTerm", 2);
|
package/package.json
CHANGED