@startinblox/components-ds4go 3.2.2 → 3.3.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.
@@ -196,17 +196,21 @@ export class Ds4goCustomerModal extends OrbitComponent {
196
196
  .map(([negotiationId]) => negotiationId);
197
197
 
198
198
  // Filter negotiations where counterPartyId === participant_id OR participant is in agreements
199
- this.negotiations = allNegotiations.filter((n: ContractNegotiation) => {
200
- const counterPartyId = n.counterPartyId;
201
- if (counterPartyId) {
202
- const counterPartyParticipantId = counterPartyId.split("/").pop();
203
- if (counterPartyParticipantId?.startsWith(participantId)) {
204
- return true;
199
+ this.negotiations = allNegotiations.filter(
200
+ (n: ContractNegotiation) => {
201
+ const counterPartyId = n.counterPartyId;
202
+ if (counterPartyId) {
203
+ const counterPartyParticipantId = counterPartyId
204
+ .split("/")
205
+ .pop();
206
+ if (counterPartyParticipantId?.startsWith(participantId)) {
207
+ return true;
208
+ }
205
209
  }
206
- }
207
- // Also include negotiations that have agreements where participant is consumer or provider
208
- return negotiationIdsWithParticipant.includes(n["@id"]);
209
- });
210
+ // Also include negotiations that have agreements where participant is consumer or provider
211
+ return negotiationIdsWithParticipant.includes(n["@id"]);
212
+ },
213
+ );
210
214
 
211
215
  // Load agreements for filtered negotiations for display
212
216
  await Promise.all(
@@ -224,7 +228,9 @@ export class Ds4goCustomerModal extends OrbitComponent {
224
228
  (n: ExtendedContractNegotiation) => {
225
229
  if (!n.agreement) return false;
226
230
  if (!n.agreement.providerId) return false;
227
- const providerParticipantId = n.agreement.providerId.split("/").pop();
231
+ const providerParticipantId = n.agreement.providerId
232
+ .split("/")
233
+ .pop();
228
234
  return providerParticipantId?.startsWith(participantId);
229
235
  },
230
236
  );
@@ -233,15 +239,21 @@ export class Ds4goCustomerModal extends OrbitComponent {
233
239
  (n: ExtendedContractNegotiation) => {
234
240
  if (!n.agreement) return false;
235
241
  if (!n.agreement.consumerId) return false;
236
- const consumerParticipantId = n.agreement.consumerId.split("/").pop();
242
+ const consumerParticipantId = n.agreement.consumerId
243
+ .split("/")
244
+ .pop();
237
245
  return consumerParticipantId?.startsWith(participantId);
238
246
  },
239
247
  );
240
248
  }
241
249
  } else {
242
- console.warn(
243
- "DSP Connector not found. Please ensure the DSP Connector is properly initialized.",
244
- );
250
+ const listener = () => {
251
+ this.caching++;
252
+ this.requestUpdate();
253
+ document.removeEventListener("dsp-connector-ready", listener);
254
+ };
255
+ document.addEventListener("dsp-connector-ready", listener);
256
+ return;
245
257
  }
246
258
 
247
259
  return this.object;
@@ -286,9 +298,12 @@ export class Ds4goCustomerModal extends OrbitComponent {
286
298
  </div></tems-division
287
299
  >`
288
300
  : nothing}
289
- ${this.providerNegotiations && this.providerNegotiations.length > 0
301
+ ${this.providerNegotiations &&
302
+ this.providerNegotiations.length > 0
290
303
  ? html`<tems-division type="h4"
291
- ><div>${msg("Provider Negotiations")}</div></tems-division
304
+ ><div>
305
+ ${msg("Provider Negotiations")}
306
+ </div></tems-division
292
307
  >
293
308
  <div class="card-grid card-grid-vertical">
294
309
  ${this.providerNegotiations.map(
@@ -313,9 +328,12 @@ export class Ds4goCustomerModal extends OrbitComponent {
313
328
  )}
314
329
  </div>`
315
330
  : nothing}
316
- ${this.consumerNegotiations && this.consumerNegotiations.length > 0
331
+ ${this.consumerNegotiations &&
332
+ this.consumerNegotiations.length > 0
317
333
  ? html`<tems-division type="h4"
318
- ><div>${msg("Customer Negotiations")}</div></tems-division
334
+ ><div>
335
+ ${msg("Customer Negotiations")}
336
+ </div></tems-division
319
337
  >
320
338
  <div class="card-grid card-grid-vertical">
321
339
  ${this.consumerNegotiations.map(
@@ -116,9 +116,13 @@ export class Ds4goFactBundleModal extends OrbitComponent {
116
116
  }
117
117
  });
118
118
  } else {
119
- console.warn(
120
- "DSP Connector not found. Please ensure the DSP Connector is properly initialized.",
121
- );
119
+ const listener = () => {
120
+ this.caching++;
121
+ this.requestUpdate();
122
+ document.removeEventListener("dsp-connector-ready", listener);
123
+ };
124
+ document.addEventListener("dsp-connector-ready", listener);
125
+ return;
122
126
  }
123
127
 
124
128
  return this.object;
@@ -0,0 +1,298 @@
1
+ import * as utils from "@helpers";
2
+ import { msg, str } from "@lit/localize";
3
+ import { Task } from "@lit/task";
4
+ import type {
5
+ Resource,
6
+ OrbitComponent as OrbitComponentConfig,
7
+ } from "@src/component";
8
+ import {
9
+ OrbitDSPComponent,
10
+ type TemsSearchObject,
11
+ } from "@startinblox/solid-tems-shared";
12
+ import { css, html, nothing } from "lit";
13
+ import { customElement, property, state } from "lit/decorators.js";
14
+
15
+ export interface DSPProviderConfig {
16
+ name: string;
17
+ address: string;
18
+ color?: string;
19
+ participantId?: string;
20
+ }
21
+
22
+ @customElement("solid-dsp-catalog")
23
+ export class DSPCatalog extends OrbitDSPComponent {
24
+ constructor() {
25
+ super();
26
+ utils.setupCacheInvalidation(this, {
27
+ keywords: ["groups", "objects", "dsp"],
28
+ });
29
+ }
30
+
31
+ static styles = css`
32
+ .modal {
33
+ position: fixed;
34
+ top: 0;
35
+ left: 0;
36
+ right: 0;
37
+ bottom: 0;
38
+ background-color: rgba(0, 2, 49, 0.2);
39
+ z-index: 9999;
40
+ display: flex;
41
+ justify-content: center;
42
+ align-items: center;
43
+ }
44
+ `;
45
+
46
+ @state()
47
+ dspConnector: OrbitComponentConfig | undefined;
48
+
49
+ @property({ attribute: "header", type: String })
50
+ header?: string = "DSP Catalog";
51
+
52
+ @state()
53
+ search: TemsSearchObject[] = [];
54
+
55
+ @state()
56
+ resultCount = this.objects?.length || 0;
57
+
58
+ async _afterAttach() {
59
+ // use this.dspConnector.instance to reach the connector
60
+ this.dspConnector = this.orbit?.components.find(
61
+ (c) => c.type === "dsp-connector",
62
+ );
63
+
64
+ return Promise.resolve();
65
+ }
66
+
67
+ async _responseAdaptator(response: Resource): Promise<Resource> {
68
+ if (response.images) {
69
+ if (!Array.isArray(response.images)) {
70
+ response.images = response.images["ldp:contains"]?.filter(
71
+ (i: any) => i,
72
+ ) || [response.images];
73
+ }
74
+ }
75
+
76
+ if (response.providers) {
77
+ if (!Array.isArray(response.providers)) {
78
+ response.providers = [response.providers].filter((i: any) => i);
79
+ }
80
+ } else if (response.provider) {
81
+ response.providers = [response.provider].filter((i: any) => i);
82
+ }
83
+
84
+ // Map DSP provider metadata to standard format
85
+ if (response._provider) {
86
+ response.providers = [
87
+ {
88
+ name: response._provider,
89
+ "@id": response._providerAddress,
90
+ color: response._providerColor,
91
+ },
92
+ ];
93
+ }
94
+
95
+ if (response.categories) {
96
+ if (!Array.isArray(response.categories)) {
97
+ response.categories = response.categories["ldp:contains"]?.filter(
98
+ (i: any) => i,
99
+ ) || [response.categories];
100
+ }
101
+ }
102
+
103
+ // Map DSP dataset fields to TEMS format
104
+ if (response["dcterms:title"] && !response.name) {
105
+ response.name = response["dcterms:title"];
106
+ }
107
+ if (response["dcterms:description"] && !response.description) {
108
+ response.description = response["dcterms:description"];
109
+ }
110
+
111
+ return response;
112
+ }
113
+
114
+ _getResource = new Task(this, {
115
+ task: async ([objSrc]) => {
116
+ if (
117
+ !this.orbit ||
118
+ (!this.noRouter &&
119
+ this.route &&
120
+ this.currentRoute &&
121
+ !this.route.startsWith(this.currentRoute))
122
+ )
123
+ return;
124
+
125
+ // Initialize providers from component parameters if not already set
126
+ if (
127
+ (!this.providers || this.providers.length === 0) &&
128
+ this.component?.parameters?.providers
129
+ ) {
130
+ this.providers = this.component.parameters.providers;
131
+ }
132
+
133
+ let datasets: Resource[] | undefined;
134
+ if (this.dspConnector?.instance) {
135
+ await this.dspConnector.instance.loadAll();
136
+ datasets = this.dspConnector.instance.datas;
137
+ } else {
138
+ const listener = () => {
139
+ this.caching++;
140
+ this.requestUpdate();
141
+ document.removeEventListener("dsp-connector-ready", listener);
142
+ };
143
+ document.addEventListener("dsp-connector-ready", listener);
144
+ return;
145
+ }
146
+
147
+ if (datasets !== undefined && Array.isArray(datasets)) {
148
+ // Process each dataset through the response adaptator
149
+ this.datas = await Promise.all(
150
+ datasets.map(
151
+ async (dataset) => await this._responseAdaptator(dataset),
152
+ ),
153
+ );
154
+ }
155
+
156
+ if (objSrc) {
157
+ this.object = this.datas.find((obj: Resource) => obj["@id"] === objSrc);
158
+ } else {
159
+ this.object = undefined;
160
+ }
161
+
162
+ return this.datas;
163
+ },
164
+ args: () => [this.dataSrc, this.caching, this.currentRoute],
165
+ });
166
+
167
+ _search(e: Event) {
168
+ e.preventDefault();
169
+ this.search = e.detail;
170
+ this.filterCount = this.search.filter((s) => s.name !== "search").length;
171
+ }
172
+
173
+ _openModal(e: Event) {
174
+ e.preventDefault();
175
+ if (this.route) {
176
+ if ("use-id" in (this.component.routeAttributes || {})) {
177
+ utils.requestNavigation(this.route, e.detail["@id"]);
178
+ } else {
179
+ const rdfType = e.detail["@type"]?.at(-1) ?? e.detail["@type"];
180
+ if (rdfType) {
181
+ const compatibleComponents = window.orbit?.components?.filter(
182
+ (c) => c?.routeAttributes?.["rdf-type"] === rdfType,
183
+ );
184
+
185
+ if (compatibleComponents?.[0]?.route) {
186
+ utils.requestNavigation(
187
+ compatibleComponents[0]?.route,
188
+ e.detail["@id"],
189
+ );
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ _closeModal(e: Event) {
197
+ e.preventDefault();
198
+ if (this.route) utils.requestNavigation(this.route, this.defaultDataSrc);
199
+ }
200
+
201
+ _closeModalFromBackground(e: Event) {
202
+ e.preventDefault();
203
+ if (this.route && e.target?.classList.contains("modal"))
204
+ utils.requestNavigation(this.route, this.defaultDataSrc);
205
+ }
206
+
207
+ _resultCountUpdate(e: Event) {
208
+ this.resultCount = e.detail ?? 0;
209
+ }
210
+
211
+ render() {
212
+ // Can't use gatekeeper as dataSrc can be none here
213
+ if (
214
+ !this.orbit ||
215
+ (!this.noRouter &&
216
+ this.route &&
217
+ this.currentRoute &&
218
+ !this.route.startsWith(this.currentRoute))
219
+ ) {
220
+ return nothing;
221
+ }
222
+
223
+ return this._getResource.render({
224
+ pending: () => html`<solid-loader></solid-loader>`,
225
+ error: (e) => html`<p>${e}</p>`,
226
+ complete: (datas) => {
227
+ if (!datas || !this.providers) {
228
+ return nothing;
229
+ }
230
+ return html`<tems-viewport>
231
+ <tems-header slot="header" heading=${this.header}></tems-header>
232
+ <div slot="content">
233
+ ${this.providers.length > 0
234
+ ? html`<div
235
+ style="background: white; padding: 1rem; border-radius: 8px; margin-bottom: 1rem;"
236
+ >
237
+ <h3 style="margin: 0 0 1rem 0;">
238
+ ${msg("Provider Statistics")}
239
+ </h3>
240
+ <div
241
+ style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;"
242
+ >
243
+ ${this.providers.map((provider: DSPProviderConfig) => {
244
+ const providerDatasets = datas.filter(
245
+ (d: Resource) => d._provider === provider.name,
246
+ );
247
+ return html`
248
+ <div
249
+ style="background: #f5f5f5; padding: 1rem; border-radius: 4px; border-left: 4px solid ${provider.color ||
250
+ "#1976d2"};"
251
+ >
252
+ <div
253
+ style="font-weight: bold; margin-bottom: 0.5rem;"
254
+ >
255
+ ${provider.name}
256
+ </div>
257
+ <div style="font-size: 1.125rem; color: #1976d2;">
258
+ ${msg(str`${providerDatasets.length} datasets`)}
259
+ </div>
260
+ </div>
261
+ `;
262
+ })}
263
+ </div>
264
+ </div>`
265
+ : nothing}
266
+ <tems-catalog-filter-holder
267
+ .displayFiltering=${false}
268
+ @search=${this._search}
269
+ .search=${this.search}
270
+ .view=${"card"}
271
+ .objects=${datas}
272
+ .resultCount=${this.resultCount}
273
+ .filterCount=${this.filterCount}
274
+ ></tems-catalog-filter-holder>
275
+ <ds4go-catalog-data-holder
276
+ .view=${"card"}
277
+ .objects=${datas}
278
+ .search=${this.search}
279
+ @clicked=${this._openModal}
280
+ @result-count=${this._resultCountUpdate}
281
+ ></ds4go-catalog-data-holder>
282
+ ${this.object
283
+ ? html`<div
284
+ class="modal"
285
+ @click=${this._closeModalFromBackground}
286
+ >
287
+ <ds4go-catalog
288
+ .object=${this.object}
289
+ @close=${this._closeModal}
290
+ ></ds4go-catalog>
291
+ </div>`
292
+ : nothing}
293
+ </div>
294
+ </tems-viewport>`;
295
+ },
296
+ });
297
+ }
298
+ }
@@ -20,6 +20,14 @@ export class SolidDspConnector extends OrbitDSPComponent {
20
20
  await super._afterAttach();
21
21
  await this.loadAll();
22
22
  this.setupAutoRefresh();
23
+
24
+ this.dispatchEvent(
25
+ new CustomEvent("dsp-connector-ready", {
26
+ bubbles: true,
27
+ composed: true,
28
+ }),
29
+ );
30
+
23
31
  return Promise.resolve();
24
32
  }
25
33
 
@@ -0,0 +1,215 @@
1
+ @mixin card-content-max-height($height) {
2
+ max-height: $height;
3
+ tems-division {
4
+ max-height: $height;
5
+ }
6
+ }
7
+
8
+ :host {
9
+ box-sizing: border-box;
10
+ display: flex;
11
+ width: 100%;
12
+ max-width: 100%;
13
+ height: fit-content;
14
+ }
15
+
16
+ article {
17
+ border-radius: var(--border-radius-lg);
18
+ border: var(--border-width-sm) solid var(--color-border-primary);
19
+ width: 100%;
20
+ display: flex;
21
+ font-family: var(--font-family-body);
22
+ &.vertical {
23
+ min-width: 295px;
24
+ .card-content {
25
+ max-height: 180px;
26
+ tems-division {
27
+ max-height: 180px;
28
+ }
29
+ }
30
+ }
31
+ &.horizontal,
32
+ &.billImage {
33
+ header {
34
+ width: auto;
35
+ border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
36
+ }
37
+ header.image {
38
+ flex-shrink: 0;
39
+ width: 33%;
40
+ height: 100%;
41
+ }
42
+ main {
43
+ flex-grow: 1;
44
+ .card-content {
45
+ max-height: 78px;
46
+ tems-division {
47
+ max-height: 78px;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ &.billImage {
53
+ header.image {
54
+ width: 50%;
55
+ }
56
+ .card-content {
57
+ max-height: 206px;
58
+ tems-division {
59
+ max-height: 206px;
60
+ }
61
+ }
62
+ }
63
+ header {
64
+ display: flex;
65
+ background-repeat: no-repeat;
66
+ background-size: cover;
67
+ background-position: center center;
68
+ box-sizing: border-box;
69
+ width: 100%;
70
+ padding: 16px;
71
+ }
72
+ main {
73
+ display: flex;
74
+ flex-direction: column;
75
+ gap: var(--scale-400);
76
+ flex: 1;
77
+ .content {
78
+ flex: 1;
79
+ }
80
+ .orgLogo {
81
+ background-repeat: no-repeat;
82
+ background-size: contain;
83
+ background-position: center center;
84
+ width: 48px;
85
+ height: 48px;
86
+ }
87
+ .card-content {
88
+ flex-grow: 1;
89
+ overflow: hidden;
90
+ text-overflow: ellipsis;
91
+ width: 100%;
92
+ }
93
+ .address-line {
94
+ display: flex;
95
+ flex-direction: row;
96
+ justify-content: space-between;
97
+ align-items: end;
98
+ width: 100%;
99
+ > div:first-child {
100
+ flex: 1;
101
+ display: flex;
102
+ gap: 16px;
103
+ flex-direction: column;
104
+ }
105
+ }
106
+ .lang-date {
107
+ width: fit-content;
108
+ display: flex;
109
+ gap: 24px;
110
+ flex-direction: row;
111
+ color: var(--color-text-disabled-on);
112
+ font-size: var(--typography-size-body-sm);
113
+
114
+ font-weight: var(--font-weight-regular);
115
+ line-height: var(--line-height-body-sm);
116
+ text-transform: uppercase;
117
+ }
118
+ .address,
119
+ .source-logo {
120
+ display: flex;
121
+ flex-direction: row;
122
+ align-items: center;
123
+ gap: var(--scale-200);
124
+ color: var(--color-text-information);
125
+ font-size: var(--typography-size-body-sm);
126
+ font-style: normal;
127
+ font-weight: var(--font-weight-regular);
128
+ line-height: var(--line-height-body-sm);
129
+ text-decoration: underline;
130
+ a {
131
+ color: var(--color-text-information);
132
+ }
133
+ svg {
134
+ color: var(--color-text-information);
135
+ width: var(--icon-size-sm);
136
+ height: var(--icon-size-sm);
137
+ display: flex;
138
+ }
139
+ .source,
140
+ .logo {
141
+ display: flex;
142
+ flex-direction: row;
143
+ align-items: center;
144
+ gap: var(--scale-200);
145
+ }
146
+ // logo image in source block
147
+ .logo {
148
+ margin-left: auto;
149
+ background-repeat: no-repeat;
150
+ background-size: contain;
151
+ background-position: center center;
152
+ width: 76px;
153
+ height: 24px;
154
+ }
155
+ }
156
+ }
157
+ .tags,
158
+ .action {
159
+ display: flex;
160
+ flex-direction: row;
161
+ flex-wrap: wrap;
162
+ align-items: flex-start;
163
+ gap: var(--scale-200);
164
+ }
165
+ .action ::slotted(*) {
166
+ width: 100%;
167
+ padding: var(--scale-400);
168
+ border-top: var(--border-width-sm) solid var(--color-border-primary);
169
+ }
170
+ .action slot:not(:has-slotted) {
171
+ display: none;
172
+ }
173
+
174
+ &.vertical {
175
+ flex-direction: column;
176
+ header {
177
+ width: 100%;
178
+ border-bottom: var(--border-width-sm) solid var(--color-border-primary);
179
+ border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0;
180
+ &.image {
181
+ height: 167px;
182
+ }
183
+ }
184
+ main {
185
+ padding: var(--scale-600);
186
+ padding-bottom: var(--scale-800);
187
+ }
188
+
189
+ }
190
+
191
+ &.vertical,
192
+ &.billImage {
193
+ .lang-date {
194
+ width: 100%;
195
+ justify-content: space-between;
196
+ }
197
+ }
198
+
199
+ &.horizontal,
200
+ &.billImage {
201
+
202
+ main {
203
+ padding: var(--scale-600);
204
+ padding-bottom: var(--scale-800);
205
+
206
+
207
+ .source-logo .logo {
208
+ margin-left: 0px;
209
+ }
210
+ }
211
+ }
212
+ div:has(slot:not(:has-slotted)) {
213
+ display: none;
214
+ }
215
+ }