@startinblox/components-ds4go 3.1.3 → 3.1.4

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.
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import "./index-aCtNDRnY.js";
1
+ import "./index-DQ-N8-vz.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startinblox/components-ds4go",
3
- "version": "3.1.3",
3
+ "version": "3.1.4",
4
4
  "description": "Startin'blox DS4GO",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -143,7 +143,7 @@ export class Ds4goCustomerHolder extends ComponentObjectsHandler {
143
143
  ${this._displayObjects.map((displayObj) => {
144
144
  const tags = [
145
145
  {
146
- name: `${msg(str`Balance: ${displayObj.balance}€`)}`,
146
+ name: `${msg(str`Balance: ${displayObj.balance}`)}`,
147
147
  type: "information",
148
148
  },
149
149
  ];
@@ -0,0 +1,240 @@
1
+ import { localized, msg } from "@lit/localize";
2
+ import type {
3
+ Asset,
4
+ ContractAgreement,
5
+ PolicyDefinition,
6
+ Resource,
7
+ } from "@src/component";
8
+ import ComponentStyle from "@styles/modal/customer-modal/ds4go-contract-modal-part.scss?inline";
9
+ import { css, html, LitElement, nothing, unsafeCSS } from "lit";
10
+ import { customElement, state } from "lit/decorators.js";
11
+
12
+ type ExtendedContractAgreement = ContractAgreement & {
13
+ relatedBundle?: Resource;
14
+ relatedAsset?: Asset;
15
+ };
16
+
17
+ @customElement("ds4go-customer-contract-modal-part")
18
+ @localized()
19
+ export class DS4GOCustomerContractModalPart extends LitElement {
20
+ static styles = css`
21
+ ${unsafeCSS(ComponentStyle)}
22
+ `;
23
+
24
+ @state()
25
+ agreement?: ExtendedContractAgreement;
26
+
27
+ @state()
28
+ assets?: Asset[] = [];
29
+
30
+ @state()
31
+ bundles?: Resource[] = [];
32
+
33
+ @state()
34
+ policies?: PolicyDefinition[] = [];
35
+
36
+ @state()
37
+ expandedAssets = false;
38
+
39
+ @state()
40
+ expandedPolicies = false;
41
+
42
+ @state()
43
+ expandedFacts = false;
44
+
45
+ _toggleSection(section: "assets" | "policies" | "facts") {
46
+ if (section === "assets") {
47
+ this.expandedAssets = !this.expandedAssets;
48
+ } else if (section === "policies") {
49
+ this.expandedPolicies = !this.expandedPolicies;
50
+ } else if (section === "facts") {
51
+ this.expandedFacts = !this.expandedFacts;
52
+ }
53
+ }
54
+
55
+ _getRelatedBundle(asset?: Asset): Resource | undefined {
56
+ if (!asset?.dataAddress?.baseUrl) {
57
+ return undefined;
58
+ }
59
+ const baseUrl = asset.dataAddress.baseUrl;
60
+ return this.bundles?.find((bundle: Resource) =>
61
+ bundle["@id"].includes(baseUrl),
62
+ );
63
+ }
64
+
65
+ render() {
66
+ if (!this.agreement || !this.agreement["@id"]) {
67
+ return nothing;
68
+ }
69
+
70
+ const agreementId = this.agreement["@id"];
71
+ const relatedAsset = this.assets?.find(
72
+ (a: Asset) => a["@id"] === this.agreement?.assetId,
73
+ );
74
+
75
+ if (relatedAsset) {
76
+ this.agreement.relatedAsset = relatedAsset;
77
+ this.agreement.relatedBundle = this._getRelatedBundle(relatedAsset);
78
+ }
79
+
80
+ const relatedPolicies = this.policies?.filter(
81
+ (p: PolicyDefinition) => p["@id"] === this.agreement?.policy?.["@id"],
82
+ );
83
+
84
+ return html`<div class="contract-section">
85
+ <div class="contract-header">
86
+ <tems-division type="h5">${msg("Contract Agreement")}</tems-division>
87
+ <tems-division type="body-sm"
88
+ >${agreementId.split("/").pop()}</tems-division
89
+ >
90
+ ${this.agreement.contractSigningDate
91
+ ? html`<tems-division type="body-sm"
92
+ >${msg("Signed")}:
93
+ ${new Date(
94
+ this.agreement.contractSigningDate,
95
+ ).toLocaleString()}</tems-division
96
+ >`
97
+ : nothing}
98
+ ${this.agreement.consumerId
99
+ ? html`<tems-division type="body-sm"
100
+ >${msg("Consumer")}:
101
+ ${this.agreement.consumerId.split("/").pop()}</tems-division
102
+ >`
103
+ : nothing}
104
+ ${this.agreement.providerId
105
+ ? html`<tems-division type="body-sm"
106
+ >${msg("Provider")}:
107
+ ${this.agreement.providerId.split("/").pop()}</tems-division
108
+ >`
109
+ : nothing}
110
+ </div>
111
+
112
+ ${relatedAsset
113
+ ? html`<div class="contract-subsection">
114
+ <div
115
+ class="collapsible-header"
116
+ @click=${() => this._toggleSection("assets")}
117
+ >
118
+ <tems-division type="body-m"
119
+ >${msg("Related Asset")}</tems-division
120
+ >
121
+ <icon-material-symbols-expand-more
122
+ class="chevron ${this.expandedAssets ? "expanded" : ""}"
123
+ ></icon-material-symbols-expand-more>
124
+ </div>
125
+ <div
126
+ class="collapsible-content ${this.expandedAssets
127
+ ? "expanded"
128
+ : ""}"
129
+ >
130
+ <div class="assets-list">
131
+ <div class="asset-item">
132
+ <tems-division type="body-sm"
133
+ >${relatedAsset["@id"].split("/").pop()}</tems-division
134
+ >
135
+ <tems-division type="body-xs"
136
+ >${relatedAsset.dataAddress?.baseUrl ||
137
+ msg("No base URL")}</tems-division
138
+ >
139
+ ${relatedAsset.dataAddress?.type
140
+ ? html`<tems-division type="body-xs"
141
+ >${msg("Type")}:
142
+ ${relatedAsset.dataAddress.type}</tems-division
143
+ >`
144
+ : nothing}
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>`
149
+ : nothing}
150
+ ${this.agreement.relatedBundle
151
+ ? html`<div class="contract-subsection">
152
+ <div
153
+ class="collapsible-header"
154
+ @click=${() => this._toggleSection("facts")}
155
+ >
156
+ <tems-division type="body-m"
157
+ >${msg("Related Bundle & Facts")}</tems-division
158
+ >
159
+ <icon-material-symbols-expand-more
160
+ class="chevron ${this.expandedFacts ? "expanded" : ""}"
161
+ ></icon-material-symbols-expand-more>
162
+ </div>
163
+ <div
164
+ class="collapsible-content ${this.expandedFacts
165
+ ? "expanded"
166
+ : ""}"
167
+ >
168
+ <div class="bundle-info">
169
+ <tems-division type="h6"
170
+ >${msg("Bundle")}:
171
+ ${this.agreement.relatedBundle.name ||
172
+ this.agreement.relatedBundle["@id"]
173
+ .split("/")
174
+ .pop()}</tems-division
175
+ >
176
+ ${this.agreement.relatedBundle.description
177
+ ? html`<tems-division type="body-sm"
178
+ >${this.agreement.relatedBundle
179
+ .description}</tems-division
180
+ >`
181
+ : nothing}
182
+ </div>
183
+ <tems-division type="h6"
184
+ >${msg("Facts in this bundle")}</tems-division
185
+ >
186
+ <div class="facts-list">
187
+ ${this.agreement.relatedBundle.facts?.map((fact: Resource) => {
188
+ const tags =
189
+ fact.categories?.map((c: Resource) => ({
190
+ name: c.name,
191
+ type: "information",
192
+ })) || [];
193
+ if (tags.length > 3) {
194
+ const overflowTags = tags.length - 3;
195
+ tags.splice(3, overflowTags);
196
+ tags.push({
197
+ name: `+${overflowTags} ${msg("more")}`,
198
+ type: "information",
199
+ });
200
+ }
201
+ return html`<ds4go-card-catalog
202
+ .object=${import.meta.env.DEV ? fact : nothing}
203
+ .tags=${tags}
204
+ .header=${fact.name || nothing}
205
+ .content=${fact.description || nothing}
206
+ date=${fact.updated_at || nothing}
207
+ ></ds4go-card-catalog>`;
208
+ })}
209
+ </div>
210
+ </div>
211
+ </div>`
212
+ : nothing}
213
+ ${relatedPolicies && relatedPolicies.length > 0
214
+ ? html`<div class="contract-subsection">
215
+ <div
216
+ class="collapsible-header"
217
+ @click=${() => this._toggleSection("policies")}
218
+ >
219
+ <tems-division type="body-m">${msg("Policies")}</tems-division>
220
+ <icon-material-symbols-expand-more
221
+ class="chevron ${this.expandedPolicies ? "expanded" : ""}"
222
+ ></icon-material-symbols-expand-more>
223
+ </div>
224
+ <div
225
+ class="collapsible-content ${this.expandedPolicies
226
+ ? "expanded"
227
+ : ""}"
228
+ >
229
+ ${relatedPolicies.map(
230
+ (policy: PolicyDefinition) =>
231
+ html`<ds4go-odrl-policy-modal-part
232
+ .policy=${policy}
233
+ ></ds4go-odrl-policy-modal-part>`,
234
+ )}
235
+ </div>
236
+ </div>`
237
+ : nothing}
238
+ </div>`;
239
+ }
240
+ }
@@ -2,7 +2,12 @@ import { OrbitComponent } from "@helpers";
2
2
  import { localized, msg, str } from "@lit/localize";
3
3
  import { Task } from "@lit/task";
4
4
  import type {
5
+ Asset,
6
+ ContractAgreement,
7
+ ContractNegotiation,
8
+ NegotiationState,
5
9
  OrbitComponent as OrbitComponentConfig,
10
+ PolicyDefinition,
6
11
  Resource,
7
12
  } from "@src/component";
8
13
 
@@ -10,6 +15,11 @@ import ModalStyle from "@styles/modal/ds4go-customer-modal.scss?inline";
10
15
  import { css, html, nothing, unsafeCSS } from "lit";
11
16
  import { customElement, state } from "lit/decorators.js";
12
17
 
18
+ type ExtendedContractNegotiation = ContractNegotiation & {
19
+ agreement?: ContractAgreement;
20
+ relatedBundle?: Resource;
21
+ };
22
+
13
23
  @customElement("ds4go-customer-modal")
14
24
  @localized()
15
25
  export class Ds4goCustomerModal extends OrbitComponent {
@@ -24,6 +34,33 @@ export class Ds4goCustomerModal extends OrbitComponent {
24
34
  @state()
25
35
  dspConnector: OrbitComponentConfig | undefined;
26
36
 
37
+ @state()
38
+ assets: Asset[] = [];
39
+
40
+ @state()
41
+ policies: PolicyDefinition[] = [];
42
+
43
+ @state()
44
+ bundles: Resource[] = [];
45
+
46
+ @state()
47
+ negotiations: ExtendedContractNegotiation[] = [];
48
+
49
+ @state()
50
+ providerNegotiations: ExtendedContractNegotiation[] = [];
51
+
52
+ @state()
53
+ consumerNegotiations: ExtendedContractNegotiation[] = [];
54
+
55
+ @state()
56
+ allAgreements: ContractAgreement[] = [];
57
+
58
+ @state()
59
+ providerAgreements: ContractAgreement[] = [];
60
+
61
+ @state()
62
+ consumerAgreements: ContractAgreement[] = [];
63
+
27
64
  async _afterAttach() {
28
65
  // use this.dspConnector.instance to reach the connector
29
66
  this.dspConnector = this.orbit?.components.find(
@@ -37,13 +74,170 @@ export class Ds4goCustomerModal extends OrbitComponent {
37
74
  this.dispatchEvent(new CustomEvent("close"));
38
75
  }
39
76
 
77
+ _getNegotiationTags(negotiation: ExtendedContractNegotiation) {
78
+ const tags: Array<{ name: string; type: string }> = [
79
+ {
80
+ name: `State: ${negotiation.state}`,
81
+ type: this._getNegotiationStateType(negotiation.state),
82
+ },
83
+ ];
84
+
85
+ if (negotiation.counterPartyId) {
86
+ const counterPartyId = negotiation.counterPartyId.split("/").pop() || "";
87
+ const truncatedCounterPartyId =
88
+ counterPartyId.length > 15
89
+ ? `${counterPartyId.substring(0, 15)}...`
90
+ : counterPartyId;
91
+ tags.push({
92
+ name: `Counterparty: ${truncatedCounterPartyId}`,
93
+ type: "information",
94
+ });
95
+ }
96
+
97
+ if (negotiation.agreement) {
98
+ tags.push({
99
+ name: msg("Has Agreement"),
100
+ type: "success",
101
+ });
102
+ }
103
+
104
+ return tags;
105
+ }
106
+
107
+ _getNegotiationStateType(state: NegotiationState): string {
108
+ const successStates = ["FINALIZED", "VERIFIED", "AGREED"];
109
+ const warningStates = ["TERMINATING", "TERMINATED"];
110
+
111
+ if (successStates.includes(state)) {
112
+ return "success";
113
+ }
114
+ if (warningStates.includes(state)) {
115
+ return "warning";
116
+ }
117
+ return "information";
118
+ }
119
+
40
120
  _getResource = new Task(this, {
41
121
  task: async () => {
42
122
  if (!this.object) return;
43
123
 
44
124
  if (this.dspConnector?.instance) {
45
- await this.dspConnector.instance.loadContracts();
46
- console.log(this.dspConnector.instance.contracts);
125
+ // Get all data from DSP connector
126
+ await this.dspConnector.instance.loadAll();
127
+
128
+ this.assets = this.dspConnector.instance.assets || [];
129
+ this.policies = this.dspConnector.instance.policies || [];
130
+
131
+ // Get all fact bundles from the connector's datas
132
+ const allDatas = this.dspConnector.instance.datas || [];
133
+ this.bundles = allDatas.filter((data: Resource) => {
134
+ const type = data["@type"];
135
+ if (Array.isArray(type)) {
136
+ return type.includes("ds4go:FactBundle");
137
+ }
138
+ return type === "ds4go:FactBundle";
139
+ });
140
+
141
+ const participantId = this.object.participant_id;
142
+ if (participantId) {
143
+ // Load agreements for all negotiations to find consumer/provider relationships
144
+ const allNegotiations = this.dspConnector.instance.negotiations || [];
145
+ const negotiationToAgreementMap = new Map<
146
+ string,
147
+ ContractAgreement
148
+ >();
149
+
150
+ await Promise.all(
151
+ allNegotiations.map(async (n: ContractNegotiation) => {
152
+ if (n.contractAgreementId) {
153
+ const agreement =
154
+ await this.dspConnector?.instance?.loadAgreement(n["@id"]);
155
+ if (agreement) {
156
+ this.allAgreements.push(agreement);
157
+ negotiationToAgreementMap.set(n["@id"], agreement);
158
+ }
159
+ }
160
+ }),
161
+ );
162
+
163
+ // Separate agreements by provider/consumer role
164
+ this.providerAgreements = this.allAgreements.filter(
165
+ (a: ContractAgreement) => {
166
+ if (!a.providerId) return false;
167
+ const providerParticipantId = a.providerId.split("/").pop();
168
+ return providerParticipantId?.startsWith(participantId);
169
+ },
170
+ );
171
+
172
+ this.consumerAgreements = this.allAgreements.filter(
173
+ (a: ContractAgreement) => {
174
+ if (!a.consumerId) return false;
175
+ const consumerParticipantId = a.consumerId.split("/").pop();
176
+ return consumerParticipantId?.startsWith(participantId);
177
+ },
178
+ );
179
+
180
+ // Get negotiation IDs where participant is consumer or provider in the agreement
181
+ const negotiationIdsWithParticipant = Array.from(
182
+ negotiationToAgreementMap.entries(),
183
+ )
184
+ .filter(([, agreement]) => {
185
+ const providerParticipantId = agreement.providerId
186
+ ?.split("/")
187
+ .pop();
188
+ const consumerParticipantId = agreement.consumerId
189
+ ?.split("/")
190
+ .pop();
191
+ return (
192
+ providerParticipantId?.startsWith(participantId) ||
193
+ consumerParticipantId?.startsWith(participantId)
194
+ );
195
+ })
196
+ .map(([negotiationId]) => negotiationId);
197
+
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;
205
+ }
206
+ }
207
+ // Also include negotiations that have agreements where participant is consumer or provider
208
+ return negotiationIdsWithParticipant.includes(n["@id"]);
209
+ });
210
+
211
+ // Load agreements for filtered negotiations for display
212
+ await Promise.all(
213
+ this.negotiations.map(async (n: ExtendedContractNegotiation) => {
214
+ if (n.contractAgreementId) {
215
+ n.agreement = await this.dspConnector?.instance?.loadAgreement(
216
+ n["@id"],
217
+ );
218
+ }
219
+ }),
220
+ );
221
+
222
+ // Split negotiations by provider/consumer role based on agreement
223
+ this.providerNegotiations = this.negotiations.filter(
224
+ (n: ExtendedContractNegotiation) => {
225
+ if (!n.agreement) return false;
226
+ if (!n.agreement.providerId) return false;
227
+ const providerParticipantId = n.agreement.providerId.split("/").pop();
228
+ return providerParticipantId?.startsWith(participantId);
229
+ },
230
+ );
231
+
232
+ this.consumerNegotiations = this.negotiations.filter(
233
+ (n: ExtendedContractNegotiation) => {
234
+ if (!n.agreement) return false;
235
+ if (!n.agreement.consumerId) return false;
236
+ const consumerParticipantId = n.agreement.consumerId.split("/").pop();
237
+ return consumerParticipantId?.startsWith(participantId);
238
+ },
239
+ );
240
+ }
47
241
  } else {
48
242
  console.warn(
49
243
  "DSP Connector not found. Please ensure the DSP Connector is properly initialized.",
@@ -57,7 +251,7 @@ export class Ds4goCustomerModal extends OrbitComponent {
57
251
 
58
252
  render() {
59
253
  return this._getResource.render({
60
- pending: () => nothing,
254
+ pending: () => html`<solid-loader></solid-loader>`,
61
255
  complete: (obj?: Resource) => {
62
256
  if (!obj) {
63
257
  this._closeModal();
@@ -80,34 +274,86 @@ export class Ds4goCustomerModal extends OrbitComponent {
80
274
  >
81
275
  <tems-division type="body-m"
82
276
  ><div>
83
- ${
84
- obj.participant_id
85
- ? msg(
86
- str`Participant ID: ${String(obj.participant_id)}`,
87
- )
88
- : ""
89
- }
277
+ ${obj.participant_id
278
+ ? msg(str`Participant ID: ${String(obj.participant_id)}`)
279
+ : ""}
90
280
  </div></tems-division
91
281
  >
92
- ${
93
- obj.signed_contracts
94
- ? html`<tems-division type="h4"
95
- ><div>
96
- ${msg("Contracts signed with the customer")}
97
- </div></tems-division
282
+ ${obj.balance !== undefined && obj.balance !== null
283
+ ? html`<tems-division type="body-m"
284
+ ><div>
285
+ ${msg(str`Balance: ${String(obj.balance)}`)}
286
+ </div></tems-division
287
+ >`
288
+ : nothing}
289
+ ${this.providerNegotiations && this.providerNegotiations.length > 0
290
+ ? html`<tems-division type="h4"
291
+ ><div>${msg("Provider Negotiations")}</div></tems-division
98
292
  >
99
293
  <div class="card-grid card-grid-vertical">
100
- ${obj.signed_contracts?.map((contract: Resource) => {
101
- return html`<ds4go-card-catalog
102
- .object=${import.meta.env.DEV ? contract : nothing}
103
- .header=${contract.name || nothing}
104
- .content=${contract.description || nothing}
105
- date=${contract.updated_at || nothing}
106
- ></ds4go-card-catalog>`;
107
- })}
294
+ ${this.providerNegotiations.map(
295
+ (negotiation: ExtendedContractNegotiation) =>
296
+ html`<ds4go-card-catalog
297
+ .object=${import.meta.env.DEV
298
+ ? negotiation
299
+ : nothing}
300
+ .header=${negotiation["@id"].split("/").pop()}
301
+ .content=${negotiation.agreement
302
+ ? `Agreement: ${negotiation.agreement["@id"]
303
+ .split("/")
304
+ .pop()}`
305
+ : msg("No agreement")}
306
+ .tags=${this._getNegotiationTags(negotiation)}
307
+ date=${negotiation.createdAt
308
+ ? new Date(
309
+ negotiation.createdAt,
310
+ ).toLocaleString()
311
+ : nothing}
312
+ ></ds4go-card-catalog>`,
313
+ )}
108
314
  </div>`
109
- : nothing
110
- }
315
+ : nothing}
316
+ ${this.consumerNegotiations && this.consumerNegotiations.length > 0
317
+ ? html`<tems-division type="h4"
318
+ ><div>${msg("Customer Negotiations")}</div></tems-division
319
+ >
320
+ <div class="card-grid card-grid-vertical">
321
+ ${this.consumerNegotiations.map(
322
+ (negotiation: ExtendedContractNegotiation) =>
323
+ html`<ds4go-card-catalog
324
+ .object=${import.meta.env.DEV
325
+ ? negotiation
326
+ : nothing}
327
+ .header=${negotiation["@id"].split("/").pop()}
328
+ .content=${negotiation.agreement
329
+ ? `Agreement: ${negotiation.agreement["@id"]
330
+ .split("/")
331
+ .pop()}`
332
+ : msg("No agreement")}
333
+ .tags=${this._getNegotiationTags(negotiation)}
334
+ date=${negotiation.createdAt
335
+ ? new Date(
336
+ negotiation.createdAt,
337
+ ).toLocaleString()
338
+ : nothing}
339
+ ></ds4go-card-catalog>`,
340
+ )}
341
+ </div>`
342
+ : nothing}
343
+ ${this.consumerAgreements && this.consumerAgreements.length > 0
344
+ ? html`<tems-division type="h4"
345
+ ><div>${msg("Customer Agreements")}</div></tems-division
346
+ >
347
+ ${this.consumerAgreements.map(
348
+ (agreement: ContractAgreement) =>
349
+ html`<ds4go-customer-contract-modal-part
350
+ .agreement=${agreement}
351
+ .assets=${this.assets}
352
+ .bundles=${this.bundles}
353
+ .policies=${this.policies}
354
+ ></ds4go-customer-contract-modal-part>`,
355
+ )}`
356
+ : nothing}
111
357
  <tems-division type="body-sm"
112
358
  ><div>
113
359
  <div>
@@ -49,7 +49,15 @@ export class SolidCustomerList extends OrbitComponent {
49
49
  { key: "updated_at", value: "updated_at", cast: formatDate },
50
50
  { key: "name", value: "name" },
51
51
  { key: "participant_id", value: "participant_id" },
52
- { key: "balance", value: "balance" },
52
+ {
53
+ key: "balance",
54
+ value: "balance",
55
+ cast: (balance: string) =>
56
+ new Intl.NumberFormat(undefined, {
57
+ style: "currency",
58
+ currency: "EUR",
59
+ }).format(Number(balance)),
60
+ },
53
61
  ];
54
62
 
55
63
  async _afterAttach() {