@uxland/primary-shell 8.0.0-rc.0 → 8.0.0-rc.2

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 (84) hide show
  1. package/dist/component-BQj4158z.js +68 -0
  2. package/dist/component-BQj4158z.js.map +1 -0
  3. package/dist/{index-8n9_rIYI.js → index-BwN0QusL.js} +19320 -19795
  4. package/dist/index-BwN0QusL.js.map +1 -0
  5. package/dist/index.js +28 -17
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.umd.cjs +2667 -2694
  8. package/dist/index.umd.cjs.map +1 -1
  9. package/dist/{plugin-B5E6LZ4X.js → plugin-BE23BSP6.js} +5817 -5685
  10. package/dist/plugin-BE23BSP6.js.map +1 -0
  11. package/dist/primary/shell/src/UI/components/clinical-pathways-action-menu/clinical-pathways-action-menu.d.ts +4 -12
  12. package/dist/primary/shell/src/UI/components/communication-action-menu/communication-action-menu.d.ts +4 -12
  13. package/dist/primary/shell/src/UI/components/petitioner-action-menu/petitioner-action-menu.d.ts +4 -12
  14. package/dist/primary/shell/src/UI/components/sidenav-action-menu/sidenav-action-menu.d.ts +27 -0
  15. package/dist/primary/shell/src/UI/components/sidenav-action-menu/template.d.ts +2 -0
  16. package/dist/primary/shell/src/UI/shared-components/primaria-text-editor/primaria-rich-text-editor.d.ts +7 -1
  17. package/dist/primary/shell/src/api/broker/broker-events.d.ts +15 -0
  18. package/dist/primary/shell/src/api/broker/broker-requests.d.ts +20 -0
  19. package/dist/primary/shell/src/api/broker/primaria-broker.d.ts +4 -0
  20. package/dist/primary/shell/src/api/ecap-event-manager/ecap-event-manager.d.ts +2 -2
  21. package/dist/primary/shell/src/api/import-data-manager/component/component.d.ts +1 -0
  22. package/dist/primary/shell/src/api/region-manager/region-manager.d.ts +10 -3
  23. package/dist/primary/shell/src/api/region-manager/remove-view-idempotency.test.d.ts +1 -0
  24. package/dist/primary/shell/src/disposer.d.ts +1 -1
  25. package/dist/primary/shell/src/features/bootstrapper.d.ts +1 -1
  26. package/dist/primary/shell/src/features/exit/handler.d.ts +12 -0
  27. package/dist/primary/shell/src/features/get-user-info/backend-adapter.d.ts +4 -2
  28. package/dist/primary/shell/src/features/get-user-info/pds-token.d.ts +26 -0
  29. package/dist/primary/shell/src/features/get-visi-id/action.d.ts +2 -0
  30. package/dist/primary/shell/src/index.d.ts +5 -0
  31. package/dist/primary/shell/src/locales.d.ts +1 -0
  32. package/package.json +5 -8
  33. package/src/UI/components/clinical-pathways-action-menu/clinical-pathways-action-menu.ts +6 -41
  34. package/src/UI/components/communication-action-menu/communication-action-menu.ts +6 -41
  35. package/src/UI/components/petitioner-action-menu/petitioner-action-menu.ts +6 -41
  36. package/src/UI/components/primaria-shell/primaria-shell.ts +3 -1
  37. package/src/UI/components/sidenav-action-menu/sidenav-action-menu.ts +62 -0
  38. package/src/UI/components/{petitioner-action-menu → sidenav-action-menu}/template.ts +3 -3
  39. package/src/UI/internal-views/doctor-nav-menu.ts +9 -2
  40. package/src/UI/shared-components/bootstrapper.ts +3 -1
  41. package/src/UI/shared-components/primaria-nav-item/primaria-nav-item.ts +3 -1
  42. package/src/UI/shared-components/primaria-text-editor/primaria-rich-text-editor.ts +34 -21
  43. package/src/api/activity-monitor/activity-monitor.test.ts +1 -1
  44. package/src/api/broker/broker-events.ts +9 -0
  45. package/src/api/broker/broker-requests.ts +20 -0
  46. package/src/api/broker/factory.ts +2 -0
  47. package/src/api/broker/primaria-broker.ts +4 -0
  48. package/src/api/ecap-event-manager/ecap-event-manager.test.ts +12 -4
  49. package/src/api/ecap-event-manager/ecap-event-manager.ts +5 -1
  50. package/src/api/http-client/http-client.test.ts +12 -31
  51. package/src/api/import-data-manager/component/component.ts +3 -0
  52. package/src/api/import-data-manager/component/styles.css +25 -32
  53. package/src/api/import-data-manager/component/template.ts +16 -0
  54. package/src/api/import-data-manager/import-data-manager-impl.test.ts +2 -0
  55. package/src/api/interaction-service/interaction-service-impl.tsx +19 -8
  56. package/src/api/localization/localization.ts +44 -40
  57. package/src/api/notification-service/notification-service-impl.test.ts +2 -1
  58. package/src/api/region-manager/region-manager.test.ts +112 -3
  59. package/src/api/region-manager/region-manager.ts +85 -10
  60. package/src/api/region-manager/remove-view-idempotency.test.ts +98 -0
  61. package/src/api/token-manager/token-manager.test.ts +3 -6
  62. package/src/disposer.ts +2 -2
  63. package/src/features/bootstrapper.ts +2 -2
  64. package/src/features/exit/handler.test.ts +60 -0
  65. package/src/features/exit/handler.ts +19 -3
  66. package/src/features/get-user-info/backend-adapter.ts +30 -18
  67. package/src/features/get-user-info/handler.test.ts +8 -2
  68. package/src/features/get-user-info/pds-token.ts +28 -0
  69. package/src/features/get-visi-id/action.ts +11 -0
  70. package/src/index.ts +40 -1
  71. package/src/locales.ts +1 -0
  72. package/src/types/custom.d.ts +30 -0
  73. package/dist/component-Bt_gBs5e.js +0 -52
  74. package/dist/component-Bt_gBs5e.js.map +0 -1
  75. package/dist/index-8n9_rIYI.js.map +0 -1
  76. package/dist/plugin-B5E6LZ4X.js.map +0 -1
  77. package/dist/primary/shell/src/UI/components/clinical-pathways-action-menu/template.d.ts +0 -2
  78. package/dist/primary/shell/src/UI/components/communication-action-menu/template.d.ts +0 -2
  79. package/dist/primary/shell/src/UI/components/petitioner-action-menu/template.d.ts +0 -2
  80. package/src/UI/components/clinical-pathways-action-menu/template.ts +0 -24
  81. package/src/UI/components/communication-action-menu/styles.css +0 -29
  82. package/src/UI/components/communication-action-menu/template.ts +0 -24
  83. package/src/UI/components/petitioner-action-menu/styles.css +0 -29
  84. /package/src/UI/components/{clinical-pathways-action-menu → sidenav-action-menu}/styles.css +0 -0
@@ -1,5 +1,6 @@
1
1
  import { shellApi } from "../../api/api";
2
2
  import { getPatientCip } from "../../features/get-patient-cip/action";
3
+ import { getVisiId } from "../../features/get-visi-id/action";
3
4
  import { navigateToEcap } from "../../features/navigate-to-ecap/navigate-to-ecap";
4
5
  import { ClinicalPathwaysActionMenu } from "../components/clinical-pathways-action-menu/clinical-pathways-action-menu";
5
6
  import { PetitionerActionMenu } from "../components/petitioner-action-menu/petitioner-action-menu";
@@ -69,7 +70,10 @@ const doctorNavMenuItems: MenuItemConfig[] = [
69
70
  {
70
71
  icon: "open_in_new",
71
72
  label: "Prescripció",
72
- callbackFn: () => navigateToEcap("PRESCRIPCIO_ACTIVA"),
73
+ callbackFn: async () => {
74
+ const visiId = await getVisiId(shellApi);
75
+ navigateToEcap("PRESCRIPCIO_ACTIVA", "NO_TANCAR", visiId ? { VisiId: visiId } : {});
76
+ },
73
77
  },
74
78
  {
75
79
  icon: "open_in_new",
@@ -102,7 +106,10 @@ const doctorNavMenuItems: MenuItemConfig[] = [
102
106
  label: "Incapacitat temporal",
103
107
  type: "item",
104
108
  sortHint: "0100",
105
- callbackFn: () => navigateToEcap("REV_IT"),
109
+ callbackFn: async () => {
110
+ const visiId = await getVisiId(shellApi);
111
+ navigateToEcap("REV_IT", "NO_TANCAR", visiId ? { VisiId: visiId } : {});
112
+ },
106
113
  },
107
114
  {
108
115
  id: "visits",
@@ -12,5 +12,7 @@ export const useSharedUI = () => {
12
12
  customElement("primaria-rich-text-editor")(PrimariaRichTextEditor);
13
13
  customElement("primaria-nav-tree-menu")(PrimariaNavTreeMenu);
14
14
  customElement("primaria-character-limit-badge")(PrimariaCharacterLimitBadge);
15
- customElement("primaria-region")(PrimariaRegion);
15
+ // @ts-ignore: `PrimariaRegion` estén el mixin `PrimariaRegionHost`, que està tipat com a
16
+ // `any`, així que TypeScript no el reconeix com a `CustomElementClass`.
17
+ customElement("primaria-region")(PrimariaRegion);
16
18
  };
@@ -48,7 +48,9 @@ export class PrimariaNavItem extends regionView(LitElement) {
48
48
 
49
49
  _subscribeEvents() {
50
50
  const subscription = shellApi.broker.subscribe(BROKER_EVENTS.shell.mainViewChanged, (payload: { viewId: string }) => {
51
- this.isActive = payload.viewId === this.view?.id;
51
+ // `ViewDefinition` de @uxland/regions no declara `id`, però el shell només hi registra
52
+ // vistes que en porten.
53
+ this.isActive = payload.viewId === (this.view as { id?: string } | undefined)?.id;
52
54
  });
53
55
  this.subscriptions.push(subscription);
54
56
  }
@@ -7,6 +7,31 @@ import { fixSpellCheckerIssue } from "./utils";
7
7
 
8
8
  fixSpellCheckerIssue(Quill);
9
9
 
10
+ /**
11
+ * Una opció de la barra d'eines de Quill: el nom solt (`"bold"`), un objecte de configuració
12
+ * (`{ header: 1 }`) o un grup d'opcions, que és com Quill separa els blocs de la barra.
13
+ */
14
+ type ToolbarOption = string | Record<string, unknown> | (string | Record<string, unknown>)[];
15
+
16
+ /**
17
+ * Opcions de la barra d'eines que Quill espera com a objecte de configuració en lloc del nom
18
+ * solt. Abans era una cadena d'`else if`.
19
+ */
20
+ const TOOLBAR_OPTION_ALIASES: Record<string, ToolbarOption> = {
21
+ color: { color: [] },
22
+ ol: { list: "ordered" },
23
+ ul: { list: "bullet" },
24
+ subindex: { script: "sub" },
25
+ superindex: { script: "super" },
26
+ // outdent: { indent: "-1" },
27
+ // indent: { indent: "+1" },
28
+ size: { size: ["small", false, "large", "huge"] },
29
+ header: { header: [1, 2, 3, 4, 5, 6, false] },
30
+ background: { background: [] },
31
+ font: { font: [] },
32
+ align: { align: [] },
33
+ };
34
+
10
35
  export class PrimariaRichTextEditor extends LitElement {
11
36
  private textChangeTimeout: number | null = null;
12
37
 
@@ -124,7 +149,8 @@ export class PrimariaRichTextEditor extends LitElement {
124
149
  formats: string;
125
150
 
126
151
  _getOptions() {
127
- let toolbarOptions = [];
152
+ // Sense anotar, `[]` s'infereix com a `never[]` i totes les assignacions posteriors fallen.
153
+ let toolbarOptions: ToolbarOption[] = [];
128
154
  const availableOpts = this.availableOptions;
129
155
  if (this.options === undefined || this.options.length === 0) {
130
156
  toolbarOptions = [
@@ -145,26 +171,13 @@ export class PrimariaRichTextEditor extends LitElement {
145
171
  // ["clean"],
146
172
  ];
147
173
  } else {
148
- toolbarOptions = this.options.split(",");
149
- toolbarOptions.forEach((option) => {
150
- if (availableOpts.indexOf(option) <= -1) toolbarOptions.splice(toolbarOptions.indexOf(option), 1);
151
- });
152
- toolbarOptions = toolbarOptions.map(function (option) {
153
- if (option == "color") return { color: [] };
154
- else if (option == "ol") return { list: "ordered" };
155
- else if (option == "ul") return { list: "bullet" };
156
- else if (option == "subindex") return { script: "sub" };
157
- else if (option == "superindex") return { script: "super" };
158
- // else if (option == 'outdent') return {indent: '-1'};
159
- // else if (option == 'indent') return {indent: '+1'};
160
- else if (option == "size") return { size: ["small", false, "large", "huge"] };
161
- else if (option == "header") return { header: [1, 2, 3, 4, 5, 6, false] };
162
- else if (option == "background") return { background: [] };
163
- else if (option == "font") return { font: [] };
164
- else if (option == "align") return { align: [] };
165
- /* else if (option == 'undo') return {undo: undo_icon};
166
- else if (option == 'redo') return {redo: redo_icon}; */ else return option;
167
- });
174
+ // Abans això filtrava amb un `splice` dins d'un `forEach`, que salta elements quan n'hi ha
175
+ // dos d'invàlids seguits. Amb `filter` el resultat és el que la intenció demanava.
176
+ toolbarOptions = this.options
177
+ .split(",")
178
+ .filter((option) => availableOpts.indexOf(option) > -1)
179
+ // Les opcions amb configuració es tradueixen; la resta passen tal com són.
180
+ .map((option) => TOOLBAR_OPTION_ALIASES[option] ?? option);
168
181
  // if (toolbarOptions.indexOf("clean") < 0) {
169
182
  // toolbarOptions.push("clean");
170
183
  // }
@@ -1,3 +1,4 @@
1
+ // @vitest-environment happy-dom
1
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
3
  import { ActivityMonitorImpl } from "./activity-monitor";
3
4
 
@@ -63,5 +64,4 @@ describe("ActivityMonitor", () => {
63
64
 
64
65
  expect(monitor.getLastActivityTimestamp()).toBe(timestampBeforeStop);
65
66
  });
66
-
67
67
  });
@@ -1,8 +1,17 @@
1
+ import { PLUGIN_BROKER_EVENTS } from "@primaria/plugins-core";
2
+
1
3
  /**
2
4
  * Diccionari centralitzat d'events del broker.
3
5
  * Events agrupats per plugin/iniciativa per facilitar l'organització i escalabilitat.
6
+ *
7
+ * Els grups que hi ha aquí escrits són els que el shell publica o consumeix. Els
8
+ * contractes entre iniciatives, en què el shell no és part, es declaren a
9
+ * `@primaria/plugins-core` i s'hi escampen a sota: el shell només fa de canal de
10
+ * distribució, perquè és l'únic paquet que instal·len les iniciatives externes.
11
+ * Els que porten resposta no són aquí, sinó a `BROKER_REQUESTS`.
4
12
  */
5
13
  export const BROKER_EVENTS = {
14
+ ...PLUGIN_BROKER_EVENTS,
6
15
  activityHistory: {
7
16
  refreshDataRequested: "refresh_history_data_requested",
8
17
  errorLoadingData: "error_loading_activity_history_data",
@@ -0,0 +1,20 @@
1
+ import { PLUGIN_BROKER_REQUESTS } from "@primaria/plugins-core";
2
+
3
+ /**
4
+ * Diccionari centralitzat de requests del broker: els missatges que porten
5
+ * resposta i es criden amb `send` / `registerRequest`.
6
+ *
7
+ * Separat de `BROKER_EVENTS` a propòsit. Un event (`publish`/`subscribe`) i una
8
+ * request (`send`/`registerRequest`) són mecanismes diferents, i barrejar-los
9
+ * en una constant que es diu "events" deixa qui l'autocompleta sense saber quin
10
+ * dels dos ha de cridar.
11
+ *
12
+ * Ara mateix només conté contractes entre iniciatives, declarats a
13
+ * `@primaria/plugins-core`. Si algun dia el shell exposa requests pròpies,
14
+ * s'escriuen aquí al costat, igual que a `BROKER_EVENTS`.
15
+ */
16
+ export const BROKER_REQUESTS = {
17
+ ...PLUGIN_BROKER_REQUESTS,
18
+ } as const;
19
+
20
+ export type BrokerRequests = typeof BROKER_REQUESTS;
@@ -1,5 +1,6 @@
1
1
  import { IEvent, IEventClass, IRequest, IRequestClass } from "@uxland/harmonix";
2
2
  import { BROKER_EVENTS } from "./broker-events";
3
+ import { BROKER_REQUESTS } from "./broker-requests";
3
4
  import type { BrokerDisposableHandler, PrimariaBroker } from "./primaria-broker";
4
5
 
5
6
  /**
@@ -30,6 +31,7 @@ type Handler = (message: any) => unknown;
30
31
 
31
32
  class Broker implements PrimariaBroker {
32
33
  public readonly events = BROKER_EVENTS;
34
+ public readonly requests = BROKER_REQUESTS;
33
35
 
34
36
  /** One handler per request type (request/response). */
35
37
  private readonly requestHandlers = new Map<MessageKey, Handler>();
@@ -1,8 +1,12 @@
1
1
  import { HarmonixBroker } from "@uxland/harmonix";
2
2
  import { type BrokerEvents } from "./broker-events";
3
+ import { type BrokerRequests } from "./broker-requests";
3
4
 
4
5
  export interface PrimariaBroker extends HarmonixBroker {
6
+ /** Noms dels events: `publish` / `subscribe`. */
5
7
  events: BrokerEvents;
8
+ /** Noms de les requests: `send` / `registerRequest`. */
9
+ requests: BrokerRequests;
6
10
  /** Internal: removes every registration (shell teardown only). */
7
11
  _clear(): void;
8
12
  }
@@ -1,3 +1,4 @@
1
+ // @vitest-environment happy-dom
1
2
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
3
  import { createEcapEventManager, LAST_WORKED_DIAGNOSTICS_KEY } from "./ecap-event-manager";
3
4
 
@@ -8,6 +9,13 @@ const createMockGlobalStateManager = (data: Record<string, unknown> = {}) =>
8
9
  clearData: vi.fn(),
9
10
  }) as any;
10
11
 
12
+ /** `mock.calls[0][0]` és `unknown | undefined` amb `noUncheckedIndexedAccess`. */
13
+ const firstPostedMessage = (spy: { mock: { calls: unknown[][] } }): string => {
14
+ const payload = spy.mock.calls[0]?.[0];
15
+ if (typeof payload !== "string") throw new Error("s'esperava un missatge publicat");
16
+ return payload;
17
+ };
18
+
11
19
  describe("EcapEventManagerImpl", () => {
12
20
  const mockPostMessage = vi.fn();
13
21
  const mockConsoleLog = vi.fn();
@@ -31,7 +39,7 @@ describe("EcapEventManagerImpl", () => {
31
39
 
32
40
  eventManager.publish("click", "navigate", payload);
33
41
 
34
- const postedEvent = JSON.parse(mockPostMessage.mock.calls[0][0]);
42
+ const postedEvent = JSON.parse(firstPostedMessage(mockPostMessage));
35
43
  expect(postedEvent.DIAGNOSTICS).toBeUndefined();
36
44
  expect(globalStateManager.getData).not.toHaveBeenCalled();
37
45
  });
@@ -43,7 +51,7 @@ describe("EcapEventManagerImpl", () => {
43
51
 
44
52
  eventManager.publish("PRESCRIPCIO_ACTIVA", "NO_TANCAR", {});
45
53
 
46
- const postedEvent = JSON.parse(mockPostMessage.mock.calls[0][0]);
54
+ const postedEvent = JSON.parse(firstPostedMessage(mockPostMessage));
47
55
  expect(postedEvent.DIAGNOSTICS).toEqual(diagnostics);
48
56
  expect(globalStateManager.getData).toHaveBeenCalledWith(LAST_WORKED_DIAGNOSTICS_KEY);
49
57
  });
@@ -63,7 +71,7 @@ describe("EcapEventManagerImpl", () => {
63
71
 
64
72
  eventManager.publish(eventType, "NO_TANCAR", {});
65
73
 
66
- const postedEvent = JSON.parse(mockPostMessage.mock.calls[0][0]);
74
+ const postedEvent = JSON.parse(firstPostedMessage(mockPostMessage));
67
75
  expect(postedEvent.DIAGNOSTICS).toEqual(diagnostics);
68
76
  });
69
77
 
@@ -73,7 +81,7 @@ describe("EcapEventManagerImpl", () => {
73
81
 
74
82
  eventManager.publish("ORDRE_TRACTAMENT", "NO_TANCAR", {});
75
83
 
76
- const postedEvent = JSON.parse(mockPostMessage.mock.calls[0][0]);
84
+ const postedEvent = JSON.parse(firstPostedMessage(mockPostMessage));
77
85
  expect(postedEvent.DIAGNOSTICS).toEqual([]);
78
86
  });
79
87
 
@@ -1,7 +1,11 @@
1
+ import { LAST_WORKED_DIAGNOSTICS_KEY } from "@primaria/plugins-core";
1
2
  import type { PrimariaGlobalStateManager } from "../global-state/global-state";
2
3
  import { IEcapEvent } from "./typings";
3
4
 
4
- export const LAST_WORKED_DIAGNOSTICS_KEY = "lastWorkedDiagnostics";
5
+ // La clau la declara `@primaria/plugins-core`: qui l'escriu és clinical-course i
6
+ // qui la llegeix és aquest mànager, així que no pot viure a cap dels dos costats.
7
+ // Es re-exporta perquè els imports que ja hi havia segueixin funcionant.
8
+ export { LAST_WORKED_DIAGNOSTICS_KEY };
5
9
 
6
10
  const EVENTS_WITH_DIAGNOSTICS = [
7
11
  "PROVES_ORDRES_CLINIQUES_CONSULTAR",
@@ -1,3 +1,4 @@
1
+ // @vitest-environment happy-dom
1
2
  import axios, { AxiosInstance } from "axios";
2
3
  import AxiosMockAdapter from "axios-mock-adapter";
3
4
  import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -14,7 +15,7 @@ const newAccessToken =
14
15
  "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJidFZxNnRMWGpmcXdzbm5MR2FXMXdhdU9McDNiTmY4bWZ3Rm1SZ0lBS2VJIn0.eyJleHAiOjE3NDI5MTAzNzgsImlhdCI6MTc0MjkwOTQ3OCwianRpIjoiMTg4OTE3ZDYtNmM0OS00YWY0LTlhYjItZjEzYTM4MGY5OTgyIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsInN1YiI6Ijk1OGUyZWQ5LTJkNmUtNDZjOC1hOTE5LTU5MDQ0ZTYwMzVjMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImV0Yy1jY2YtcHJlIiwic2Vzc2lvbl9zdGF0ZSI6IjY5ODdhMzExLWM1NzQtNDc1Ni1hMzY0LWY1ZjQzZmViZjNhYiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWhlcyIsIlJPTEVfSEVTX0VUQyIsIlJPTEVfSEVTX0xBTkRJTkciXX0sInNjb3BlIjoiIiwic2lkIjoiNjk4N2EzMTEtYzU3NC00NzU2LWEzNjQtZjVmNDNmZWJmM2FiIiwiY2xpZW50SG9zdCI6IjEwLjUzLjI1NC4xNTAiLCJhY2Nlc3NfcnVsZXMiOnt9LCJhY2Nlc3NfaW5mbyI6eyJtb2R1bGVfY29kZSI6IkEwMDEiLCJyb2xlX3R5cGUiOiJOT1JNIiwidHJhY2VfdXNlcl9pZCI6IlVzZXJfSUQiLCJjZW50ZXJfdHlwZSI6IkUiLCJtcGlfcGF0aWVudF9pZCI6IjVlYzc0YzdiLTVhYTUtNGE4NC1iM2I0LWYwMmVkZGZkZjZkYyIsInVwX2NvZGUiOiIwNzczMyIsInRyYWNlX3VzZXJfZ2l2ZW5fbmFtZSI6IkdpdmVuIE5hbWUiLCJ0cmFjZV91c2VyX2ZhbWlseV9uYW1lIjoiRmFtaWx5IE5hbWUiLCJ1c2VyX3R5cGUiOiJBRE0iLCJjZW50ZXJfY29kZSI6IkUwODU4Njk2MyIsInByb2Zlc3Npb25hbF9jYXRlZ29yeSI6IjMwOTM0MzAwNiIsInNlcnZpY2VfY29kZSI6IjVTMDg5IiwiZXBfY29kZSI6IjAyMDgiLCJpZGVudGlmaWVyIjpbeyJ0eXBlIjoiRE5JIiwidmFsdWUiOiI3MzI4ODIxOUEifSx7InR5cGUiOiJOVU1DT0wiLCJ2YWx1ZSI6IjIifV0sImFsdF9pZGVudGlmaWVyIjpbeyJ0eXBlIjoiTVBJIiwidmFsdWUiOiIwNjIxY2Y3ZC03ZDYzLTQ5ZWMtODAwNi04YzA1NjkyZWQzNzcifV19LCJjbGllbnRBZGRyZXNzIjoiMTAuNTMuMjU0LjE1MCIsImNsaWVudF9pZCI6ImV0Yy1jY2YtcHJlIn0.zPxoN5d8_uxdbxeH7ly9sQdlnDxNp8_eWDzNSzjzkUPsRVkLxgjQw_WcjZfns-LNgGr0FToS6uSDIC7uj2hPpKqD7HPWbI6UPa7g-43Jf9rh-KSOXEHJWhlASi2w0n3xQHFSxHDYBwhyXlvWQ_Tr1Vh37MroWmEL84fdwqhcfZHmD9Ek52EuRRrB-XkymCmN1ZvO2GPU04LbBvQxLVzdAaKRzr1cUqHTnsEdgRduyKShPcyth01L_X7m128SIwjJsa1lYxFE_DbSDKz8dPTsxoS1DgwRxeArrkMlIdTHyxmDRezZUGhamyLuWpXLDXJCFfrTUB8-j_JnVlILHeR6O";
15
16
 
16
17
  describe("HTTP Client", () => {
17
- let broker = createBroker();
18
+ const broker = createBroker();
18
19
  let tokenManager: TokenManager;
19
20
  let axiosInstance: AxiosInstance;
20
21
  let axiosMockInstance: AxiosMockAdapter;
@@ -43,9 +44,7 @@ describe("HTTP Client", () => {
43
44
  expect(response.data).toBe("success");
44
45
  expect(axiosMockInstance.history.get?.length).toBe(1);
45
46
  // @ts-ignore
46
- expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
47
- `Bearer ${access_token}`,
48
- );
47
+ expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(`Bearer ${access_token}`);
49
48
  });
50
49
 
51
50
  it("should retry request with new token if failed with 401 after token refresh, and requests after that have new token", async () => {
@@ -71,12 +70,8 @@ describe("HTTP Client", () => {
71
70
  expect(response.status).toBe(200);
72
71
  expect(response.data).toBe("success");
73
72
  expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
74
- expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
75
- "Bearer " + newAccessToken,
76
- );
77
- expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
78
- "Bearer " + newAccessToken,
79
- );
73
+ expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(`Bearer ${newAccessToken}`);
74
+ expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(`Bearer ${newAccessToken}`);
80
75
  });
81
76
 
82
77
  it("should fail request if token refresh fails and publish an event", async () => {
@@ -86,9 +81,7 @@ describe("HTTP Client", () => {
86
81
  const brokerSpy = vi.spyOn(broker, "publish");
87
82
  axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");
88
83
 
89
- await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
90
- "Request failed with status code 401",
91
- );
84
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Request failed with status code 401");
92
85
 
93
86
  expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
94
87
  expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
@@ -121,9 +114,7 @@ describe("HTTP Client", () => {
121
114
  const brokerSpy = vi.spyOn(broker, "publish");
122
115
 
123
116
  // Perform the request
124
- await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
125
- "Mpid header is missing",
126
- );
117
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Mpid header is missing");
127
118
  expect(brokerSpy).not.toHaveBeenCalled();
128
119
  });
129
120
 
@@ -133,9 +124,7 @@ describe("HTTP Client", () => {
133
124
  });
134
125
  const brokerSpy = vi.spyOn(broker, "publish");
135
126
  // Perform the request
136
- await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
137
- "Mpid header value is invalid",
138
- );
127
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Mpid header value is invalid");
139
128
  expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.mpidHeaderInvalid, expect.any(Object));
140
129
  });
141
130
 
@@ -148,9 +137,7 @@ describe("HTTP Client", () => {
148
137
  expect(response.data).toBe("success");
149
138
  expect(axiosMockInstance.history.get?.length).toBe(1);
150
139
  // @ts-ignore
151
- expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
152
- `Bearer ${access_token}`,
153
- );
140
+ expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(`Bearer ${access_token}`);
154
141
  });
155
142
 
156
143
  it("should retry request with new token if failed with 401 after token refresh, and requests after that have new token", async () => {
@@ -177,12 +164,8 @@ describe("HTTP Client", () => {
177
164
  expect(response.status).toBe(200);
178
165
  expect(response.data).toBe("success");
179
166
  expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
180
- expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
181
- "Bearer " + newAccessToken,
182
- );
183
- expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
184
- "Bearer " + newAccessToken,
185
- );
167
+ expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(`Bearer ${newAccessToken}`);
168
+ expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(`Bearer ${newAccessToken}`);
186
169
  });
187
170
 
188
171
  it("should fail request if token refresh fails and publish an event", async () => {
@@ -192,9 +175,7 @@ describe("HTTP Client", () => {
192
175
  const brokerSpy = vi.spyOn(broker, "publish");
193
176
  axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");
194
177
 
195
- await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
196
- "Request failed with status code 401",
197
- );
178
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Request failed with status code 401");
198
179
 
199
180
  expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
200
181
  expect(brokerSpy).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {});
@@ -10,6 +10,9 @@ export class ImportDataManagerModal extends LitElement implements ConfirmationCo
10
10
  setResult: (result: undefined) => void;
11
11
  setIsValid: (isValid: boolean) => void;
12
12
  confirm: () => void;
13
+ // Injectat pel servei d'interacció igual que la resta; faltava des que es va afegir a la
14
+ // interfície.
15
+ confirmResult: (result: undefined) => void;
13
16
  cancel: () => void;
14
17
 
15
18
  render() {
@@ -9,28 +9,31 @@
9
9
  overflow: hidden;
10
10
  }
11
11
 
12
- /* .header {
13
- display: flex;
14
- flex-direction: row;
12
+ /* Three columns so the title is centred on the modal, not on the space left
13
+ over by the close button. */
14
+ .header {
15
+ display: grid;
16
+ grid-template-columns: 1fr auto 1fr;
15
17
  align-items: center;
16
- justify-content: space-between;
17
- text-align: center;
18
18
  color: black;
19
19
  padding: var(--dss-spacing-sm);
20
+ }
20
21
 
21
- .title {
22
- display: flex;
23
- flex-direction: row;
24
- gap: 8px;
25
- }
26
- } */
22
+ .header__title {
23
+ grid-column: 2;
24
+ text-align: center;
25
+ }
26
+
27
+ .header__close {
28
+ grid-column: 3;
29
+ justify-self: end;
30
+ }
27
31
 
28
32
  .content{
29
33
  display: grid;
30
34
  flex: 1;
31
35
  min-height: 0;
32
- grid-template-columns: 1fr 1fr;
33
- gap: 16px;
36
+ grid-template-columns: 50% 50%;
34
37
  padding: 16px;
35
38
  }
36
39
 
@@ -43,42 +46,32 @@
43
46
  .content primaria-region > div {
44
47
  width: 100%;
45
48
  height: 100%;
46
- border: 1px solid var(--color-neutral-200);
47
49
  border-radius: 8px;
48
- padding: 16px;
50
+ padding: 0 16px;
49
51
  box-sizing: border-box;
50
52
  }
51
53
 
52
- /* Right column: stacks plugin cards and scrolls as a whole. */
53
- .content > primaria-region[name="import-data-region"] > div {
54
- overflow-y: auto;
55
- }
56
-
57
- /* Left column: vertical stack of multiple views with a gap. The last view
58
- takes the remaining space and handles its own scroll; preceding views
59
- keep their natural height. */
60
- .content > primaria-region[name="import-data-activity-history-region"] > div {
54
+ /* Both columns are a vertical stack of injected views, 16px apart, that scrolls
55
+ as a whole. Each view keeps its natural height and bounds its own inner
56
+ scroll, and must not shrink — otherwise the column squeezes the views instead
57
+ of scrolling. */
58
+ .content > primaria-region > div {
61
59
  display: flex;
62
60
  flex-direction: column;
63
61
  gap: 16px;
64
- overflow: hidden;
62
+ overflow-y: auto;
65
63
  }
66
64
 
67
- .content > primaria-region[name="import-data-activity-history-region"] > div > * {
65
+ .content > primaria-region > div > * {
68
66
  flex-shrink: 0;
69
67
  }
70
68
 
71
- .content > primaria-region[name="import-data-activity-history-region"] > div > *:last-child {
72
- flex: 1;
73
- min-height: 0;
74
- }
75
-
76
69
  .footer {
77
70
  display: flex;
78
71
  flex-direction: row;
79
72
  align-items: center;
80
73
  background: white;
81
- justify-content: end;
74
+ justify-content: center;
82
75
  gap: 12px;
83
76
  height: 56px;
84
77
  border-top: 1px solid var(--color-neutral-100);
@@ -6,6 +6,21 @@ import { ImportDataManagerModal } from "./component";
6
6
  export const template = (props: ImportDataManagerModal) => {
7
7
  return html`
8
8
  <div class="container">
9
+ <div class="header">
10
+ <dss-typography class="header__title" tag="h2" variant="headline-4">
11
+ ${translate("importDataManager.title")}
12
+ </dss-typography>
13
+ <dss-icon-button
14
+ class="header__close"
15
+ icon="close"
16
+ size="lg"
17
+ variant="neutral"
18
+ label="${translate("importDataManager.actions.close")}"
19
+ @onClick="${() => {
20
+ props.cancel();
21
+ }}"
22
+ ></dss-icon-button>
23
+ </div>
9
24
  <div class="content">
10
25
  <primaria-region name="${shellApi.regionManager.regions.shell.importDataActivityHistory}"></primaria-region>
11
26
  <primaria-region name="${shellApi.regionManager.regions.shell.importData}"></primaria-region>
@@ -20,6 +35,7 @@ export const template = (props: ImportDataManagerModal) => {
20
35
  variant="secondary"
21
36
  ></dss-button>
22
37
  <dss-button
38
+ icon="add_circle"
23
39
  label="${translate("importDataManager.actions.import")}"
24
40
  @click="${() => {
25
41
  props._accept();
@@ -11,6 +11,8 @@ import {
11
11
  class MockInteractionService extends PrimariaInteractionService {
12
12
  private mockConfirmResult: { confirmed: boolean; result?: unknown } = { confirmed: true };
13
13
 
14
+ dispose(): void {}
15
+
14
16
  // Mètode per configurar el resultat del mock
15
17
  setMockConfirmResult(result: { confirmed: boolean; result?: unknown }) {
16
18
  this.mockConfirmResult = result;
@@ -17,6 +17,14 @@ const defaultOptions: ConfirmationOptions = {
17
17
  hideIcon: false,
18
18
  };
19
19
 
20
+ /**
21
+ * Un diàleg pot ser un component de React o un web component. Només el primer es pot renderitzar
22
+ * com a JSX, i cal un predicat de tipus perquè TypeScript ho estrenyi al punt d'ús: guardar el
23
+ * resultat en un booleà no li serveix.
24
+ */
25
+ const isWebComponentConstructor = (component: unknown): component is new () => HTMLElement =>
26
+ typeof component === "function" && component.prototype instanceof HTMLElement;
27
+
20
28
  export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
21
29
  private readonly openDialogCancellers = new Set<() => void>();
22
30
 
@@ -44,13 +52,13 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
44
52
  }
45
53
 
46
54
  const DialogWrapper = () => {
47
- const [result, setResult] = useState<ConfirmationResult<TResult | undefined>>();
55
+ const [result, setResult] = useState<TResult>();
48
56
  const [isValid, setIsValid] = useState(true);
49
- const dialogComponentRef = useRef(null);
57
+ const dialogComponentRef = useRef<HTMLDivElement>(null);
50
58
  const dialogRef = useRef<HTMLDivElement>(null);
51
59
  const modalRef = useRef<HTMLDivElement>(null);
52
60
 
53
- const isWebComponent = typeof DialogComponent === "function" && DialogComponent.prototype instanceof HTMLElement;
61
+ const isWebComponent = isWebComponentConstructor(DialogComponent);
54
62
 
55
63
  useEffect(() => {
56
64
  window.addEventListener("keydown", handleKeydown);
@@ -73,7 +81,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
73
81
  handleClose(false);
74
82
  }
75
83
  };
76
- const handleOutsideClick = (event: Event) => {
84
+ const handleOutsideClick = (event: React.MouseEvent<HTMLDivElement>) => {
77
85
  if (dialogRef.current) {
78
86
  if (event.target === modalRef.current && finalOptions.closeOnOutsideClick) {
79
87
  handleClose(false);
@@ -81,6 +89,9 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
81
89
  }
82
90
  };
83
91
 
92
+ // L'efecte només ha de crear el web component quan canvia el component o les dades.
93
+ // Afegir-hi els handlers i `isValid` el faria recrear a cada render, perdent l'estat.
94
+ // biome-ignore lint/correctness/useExhaustiveDependencies: recrear-lo perdria l'estat
84
95
  useEffect(() => {
85
96
  if (isWebComponent && dialogComponentRef.current) {
86
97
  const parent = dialogComponentRef.current;
@@ -113,7 +124,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
113
124
  resolvePromise(finalResult, true);
114
125
  };
115
126
 
116
- const resolvePromise = (finalResult: TResult, confirmed: boolean) => {
127
+ const resolvePromise = (finalResult: TResult | undefined, confirmed: boolean) => {
117
128
  if (isClosing) return;
118
129
 
119
130
  isClosing = true;
@@ -153,7 +164,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
153
164
  };
154
165
 
155
166
  const _renderContent = () => {
156
- return isWebComponent ? (
167
+ return isWebComponentConstructor(DialogComponent) ? (
157
168
  <div ref={dialogComponentRef} />
158
169
  ) : (
159
170
  <DialogComponent
@@ -181,7 +192,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
181
192
  fill
182
193
  class={`dss-dialog-header-state--${getHeadIconVariantClass()}`}
183
194
  icon={getHeadIcon()}
184
- ></dss-icon>
195
+ />
185
196
  )}
186
197
  {finalOptions.title}
187
198
  </div>
@@ -193,7 +204,7 @@ export class PrimariaInteractionServiceImpl extends PrimariaInteractionService {
193
204
  variant="neutral"
194
205
  icon="close"
195
206
  onClick={() => handleClose(false)}
196
- ></dss-icon-button>
207
+ />
197
208
  )}
198
209
  </div>
199
210
  )}