diff --git a/cypress/e2e/blog.cy.ts b/cypress/e2e/blog.cy.ts index cfc50071..53770ae9 100644 --- a/cypress/e2e/blog.cy.ts +++ b/cypress/e2e/blog.cy.ts @@ -1,5 +1,5 @@ import { loginAcc, blogEntry, local_items } from "../support/constants" -import * as moment from 'moment'; +import moment from 'moment'; describe('/blog',{ viewportHeight: 800, diff --git a/cypress/e2e/dashboard.cy.ts b/cypress/e2e/dashboard.cy.ts index 192d61b3..ee3a10a5 100644 --- a/cypress/e2e/dashboard.cy.ts +++ b/cypress/e2e/dashboard.cy.ts @@ -1,5 +1,5 @@ import { category_launched, init_config, init_stat, local_items, login_token, product_offering, checkHeaderPostLogin, checkHeaderPreLogin, loginAcc } from "../support/constants" -import * as moment from 'moment'; +import moment from 'moment'; describe('/dashboard',{ viewportHeight: 800, diff --git a/cypress/support/constants.ts b/cypress/support/constants.ts index 17fd72bf..c7509316 100644 --- a/cypress/support/constants.ts +++ b/cypress/support/constants.ts @@ -32,6 +32,7 @@ export const init_config = { 'quotesEnabled': false, 'tenderingEnabled': false, 'dataSpaceEnabled': false, + 'dspEnabled': false, 'launchValidationEnabled': false, 'tenderDevButtonsOpenCloseEnabled': false, 'aiEnabled': false, diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 90af9824..51382324 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -9,7 +9,7 @@ import { Router } from '@angular/router'; import { LoginInfo } from 'src/app/models/interfaces'; import { ApiServiceService } from 'src/app/services/product-service.service'; import { RefreshLoginServiceService } from "src/app/services/refresh-login-service.service"; -import * as moment from 'moment'; +import moment from 'moment'; import {ThemeService} from "./services/theme.service"; import {environment} from "../environments/environment"; import { filter } from 'rxjs'; diff --git a/src/app/data/featuresConfig.ts b/src/app/data/featuresConfig.ts index 4ecb89d3..220523f3 100644 --- a/src/app/data/featuresConfig.ts +++ b/src/app/data/featuresConfig.ts @@ -5,6 +5,7 @@ export type FeatureFlagKey = | 'quotesEnabled' | 'tenderingEnabled' | 'dataSpaceEnabled' + | 'dspEnabled' | 'launchValidationEnabled' | 'tenderDevButtonsOpenCloseEnabled' | 'aiEnabled'; @@ -45,6 +46,11 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [ label: 'Data space', description: 'Enable data space fields in organization and offer forms.' }, + { + key: 'dspEnabled', + label: 'DSP', + description: 'Enable DSP-related data space contract and schema fields.' + }, { key: 'launchValidationEnabled', label: 'Launch validation', @@ -76,6 +82,7 @@ export function readFeaturesConfig(config: any): FeaturesConfig { result.quotesEnabled = readBoolean(source, 'quotesEnabled'); result.tenderingEnabled = readBoolean(source, 'tenderingEnabled'); result.dataSpaceEnabled = readBoolean(source, 'dataSpaceEnabled'); + result.dspEnabled = readBoolean(source, 'dspEnabled'); result.launchValidationEnabled = readBoolean(source, 'launchValidationEnabled'); result.tenderDevButtonsOpenCloseEnabled = readBoolean(source, 'tenderDevButtonsOpenCloseEnabled'); result.aiEnabled = readBoolean(source, 'aiEnabled'); @@ -91,6 +98,7 @@ export function applyRuntimeFeaturesConfig(config: any): void { environment.QUOTES_ENABLED = features.quotesEnabled ?? false; environment.TENDER_ENABLED = features.tenderingEnabled ?? false; environment.DATA_SPACE_ENABLED = features.dataSpaceEnabled ?? false; + environment.DSP_ENABLED = features.dspEnabled ?? environment.DSP_ENABLED; environment.LAUNCH_VALIDATION_ENABLED = features.launchValidationEnabled ?? false; environment.TENDER_DEV_BUTTONS_OPEN_CLOSE_ENABLED = features.tenderDevButtonsOpenCloseEnabled ?? false; environment.AI_SEARCH_ENABLED = features.aiEnabled ?? false; diff --git a/src/app/guard/auth.guard.spec.ts b/src/app/guard/auth.guard.spec.ts index d5c42b54..f7f3af10 100644 --- a/src/app/guard/auth.guard.spec.ts +++ b/src/app/guard/auth.guard.spec.ts @@ -3,7 +3,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { RouterTestingModule } from '@angular/router/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; -import * as moment from 'moment'; +import moment from 'moment'; import { LocalStorageService } from '../services/local-storage.service'; import { AuthGuard } from './auth.guard'; diff --git a/src/app/guard/auth.guard.ts b/src/app/guard/auth.guard.ts index e45221d9..e2f01f6d 100644 --- a/src/app/guard/auth.guard.ts +++ b/src/app/guard/auth.guard.ts @@ -3,7 +3,7 @@ import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from import {LocalStorageService} from "../services/local-storage.service"; import { Observable } from 'rxjs'; import { LoginInfo } from '../models/interfaces'; -import * as moment from 'moment'; +import moment from 'moment'; import { environment } from 'src/environments/environment'; @Injectable({ diff --git a/src/app/interceptors/requests-interceptor.spec.ts b/src/app/interceptors/requests-interceptor.spec.ts index 769e7152..85376c60 100644 --- a/src/app/interceptors/requests-interceptor.spec.ts +++ b/src/app/interceptors/requests-interceptor.spec.ts @@ -1,7 +1,7 @@ import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import * as moment from 'moment'; +import moment from 'moment'; import { environment } from 'src/environments/environment'; import { PROVIDER_COUNTRY_LIST_URL } from '../models/search-organizations-filters.model'; import { LocalStorageService } from '../services/local-storage.service'; diff --git a/src/app/interceptors/requests-interceptor.ts b/src/app/interceptors/requests-interceptor.ts index 0a97a64a..40a30ca4 100644 --- a/src/app/interceptors/requests-interceptor.ts +++ b/src/app/interceptors/requests-interceptor.ts @@ -8,7 +8,7 @@ import { import { Observable } from 'rxjs'; import {LocalStorageService} from "../services/local-storage.service"; import { LoginInfo } from '../models/interfaces'; -import * as moment from 'moment'; +import moment from 'moment'; import { environment } from 'src/environments/environment'; export function shouldAttachAuthHeaders(requestUrl: string, internalBaseUrls: string[]): boolean { diff --git a/src/app/models/formFields/form-field.model.ts b/src/app/models/formFields/form-field.model.ts new file mode 100644 index 00000000..d95774b1 --- /dev/null +++ b/src/app/models/formFields/form-field.model.ts @@ -0,0 +1,37 @@ +interface BaseFormField { + name: string; + label: string; + required?: boolean; + colSpan?: number; + defaultValue?: any; +} + +export interface StringFormField extends BaseFormField { + type: 'string'; + maxLength?: number; + placeholder?: string; +} + +export interface NumberFormField extends BaseFormField { + type: 'number'; + min?: number; + max?: number; + step?: number; +} + +export interface SelectOption { + value: string; + label: string; +} + +export interface SelectableFormField extends BaseFormField { + type: 'select'; + options: SelectOption[]; + multiple?: boolean; +} + +export interface BooleanFormField extends BaseFormField { + type: 'boolean'; +} + +export type FormField = StringFormField | NumberFormField | SelectableFormField | BooleanFormField; diff --git a/src/app/models/paginated-list.spec.ts b/src/app/models/paginated-list.spec.ts new file mode 100644 index 00000000..64c6692f --- /dev/null +++ b/src/app/models/paginated-list.spec.ts @@ -0,0 +1,196 @@ +import { TestBed } from '@angular/core/testing'; +import { PaginatedList } from './paginated-list'; + +describe('PaginatedList', () => { + const PAGE_SIZE = 10; + + function makeItems(count: number, offset = 0): string[] { + return Array.from({ length: count }, (_, i) => `item-${offset + i}`); + } + + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + + it('should start with empty items, loading false and hasMore false', () => { + const list = new PaginatedList(() => Promise.resolve([]), PAGE_SIZE); + expect(list.items()).toEqual([]); + expect(list.loading()).toBeFalse(); + expect(list.loadingMore()).toBeFalse(); + expect(list.hasMore()).toBeFalse(); + }); + + it('load should set loading=true during fetch and false when done', async () => { + const loadingStates: boolean[] = []; + const fetcher = jasmine.createSpy().and.callFake(() => { + loadingStates.push(list.loading()); + return Promise.resolve([]); + }); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + expect(loadingStates[0]).toBeTrue(); + expect(list.loading()).toBeFalse(); + }); + + it('load should populate items from the first fetch call', async () => { + const page0 = makeItems(10); + const fetcher = jasmine.createSpy() + .and.returnValues(Promise.resolve(page0), Promise.resolve([])); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + expect(list.items()).toEqual(page0); + }); + + it('load should set hasMore=true when prefetch returns non-null items', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues(Promise.resolve(makeItems(10)), Promise.resolve(makeItems(10, 10))); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + expect(list.hasMore()).toBeTrue(); + }); + + it('load should set hasMore=false when prefetch returns empty array', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues(Promise.resolve(makeItems(5)), Promise.resolve([])); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + expect(list.hasMore()).toBeFalse(); + }); + + it('load should set hasMore=false when prefetch returns array of nulls', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues(Promise.resolve(makeItems(5)), Promise.resolve([null, null] as any)); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + expect(list.hasMore()).toBeFalse(); + }); + + it('load should reset items and state on reload', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues( + Promise.resolve(makeItems(10)), // first load: page 0 + Promise.resolve(makeItems(10, 10)), // first load: prefetch + Promise.resolve(makeItems(10, 20)), // loadMore: next prefetch + Promise.resolve(makeItems(3, 30)), // second load: page 0 + Promise.resolve([]) // second load: prefetch + ); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + await list.loadMore(); + await list.load(); + expect(list.items()).toEqual(makeItems(3, 30)); + }); + + it('load should set loading=false even when fetcher rejects', async () => { + const fetcher = jasmine.createSpy().and.returnValue(Promise.reject(new Error('fail'))); + const list = new PaginatedList(fetcher, PAGE_SIZE); + try { + await list.load(); + } catch { + // expected + } + expect(list.loading()).toBeFalse(); + }); + + it('loadMore should do nothing when hasMore is false', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues(Promise.resolve(makeItems(5)), Promise.resolve([])); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + const callCount = fetcher.calls.count(); + + await list.loadMore(); + expect(fetcher.calls.count()).toBe(callCount); + expect(list.items()).toEqual(makeItems(5)); + }); + + it('loadMore should set loadingMore=true during fetch and false when done', async () => { + let resolveSecond!: (v: string[]) => void; + const fetcher = jasmine.createSpy() + .and.returnValues( + Promise.resolve(makeItems(10)), + Promise.resolve(makeItems(10, 10)), + new Promise(r => { resolveSecond = r; }) + ); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + + const morePromise = list.loadMore(); + expect(list.loadingMore()).toBeTrue(); + resolveSecond([]); + await morePromise; + expect(list.loadingMore()).toBeFalse(); + }); + + it('loadMore should append prefetched items to existing items', async () => { + const page0 = makeItems(10); + const page1 = makeItems(10, 10); + const fetcher = jasmine.createSpy() + .and.returnValues( + Promise.resolve(page0), + Promise.resolve(page1), + Promise.resolve([]) + ); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + await list.loadMore(); + expect(list.items()).toEqual([...page0, ...page1]); + }); + + it('loadMore should update hasMore based on next prefetch result', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues( + Promise.resolve(makeItems(10)), + Promise.resolve(makeItems(10, 10)), + Promise.resolve(makeItems(5, 20)), + Promise.resolve([]) + ); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + expect(list.hasMore()).toBeTrue(); + await list.loadMore(); + expect(list.hasMore()).toBeTrue(); + await list.loadMore(); + expect(list.hasMore()).toBeFalse(); + }); + + it('loadMore should set loadingMore=false even when fetcher rejects', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues( + Promise.resolve(makeItems(10)), + Promise.resolve(makeItems(10, 10)), + Promise.reject(new Error('fail')) + ); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + try { + await list.loadMore(); + } catch { + // expected + } + expect(list.loadingMore()).toBeFalse(); + }); + + it('load should call fetcher with page 0 first, then pageSize', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues(Promise.resolve([]), Promise.resolve([])); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + expect(fetcher.calls.argsFor(0)).toEqual([0]); + expect(fetcher.calls.argsFor(1)).toEqual([PAGE_SIZE]); + }); + + it('loadMore should advance the page cursor on each call', async () => { + const fetcher = jasmine.createSpy() + .and.returnValues( + Promise.resolve(makeItems(10)), + Promise.resolve(makeItems(10, 10)), + Promise.resolve(makeItems(10, 20)), + Promise.resolve([]) + ); + const list = new PaginatedList(fetcher, PAGE_SIZE); + await list.load(); + await list.loadMore(); + expect(fetcher.calls.argsFor(2)).toEqual([PAGE_SIZE * 2]); + }); +}); diff --git a/src/app/models/paginated-list.ts b/src/app/models/paginated-list.ts new file mode 100644 index 00000000..de59c6e6 --- /dev/null +++ b/src/app/models/paginated-list.ts @@ -0,0 +1,49 @@ +import { signal } from '@angular/core'; + +export class PaginatedList { + readonly items = signal([]); + readonly loading = signal(false); + readonly loadingMore = signal(false); + readonly hasMore = signal(false); + + private page = 0; + private prefetched: T[] = []; + private readonly fetcher: (page: number) => Promise; + private readonly pageSize: number; + + constructor(fetcher: (page: number) => Promise, pageSize: number) { + this.fetcher = fetcher; + this.pageSize = pageSize; + } + + async load(): Promise { + this.loading.set(true); + this.page = 0; + this.items.set([]); + this.prefetched = []; + + try { + this.items.set(await this.fetcher(0)); + this.page = this.pageSize; + this.prefetched = await this.fetcher(this.page); + this.page += this.pageSize; + this.hasMore.set(this.prefetched.some(item => item != null)); + } finally { + this.loading.set(false); + } + } + + async loadMore(): Promise { + if (!this.hasMore()) return; + this.loadingMore.set(true); + + try { + this.items.set([...this.items(), ...this.prefetched]); + this.prefetched = await this.fetcher(this.page); + this.page += this.pageSize; + this.hasMore.set(this.prefetched.some(item => item != null)); + } finally { + this.loadingMore.set(false); + } + } +} diff --git a/src/app/pages/admin/categories/create-category/create-category.component.ts b/src/app/pages/admin/categories/create-category/create-category.component.ts index 99d74ee5..8dbf7be6 100644 --- a/src/app/pages/admin/categories/create-category/create-category.component.ts +++ b/src/app/pages/admin/categories/create-category/create-category.component.ts @@ -6,7 +6,7 @@ import {EventMessageService} from "src/app/services/event-message.service"; import { LoginInfo } from 'src/app/models/interfaces'; import { initFlowbite } from 'flowbite'; import { FormGroup, FormControl, Validators } from '@angular/forms'; -import * as moment from 'moment'; +import moment from 'moment'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; diff --git a/src/app/pages/admin/categories/update-category/update-category.component.ts b/src/app/pages/admin/categories/update-category/update-category.component.ts index 21710408..70189e15 100644 --- a/src/app/pages/admin/categories/update-category/update-category.component.ts +++ b/src/app/pages/admin/categories/update-category/update-category.component.ts @@ -6,7 +6,7 @@ import {EventMessageService} from "src/app/services/event-message.service"; import { LoginInfo } from 'src/app/models/interfaces'; import { initFlowbite } from 'flowbite'; import { FormGroup, FormControl, Validators } from '@angular/forms'; -import * as moment from 'moment'; +import moment from 'moment'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; diff --git a/src/app/pages/catalogs/catalogs.component.html b/src/app/pages/catalogs/catalogs.component.html index 494796a4..6352b22c 100644 --- a/src/app/pages/catalogs/catalogs.component.html +++ b/src/app/pages/catalogs/catalogs.component.html @@ -85,7 +85,7 @@

+
+
} } @else { -
+
} } @else { -
+
{{ activeCategoryName || 'All Categories' }} @@ -13,136 +14,165 @@
- - + +
- @if (showCategoryDropdown) { -
-
+
+
@for (cat of rootCategories; track cat.id) { - + } -
+
}
@if (searchEnabled) { -
-
- @if (aiSearchEnabled) { -
- -
- } - - + +
+ @if (aiSearchEnabled) { +
+
- + } + + +
+ }
@if (toolbarFilters.length > 0) { -
+
@for (filter of toolbarFilters; track filter.key) { -
- + @if (filter.open) { +
+
+ @for (option of filter.options; track option.id) { + - @if (filter.open) { -
-
- @for (option of filter.options; track option.id) { - }
-
- } + {{ option.name }} + + } +
+ } +
}
- -
+
+
}
@@ -151,32 +181,39 @@

@if (showDrawer) { -
- - -
+
+ + +
} @else if (activeCategoryName) { - }
- @@ -185,7 +222,7 @@

Filters

@if (!loading) { - {{ visibleProducts.length }} results + {{ visibleProducts.length }} results } -
- -
+ +
+ +
-
-

{{ 'CREATE_PROD_SPEC._new' | translate }}

-
    - @for (step of steps; track i; let i = $index) { -
  1. - - {{ i + 1 }} - - -
  2. - } -
-
-
- @if (currentStep === 0) { -
-
- - - - +
+

{{ 'CREATE_PROD_SPEC._new' | translate }} +

+
    + @for (step of steps; track i; let i = $index) { +
  1. + + {{ i + 1 }} + + +
  2. + } +
+
+
+ @if (currentStep.id === 'general') { + +
+ + + + -
-
- - - - +
+
+ + + + -
- - +
- - - } - @if (currentStep === 1 && BUNDLE_ENABLED) { -
- - -
- @if(bundleChecked){ - @if(loadingBundle){ -
- - Loading... -
- } @else { - @if(prodSpecs.length==0){ -
- -
- }@else{ -
- - - - - - - - - - - - @for (prod of prodSpecs; track prod.id) { - -
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._select' | translate }} -
+ + + + @if(bundleChecked){ + @if(loadingBundle){ +
+ + Loading... +
+ } @else { + @if(prodSpecs.length==0){ +
+ +
+ }@else{ +
+ + + + + + + + + + + + @for (prod of prodSpecs; track prod.id) { + + - - - - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._select' | translate }} +
- {{prod.name}} - - -
-
- } - } - @if (!loadingBundle_more) { - @if (bundlePageCheck) { -
- -
- } - } @else { -
- - Loading... -
- } - } - - } - @if (isCurrentStep('compliance')) { -

- {{ 'CREATE_PROD_SPEC._self_attestation' | translate }} -

-
+ } + @if (currentStep.id === 'compliance') { +

+ {{ 'CREATE_PROD_SPEC._self_attestation' | translate }} +

+
-

- {{ selfAtt ? selfAtt.productSpecCharacteristicValue[0].value : 'Upload your self attestation document' }} -

+

+ {{ selfAtt ? selfAtt.productSpecCharacteristicValue[0].value : 'Upload your self attestation document' }} +

-
- - @if(selfAtt){ - - } -
+
+ + @if(selfAtt){ + + } +
-
- @if(availableISOS.length>0){ - - @if(buttonISOClicked){ - - - } - } +
+ @if(availableISOS.length>0){ + + @if(buttonISOClicked){ + + + } + } -
- - - - - - - - - - @for (sel of selectedISOS; track sel.name; let idx = $index) { - - + + } @empty { +
+ +
+ } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._value' | translate }} - - {{ 'CREATE_PROD_SPEC._actions' | translate }} -
+ + + + + + + + + + @for (sel of selectedISOS; track sel.name; let idx = $index) { + + - - - - } @empty { -
- -
- } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._value' | translate }} + + {{ 'CREATE_PROD_SPEC._actions' | translate }} +
- {{normalizeName(sel.name)}} - - {{sel.url}} - - - - -
- - -
- -
- -
+ + +
+
+ +
+ +
+ + + {{ 'CREATE_PROD_SPEC._file_check' | translate }} + +
+
+
- - + -
- - @if(additionalISOS.length>0){ -

- You added the following additional certifications: -

-
- - - - - - - - - - @for (cert of additionalISOS; track cert.name; let idx = $index) { - - + + + + } + +
- {{ 'UPDATE_PROD_SPEC._product_name' | translate }} - - {{ 'UPDATE_PROD_SPEC._value' | translate }} - - {{ 'UPDATE_PROD_SPEC._actions' | translate }} -
+ You added the following additional certifications: + +
+ + + + + + + + + + @for (cert of additionalISOS; track cert.name; let idx = $index) { + + - - - - } - -
+ {{ 'UPDATE_PROD_SPEC._product_name' | translate }} + + {{ 'UPDATE_PROD_SPEC._value' | translate }} + + {{ 'UPDATE_PROD_SPEC._actions' | translate }} +
- {{normalizeName(cert.name)}} - - {{cert.url}} - - -
-
- } - @if(showCert==false){ -
- -
- } @else { -
-

- Add an additional certification: -

-
-
- - -
- @if(isoToCreate==''){ - - -
- -
-

{{ 'UPDATE_PROD_SPEC._drop_files' | translate }} - -

-
-
-
-
- } @else { - - } -
-
- - -
-
- } + {{normalizeName(cert.name)}} +
+ {{cert.url}} + + +
+
+ } + @if(showCert==false){ +
+ +
+ } @else { +
+

+ Add an additional certification: +

+
+
+ + +
+ @if(isoToCreate==''){ + + +
+ +
+

{{ 'UPDATE_PROD_SPEC._drop_files' | translate }} + +

+
+
+
+
+ } @else { + + } +
+
+ + +
+
+ } - } - @if (isCurrentStep('characteristics') || isCurrentStep('dataspace')) { - @if (getFilteredCharacteristicsForCurrentStep().length === 0){ -
- -
- } @else { -
- - - - - - - - - - - @for (prod of getFilteredCharacteristicsForCurrentStep(); track prod.id) { - - + + + + } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._values' | translate }} - - {{ 'CREATE_PROD_SPEC._actions' | translate }} -
+ + + } @else { +
+ + + + + + + + + + + @for (prod of getFilteredCharacteristicsForCurrentStep(); track prod.id) { + + - - - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._values' | translate }} + + {{ 'CREATE_PROD_SPEC._actions' | translate }} +
- {{prod.name}} - - @for (char of prod.productSpecCharacteristicValue; track char; let last=$last) { - @if(char.value || char.value == 0){ - @if(isJsonCharacteristicType(prod.valueType)){ - @if(!last){ - {{getValuePreview(char.value)}}, - } @else { - {{getValuePreview(char.value)}} - } - } @else if(char.unitOfMeasure){ - @if(!last){ - {{char.value}} ({{char?.unitOfMeasure}}), - } @else { - {{char.value}} ({{char?.unitOfMeasure}}) - } - } @else { - @if(!last){ - {{char.value}}, - } @else { - {{char.value}} - } - } - } @else { - @if(!last){ - {{char.valueFrom}} - {{char.valueTo}} ({{char?.unitOfMeasure}}), - } @else { - {{char.valueFrom}} - {{char.valueTo}} ({{char?.unitOfMeasure}}) - } - } - } - - -
-
- } - @if(!showCreateChar){ -
- -
- } @else { -
-
- - -
-
- - -
-
- - -
- @if(!isDataspaceConfigurationStep() && charTypeSelected !== 'boolean' && !isJsonCharacteristicType(charTypeSelected)){ -
- - -
- @if(isOptional){ -
- - -
- } - } -
-
- @if(creatingChars.length > 0 && charTypeSelected !== 'boolean'){ - -
- @if(charTypeSelected === 'range'){ - @for (char of creatingChars; track char; let idx = $index) { -
-
- - -
- -
- } - } @else { - @for (char of creatingChars; track char; let idx = $index) { -
-
- - @if(charTypeSelected === 'number'){ - - } @else if (isJsonCharacteristicType(charTypeSelected)) { - - } @else { - - } -
- -
- } - } -
- } - - @if(isTextCharacteristicType(charTypeSelected)){ -
- - -
- } @else if (charTypeSelected === 'number'){ -
-
- - {{ 'CREATE_PROD_SPEC._value' | translate }} - - -
-
- - {{ 'CREATE_PROD_SPEC._unit' | translate }} - - -
+ {{prod.description}} +
+ @for (char of prod.productSpecCharacteristicValue; track char; let last=$last) { + @if(char.value || char.value == 0){ + @if(isJsonCharacteristicType(prod.valueType)){ + @if(!last){ + {{getValuePreview(char.value)}}, + } @else { + {{getValuePreview(char.value)}} + } + } @else if(char.unitOfMeasure){ + @if(!last){ + {{char.value}} ({{char?.unitOfMeasure}}), + } @else { + {{char.value}} ({{char?.unitOfMeasure}}) + } + } @else { + @if(!last){ + {{char.value}}, + } @else { + {{char.value}} + } + } + } @else { + @if(!last){ + {{char.valueFrom}} - {{char.valueTo}} ({{char?.unitOfMeasure}}), + } @else { + {{char.valueFrom}} - {{char.valueTo}} ({{char?.unitOfMeasure}}) + } + } + } + + +
+
+ } + @if(!showCreateChar){ +
+ +
+ } @else { +
+
+ + +
+
+ + +
+
+ + +
+ @if(!isDataspaceConfigurationStep() && charTypeSelected !== 'boolean' && + !isJsonCharacteristicType(charTypeSelected)){ +
+ + +
+ @if(isOptional){ +
+ + +
+ } + } +
+
+ @if(creatingChars.length > 0 && charTypeSelected !== 'boolean'){ + +
+ @if(charTypeSelected === 'range'){ + @for (char of creatingChars; track char; let idx = $index) { +
+
+ + +
+ +
+ } + } @else { + @for (char of creatingChars; track char; let idx = $index) { +
+
+ + @if(charTypeSelected === 'number'){ + + } @else if (isJsonCharacteristicType(charTypeSelected)) { + + } @else { + + } +
+ +
+ } + } +
+ } + + @if(isTextCharacteristicType(charTypeSelected)){ +
+ + +
+ } @else if (charTypeSelected === 'number'){ +
+
+ + {{ 'CREATE_PROD_SPEC._value' | translate }} + + +
+
+ + {{ 'CREATE_PROD_SPEC._unit' | translate }} + + +
- -
- } @else if (charTypeSelected === 'boolean'){ -
- - -
- } @else if (charTypeSelected === 'range' && creatingChars.length==0){ -
-
- - {{ 'CREATE_PROD_SPEC._from' | translate }} - - -
-
- - {{ 'CREATE_PROD_SPEC._to' | translate }} - - -
-
- - {{ 'CREATE_PROD_SPEC._unit' | translate }} - - -
- -
- } @else if (isJsonCharacteristicType(charTypeSelected)){ - @if(creatingChars.length === 0){ -
- - - -
- } - } -
- -
+ +
+ } @else if (charTypeSelected === 'boolean'){ +
+ + +
+ } @else if (charTypeSelected === 'range' && creatingChars.length==0){ +
+
+ + {{ 'CREATE_PROD_SPEC._from' | translate }} + + +
+
+ + {{ 'CREATE_PROD_SPEC._to' | translate }} + + +
+
+ + {{ 'CREATE_PROD_SPEC._unit' | translate }} + + +
+ +
+ } @else if (isJsonCharacteristicType(charTypeSelected)){ + @if(creatingChars.length === 0){ +
+ + + +
+ } + } +
+ +
- - } - - } + } - @if (isCurrentStep('resource')) { - @if(loadingResourceSpec){ -
- - Loading... -
- } @else { - @if(resourceSpecs.length==0){ -
- -
- }@else{ -
- - - - - - - - - - - @for (res of resourceSpecs; track res.id) { - -
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._select' | translate }} -
+ + Loading... + + } @else { + @if(resourceSpecs.length==0){ +
+ +
+ }@else{ +
+ + + + + + + + + + + @for (res of resourceSpecs; track res.id) { + + - - - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._select' | translate }} +
- {{res.name}} - - -
-
- } - } - @if (!loadingResourceSpec_more) { - @if (resourceSpecPageCheck) { -
- -
- } - } @else { -
- - Loading... -
- } - - } - @if (isCurrentStep('service')) { - @if(loadingServiceSpec){ -
- - Loading... -
- } @else { - @if(serviceSpecs.length==0){ -
- -
- }@else{ -
- - - - - - - - - - - @for (serv of serviceSpecs; track serv.id) { - -
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._select' | translate }} -
+ + Loading... + + } @else { + @if(serviceSpecs.length==0){ +
+ +
+ }@else{ +
+ + + + + + + + + + + @for (serv of serviceSpecs; track serv.id) { + + - - - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._select' | translate }} +
- {{serv.name}} - - -
-
- } - } - @if (!loadingServiceSpec_more) { - @if (serviceSpecPageCheck) { -
- -
- } - } @else { -
- - Loading... -
- } - + } + @if (currentStep.id === 'dsp_config') { +
+

DSP Config

+ + @if (endpointUrls.length > 0) { +
+ + + + + + + + + + @for (entry of endpointUrls; track entry; let idx = $index) { + + + + + } - @if (isCurrentStep('attachments')) { -
-

{{ 'CREATE_PROD_SPEC._add_prod_img' | translate }}

-
- -
- -
- @if(showImgPreview){ -
- - -
- } @else { -
- - -
- -
-

{{ 'CREATE_PROD_SPEC._drop_files' | translate }} - -

-
-
-
-
-
- -
- -
- {{ 'CREATE_PROD_SPEC._file_name' | translate }} - -
-
+
+
Endpoint URLDescriptionActions
{{ entry.url }}{{ entry.description }} + +
+
+ } -
- - +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+
- - -
-
+ @if (endpointUrls.length === 0) { +

At least one endpoint URL is required.

+ } +
- } -

{{ 'CREATE_PROD_SPEC._add_att' | translate }}

- @if(prodAttachments.length == 0){ -
- -
- } @else { -
- - - - - - - - - - @for (att of prodAttachments; track att) { - - + + + + } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._value' | translate }} - - {{ 'CREATE_PROD_SPEC._actions' | translate }} -
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + @if (dspConfigForm.get('targetSpecification')?.touched) { + @if (dspConfigForm.get('targetSpecification')?.errors?.['required']) { +

This field is required

+ } @else if (dspConfigForm.get('targetSpecification')?.errors?.['invalidJson']) { +

Invalid JSON

+ } + } +
+
+ + + @if (dspConfigForm.get('serviceConfiguration')?.touched) { + @if (dspConfigForm.get('serviceConfiguration')?.errors?.['required']) { +

This field is required

+ } @else if (dspConfigForm.get('serviceConfiguration')?.errors?.['invalidJson']) { +

Invalid JSON

+ } + } +
+
+ + + @if (dspConfigForm.get('credentialsConfig')?.touched) { + @if (dspConfigForm.get('credentialsConfig')?.errors?.['required']) { +

This field is required

+ } @else if (dspConfigForm.get('credentialsConfig')?.errors?.['invalidJson']) { +

Invalid JSON

+ } + } +
+
+ + + @if (dspConfigForm.get('policyConfig')?.touched) { + @if (dspConfigForm.get('policyConfig')?.errors?.['required']) { +

This field is required

+ } @else if (dspConfigForm.get('policyConfig')?.errors?.['invalidJson']) { +

Invalid JSON

+ } + } +
+ + + } + + @if (currentStep.id === 'attachments') { +
+

{{ 'CREATE_PROD_SPEC._add_prod_img' | translate }}

+
+ +
+ +
+ @if(showImgPreview){ +
+ + +
+ } @else { +
+ + +
+ +
+

{{ 'CREATE_PROD_SPEC._drop_files' | translate }} + +

+
+
+
+
+
+ +
+ +
+ {{ 'CREATE_PROD_SPEC._file_name' | translate }} + +
+
+ +
+ + + + + +
+
+ + + } +

{{ 'CREATE_PROD_SPEC._add_att' | translate }}

+ @if(prodAttachments.length == 0){ +
+ +
+ } @else { +
+ + + + + + + + + + @for (att of prodAttachments; track att) { + + - - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._value' | translate }} + + {{ 'CREATE_PROD_SPEC._actions' | translate }} +
- {{att.name}} - - {{att.url}} - - -
-
- } - @if(showNewAtt==false){ -
- -
- } @else { -
-
-
- - -
- @if(attachToCreate.url==''){ - - -
- -
-

{{ 'CREATE_PROD_SPEC._drop_files' | translate }} - -

-
-
-
-
- } @else { - - } + {{att.name}} +
+ {{att.url}} + + +
+
+ } + @if(showNewAtt==false){ +
+ +
+ } @else { +
+
+
+ + +
+ @if(attachToCreate.url==''){ + + +
+ +
+

{{ 'CREATE_PROD_SPEC._drop_files' | translate }} + +

+
+
+
+
+ } @else { + + } -
-
- -
-
- } - - } - @if (isCurrentStep('relationships')) { -
- @if (prodRelationships.length === 0){ -
- -
- } @else { -
- - - - - - - - - - - - @for (rel of prodRelationships; track rel) { - - -
- {{ 'CREATE_PROD_SPEC._relationship_type' | translate }} - - {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._actions' | translate }} -
- {{rel.relationshipType}} - + @if (prodRelationships.length === 0){ +
+ +
+ } @else { +
+ + + + + + + + + + + + @for (rel of prodRelationships; track rel) { + + + - - - - - } - -
+ {{ 'CREATE_PROD_SPEC._relationship_type' | translate }} + + {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._actions' | translate }} +
+ {{rel.relationshipType}} + - {{rel.productSpec.name}} - - -
-
- } - @if(showCreateRel==false){ -
- -
- } @else { - - - @if(loadingprodSpecRel){ -
- - Loading... -
- } @else { - @if(prodSpecRels.length==0){ -
- -
- }@else{ -
- - - - - - - - - - @for (rel of prodSpecRels; track rel) { - - + + + + } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._type' | translate }} -
+ +
+
+ } + @if(showCreateRel==false){ +
+ +
+ } @else { + + + @if(loadingprodSpecRel){ +
+ + Loading... +
+ } @else { + @if(prodSpecRels.length==0){ +
+ +
+ }@else{ +
+ + + + + + + + + + @for (rel of prodSpecRels; track rel) { + + - - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._type' | translate }} +
- {{rel.name}} - - @if(rel.isBundle == false){ - Simple - } @else { - Bundle - } -
-
- } - @if (!loadingprodSpecRel_more) { - @if (prodSpecRelPageCheck) { -
- -
- } - } @else { -
- - Loading... -
- } - } -
- -
- } - - - } - @if (isCurrentStep('summary')) { -
- @if(loading){ -
- - Loading... -
- } @else { -
-
- - + @if(productSpecToCreate?.productNumber!=''){ + + + } +
+
-
- - @if(productSpecToCreate?.lifecycleStatus == 'Active'){ - {{productSpecToCreate?.lifecycleStatus}} - } @else if(productSpecToCreate?.lifecycleStatus == 'Launched') { - {{productSpecToCreate?.lifecycleStatus}} - } @else if(productSpecToCreate?.lifecycleStatus == 'Retired') { - {{productSpecToCreate?.lifecycleStatus}} - } @else if(productSpecToCreate?.lifecycleStatus == 'Obsolete') { - {{productSpecToCreate?.lifecycleStatus}} - } -
+
+ + @if(productSpecToCreate?.lifecycleStatus == 'Active'){ + {{productSpecToCreate?.lifecycleStatus}} + } @else if(productSpecToCreate?.lifecycleStatus == 'Launched') { + {{productSpecToCreate?.lifecycleStatus}} + } @else if(productSpecToCreate?.lifecycleStatus == 'Retired') { + {{productSpecToCreate?.lifecycleStatus}} + } @else if(productSpecToCreate?.lifecycleStatus == 'Obsolete') { + {{productSpecToCreate?.lifecycleStatus}} + } +
- @if(productSpecToCreate?.description !=''){ - -
- {{ 'UPDATE_PROD_SPEC._product_description' | translate + }} +
+ -
- } + }" + class="bg-gray-50 text-wrap break-words dark:bg-secondary-300 dark:text-white text-gray-900" + [data]="productSpecToCreate?.description">
+
+ } - @if(imgPreview!=''){ - -
- -
- } + @if(imgPreview!=''){ + +
+ +
+ } - @if(prodSpecsBundle.length>0){ - -
- - - - - - - - - @for (bun of prodSpecsBundle; track bun) { - - + + + } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._status' | translate }} -
{{ 'CREATE_PROD_SPEC._bundle' | translate }} +
+ + + + + + + + + @for (bun of prodSpecsBundle; track bun) { + + - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._status' | translate }} +
- {{bun.name}} - - @if(bun.lifecycleStatus == 'Active'){ - {{bun.lifecycleStatus}} - } @else if(bun.lifecycleStatus == 'Launched') { - {{bun.lifecycleStatus}} - } @else if(bun.lifecycleStatus == 'Retired') { - {{bun.lifecycleStatus}} - } @else if(bun.lifecycleStatus == 'Obsolete') { - {{bun.lifecycleStatus}} - } -
-
- } + {{bun.name}} +
+ @if(bun.lifecycleStatus == 'Active'){ + {{bun.lifecycleStatus}} + } @else if(bun.lifecycleStatus == 'Launched') { + {{bun.lifecycleStatus}} + } @else if(bun.lifecycleStatus == 'Retired') { + {{bun.lifecycleStatus}} + } @else if(bun.lifecycleStatus == 'Obsolete') { + {{bun.lifecycleStatus}} + } +
+
+ } - @if(prodChars.length>0){ - -
- - - - - - - - - - @for (prod of productSpecToCreate?.productSpecCharacteristic; track prod;) { - - + + + } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._values' | translate }} -
{{ 'CREATE_PROD_SPEC._chars' | translate }} +
+ + + + + + + + + + @for (prod of productSpecToCreate?.productSpecCharacteristic; track prod;) { + + - - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._values' | translate }} +
- {{prod.name}} - - @for (char of prod.productSpecCharacteristicValue; track char; let last = $last) { - @if(char.value || char.value == 0){ - @if(isJsonCharacteristicType(prod.valueType)){ - @if(!last){ - {{getValuePreview(char.value)}}, - } @else { - {{getValuePreview(char.value)}} - } - } @else if(char.unitOfMeasure){ - @if(!last){ - {{char.value}} ({{char?.unitOfMeasure}}), - } @else { - {{char.value}} ({{char?.unitOfMeasure}}) - } - } @else { - @if(!last){ - {{char.value}}, - } @else { - {{char.value}} - } - } - } @else { - @if(!last){ - {{char.valueFrom}} - {{char.valueTo}} ({{char?.unitOfMeasure}}), - } @else { - {{char.valueFrom}} - {{char.valueTo}} ({{char?.unitOfMeasure}}) - } - } - } -
-
- } + {{prod.description}} +
+ @for (char of prod.productSpecCharacteristicValue; track char; let last = $last) { + @if(char.value || char.value == 0){ + @if(isJsonCharacteristicType(prod.valueType)){ + @if(!last){ + {{getValuePreview(char.value)}}, + } @else { + {{getValuePreview(char.value)}} + } + } @else if(char.unitOfMeasure){ + @if(!last){ + {{char.value}} ({{char?.unitOfMeasure}}), + } @else { + {{char.value}} ({{char?.unitOfMeasure}}) + } + } @else { + @if(!last){ + {{char.value}}, + } @else { + {{char.value}} + } + } + } @else { + @if(!last){ + {{char.valueFrom}} - {{char.valueTo}} ({{char?.unitOfMeasure}}), + } @else { + {{char.valueFrom}} - {{char.valueTo}} ({{char?.unitOfMeasure}}) + } + } + } +
+
+ } - @if(selectedResourceSpecs.length>0){ - -
- - - - - - - - @for (res of selectedResourceSpecs; track res) { - - + + } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} -
{{ 'CREATE_PROD_SPEC._resource' | translate }} +
+ + + + + + + + @for (res of selectedResourceSpecs; track res) { + + - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} +
- {{res.name}} -
-
- } + {{res.name}} +
+
+ } - @if(selectedServiceSpecs.length>0){ - -
- - - - - - - - @for (serv of selectedServiceSpecs; track serv) { - - + + } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} -
{{ 'CREATE_PROD_SPEC._service' | translate }} +
+ + + + + + + + @for (serv of selectedServiceSpecs; track serv) { + + - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} +
- {{serv.name}} -
-
- } + {{serv.name}} +
+
+ } - @if(prodAttachments.length>0){ - -
- - - - - - - - - @for (att of productSpecToCreate?.attachment; track att) { - - + + + } + +
- {{ 'CREATE_PROD_SPEC._product_name' | translate }} - - {{ 'CREATE_PROD_SPEC._value' | translate }} -
{{ 'CREATE_PROD_SPEC._attachments' | translate }} +
+ + + + + + + + + @for (att of productSpecToCreate?.attachment; track att) { + + - - - } - -
+ {{ 'CREATE_PROD_SPEC._product_name' | translate }} + + {{ 'CREATE_PROD_SPEC._value' | translate }} +
- {{att.name}} - - {{att.url}} -
-
- } + {{att.name}} +
+ {{att.url}} +
+
+ } - @if(prodRelationships.length>0){ - -
- - - - - - - - - - - @for (rel of prodRelationships; track rel) { - - -
- {{ 'CREATE_PROD_SPEC._relationship_type' | translate }} - - {{ 'CREATE_PROD_SPEC._product_name' | translate }} -
- {{rel.relationshipType}} - {{ 'CREATE_PROD_SPEC._relationships' | translate }} +
+ + + + + + + + + + + @for (rel of prodRelationships; track rel) { + + + - - - - } - -
+ {{ 'CREATE_PROD_SPEC._relationship_type' | translate }} + + {{ 'CREATE_PROD_SPEC._product_name' | translate }} +
+ {{rel.relationshipType}} + - {{rel.productSpec.name}} -
-
- } - } - - - } + + } - - -
- + class="px-4 py-2 flex items-center justify-center text-base font-medium rounded-lg bg-white border border-gray-300 dark:bg-gray-800 dark:border-gray-700 "> + + + + {{ 'CREATE_OFFER._previous' | translate }} + - - @if (isCurrentStep('summary')) { - - } -
- + class="px-4 py-2 flex items-center justify-center text-base font-medium rounded-lg text-gray-500 bg-white border border-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400"> + {{ 'CREATE_OFFER._next_step' | translate }} + + + + + @if (currentStep.id === 'summary') { + + } + + @if (showUploadFile){ -