From 3601e2b422deb094d3866e71e496d7538918fe76 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Thu, 14 Mar 2024 16:05:58 +0100 Subject: [PATCH 01/21] chore(parts): 630 create extended part detailed View --- .../step_definitions/admin-contracts.ts | 10 +- .../user-navigation/user-menu.component.scss | 1 + .../admin/presentation/admin.component.scss | 2 +- .../parts/detail/parts-detail.component.html | 282 ++++++++++++++++++ .../parts/detail/parts-detail.component.scss | 234 +++++++++++++++ .../detail/parts-detail.component.spec.ts | 21 ++ .../parts/detail/parts-detail.component.ts | 158 ++++++++++ .../page/parts/detail/parts-detail.module.ts | 22 ++ .../app/modules/page/parts/parts.module.ts | 34 ++- .../app/modules/page/parts/parts.routing.ts | 18 +- .../parts/presentation/parts.component.ts | 55 ++-- .../part-details/core/partDetails.facade.ts | 22 +- 12 files changed, 801 insertions(+), 58 deletions(-) create mode 100644 frontend/src/app/modules/page/parts/detail/parts-detail.component.html create mode 100644 frontend/src/app/modules/page/parts/detail/parts-detail.component.scss create mode 100644 frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts create mode 100644 frontend/src/app/modules/page/parts/detail/parts-detail.component.ts create mode 100644 frontend/src/app/modules/page/parts/detail/parts-detail.module.ts diff --git a/frontend/cypress/support/step_definitions/admin-contracts.ts b/frontend/cypress/support/step_definitions/admin-contracts.ts index b403bc36d3..e85d3175b0 100644 --- a/frontend/cypress/support/step_definitions/admin-contracts.ts +++ b/frontend/cypress/support/step_definitions/admin-contracts.ts @@ -1,5 +1,5 @@ -import { Then, When } from '@badeball/cypress-cucumber-preprocessor'; -import { AdminPage, AdminViewTab } from '../../integration/pages/AdminPage'; +import {Then, When} from '@badeball/cypress-cucumber-preprocessor'; +import {AdminPage, AdminViewTab} from '../../integration/pages/AdminPage'; let currentContractId: string; @@ -8,13 +8,13 @@ When('navigate to administration view tab {string}', (tabName: AdminViewTab) => const header = AdminPage.getHeaderOfTabView(tabName); switch (tabName) { case AdminViewTab.BPN_CONFIGURATION_VIEW: - header.contains('BPN - EDC Konfiguration').should('be.visible'); + header.contains('BPN - EDC Konfiguration').should('be.visible') || header.contains('BPN - EDC configuration').should('be.visible'); break; case AdminViewTab.IMPORT_VIEW: - header.contains('Trace-X Datenimport').should('be.visible'); + header.contains('Trace-X Datenimport').should('be.visible') || header.contains('Trace-X Data import').should('be.visible'); break; case AdminViewTab.CONTRACT_VIEW: - header.contains('Verträge').should('be.visible'); + header.contains('Verträge').should('be.visible') || header.contains('Contracts').should('be.visible'); break; default: { throw new Error(`The View Tab header ${ tabName } did not load or is not existing`); diff --git a/frontend/src/app/modules/core/layout/header/user-navigation/user-menu.component.scss b/frontend/src/app/modules/core/layout/header/user-navigation/user-menu.component.scss index c204e04260..52d3cb878e 100644 --- a/frontend/src/app/modules/core/layout/header/user-navigation/user-menu.component.scss +++ b/frontend/src/app/modules/core/layout/header/user-navigation/user-menu.component.scss @@ -49,6 +49,7 @@ right: -5px; transition: opacity; border-radius: 16px; + z-index: 101; } .user-menu-items { diff --git a/frontend/src/app/modules/page/admin/presentation/admin.component.scss b/frontend/src/app/modules/page/admin/presentation/admin.component.scss index 0b2aeaa5d7..ad692e9da4 100644 --- a/frontend/src/app/modules/page/admin/presentation/admin.component.scss +++ b/frontend/src/app/modules/page/admin/presentation/admin.component.scss @@ -26,7 +26,7 @@ position: relative; transition: all 0.5s; max-height: 80vh; - overflow: hidden; + overflow: auto; } .sidenav--expanded { diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html new file mode 100644 index 0000000000..97e6bf3767 --- /dev/null +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -0,0 +1,282 @@ + +
+
+ +
+ arrow_back + {{ 'actions.goBack' | i18n }} +
+
+
+
+ +
+
+
+
+ +
+
+ + +
+ +
+ + +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ + +
+ + + + {{ 'partDetail.investigation.tab.header' | i18n }} + + {{ 'partDetail.investigation.tab.description' | i18n }} + announcement + + + + + + + + + + + + + + + + + {{ 'partDetail.tractionBatteryCodeTitle' | i18n }} + + + + + + + {{ 'partDetail.subcomponents' | i18n }} + + + +
+ + + + + + + + + + + + + + + + +
{{'partDetail.position' | i18n}} 000{{i + 1}} {{'partDetail.productType' | i18n }}{{subcomponent.productType}} {{'partDetail.tractionBatteryCode' | i18n }} {{subcomponent.tractionBatteryCode}}
+
+
+ +
+
+
+ +
+
+
+ + + + + + {{ 'partDetail.relations' | i18n }} + + + + + open_in_new + + + + + + + + + + + {{ 'dataLoading.error' | i18n }} + + {{ customerOrPartSiteDetails.error.message }} + + + + + + + {{ 'dataLoading.inProgress' | i18n }} + + + + + + + + + + + + {{ 'partDetail.overview' | i18n }} + + + + + + + + + +
+

{{ 'partDetail.' + item.key | i18n }}

+

{{ item.value | autoFormat }} + +

+
+
+ + +
+

{{ 'partDetail.' + item.key | i18n }}

+

{{ item.value | autoFormat }}

+
+
+ + +
+ +

{{ 'partDetail.' + item.key | i18n }}

+
+ + edit +
+
+ +
+ + close + +
+
+
+ + + + + + + + diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss b/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss new file mode 100644 index 0000000000..0b40ac3fca --- /dev/null +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss @@ -0,0 +1,234 @@ +/******************************************************************************** + * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2022, 2023 ZF Friedrichshafen AG + * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +mat-card { + height: 100% !important; +} + +.part-detail { + + &--spinner { + display: flex; + justify-content: center; + align-items: center; + } + + &--container { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 25px 25px; + margin-bottom: 25px; + } + + @media(max-width: 1023px) { + &--container { + grid-template-columns: repeat(1, 1fr); + } + } + + &--container > div { + min-height: 200px; + max-height: 600px; + } + + &--open { + pointer-events: unset; + } + + &--sidenav { + @apply flex bg-dustyGrayShadeWildSand; + align-items: center; + justify-content: center; + min-width: 900px; + padding: 25px; + } + + &--relation__icon { + &:hover { + cursor: pointer; + } + } + + + &-subcomponents-table-container { + flex: 1; + height: 350px; + overflow: auto; + } +} + +.part-detail--tractionBattery-content { + display: flex; + flex-wrap: wrap; + gap: 25px; +} + +.part-detail--tractionBattery-details { + flex: 1; +} + +.overview { + grid-column: span 2; + grid-row: span 1; +} + +.relation { + grid-column: span 2; + grid-row: span 2; + mat-card-content { + height: 90%; + } +} + +.import-state { + grid-column: span 2; + grid-row: span 1; +} + +.manufacturer { + grid-column: span 2; + grid-row: span 1; +} + +.specific { + grid-column: span 2; + grid-row: span 1; +} + +.tractionbattery { + grid-column: span 4; + grid-row: span 1; +} + +@media(max-width: 1023px) { + .overview { + grid-column: span 1; + grid-row: span 1; + } + + .relation { + grid-column: span 1; + grid-row: span 1; + } + + .import-state { + grid-column: span 1; + grid-row: span 1; + } + + .manufacturer { + grid-column: span 1; + grid-row: span 1; + } + + .specific { + grid-column: span 1; + grid-row: span 1; + } + + .tractionbattery { + grid-column: span 1; + grid-row: span 3; + } +} + +.card-list { + &--icon { + display: flex; + + &:hover { + @apply text-primary; + cursor: pointer; + } + } + + &--row { + display: grid; + grid-template-columns: 35% 65%; + grid-template-rows: minmax(23px, auto); + align-items: center; + + > span { + line-height: 1.25rem; + } + + @media (max-width: 1440px) { + grid-template-columns: 40% 60%; + } + + @media (max-width: 1280px) { + grid-template-columns: 50% 50%; + } + + @media (max-width: 1024px) { + grid-template-columns: 30% 70%; + } + + &--textField { + display: grid; + grid-template-columns: 35% 65%; + grid-template-rows: minmax(23px, auto); + align-items: start; + margin-top: 4px; + margin-bottom: 10px; + } + } + + &--key { + padding-right: 10px; + font-weight: bold; + } + + &--value { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin-left: 15px; + + &--textField { + max-height: 100px; + overflow-y: auto; + white-space: wrap; + text-overflow: unset; + } + } + + &--qualityType { + grid-area: c; + display: flex; + align-items: center; + width: 100%; + + & > app-select { + width: 90%; + } + + & > mat-icon { + width: 10%; + text-align: center; + cursor: pointer; + + &:hover { + @apply text-primary; + } + } + } +} diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts new file mode 100644 index 0000000000..b80f3ffa0b --- /dev/null +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts @@ -0,0 +1,21 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {PartsDetailComponent} from './parts-detail.component'; + +describe('PartsDetailComponent', () => { + let component: PartsDetailComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [PartsDetailComponent] + }); + fixture = TestBed.createComponent(PartsDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts new file mode 100644 index 0000000000..e52dd32a6c --- /dev/null +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -0,0 +1,158 @@ +import {Component, Input} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import {ActivatedRoute, Router} from '@angular/router'; +import {RoleService} from '@core/user/role.service'; +import {TractionBatteryCode} from '@page/parts/model/aspectModels.model'; +import {Owner} from '@page/parts/model/owner.enum'; + +import {Part, QualityType} from '@page/parts/model/parts.model'; +import {PartsAssembler} from '@shared/assembler/parts.assembler'; +import {SelectOption} from '@shared/components/select/select.component'; +import {State} from '@shared/model/state'; +import {View} from '@shared/model/view.model'; +import {NotificationAction} from '@shared/modules/notification/notification-action.enum'; + +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {Observable, Subscription} from 'rxjs'; +import {filter, tap} from 'rxjs/operators'; +import {Location} from "@angular/common"; + +@Component({ + selector: 'app-parts-detail', + templateUrl: './parts-detail.component.html', + styleUrls: ['./parts-detail.component.scss'] +}) +export class PartsDetailComponent { + @Input() showRelation = true; + @Input() showStartInvestigation = true; + + public shortenPartDetails$: Observable>; + public readonly selectedPartDetails$: Observable>; + public readonly manufacturerDetails$: Observable>; + public readonly customerOrPartSiteDetails$: Observable>; + public readonly tractionBatteryDetails$: Observable>; + public readonly importStateDetails$: Observable>; + public readonly tractionBatterySubcomponents$: Observable>; + + public readonly displayedColumns: string[]; + + public isAsPlannedPart: boolean = false; + + public customerOrPartSiteDetailsHeader$: Subscription; + public customerOrPartSiteHeader: string; + + public showQualityTypeDropdown = false; + public qualityTypeOptions: SelectOption[]; + + public qualityTypeControl = new FormControl(null); + public readonly isOpen$: Observable; + + private readonly isOpenState: State = new State(false); + + public authorizationTooltipMessage: string; + public currentPartId: string; + public pageIndexHistory: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string} + + constructor(private readonly partDetailsFacade: PartDetailsFacade, private readonly router: Router, private readonly route: ActivatedRoute, public roleService: RoleService, private location: Location) { + this.isOpen$ = this.isOpenState.observable; + this.setIsOpen(true); + + this.currentPartId = this.route.snapshot.params['partId']; + this.partDetailsFacade.setPartById(this.currentPartId); + this.selectedPartDetails$ = this.partDetailsFacade.selectedPart$; + this.shortenPartDetails$ = this.partDetailsFacade.selectedPart$; + + this.shortenPartDetails$ = this.partDetailsFacade.selectedPart$.pipe( + PartsAssembler.mapPartForView(), + tap(({ data }) => { + this.qualityTypeControl.patchValue(data?.qualityType, { emitEvent: false, onlySelf: true }) + }), + ); + + this.manufacturerDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForManufacturerView()); + this.customerOrPartSiteDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForCustomerOrPartSiteView()); + this.tractionBatteryDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForTractionBatteryCodeDetailsView()); + this.tractionBatterySubcomponents$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForTractionBatteryCodeSubComponentsView()) as unknown as Observable>; + + this.importStateDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForAssetStateDetailsView()); + + this.customerOrPartSiteDetailsHeader$ = this.customerOrPartSiteDetails$?.subscribe(data => { + if (data?.data?.functionValidFrom) { + this.customerOrPartSiteHeader = 'partDetail.partSiteInformationData'; + } else { + this.customerOrPartSiteHeader = 'partDetail.customerData'; + } + }); + + this.qualityTypeOptions = Object.values(QualityType).map(value => ({ + label: value, + value: value, + })); + + this.selectedPartDetails$.subscribe(part => { + + if(part?.data?.semanticDataModel) { + this.isAsPlannedPart = part.data.semanticDataModel.toString() === 'PartAsPlanned'; + } + + if(part?.data?.children?.length > 0 ) { + this.authorizationTooltipMessage = this.getRestrictionMessageKey(true); + } else { + this.authorizationTooltipMessage = this.getRestrictionMessageKey(false); + } + }); + + this.displayedColumns = [ 'position', 'productType', 'tractionBatteryCode' ]; + } + + public ngOnInit(): void { + this.route.queryParams.subscribe((params: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string}) => { + this.pageIndexHistory = params; + console.log(this.pageIndexHistory) + }) + } + + public ngOnDestroy(): void { + this.partDetailsFacade.selectedPart = null; + } + + public ngAfterViewInit(): void { + this.partDetailsFacade.selectedPart$.pipe(filter(({ data }) => !!data)).subscribe(_ => this.setIsOpen(true)); + } + + public setIsOpen(openState: boolean) { + this.isOpenState.update(openState); + + if (!openState) { + this.partDetailsFacade.selectedPart = null; + } + } + + public openRelationPage(part: Part): void { + this.partDetailsFacade.selectedPart = null; + this.router.navigate([ `parts/relations/${ part.id }` ]).then(_ => window.location.reload()); + } + + getRestrictionMessageKey(hasChildren: boolean): string { + if(this.isAsPlannedPart) { + return 'routing.notAllowedForAsPlanned'; + } + else if(!hasChildren) { + return 'routing.noChildPartsForInvestigation'; + } + else if(this.roleService.isAdmin()) { + return 'routing.unauthorized'; + } else { + return null; + } + + } + + protected readonly NotificationAction = NotificationAction; + protected readonly Owner = Owner; + + navigateToPartsView() { + const navigationExtras = this.pageIndexHistory ? {queryParams: this.pageIndexHistory} : null + this.router.navigate(['parts'], navigationExtras); + } +} diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts new file mode 100644 index 0000000000..2fdf40c61b --- /dev/null +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts @@ -0,0 +1,22 @@ +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {getI18nPageProvider} from '@core/i18n'; +import {PartsDetailComponent} from '@page/parts/detail/parts-detail.component'; +import {LoadedElementsFacade} from '@shared/modules/relations/core/loaded-elements.facade'; +import {LoadedElementsState} from '@shared/modules/relations/core/loaded-elements.state'; +import {RelationsModule} from '@shared/modules/relations/relations.module'; +import {SharedModule} from '@shared/shared.module'; +import {TemplateModule} from '@shared/template.module'; + +@NgModule({ + declarations: [ PartsDetailComponent ], + imports: [ CommonModule, TemplateModule, SharedModule, RelationsModule], + providers: [ + LoadedElementsFacade, + LoadedElementsState, + ...getI18nPageProvider([ 'page.parts', 'partDetail' ]), + ], + exports: [ PartsDetailComponent ], +}) +export class PartsDetailModule { +} diff --git a/frontend/src/app/modules/page/parts/parts.module.ts b/frontend/src/app/modules/page/parts/parts.module.ts index a25936aa7b..fc62f7a637 100644 --- a/frontend/src/app/modules/page/parts/parts.module.ts +++ b/frontend/src/app/modules/page/parts/parts.module.ts @@ -19,22 +19,24 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { MatDialogModule } from '@angular/material/dialog'; -import { getI18nPageProvider } from '@core/i18n'; -import { PartsFacade } from '@page/parts/core/parts.facade'; -import { PartsState } from '@page/parts/core/parts.state'; -import { PartDetailsModule } from '@shared/modules/part-details/partDetails.module'; -import { RelationsModule } from '@shared/modules/relations/relations.module'; -import { FormatPartSemanticDataModelToCamelCasePipe } from '@shared/pipes/format-part-semantic-data-model-to-camelcase.pipe'; -import { BomLifecycleSettingsService } from '@shared/service/bom-lifecycle-settings.service'; -import { SharedModule } from '@shared/shared.module'; -import { TemplateModule } from '@shared/template.module'; -import { AngularSplitModule } from 'angular-split'; -import { PartsRoutingModule } from './parts.routing'; -import { PartsComponent } from './presentation/parts.component'; -import { RelationComponent } from './presentation/relation/relation.component'; +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {MatDialogModule} from '@angular/material/dialog'; +import {getI18nPageProvider} from '@core/i18n'; +import {PartsFacade} from '@page/parts/core/parts.facade'; +import {PartsState} from '@page/parts/core/parts.state'; +import {PartDetailsModule} from '@shared/modules/part-details/partDetails.module'; +import {RelationsModule} from '@shared/modules/relations/relations.module'; +import { + FormatPartSemanticDataModelToCamelCasePipe +} from '@shared/pipes/format-part-semantic-data-model-to-camelcase.pipe'; +import {BomLifecycleSettingsService} from '@shared/service/bom-lifecycle-settings.service'; +import {SharedModule} from '@shared/shared.module'; +import {TemplateModule} from '@shared/template.module'; +import {AngularSplitModule} from 'angular-split'; +import {PartsRoutingModule} from './parts.routing'; +import {PartsComponent} from './presentation/parts.component'; +import {RelationComponent} from './presentation/relation/relation.component'; @NgModule({ declarations: [ PartsComponent, RelationComponent ], diff --git a/frontend/src/app/modules/page/parts/parts.routing.ts b/frontend/src/app/modules/page/parts/parts.routing.ts index 2e52d74a0a..1e859e61ea 100644 --- a/frontend/src/app/modules/page/parts/parts.routing.ts +++ b/frontend/src/app/modules/page/parts/parts.routing.ts @@ -19,11 +19,12 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { RelationComponent } from '@page/parts/presentation/relation/relation.component'; -import { I18NEXT_NAMESPACE_RESOLVER } from 'angular-i18next'; -import { PartsComponent } from './presentation/parts.component'; +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {PartsDetailComponent} from '@page/parts/detail/parts-detail.component'; +import {RelationComponent} from '@page/parts/presentation/relation/relation.component'; +import {I18NEXT_NAMESPACE_RESOLVER} from 'angular-i18next'; +import {PartsComponent} from './presentation/parts.component'; export /** @type {*} */ const PARTS_ROUTING: Routes = [ @@ -48,6 +49,13 @@ const PARTS_ROUTING: Routes = [ data: { i18nextNamespaces: [ 'page.parts', 'partDetail' ] }, resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, }, + { + path: ':partId', + pathMatch: 'full', + component: PartsDetailComponent, + data: { i18nextNamespaces: [ 'page.parts', 'partDetail' ] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + }, ]; @NgModule({ diff --git a/frontend/src/app/modules/page/parts/presentation/parts.component.ts b/frontend/src/app/modules/page/parts/presentation/parts.component.ts index 47bbdeb625..33ce1694d4 100644 --- a/frontend/src/app/modules/page/parts/presentation/parts.component.ts +++ b/frontend/src/app/modules/page/parts/presentation/parts.component.ts @@ -19,28 +19,29 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; -import { Pagination } from '@core/model/pagination.model'; -import { RoleService } from '@core/user/role.service'; -import { PartsFacade } from '@page/parts/core/parts.facade'; -import { resetMultiSelectionAutoCompleteComponent } from '@page/parts/core/parts.helper'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; -import { BomLifecycleSize } from '@shared/components/bom-lifecycle-activator/bom-lifecycle-activator.model'; -import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; -import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component'; -import { TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; -import { ToastService } from '@shared/components/toasts/toast.service'; -import { toAssetFilter, toGlobalSearchAssetFilter } from '@shared/helper/filter-helper'; -import { setMultiSorting } from '@shared/helper/table-helper'; -import { NotificationType } from '@shared/model/notification.model'; -import { View } from '@shared/model/view.model'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { BomLifecycleSettingsService, UserSettingView } from '@shared/service/bom-lifecycle-settings.service'; -import { StaticIdService } from '@shared/service/staticId.service'; -import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; -import { map } from 'rxjs/operators'; +import {AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChildren} from '@angular/core'; +import {FormControl, FormGroup} from '@angular/forms'; +import {Router} from '@angular/router'; +import {Pagination} from '@core/model/pagination.model'; +import {RoleService} from '@core/user/role.service'; +import {PartsFacade} from '@page/parts/core/parts.facade'; +import {resetMultiSelectionAutoCompleteComponent} from '@page/parts/core/parts.helper'; +import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; +import {AssetAsBuiltFilter, AssetAsPlannedFilter, Part} from '@page/parts/model/parts.model'; +import {BomLifecycleSize} from '@shared/components/bom-lifecycle-activator/bom-lifecycle-activator.model'; +import {TableType} from '@shared/components/multi-select-autocomplete/table-type.model'; +import {PartsTableComponent} from '@shared/components/parts-table/parts-table.component'; +import {TableEventConfig, TableHeaderSort} from '@shared/components/table/table.model'; +import {ToastService} from '@shared/components/toasts/toast.service'; +import {toAssetFilter, toGlobalSearchAssetFilter} from '@shared/helper/filter-helper'; +import {setMultiSorting} from '@shared/helper/table-helper'; +import {NotificationType} from '@shared/model/notification.model'; +import {View} from '@shared/model/view.model'; +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {BomLifecycleSettingsService, UserSettingView} from '@shared/service/bom-lifecycle-settings.service'; +import {StaticIdService} from '@shared/service/staticId.service'; +import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; +import {map} from 'rxjs/operators'; @Component({ @@ -82,7 +83,8 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { private readonly staticIdService: StaticIdService, private readonly userSettingService: BomLifecycleSettingsService, public toastService: ToastService, - public roleService: RoleService + public roleService: RoleService, + public router: Router ) { this.partsAsBuilt$ = this.partsFacade.partsAsBuilt$; this.partsAsPlanned$ = this.partsFacade.partsAsPlanned$; @@ -165,6 +167,13 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { public onSelectItem($event: Record): void { this.partDetailsFacade.selectedPart = $event as unknown as Part; + const currentUrlPath = this.router.routerState.snapshot.url; + let tableData = {}; + for(let component of this.partsTableComponents) { + tableData[component.tableType+"_PAGE"] = component.pageIndex; + } + this.router.navigate([`${currentUrlPath}/${$event?.id}`], {queryParams: tableData}) + //this.router.navigate([]) } public onAsBuiltTableConfigChange({page, pageSize, sorting}: TableEventConfig): void { diff --git a/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts b/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts index 54ac31bfe9..59cd8db57d 100644 --- a/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts +++ b/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts @@ -19,14 +19,14 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { Injectable } from '@angular/core'; -import { Part } from '@page/parts/model/parts.model'; -import { View } from '@shared/model/view.model'; -import { PartDetailsState } from '@shared/modules/part-details/core/partDetails.state'; -import { PartsService } from '@shared/service/parts.service'; -import { Observable, of } from 'rxjs'; -import { catchError, map, tap } from 'rxjs/operators'; -import { SortDirection } from '../../../../../mocks/services/pagination.helper'; +import {Injectable} from '@angular/core'; +import {Part} from '@page/parts/model/parts.model'; +import {View} from '@shared/model/view.model'; +import {PartDetailsState} from '@shared/modules/part-details/core/partDetails.state'; +import {PartsService} from '@shared/service/parts.service'; +import {Observable, of} from 'rxjs'; +import {catchError, map, tap} from 'rxjs/operators'; +import {SortDirection} from '../../../../../mocks/services/pagination.helper'; @Injectable() export class PartDetailsFacade { @@ -36,6 +36,12 @@ export class PartDetailsFacade { ) { } + public setPartById(urn: string) { + this.partsService.getPart(urn).subscribe(part => { + this.partDetailsState.selectedPart = { data: part} + }) + } + public get selectedPart$(): Observable> { return this.partDetailsState.selectedPart$; } From 279b78f6cc9c589edfd5218df850941e8e2c1aa9 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 15 Mar 2024 11:30:46 +0100 Subject: [PATCH 02/21] chore(parts): 630 create extended part detailed View --- frontend/src/app/modules/page/parts/parts.module.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/modules/page/parts/parts.module.ts b/frontend/src/app/modules/page/parts/parts.module.ts index fc62f7a637..501c8c89a5 100644 --- a/frontend/src/app/modules/page/parts/parts.module.ts +++ b/frontend/src/app/modules/page/parts/parts.module.ts @@ -37,10 +37,11 @@ import {AngularSplitModule} from 'angular-split'; import {PartsRoutingModule} from './parts.routing'; import {PartsComponent} from './presentation/parts.component'; import {RelationComponent} from './presentation/relation/relation.component'; +import {PartsDetailModule} from "@page/parts/detail/parts-detail.module"; @NgModule({ declarations: [ PartsComponent, RelationComponent ], - imports: [ CommonModule, TemplateModule, SharedModule, PartsRoutingModule, RelationsModule, PartDetailsModule, AngularSplitModule, MatDialogModule ], + imports: [ CommonModule, TemplateModule, SharedModule, PartsRoutingModule, RelationsModule, PartDetailsModule, AngularSplitModule, MatDialogModule, PartsDetailModule ], providers: [ PartsState, BomLifecycleSettingsService, PartsFacade, FormatPartSemanticDataModelToCamelCasePipe, ...getI18nPageProvider([ 'page.parts', 'partDetail' ]) ], }) export class PartsModule { From 80ef5f102e77d4f481fc9516686c8b31d7e57e20 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 15 Mar 2024 13:10:16 +0100 Subject: [PATCH 03/21] chore(parts): 630 moved startInvestigationComponent to new detailed view --- .../parts/detail/parts-detail.component.html | 4 +-- .../page/parts/detail/parts-detail.module.ts | 27 +++++++++++-------- .../part-details/partDetails.module.ts | 3 +-- .../presentation/part-detail.component.html | 5 ---- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html index 97e6bf3767..c9395236da 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -99,13 +99,13 @@ announcement - + diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts index 2fdf40c61b..192ba11bd6 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.module.ts @@ -1,17 +1,22 @@ -import {CommonModule} from '@angular/common'; -import {NgModule} from '@angular/core'; -import {getI18nPageProvider} from '@core/i18n'; -import {PartsDetailComponent} from '@page/parts/detail/parts-detail.component'; -import {LoadedElementsFacade} from '@shared/modules/relations/core/loaded-elements.facade'; -import {LoadedElementsState} from '@shared/modules/relations/core/loaded-elements.state'; -import {RelationsModule} from '@shared/modules/relations/relations.module'; -import {SharedModule} from '@shared/shared.module'; -import {TemplateModule} from '@shared/template.module'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { getI18nPageProvider } from '@core/i18n'; +import { PartsDetailComponent } from '@page/parts/detail/parts-detail.component'; +import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; +import { PartDetailsState } from '@shared/modules/part-details/core/partDetails.state'; +import { StartInvestigationComponent } from '@shared/modules/part-details/presentation/start-investigation/start-investigation.component'; +import { LoadedElementsFacade } from '@shared/modules/relations/core/loaded-elements.facade'; +import { LoadedElementsState } from '@shared/modules/relations/core/loaded-elements.state'; +import { RelationsModule } from '@shared/modules/relations/relations.module'; +import { SharedModule } from '@shared/shared.module'; +import { TemplateModule } from '@shared/template.module'; @NgModule({ - declarations: [ PartsDetailComponent ], - imports: [ CommonModule, TemplateModule, SharedModule, RelationsModule], + declarations: [ PartsDetailComponent, StartInvestigationComponent ], + imports: [ CommonModule, TemplateModule, SharedModule, RelationsModule, /* remove PartDetailsModule and put StartInvestigationComponent inside declarations when the old part details view gets removed*/], providers: [ + PartDetailsState, + PartDetailsFacade, LoadedElementsFacade, LoadedElementsState, ...getI18nPageProvider([ 'page.parts', 'partDetail' ]), diff --git a/frontend/src/app/modules/shared/modules/part-details/partDetails.module.ts b/frontend/src/app/modules/shared/modules/part-details/partDetails.module.ts index 03e882342f..49e1a77d41 100644 --- a/frontend/src/app/modules/shared/modules/part-details/partDetails.module.ts +++ b/frontend/src/app/modules/shared/modules/part-details/partDetails.module.ts @@ -30,10 +30,9 @@ import { RelationsModule } from '../relations/relations.module'; import { PartDetailsFacade } from './core/partDetails.facade'; import { PartDetailsState } from './core/partDetails.state'; import { PartDetailComponent } from './presentation/part-detail.component'; -import { StartInvestigationComponent } from './presentation/start-investigation/start-investigation.component'; @NgModule({ - declarations: [ PartDetailComponent, StartInvestigationComponent ], + declarations: [ PartDetailComponent ], imports: [ CommonModule, TemplateModule, SharedModule, RelationsModule ], providers: [ PartDetailsState, diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.html b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.html index 37cdfbc044..aabc6be7b3 100644 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.html +++ b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.html @@ -93,11 +93,6 @@ announcement - From 68ce0435633b044cf8d53867c23090d5e9c89193 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 15 Mar 2024 14:14:29 +0100 Subject: [PATCH 04/21] chore(parts): 630 implement table page by url params on parts table --- .../parts/presentation/parts.component.ts | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/frontend/src/app/modules/page/parts/presentation/parts.component.ts b/frontend/src/app/modules/page/parts/presentation/parts.component.ts index 33ce1694d4..6d29a6990a 100644 --- a/frontend/src/app/modules/page/parts/presentation/parts.component.ts +++ b/frontend/src/app/modules/page/parts/presentation/parts.component.ts @@ -19,29 +19,29 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import {AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChildren} from '@angular/core'; -import {FormControl, FormGroup} from '@angular/forms'; -import {Router} from '@angular/router'; -import {Pagination} from '@core/model/pagination.model'; -import {RoleService} from '@core/user/role.service'; -import {PartsFacade} from '@page/parts/core/parts.facade'; -import {resetMultiSelectionAutoCompleteComponent} from '@page/parts/core/parts.helper'; -import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; -import {AssetAsBuiltFilter, AssetAsPlannedFilter, Part} from '@page/parts/model/parts.model'; -import {BomLifecycleSize} from '@shared/components/bom-lifecycle-activator/bom-lifecycle-activator.model'; -import {TableType} from '@shared/components/multi-select-autocomplete/table-type.model'; -import {PartsTableComponent} from '@shared/components/parts-table/parts-table.component'; -import {TableEventConfig, TableHeaderSort} from '@shared/components/table/table.model'; -import {ToastService} from '@shared/components/toasts/toast.service'; -import {toAssetFilter, toGlobalSearchAssetFilter} from '@shared/helper/filter-helper'; -import {setMultiSorting} from '@shared/helper/table-helper'; -import {NotificationType} from '@shared/model/notification.model'; -import {View} from '@shared/model/view.model'; -import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; -import {BomLifecycleSettingsService, UserSettingView} from '@shared/service/bom-lifecycle-settings.service'; -import {StaticIdService} from '@shared/service/staticId.service'; -import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; -import {map} from 'rxjs/operators'; +import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { Pagination } from '@core/model/pagination.model'; +import { RoleService } from '@core/user/role.service'; +import { PartsFacade } from '@page/parts/core/parts.facade'; +import { resetMultiSelectionAutoCompleteComponent } from '@page/parts/core/parts.helper'; +import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; +import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; +import { BomLifecycleSize } from '@shared/components/bom-lifecycle-activator/bom-lifecycle-activator.model'; +import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; +import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component'; +import { TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; +import { ToastService } from '@shared/components/toasts/toast.service'; +import { toAssetFilter, toGlobalSearchAssetFilter } from '@shared/helper/filter-helper'; +import { setMultiSorting } from '@shared/helper/table-helper'; +import { NotificationType } from '@shared/model/notification.model'; +import { View } from '@shared/model/view.model'; +import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; +import { BomLifecycleSettingsService, UserSettingView } from '@shared/service/bom-lifecycle-settings.service'; +import { StaticIdService } from '@shared/service/staticId.service'; +import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; +import { map } from 'rxjs/operators'; @Component({ @@ -74,6 +74,7 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { public DEFAULT_PAGE_SIZE = 50; public ctrlKeyState = false; isPublisherOpen$ = new Subject(); + public currentPartTablePage = {AS_BUILT_OWN_PAGE: 0, AS_PLANNED_OWN_PAGE: 0} @ViewChildren(PartsTableComponent) partsTableComponents: QueryList; @@ -84,7 +85,8 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { private readonly userSettingService: BomLifecycleSettingsService, public toastService: ToastService, public roleService: RoleService, - public router: Router + public router: Router, + public route: ActivatedRoute ) { this.partsAsBuilt$ = this.partsFacade.partsAsBuilt$; this.partsAsPlanned$ = this.partsFacade.partsAsPlanned$; @@ -111,16 +113,17 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { this.partsFacade.setPartsAsPlanned(); this.searchFormGroup.addControl('partSearch', new FormControl([])); this.searchControl = this.searchFormGroup.get('partSearch') as unknown as FormControl; + this.route.queryParams.subscribe(params => this.setupPageByUrlParams(params)); } filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.partsFacade.setPartsAsBuilt(0, this.DEFAULT_PAGE_SIZE, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); + this.partsFacade.setPartsAsBuilt(this.currentPartTablePage['AS_BUILT_OWN_PAGE'], this.DEFAULT_PAGE_SIZE, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.partsFacade.setPartsAsPlanned(0, this.DEFAULT_PAGE_SIZE, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, false)); + this.partsFacade.setPartsAsPlanned(this.currentPartTablePage['AS_PLANNED_OWN_PAGE'], this.DEFAULT_PAGE_SIZE, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, false)); } } @@ -167,24 +170,22 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { public onSelectItem($event: Record): void { this.partDetailsFacade.selectedPart = $event as unknown as Part; - const currentUrlPath = this.router.routerState.snapshot.url; let tableData = {}; for(let component of this.partsTableComponents) { tableData[component.tableType+"_PAGE"] = component.pageIndex; } - this.router.navigate([`${currentUrlPath}/${$event?.id}`], {queryParams: tableData}) - //this.router.navigate([]) + this.router.navigate([`parts/${$event?.id}`], {queryParams: tableData}) } public onAsBuiltTableConfigChange({page, pageSize, sorting}: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_BUILT); - + this.currentPartTablePage['AS_BUILT_OWN_PAGE'] = page; let pageSizeValue = this.DEFAULT_PAGE_SIZE; if (pageSize !== 0) { pageSizeValue = pageSize; } - - if (this.assetAsBuiltFilter) { + //if any filter is applied + if (this.assetAsBuiltFilter && Object.keys(this.assetAsBuiltFilter).filter(key => this.assetAsBuiltFilter[key].length).length) { this.partsFacade.setPartsAsBuilt(0, pageSizeValue, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.partsFacade.setPartsAsBuilt(page, pageSizeValue, this.tableAsBuiltSortList); @@ -194,13 +195,14 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { public onAsPlannedTableConfigChange({page, pageSize, sorting}: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); + this.currentPartTablePage['AS_PLANNED_OWN_PAGE'] = page; let pageSizeValue = this.DEFAULT_PAGE_SIZE; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter) { + if (this.assetsAsPlannedFilter && Object.keys(this.assetsAsPlannedFilter).filter(key => this.assetsAsPlannedFilter[key].length).length) { this.partsFacade.setPartsAsPlanned(0, pageSizeValue, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.partsFacade.setPartsAsPlanned(page, pageSizeValue, this.tableAsPlannedSortList); @@ -258,6 +260,14 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { } } + private setupPageByUrlParams(params: Params ) { + if(!params) { + return; + } + this.onAsBuiltTableConfigChange({page: params['AS_BUILT_OWN_PAGE'], pageSize: 50, sorting: ["",null]}); + this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_OWN_PAGE'], pageSize: 50, sorting: ["", null]}); + } + protected readonly UserSettingView = UserSettingView; protected readonly TableType = TableType; protected readonly MainAspectType = MainAspectType; From f4cb552631ec4cad44504e674e004a1c15b4e365 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 15 Mar 2024 16:25:39 +0100 Subject: [PATCH 05/21] chore(parts): 630 implement table page by url params on other parts table --- .../page/other-parts/other-parts.routing.ts | 8 ++++ .../customer-parts.component.ts | 35 ++++++++++++++--- .../supplier-parts.component.ts | 35 +++++++++++++---- .../parts/detail/parts-detail.component.ts | 39 ++++++++++--------- .../parts/presentation/parts.component.ts | 4 +- 5 files changed, 87 insertions(+), 34 deletions(-) diff --git a/frontend/src/app/modules/page/other-parts/other-parts.routing.ts b/frontend/src/app/modules/page/other-parts/other-parts.routing.ts index 018739d49c..64d7e470f2 100644 --- a/frontend/src/app/modules/page/other-parts/other-parts.routing.ts +++ b/frontend/src/app/modules/page/other-parts/other-parts.routing.ts @@ -22,6 +22,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { OtherPartsComponent } from '@page/other-parts/presentation/other-parts.component'; +import { PartsDetailComponent } from '@page/parts/detail/parts-detail.component'; import { I18NEXT_NAMESPACE_RESOLVER } from 'angular-i18next'; export /** @type {*} */ @@ -33,6 +34,13 @@ const PARTS_ROUTING: Routes = [ data: { i18nextNamespaces: [ 'page.otherParts', 'partDetail' ] }, resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, }, + { + path: ':partId', + pathMatch: 'full', + component: PartsDetailComponent, + data: { i18nextNamespaces: [ 'page.otherParts', 'partDetail' ] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + }, ]; @NgModule({ diff --git a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts index b5a7b59405..6cc645033f 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts @@ -19,6 +19,7 @@ import { Component, Input, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { Pagination } from '@core/model/pagination.model'; import { OtherPartsFacade } from '@page/other-parts/core/other-parts.facade'; import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; @@ -47,8 +48,8 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { public readonly customerTabLabelId = this.staticIdService.generateId('OtherParts.customerTabLabel'); - public tableCustomerAsBuiltSortList: TableHeaderSort[]; - public tableCustomerAsPlannedSortList: TableHeaderSort[]; + public tableCustomerAsBuiltSortList: TableHeaderSort[] = []; + public tableCustomerAsPlannedSortList: TableHeaderSort[] = []; private ctrlKeyState = false; @@ -58,10 +59,15 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { assetAsBuiltFilter: AssetAsBuiltFilter; assetsAsPlannedFilter: AssetAsPlannedFilter; + public currentPartTablePage = {AS_BUILT_CUSTOMER_PAGE: 0, AS_PLANNED_CUSTOMER_PAGE: 0} + + constructor( private readonly otherPartsFacade: OtherPartsFacade, private readonly partDetailsFacade: PartDetailsFacade, private readonly staticIdService: StaticIdService, + private readonly router: Router, + private readonly route: ActivatedRoute ) { window.addEventListener('keydown', (event) => { @@ -73,6 +79,8 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { } public ngOnInit(): void { + this.route.queryParams.subscribe(params => this.setupPageByUrlParams(params)); + if (this.bomLifecycle === MainAspectType.AS_BUILT) { this.customerPartsAsBuilt$ = this.otherPartsFacade.customerPartsAsBuilt$; this.tableCustomerAsBuiltSortList = []; @@ -97,10 +105,10 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.otherPartsFacade.setCustomerPartsAsBuilt(0, 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); + this.otherPartsFacade.setCustomerPartsAsBuilt(this.currentPartTablePage['AS_BUILT_CUSTOMER_PAGE'], 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.otherPartsFacade.setCustomerPartsAsPlanned(0, 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); + this.otherPartsFacade.setCustomerPartsAsPlanned(this.currentPartTablePage['AS_PLANNED_CUSTOMER_PAGE'], 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); } } @@ -110,17 +118,23 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { public onSelectItem(event: Record): void { this.partDetailsFacade.selectedPart = event as unknown as Part; + let tableData = {}; + for(let component of this.partsTableComponents) { + tableData[component.tableType+"_PAGE"] = component.pageIndex; + } + this.router.navigate([`otherParts/${event?.id}`], {queryParams: tableData}) } public onAsBuiltTableConfigChange({ page, pageSize, sorting }: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_BUILT); + this.currentPartTablePage['AS_BUILT_CUSTOMER_PAGE'] = page; let pageSizeValue = 50; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetAsBuiltFilter) { + if (this.assetAsBuiltFilter && Object.keys(this.assetAsBuiltFilter).filter(key => this.assetAsBuiltFilter[key].length).length) { this.otherPartsFacade.setCustomerPartsAsBuilt(0, pageSizeValue, this.tableCustomerAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.otherPartsFacade.setCustomerPartsAsBuilt(page, pageSizeValue, this.tableCustomerAsBuiltSortList); @@ -129,13 +143,14 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { public onAsPlannedTableConfigChange({ page, pageSize, sorting }: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); + this.currentPartTablePage['AS_PLANNED_CUSTOMER_PAGE'] = page; let pageSizeValue = 50; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter) { + if (this.assetsAsPlannedFilter && Object.keys(this.assetsAsPlannedFilter).filter(key => this.assetsAsPlannedFilter[key].length).length) { this.otherPartsFacade.setCustomerPartsAsPlanned(0, pageSizeValue, this.tableCustomerAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.otherPartsFacade.setCustomerPartsAsPlanned(page, pageSizeValue, this.tableCustomerAsPlannedSortList); @@ -147,6 +162,14 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { TableSortingUtil.setTableSortingList(sorting, tableSortList, this.ctrlKeyState); } + private setupPageByUrlParams(params: Params ) { + if(!params) { + return; + } + this.onAsBuiltTableConfigChange({page: params['AS_BUILT_CUSTOMER_PAGE'], pageSize: 50, sorting: null}); + this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_CUSTOMER_PAGE'], pageSize: 50, sorting: null}); + } + protected readonly MainAspectType = MainAspectType; protected readonly TableType = TableType; } diff --git a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts index bfaa358b3a..23c81f53ba 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts @@ -19,6 +19,7 @@ import { Component, Input, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { Pagination } from '@core/model/pagination.model'; import { OtherPartsFacade } from '@page/other-parts/core/other-parts.facade'; import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; @@ -53,8 +54,8 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { public readonly supplierTabLabelId = this.staticIdService.generateId('OtherParts.supplierTabLabel'); - public tableSupplierAsBuiltSortList: TableHeaderSort[]; - public tableSupplierAsPlannedSortList: TableHeaderSort[]; + public tableSupplierAsBuiltSortList: TableHeaderSort[] = []; + public tableSupplierAsPlannedSortList: TableHeaderSort[] = []; private ctrlKeyState = false; @@ -66,10 +67,14 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { assetAsBuiltFilter: AssetAsBuiltFilter; assetsAsPlannedFilter: AssetAsPlannedFilter; + public currentPartTablePage = {AS_BUILT_SUPPLIER_PAGE: 0, AS_PLANNED_SUPPLIER_PAGE: 0} + constructor( private readonly otherPartsFacade: OtherPartsFacade, private readonly partDetailsFacade: PartDetailsFacade, private readonly staticIdService: StaticIdService, + private readonly router: Router, + private readonly route: ActivatedRoute ) { window.addEventListener('keydown', (event) => { @@ -81,6 +86,8 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { } public ngOnInit(): void { + this.route.queryParams.subscribe(params => this.setupPageByUrlParams(params)); + if (this.bomLifecycle === MainAspectType.AS_BUILT) { this.supplierPartsAsBuilt$ = this.otherPartsFacade.supplierPartsAsBuilt$; this.tableSupplierAsBuiltSortList = []; @@ -95,10 +102,10 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.otherPartsFacade.setSupplierPartsAsBuilt(0, 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); + this.otherPartsFacade.setSupplierPartsAsBuilt(this.currentPartTablePage?.['AS_BUILT_SUPPLIER_PAGE'], 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.otherPartsFacade.setSupplierPartsAsPlanned(0, 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); + this.otherPartsFacade.setSupplierPartsAsPlanned(this.currentPartTablePage?.['AS_PLANNED_SUPPLIER_PAGE'], 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); } } @@ -118,18 +125,23 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { public onSelectItem(event: Record): void { this.partDetailsFacade.selectedPart = event as unknown as Part; + let tableData = {}; + for(let component of this.partsTableComponents) { + tableData[component.tableType+"_PAGE"] = component.pageIndex; + } + this.router.navigate([`otherParts/${event?.id}`], {queryParams: tableData}) } public onAsBuiltTableConfigChange({ page, pageSize, sorting }: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_BUILT); + this.currentPartTablePage['AS_BUILT_SUPPLIER_PAGE'] = page; let pageSizeValue = 50; if (pageSize !== 0) { pageSizeValue = pageSize; } - - if (this.assetAsBuiltFilter) { + if (this.assetAsBuiltFilter && Object.keys(this.assetAsBuiltFilter).filter(key => this.assetAsBuiltFilter[key].length).length) { this.otherPartsFacade.setSupplierPartsAsBuilt(0, pageSizeValue, this.tableSupplierAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.otherPartsFacade.setSupplierPartsAsBuilt(page, pageSizeValue, this.tableSupplierAsBuiltSortList); @@ -139,13 +151,14 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { public onAsPlannedTableConfigChange({ page, pageSize, sorting }: TableEventConfig): void { this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); + this.currentPartTablePage['AS_PLANNED_SUPPLIER_PAGE'] = page; let pageSizeValue = 50; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter) { + if (this.assetsAsPlannedFilter && Object.keys(this.assetsAsPlannedFilter).filter(key => this.assetsAsPlannedFilter[key].length).length) { this.otherPartsFacade.setSupplierPartsAsPlanned(0, pageSizeValue, this.tableSupplierAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.otherPartsFacade.setSupplierPartsAsPlanned(page, pageSizeValue, this.tableSupplierAsPlannedSortList); @@ -159,6 +172,14 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { TableSortingUtil.setTableSortingList(sorting, tableSortList, this.ctrlKeyState); } + private setupPageByUrlParams(params: Params ) { + if(!params) { + return; + } + this.onAsBuiltTableConfigChange({page: params['AS_BUILT_SUPPLIER_PAGE'], pageSize: 50, sorting: null}); + this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_SUPPLIER_PAGE'], pageSize: 50, sorting: null}); + } + protected readonly MainAspectType = MainAspectType; protected readonly TableType = TableType; protected readonly NotificationType = NotificationType; diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts index e52dd32a6c..62d959bfbc 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -1,21 +1,21 @@ -import {Component, Input} from '@angular/core'; -import {FormControl} from '@angular/forms'; -import {ActivatedRoute, Router} from '@angular/router'; -import {RoleService} from '@core/user/role.service'; -import {TractionBatteryCode} from '@page/parts/model/aspectModels.model'; -import {Owner} from '@page/parts/model/owner.enum'; - -import {Part, QualityType} from '@page/parts/model/parts.model'; -import {PartsAssembler} from '@shared/assembler/parts.assembler'; -import {SelectOption} from '@shared/components/select/select.component'; -import {State} from '@shared/model/state'; -import {View} from '@shared/model/view.model'; -import {NotificationAction} from '@shared/modules/notification/notification-action.enum'; - -import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; -import {Observable, Subscription} from 'rxjs'; -import {filter, tap} from 'rxjs/operators'; -import {Location} from "@angular/common"; +import { Location } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RoleService } from '@core/user/role.service'; +import { TractionBatteryCode } from '@page/parts/model/aspectModels.model'; +import { Owner } from '@page/parts/model/owner.enum'; + +import { Part, QualityType } from '@page/parts/model/parts.model'; +import { PartsAssembler } from '@shared/assembler/parts.assembler'; +import { SelectOption } from '@shared/components/select/select.component'; +import { State } from '@shared/model/state'; +import { View } from '@shared/model/view.model'; +import { NotificationAction } from '@shared/modules/notification/notification-action.enum'; + +import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; +import { Observable, Subscription } from 'rxjs'; +import { filter, tap } from 'rxjs/operators'; @Component({ selector: 'app-parts-detail', @@ -152,7 +152,8 @@ export class PartsDetailComponent { protected readonly Owner = Owner; navigateToPartsView() { + const parentPath = this.router.routerState.snapshot.url.split('/')[1]; //otherParts const navigationExtras = this.pageIndexHistory ? {queryParams: this.pageIndexHistory} : null - this.router.navigate(['parts'], navigationExtras); + this.router.navigate([parentPath], navigationExtras); } } diff --git a/frontend/src/app/modules/page/parts/presentation/parts.component.ts b/frontend/src/app/modules/page/parts/presentation/parts.component.ts index 6d29a6990a..ff33ada52b 100644 --- a/frontend/src/app/modules/page/parts/presentation/parts.component.ts +++ b/frontend/src/app/modules/page/parts/presentation/parts.component.ts @@ -264,8 +264,8 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { if(!params) { return; } - this.onAsBuiltTableConfigChange({page: params['AS_BUILT_OWN_PAGE'], pageSize: 50, sorting: ["",null]}); - this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_OWN_PAGE'], pageSize: 50, sorting: ["", null]}); + this.onAsBuiltTableConfigChange({page: params['AS_BUILT_OWN_PAGE'], pageSize: 50, sorting: null}); + this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_OWN_PAGE'], pageSize: 50, sorting: null}); } protected readonly UserSettingView = UserSettingView; From 57a1d89a87c27b4cbecac66db6dcbf9ac9fc1276 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 15 Mar 2024 17:21:33 +0100 Subject: [PATCH 06/21] chore(parts): 630 add parts action buttons --- .../parts/detail/parts-detail.component.html | 27 ++++++++++++++++--- .../parts/detail/parts-detail.component.scss | 13 +++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html index c9395236da..026cd4eeba 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -18,7 +18,7 @@ SPDX-License-Identifier: Apache-2.0 --> -
+
-
- +
+
+ announcement +
+
+ published_with_changes +
diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss b/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss index 0b40ac3fca..b760697ef0 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss @@ -232,3 +232,16 @@ mat-card { } } } + +.action-button-tile { + width: 3rem; + display: flex; + height: 3rem; + justify-content: center; + align-items: center; + border-radius: 4px; +} + +.action-button-tile:hover { + background-color: #e7e7e7; +} From 242962fa0aa35d7285ca0d6b6901586c207f2258 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Mon, 18 Mar 2024 14:19:18 +0100 Subject: [PATCH 07/21] chore(parts): 630 add parts action buttons --- .../parts/detail/parts-detail.component.html | 27 ++++++---- .../parts/detail/parts-detail.component.scss | 24 ++++----- .../parts/detail/parts-detail.component.ts | 54 +++++++++++++++---- .../modules/shared/service/parts.service.ts | 26 +++++---- 4 files changed, 89 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html index 026cd4eeba..6287e3e669 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -22,7 +22,7 @@
arrow_back @@ -31,16 +31,21 @@
-
- + announcement + iconName="announcement" + class="action-button-tile mr-2" + [isDisabled]="authorizationTooltipMessage !== null" + > +
->; public readonly selectedPartDetails$: Observable>; @@ -37,6 +37,7 @@ export class PartsDetailComponent { public readonly displayedColumns: string[]; public isAsPlannedPart: boolean = false; + public isPersistentPart: boolean = false; public customerOrPartSiteDetailsHeader$: Subscription; public customerOrPartSiteHeader: string; @@ -95,10 +96,14 @@ export class PartsDetailComponent { this.isAsPlannedPart = part.data.semanticDataModel.toString() === 'PartAsPlanned'; } + if(part?.data?.importState === ImportState.PERSISTENT) { + this.isPersistentPart = true; + } + if(part?.data?.children?.length > 0 ) { - this.authorizationTooltipMessage = this.getRestrictionMessageKey(true); + this.authorizationTooltipMessage = this.setRestrictionMessageKeyForInvestigationOnSubcomponents(true); } else { - this.authorizationTooltipMessage = this.getRestrictionMessageKey(false); + this.authorizationTooltipMessage = this.setRestrictionMessageKeyForInvestigationOnSubcomponents(false); } }); @@ -108,7 +113,6 @@ export class PartsDetailComponent { public ngOnInit(): void { this.route.queryParams.subscribe((params: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string}) => { this.pageIndexHistory = params; - console.log(this.pageIndexHistory) }) } @@ -132,8 +136,12 @@ export class PartsDetailComponent { this.partDetailsFacade.selectedPart = null; this.router.navigate([ `parts/relations/${ part.id }` ]).then(_ => window.location.reload()); } - - getRestrictionMessageKey(hasChildren: boolean): string { + // valid investigation for subcomponent: + // - is supplier part + // - is as built part + // - has child parts + // - role is not admin + private setRestrictionMessageKeyForInvestigationOnSubcomponents(hasChildren: boolean): string { if(this.isAsPlannedPart) { return 'routing.notAllowedForAsPlanned'; } @@ -143,17 +151,45 @@ export class PartsDetailComponent { else if(this.roleService.isAdmin()) { return 'routing.unauthorized'; } else { - return null; + return 'routing.start_investigation_for_subcomponents'; + } + + } + // valid create quality incident for part (alert/investigation will be handled in create incident view): + // - is not as planned part + // - part owner is own + // - is persistent + // - is not admin role + private setRestrictionMessageKeyForCreateQualityIncident(): string { + if(this.isAsPlannedPart) { + return 'routing.notAllowedForAsPlanned'; + } + else if(!this.isPersistentPart) { + return 'routing.notAllowedForNonPersistentPart'; + } + else if(this.roleService.isAdmin()) { + return 'routing.unauthorized'; + } else { + return 'routing.start_quality_incident'; } } + // valid publish assets on asset (allowance of state is handled in publish assets view) + // - part owner is own + // - is not admin role + private + + protected readonly NotificationAction = NotificationAction; protected readonly Owner = Owner; - navigateToPartsView() { + navigateToParentPath() { const parentPath = this.router.routerState.snapshot.url.split('/')[1]; //otherParts const navigationExtras = this.pageIndexHistory ? {queryParams: this.pageIndexHistory} : null this.router.navigate([parentPath], navigationExtras); } + + + } diff --git a/frontend/src/app/modules/shared/service/parts.service.ts b/frontend/src/app/modules/shared/service/parts.service.ts index e6618a32df..a7a60a13e3 100644 --- a/frontend/src/app/modules/shared/service/parts.service.ts +++ b/frontend/src/app/modules/shared/service/parts.service.ts @@ -37,8 +37,8 @@ import { PartsAssembler } from '@shared/assembler/parts.assembler'; import { TableHeaderSort } from '@shared/components/table/table.model'; import { enrichFilterAndGetUpdatedParams } from '@shared/helper/filter-helper'; import _deepClone from 'lodash-es/cloneDeep'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { forkJoin, Observable, of } from 'rxjs'; +import { catchError, filter, map } from 'rxjs/operators'; import { SortDirection } from '../../../mocks/services/pagination.helper'; @Injectable() @@ -99,14 +99,20 @@ export class PartsService { const encodedId = encodeURIComponent(id); - let resultsAsBuilt = this.apiService.get(`${ this.url }/assets/as-built/${ encodedId }`) - .pipe(map(part => PartsAssembler.assemblePart(part, MainAspectType.AS_BUILT))); - - let resultsAsPlanned = this.apiService.get(`${ this.url }/assets/as-planned/${ encodedId }`) - .pipe(map(part => PartsAssembler.assemblePart(part, MainAspectType.AS_PLANNED))); - - return resultsAsBuilt || resultsAsPlanned; - + const resultsAsBuilt = this.apiService.get(`${ this.url }/assets/as-built/${ encodedId }`).pipe( + map(part => PartsAssembler.assemblePart(part, MainAspectType.AS_BUILT)), + catchError(() => of(null)) + ); + const resultsAsPlanned = this.apiService.get(`${ this.url }/assets/as-planned/${ encodedId }`).pipe( + map(part => PartsAssembler.assemblePart(part, MainAspectType.AS_PLANNED)), + catchError(() => of(null)) + ); + + // Combine both observables and filter out null values from the array + return forkJoin([resultsAsBuilt, resultsAsPlanned]).pipe( + filter(([partAsBuilt, partAsPlanned]) => partAsBuilt !== null || partAsPlanned !== null), + map(([partAsBuilt, partAsPlanned]) => partAsBuilt || partAsPlanned) + ); } From 63a40b40aab45dd01fcf34c5d46f52d1977b4a86 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Mon, 18 Mar 2024 18:29:42 +0100 Subject: [PATCH 08/21] chore(parts): 630 provide tooltip and restriction logic on action buttons and connect asset publisher --- .../parts/detail/parts-detail.component.html | 39 +++++---- .../parts/detail/parts-detail.component.ts | 84 ++++++++++++------- 2 files changed, 80 insertions(+), 43 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html index 6287e3e669..1cc8a34ed5 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -32,31 +32,33 @@
-
- published_with_changes +
+
@@ -111,7 +113,7 @@ + + + + + diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts index 513a81f048..e151bf6248 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -1,21 +1,21 @@ -import { Location } from '@angular/common'; -import { Component, Input } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { ActivatedRoute, Router } from '@angular/router'; -import { RoleService } from '@core/user/role.service'; -import { TractionBatteryCode } from '@page/parts/model/aspectModels.model'; -import { Owner } from '@page/parts/model/owner.enum'; - -import { ImportState, Part, QualityType } from '@page/parts/model/parts.model'; -import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { SelectOption } from '@shared/components/select/select.component'; -import { State } from '@shared/model/state'; -import { View } from '@shared/model/view.model'; -import { NotificationAction } from '@shared/modules/notification/notification-action.enum'; - -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { Observable, Subscription } from 'rxjs'; -import { filter, tap } from 'rxjs/operators'; +import {Location} from '@angular/common'; +import {Component, Input} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import {ActivatedRoute, Router} from '@angular/router'; +import {RoleService} from '@core/user/role.service'; +import {TractionBatteryCode} from '@page/parts/model/aspectModels.model'; +import {Owner} from '@page/parts/model/owner.enum'; + +import {ImportState, Part, QualityType} from '@page/parts/model/parts.model'; +import {PartsAssembler} from '@shared/assembler/parts.assembler'; +import {SelectOption} from '@shared/components/select/select.component'; +import {State} from '@shared/model/state'; +import {View} from '@shared/model/view.model'; +import {NotificationAction} from '@shared/modules/notification/notification-action.enum'; + +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {Observable, Subject, Subscription} from 'rxjs'; +import {filter, tap} from 'rxjs/operators'; @Component({ selector: 'app-parts-detail', @@ -38,6 +38,13 @@ export class PartsDetailComponent { public isAsPlannedPart: boolean = false; public isPersistentPart: boolean = false; + public isOwnPart: boolean = false; + public hasChildren: boolean = false; + + public investigationOnSubcomponentsTooltipMessage: string; + public incidentCreationTooltipMessage: string; + public publishAssetsTooltipMessage: string; + public customerOrPartSiteDetailsHeader$: Subscription; public customerOrPartSiteHeader: string; @@ -47,10 +54,11 @@ export class PartsDetailComponent { public qualityTypeControl = new FormControl(null); public readonly isOpen$: Observable; + public readonly isPublisherOpen$ = new Subject(); private readonly isOpenState: State = new State(false); - public authorizationTooltipMessage: string; + public currentPartId: string; public pageIndexHistory: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string} @@ -100,11 +108,19 @@ export class PartsDetailComponent { this.isPersistentPart = true; } + if(part?.data?.owner === Owner.OWN) { + this.isOwnPart = true; + } + if(part?.data?.children?.length > 0 ) { - this.authorizationTooltipMessage = this.setRestrictionMessageKeyForInvestigationOnSubcomponents(true); - } else { - this.authorizationTooltipMessage = this.setRestrictionMessageKeyForInvestigationOnSubcomponents(false); + this.hasChildren = true; } + + this.incidentCreationTooltipMessage = this.setRestrictionMessageKeyForIncidentCreation(); + this.publishAssetsTooltipMessage = this.setRestrictionMessageKeyForPublishAssets(); + this.investigationOnSubcomponentsTooltipMessage = this.setRestrictionMessageKeyForInvestigationOnSubcomponents() + + }); this.displayedColumns = [ 'position', 'productType', 'tractionBatteryCode' ]; @@ -141,17 +157,17 @@ export class PartsDetailComponent { // - is as built part // - has child parts // - role is not admin - private setRestrictionMessageKeyForInvestigationOnSubcomponents(hasChildren: boolean): string { + private setRestrictionMessageKeyForInvestigationOnSubcomponents(): string { if(this.isAsPlannedPart) { return 'routing.notAllowedForAsPlanned'; } - else if(!hasChildren) { + else if(!this.hasChildren) { return 'routing.noChildPartsForInvestigation'; } else if(this.roleService.isAdmin()) { return 'routing.unauthorized'; } else { - return 'routing.start_investigation_for_subcomponents'; + return 'routing.startInvestigation'; } } @@ -160,7 +176,7 @@ export class PartsDetailComponent { // - part owner is own // - is persistent // - is not admin role - private setRestrictionMessageKeyForCreateQualityIncident(): string { + private setRestrictionMessageKeyForIncidentCreation(): string { if(this.isAsPlannedPart) { return 'routing.notAllowedForAsPlanned'; } @@ -170,15 +186,25 @@ export class PartsDetailComponent { else if(this.roleService.isAdmin()) { return 'routing.unauthorized'; } else { - return 'routing.start_quality_incident'; + return 'routing.createIncident'; } } // valid publish assets on asset (allowance of state is handled in publish assets view) // - part owner is own - // - is not admin role - private + // - is admin role + private setRestrictionMessageKeyForPublishAssets() { + if(!this.roleService.isAdmin()) { + return 'routing.unauthorized'; + } + if(!this.isOwnPart) { + return 'routing.OnlyAllowedForOwnParts'; + } else { + return 'routing.publishAssets' + } + + } protected readonly NotificationAction = NotificationAction; From f3bcf7785e1369198c57a89741ec2bebb23b3e24 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Tue, 19 Mar 2024 10:13:24 +0100 Subject: [PATCH 09/21] chore(parts): 630 add create notification view to detail action --- .../parts/detail/parts-detail.component.html | 10 ++++ .../parts/detail/parts-detail.component.scss | 6 +++ .../parts/detail/parts-detail.component.ts | 48 ++++++++++--------- .../asset-publisher.component.html | 8 ++-- .../asset-publisher.component.ts | 2 +- .../notification-request.component.html | 14 +++--- frontend/src/assets/locales/de/common.json | 7 ++- frontend/src/assets/locales/en/common.json | 7 ++- 8 files changed, 66 insertions(+), 36 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html index 1cc8a34ed5..4a4cf12c47 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -43,6 +43,7 @@ iconName="announcement" class="action-button-tile mr-2" [isDisabled]="incidentCreationTooltipMessage !== 'routing.createIncident'" + (click)="isNotificationRequestOpen.next(true)" >
@@ -317,3 +318,12 @@ > + + + + diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss b/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss index 235251dfb9..96ea7b56c2 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.scss @@ -245,3 +245,9 @@ mat-card { //.action-button-tile:hover { // background-color: #e7e7e7; //} + +:host { + th { + z-index: 4 !important; + } +} diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts index e151bf6248..0c4d9756ba 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -1,21 +1,22 @@ -import {Location} from '@angular/common'; -import {Component, Input} from '@angular/core'; -import {FormControl} from '@angular/forms'; -import {ActivatedRoute, Router} from '@angular/router'; -import {RoleService} from '@core/user/role.service'; -import {TractionBatteryCode} from '@page/parts/model/aspectModels.model'; -import {Owner} from '@page/parts/model/owner.enum'; - -import {ImportState, Part, QualityType} from '@page/parts/model/parts.model'; -import {PartsAssembler} from '@shared/assembler/parts.assembler'; -import {SelectOption} from '@shared/components/select/select.component'; -import {State} from '@shared/model/state'; -import {View} from '@shared/model/view.model'; -import {NotificationAction} from '@shared/modules/notification/notification-action.enum'; - -import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; -import {Observable, Subject, Subscription} from 'rxjs'; -import {filter, tap} from 'rxjs/operators'; +import { Location } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RoleService } from '@core/user/role.service'; +import { TractionBatteryCode } from '@page/parts/model/aspectModels.model'; +import { Owner } from '@page/parts/model/owner.enum'; + +import { ImportState, Part, QualityType } from '@page/parts/model/parts.model'; +import { PartsAssembler } from '@shared/assembler/parts.assembler'; +import { SelectOption } from '@shared/components/select/select.component'; +import { NotificationType } from '@shared/model/notification.model'; +import { State } from '@shared/model/state'; +import { View } from '@shared/model/view.model'; +import { NotificationAction } from '@shared/modules/notification/notification-action.enum'; + +import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; +import { filter, tap } from 'rxjs/operators'; @Component({ selector: 'app-parts-detail', @@ -55,6 +56,8 @@ export class PartsDetailComponent { public qualityTypeControl = new FormControl(null); public readonly isOpen$: Observable; public readonly isPublisherOpen$ = new Subject(); + public readonly isNotificationRequestOpen = new BehaviorSubject(false); + private readonly isOpenState: State = new State(false); @@ -99,9 +102,9 @@ export class PartsDetailComponent { })); this.selectedPartDetails$.subscribe(part => { - + const loweredSemanticDataModel = part.data.semanticDataModel.toString().toLowerCase(); if(part?.data?.semanticDataModel) { - this.isAsPlannedPart = part.data.semanticDataModel.toString() === 'PartAsPlanned'; + this.isAsPlannedPart = loweredSemanticDataModel === 'partasplanned' || loweredSemanticDataModel === 'tombstoneasplanned'|| loweredSemanticDataModel === 'tombstoneasbuilt' || loweredSemanticDataModel === 'unknown'; } if(part?.data?.importState === ImportState.PERSISTENT) { @@ -130,6 +133,7 @@ export class PartsDetailComponent { this.route.queryParams.subscribe((params: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string}) => { this.pageIndexHistory = params; }) + this.selectedPartDetails$.subscribe(next => console.log(next)) } public ngOnDestroy(): void { @@ -199,7 +203,7 @@ export class PartsDetailComponent { return 'routing.unauthorized'; } if(!this.isOwnPart) { - return 'routing.OnlyAllowedForOwnParts'; + return 'routing.onlyAllowedForOwnParts'; } else { return 'routing.publishAssets' } @@ -217,5 +221,5 @@ export class PartsDetailComponent { } - + protected readonly NotificationType = NotificationType; } diff --git a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.html b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.html index 0e395920e7..367a315ef0 100644 --- a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.html +++ b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.html @@ -5,8 +5,8 @@

{{'publisher.selectedAssets' | i18n}}:

- {{asset.nameAtManufacturer}} - {{asset.id}} + {{asset?.nameAtManufacturer}} + {{asset?.id}} @@ -17,8 +17,8 @@

{{'publisher.policyToApply' | i18n}}:

{{'publisher.selectPolicyLabel' | i18n}} - - {{policy.policyId}} + + {{policy?.policyId}} {{'publisher.selectPolicyError' | i18n}} diff --git a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts index d40443d15e..b5ac0d0ee1 100644 --- a/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts +++ b/frontend/src/app/modules/shared/components/asset-publisher/asset-publisher.component.ts @@ -61,6 +61,6 @@ export class AssetPublisherComponent { } checkForIllegalAssetStateToPublish(): boolean { - return this.selectedAssets.some(part => (part.importState !== ImportState.TRANSIENT && part.importState !== ImportState.ERROR)) + return this.selectedAssets.some(part => (part?.importState !== ImportState.TRANSIENT && part?.importState !== ImportState.ERROR)) } } diff --git a/frontend/src/app/modules/shared/components/request-notification/notification-request.component.html b/frontend/src/app/modules/shared/components/request-notification/notification-request.component.html index a064c2e38f..975c3aa3e7 100644 --- a/frontend/src/app/modules/shared/components/request-notification/notification-request.component.html +++ b/frontend/src/app/modules/shared/components/request-notification/notification-request.component.html @@ -26,13 +26,13 @@

{{ this.context + '.headline' - close - - {{ part.nameAtManufacturer || part.id }} - @@ -42,13 +42,13 @@

{{ this.context + '.headline'

{{ 'requestNotification.restoreItem' | i18n }}

restore - {{ removedItemsHistory[0].nameAtManufacturer || removedItemsHistory[0].id }} + {{ removedItemsHistory[0]?.nameAtManufacturer || removedItemsHistory[0]?.id }}

diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index e36eb6e7cd..34e437885e 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -18,7 +18,12 @@ "noChildPartsForInvestigation": "Diese Funktion ist fĂ¼r Produkte ohne Bauteile nicht verfĂ¼gbar.", "noCustomerAsPlannedParts": "Produkte von Kunden im Lebenszyklus \"AsPlanned\" sind nicht verfĂ¼gbar.", "illegalAssetStateToPublish": "Ein oder mehrere ausgewählte Produkte befinden sich nicht im erforderlichen Importstatus \"TRANSIENT\" oder \"ERROR\".", - "adminContract": "Verträge" + "adminContract": "Verträge", + "createIncident": "Mitteilung erstellen", + "startInvestigation": "Qualitätsuntersuchung fĂ¼r Bauteile erstellen", + "notAllowedForNonPersistentPart": "Qualitätsmitteilungen sind nur fĂ¼r Produkte im Importstatus \"PERSISTENT\" möglich.", + "onlyAllowedForOwnParts": "Diese Funktion ist nur fĂ¼r eigene Produkte möglich.", + "publishAssets": "Produkt veröffentlichen" }, "pageTitle": { "dashboard": "Dashboard", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 41016ad336..1a6c050b13 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -18,7 +18,12 @@ "noChildPartsForInvestigation": "This function is not available for Parts without components.", "noCustomerAsPlannedParts": "Customer parts in lifecycle \"AsPlanned\" are not available.", "illegalAssetStateToPublish": "One or more selected parts are not in the required import state \"TRANSIENT\" or \"ERROR\".", - "adminContract": "Contracts" + "adminContract": "Contracts", + "createIncident": "Create quality notification", + "startInvestigation": "Start Quality investigation on subcomponents", + "notAllowedForNonPersistentPart": "Quality notifications can only be created for parts that are in the import state \"PERSISTENT\".", + "onlyAllowedForOwnParts": "This functionality is only possible for own parts.", + "publishAssets": "Publish asset" }, "pageTitle": { "dashboard": "Dashboard", From 57e9368abf852502e01aaf6dcd13a3299164da1a Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Tue, 19 Mar 2024 10:14:20 +0100 Subject: [PATCH 10/21] chore(parts): 630 add create notification view to detail action --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66b5c9a303..b3e9c4d4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ _**For better traceability add the corresponding GitHub issue number in each cha ## [UNRELEASED - DD.MM.YYYY] +### Added +- #630 Added Parts extended detailed view + ## [10.7.0 - 18.03.2024] ### Added From bdd423e5d0ea4fa94aa5b044ca6ca87af9a08c1a Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Tue, 19 Mar 2024 16:16:30 +0100 Subject: [PATCH 11/21] chore(parts): 630 bugfix --- .../presentation/customer-parts/customer-parts.component.ts | 4 ++-- .../presentation/supplier-parts/supplier-parts.component.ts | 4 ++-- .../app/modules/page/parts/presentation/parts.component.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts index 6cc645033f..3fd0bbccac 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts @@ -105,10 +105,10 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.otherPartsFacade.setCustomerPartsAsBuilt(this.currentPartTablePage['AS_BUILT_CUSTOMER_PAGE'], 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); + this.otherPartsFacade.setCustomerPartsAsBuilt(this.currentPartTablePage['AS_BUILT_CUSTOMER_PAGE'] ?? 0, 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.otherPartsFacade.setCustomerPartsAsPlanned(this.currentPartTablePage['AS_PLANNED_CUSTOMER_PAGE'], 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); + this.otherPartsFacade.setCustomerPartsAsPlanned(this.currentPartTablePage['AS_PLANNED_CUSTOMER_PAGE'] ?? 0, 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); } } diff --git a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts index 23c81f53ba..bb3b9e0b53 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts @@ -102,10 +102,10 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.otherPartsFacade.setSupplierPartsAsBuilt(this.currentPartTablePage?.['AS_BUILT_SUPPLIER_PAGE'], 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); + this.otherPartsFacade.setSupplierPartsAsBuilt(this.currentPartTablePage?.['AS_BUILT_SUPPLIER_PAGE'] ?? 0, 50, [], toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.otherPartsFacade.setSupplierPartsAsPlanned(this.currentPartTablePage?.['AS_PLANNED_SUPPLIER_PAGE'], 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); + this.otherPartsFacade.setSupplierPartsAsPlanned(this.currentPartTablePage?.['AS_PLANNED_SUPPLIER_PAGE'] ?? 0, 50, [], toAssetFilter(this.assetsAsPlannedFilter, false)); } } diff --git a/frontend/src/app/modules/page/parts/presentation/parts.component.ts b/frontend/src/app/modules/page/parts/presentation/parts.component.ts index ff33ada52b..4ae5289237 100644 --- a/frontend/src/app/modules/page/parts/presentation/parts.component.ts +++ b/frontend/src/app/modules/page/parts/presentation/parts.component.ts @@ -120,10 +120,10 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { filterActivated(isAsBuilt: boolean, assetFilter: any): void { if (isAsBuilt) { this.assetAsBuiltFilter = assetFilter; - this.partsFacade.setPartsAsBuilt(this.currentPartTablePage['AS_BUILT_OWN_PAGE'], this.DEFAULT_PAGE_SIZE, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); + this.partsFacade.setPartsAsBuilt(this.currentPartTablePage['AS_BUILT_OWN_PAGE'] ?? 0, this.DEFAULT_PAGE_SIZE, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.assetsAsPlannedFilter = assetFilter; - this.partsFacade.setPartsAsPlanned(this.currentPartTablePage['AS_PLANNED_OWN_PAGE'], this.DEFAULT_PAGE_SIZE, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, false)); + this.partsFacade.setPartsAsPlanned(this.currentPartTablePage['AS_PLANNED_OWN_PAGE'] ?? 0, this.DEFAULT_PAGE_SIZE, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, false)); } } From a912b59341a25288e709534073b663080a212304 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Tue, 19 Mar 2024 16:19:22 +0100 Subject: [PATCH 12/21] chore(parts): 630 bugfix --- .../src/app/modules/page/parts/detail/parts-detail.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts index 0c4d9756ba..c5a7814ceb 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -102,7 +102,7 @@ export class PartsDetailComponent { })); this.selectedPartDetails$.subscribe(part => { - const loweredSemanticDataModel = part.data.semanticDataModel.toString().toLowerCase(); + const loweredSemanticDataModel = part?.data?.semanticDataModel?.toString()?.toLowerCase(); if(part?.data?.semanticDataModel) { this.isAsPlannedPart = loweredSemanticDataModel === 'partasplanned' || loweredSemanticDataModel === 'tombstoneasplanned'|| loweredSemanticDataModel === 'tombstoneasbuilt' || loweredSemanticDataModel === 'unknown'; } From 0328d1789188f8e4866f27d90ede6482f7bb35b1 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Wed, 20 Mar 2024 14:59:57 +0100 Subject: [PATCH 13/21] chore(parts): 630 fix and add tests --- .../detail/parts-detail.component.spec.ts | 61 +++++++++++++++---- .../parts/detail/parts-detail.component.ts | 3 +- .../part-details/core/partDetails.facade.ts | 19 +++--- .../part-detail.component.spec.ts | 8 --- .../shared/service/parts.service.spec.ts | 15 ++--- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts index b80f3ffa0b..f98f5db414 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts @@ -1,21 +1,58 @@ -import {ComponentFixture, TestBed} from '@angular/core/testing'; +import { LayoutModule } from '@layout/layout.module'; +import { PartsState } from '@page/parts/core/parts.state'; +import { PartsDetailModule } from '@page/parts/detail/parts-detail.module'; +import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; +import { PartsAssembler } from '@shared/assembler/parts.assembler'; +import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; +import { PartDetailsState } from '@shared/modules/part-details/core/partDetails.state'; +import { screen } from '@testing-library/angular'; +import { renderComponent } from '@tests/test-render.utils'; +import { MOCK_part_1 } from '../../../../mocks/services/parts-mock/partsAsBuilt/partsAsBuilt.test.model'; -import {PartsDetailComponent} from './parts-detail.component'; +import { PartsDetailComponent } from './parts-detail.component'; -describe('PartsDetailComponent', () => { - let component: PartsDetailComponent; - let fixture: ComponentFixture; +let PartsStateMock: PartsState; +let PartDetailsStateMock: PartDetailsState; +const part = PartsAssembler.assemblePart(MOCK_part_1, MainAspectType.AS_BUILT); +describe('PartsDetailComponent', () => { beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [PartsDetailComponent] + PartDetailsStateMock = new PartDetailsState(); + PartDetailsStateMock.selectedPart = { data: part }; + + PartsStateMock = new PartsState(); + }); + + const renderPartsDetailComponent = async ({ roles = [] } = {}) => { + return await renderComponent(PartsDetailComponent, { + declarations: [ PartsDetailComponent ], + imports: [ PartsDetailModule, LayoutModule ], + providers: [ + PartDetailsFacade, + { provide: PartsState, useFactory: () => PartsStateMock }, + { provide: PartDetailsState, useFactory: () => PartDetailsStateMock }, + ], + roles, }); - fixture = TestBed.createComponent(PartsDetailComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + }; + + it('should render an open sidenav with part details', async () => { + const {fixture} = await renderPartsDetailComponent(); + const {componentInstance} = fixture; + const spy = spyOn(componentInstance.partDetailsFacade, 'setPartById') + const nameElement = await screen.findByText('BMW AG'); + const productionDateElement = await screen.findByText('2022-02-04T13:48:54'); + + expect(nameElement).toBeInTheDocument(); + expect(productionDateElement).toBeInTheDocument(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should render child-component table', async () => { + const {fixture} = await renderPartsDetailComponent({roles: ['user']}); + const {componentInstance} = fixture; + + const childTableHeadline = await screen.findByText('partDetail.investigation.headline'); + expect(childTableHeadline).toBeInTheDocument(); + expect(await screen.findByText('partDetail.investigation.noSelection.header')).toBeInTheDocument(); }); }); diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts index c5a7814ceb..c84d4d3f8e 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -65,7 +65,7 @@ export class PartsDetailComponent { public currentPartId: string; public pageIndexHistory: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string} - constructor(private readonly partDetailsFacade: PartDetailsFacade, private readonly router: Router, private readonly route: ActivatedRoute, public roleService: RoleService, private location: Location) { + constructor(public readonly partDetailsFacade: PartDetailsFacade, private readonly router: Router, private readonly route: ActivatedRoute, public roleService: RoleService, private location: Location) { this.isOpen$ = this.isOpenState.observable; this.setIsOpen(true); @@ -133,7 +133,6 @@ export class PartsDetailComponent { this.route.queryParams.subscribe((params: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string}) => { this.pageIndexHistory = params; }) - this.selectedPartDetails$.subscribe(next => console.log(next)) } public ngOnDestroy(): void { diff --git a/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts b/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts index 59cd8db57d..50587d9690 100644 --- a/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts +++ b/frontend/src/app/modules/shared/modules/part-details/core/partDetails.facade.ts @@ -19,14 +19,14 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import {Injectable} from '@angular/core'; -import {Part} from '@page/parts/model/parts.model'; -import {View} from '@shared/model/view.model'; -import {PartDetailsState} from '@shared/modules/part-details/core/partDetails.state'; -import {PartsService} from '@shared/service/parts.service'; -import {Observable, of} from 'rxjs'; -import {catchError, map, tap} from 'rxjs/operators'; -import {SortDirection} from '../../../../../mocks/services/pagination.helper'; +import { Injectable } from '@angular/core'; +import { Part } from '@page/parts/model/parts.model'; +import { View } from '@shared/model/view.model'; +import { PartDetailsState } from '@shared/modules/part-details/core/partDetails.state'; +import { PartsService } from '@shared/service/parts.service'; +import { Observable, of } from 'rxjs'; +import { catchError, map, tap } from 'rxjs/operators'; +import { SortDirection } from '../../../../../mocks/services/pagination.helper'; @Injectable() export class PartDetailsFacade { @@ -37,6 +37,9 @@ export class PartDetailsFacade { } public setPartById(urn: string) { + if(!urn || typeof urn !== 'string') { + return; + } this.partsService.getPart(urn).subscribe(part => { this.partDetailsState.selectedPart = { data: part} }) diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts index 63fea82a57..56e613f8ed 100644 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts @@ -80,12 +80,4 @@ describe('PartDetailComponent', () => { expect(nameElement).toBeInTheDocument(); expect(productionDateElement).toBeInTheDocument(); }); - - it('should render child-component table', async () => { - await renderPartDetailComponent({ roles: [ 'user' ] }); - - const childTableHeadline = await screen.findByText('partDetail.investigation.headline'); - expect(childTableHeadline).toBeInTheDocument(); - expect(await screen.findByText('partDetail.investigation.noSelection.header')).toBeInTheDocument(); - }); }); diff --git a/frontend/src/app/modules/shared/service/parts.service.spec.ts b/frontend/src/app/modules/shared/service/parts.service.spec.ts index c328da725d..4842167123 100644 --- a/frontend/src/app/modules/shared/service/parts.service.spec.ts +++ b/frontend/src/app/modules/shared/service/parts.service.spec.ts @@ -17,18 +17,18 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { PartsService } from '@shared/service/parts.service'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { ApiService } from '@core/api/api.service'; -import { TableHeaderSort } from '@shared/components/table/table.model'; +import { AuthService } from '@core/auth/auth.service'; import { Pagination } from '@core/model/pagination.model'; -import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; import { environment } from '@env'; +import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; +import { TableHeaderSort } from '@shared/components/table/table.model'; +import { PartsService } from '@shared/service/parts.service'; import { KeycloakService } from 'keycloak-angular'; -import { AuthService } from '@core/auth/auth.service'; -import { mockAssets } from '../../../mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.test.model'; import { MOCK_part_1 } from '../../../mocks/services/parts-mock/partsAsBuilt/partsAsBuilt.test.model'; +import { mockAssets } from '../../../mocks/services/parts-mock/partsAsPlanned/partsAsPlanned.test.model'; describe('PartsService', () => { let service: PartsService; @@ -44,10 +44,6 @@ describe('PartsService', () => { authService = TestBed.inject(AuthService); }); - afterEach(() => { - httpMock.verify(); - }); - it('should be created', () => { expect(service).toBeTruthy(); }); @@ -95,7 +91,6 @@ describe('PartsService', () => { req.flush(MOCK_part_1); - httpMock.verify(); }); it('should call the getPartsAsBuilt API and return parts filtered', () => { From 1fe22ce3b04bb6c98d474a61d7bd4ccf97c9ba68 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Wed, 20 Mar 2024 15:54:03 +0100 Subject: [PATCH 14/21] chore(parts): 630 fix and add tests --- .../parts/detail/parts-detail.component.html | 15 ++-- .../detail/parts-detail.component.spec.ts | 72 ++++++++++++++++++- .../parts/detail/parts-detail.component.ts | 16 ++--- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html index 4a4cf12c47..00f7541eec 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -151,16 +151,16 @@ {{ 'partDetail.tractionBatteryCodeTitle' | i18n }} - - + + {{ 'partDetail.subcomponents' | i18n }} - - + +
- - @@ -199,14 +197,13 @@ {{ 'partDetail.relations' | i18n }} - - + + open_in_new - diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts index f98f5db414..ec5e4a2050 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts @@ -36,7 +36,7 @@ describe('PartsDetailComponent', () => { }); }; - it('should render an open sidenav with part details', async () => { + it('should render part details', async () => { const {fixture} = await renderPartsDetailComponent(); const {componentInstance} = fixture; const spy = spyOn(componentInstance.partDetailsFacade, 'setPartById') @@ -55,4 +55,74 @@ describe('PartsDetailComponent', () => { expect(childTableHeadline).toBeInTheDocument(); expect(await screen.findByText('partDetail.investigation.noSelection.header')).toBeInTheDocument(); }); + + + it('should set selected part on null if click on relation page', async () => { + const {fixture} = await renderPartsDetailComponent({roles: ['user']}); + const {componentInstance} = fixture; + + componentInstance.openRelationPage(part); + expect(componentInstance.partDetailsFacade.selectedPart).toEqual(null); + }); + + fit('should correctly set restriction keys for actions', async () => { + let {fixture} = await renderPartsDetailComponent({roles: ['user']}); + let {componentInstance} = fixture; + + // subcomponent investigation success + componentInstance.isAsPlannedPart = false; + componentInstance.hasChildren = true; + + expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.startInvestigation"); + + // incident creation success + componentInstance.isPersistentPart = true; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.createIncident") + + // publish assets - not admin + expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.unauthorized"); + + + }); + + fit('should correctly set restriction keys for publish assets', async () => { + let {fixture} = await renderPartsDetailComponent({roles: ['admin']}); + let {componentInstance} = fixture; + + // publish assets success + componentInstance.isOwnPart = true; + expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.publishAssets") + + // publish assets - not own Part + componentInstance.isOwnPart = false; + expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.onlyAllowedForOwnParts"); + + // sucomponent investigation - not as built + componentInstance.isAsPlannedPart = true; + expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.notAllowedForAsPlanned") + + // subcomponent investigation - no child parts + componentInstance.isAsPlannedPart = false; + componentInstance.hasChildren = false; + expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.noChildPartsForInvestigation"); + + // subcomponent investigation - not user role + componentInstance.hasChildren = true; + expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.unauthorized"); + + // incident creation - not as built + componentInstance.isAsPlannedPart = true; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.notAllowedForAsPlanned"); + + // incident creation - not persistent part + componentInstance.isAsPlannedPart = false; + componentInstance.isPersistentPart = false; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.notAllowedForNonPersistentPart"); + + // incident creation - not user role + componentInstance.isPersistentPart = true; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.unauthorized") + + }); + }); diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts index c84d4d3f8e..04a853360c 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -54,20 +54,16 @@ export class PartsDetailComponent { public qualityTypeOptions: SelectOption[]; public qualityTypeControl = new FormControl(null); - public readonly isOpen$: Observable; + public readonly isPublisherOpen$ = new Subject(); public readonly isNotificationRequestOpen = new BehaviorSubject(false); - - - private readonly isOpenState: State = new State(false); + private readonly isStartInvestigationOpen: State = new State(false); public currentPartId: string; public pageIndexHistory: {AS_BUILT_PAGE: string, AS_PLANNED_PAGE: string} constructor(public readonly partDetailsFacade: PartDetailsFacade, private readonly router: Router, private readonly route: ActivatedRoute, public roleService: RoleService, private location: Location) { - this.isOpen$ = this.isOpenState.observable; - this.setIsOpen(true); this.currentPartId = this.route.snapshot.params['partId']; this.partDetailsFacade.setPartById(this.currentPartId); @@ -144,7 +140,7 @@ export class PartsDetailComponent { } public setIsOpen(openState: boolean) { - this.isOpenState.update(openState); + this.isStartInvestigationOpen.update(openState); if (!openState) { this.partDetailsFacade.selectedPart = null; @@ -160,7 +156,7 @@ export class PartsDetailComponent { // - is as built part // - has child parts // - role is not admin - private setRestrictionMessageKeyForInvestigationOnSubcomponents(): string { + setRestrictionMessageKeyForInvestigationOnSubcomponents(): string { if(this.isAsPlannedPart) { return 'routing.notAllowedForAsPlanned'; } @@ -179,7 +175,7 @@ export class PartsDetailComponent { // - part owner is own // - is persistent // - is not admin role - private setRestrictionMessageKeyForIncidentCreation(): string { + setRestrictionMessageKeyForIncidentCreation(): string { if(this.isAsPlannedPart) { return 'routing.notAllowedForAsPlanned'; } @@ -197,7 +193,7 @@ export class PartsDetailComponent { // valid publish assets on asset (allowance of state is handled in publish assets view) // - part owner is own // - is admin role - private setRestrictionMessageKeyForPublishAssets() { + setRestrictionMessageKeyForPublishAssets() { if(!this.roleService.isAdmin()) { return 'routing.unauthorized'; } From d8a0a63cce75d48619fc0dcdd11df4c2cc9f4558 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Wed, 20 Mar 2024 16:29:34 +0100 Subject: [PATCH 15/21] chore(parts): 630 fix and add tests --- .../parts/detail/parts-detail.component.html | 2 +- .../detail/parts-detail.component.spec.ts | 33 +++++++------ .../parts/detail/parts-detail.component.ts | 49 ++++++++++--------- frontend/src/assets/locales/de/common.json | 3 +- frontend/src/assets/locales/en/common.json | 3 +- 5 files changed, 47 insertions(+), 43 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html index 00f7541eec..80d5a2f3f5 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.html +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.html @@ -320,7 +320,7 @@ diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts index ec5e4a2050..d9e68daa8c 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts @@ -1,15 +1,16 @@ -import { LayoutModule } from '@layout/layout.module'; -import { PartsState } from '@page/parts/core/parts.state'; -import { PartsDetailModule } from '@page/parts/detail/parts-detail.module'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { PartDetailsState } from '@shared/modules/part-details/core/partDetails.state'; -import { screen } from '@testing-library/angular'; -import { renderComponent } from '@tests/test-render.utils'; -import { MOCK_part_1 } from '../../../../mocks/services/parts-mock/partsAsBuilt/partsAsBuilt.test.model'; - -import { PartsDetailComponent } from './parts-detail.component'; +import {LayoutModule} from '@layout/layout.module'; +import {PartsState} from '@page/parts/core/parts.state'; +import {PartsDetailModule} from '@page/parts/detail/parts-detail.module'; +import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; +import {PartsAssembler} from '@shared/assembler/parts.assembler'; +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {PartDetailsState} from '@shared/modules/part-details/core/partDetails.state'; +import {screen} from '@testing-library/angular'; +import {renderComponent} from '@tests/test-render.utils'; +import {MOCK_part_1} from '../../../../mocks/services/parts-mock/partsAsBuilt/partsAsBuilt.test.model'; + +import {PartsDetailComponent} from './parts-detail.component'; +import {Owner} from "@page/parts/model/owner.enum"; let PartsStateMock: PartsState; let PartDetailsStateMock: PartDetailsState; @@ -65,7 +66,7 @@ describe('PartsDetailComponent', () => { expect(componentInstance.partDetailsFacade.selectedPart).toEqual(null); }); - fit('should correctly set restriction keys for actions', async () => { + it('should correctly set restriction keys for actions', async () => { let {fixture} = await renderPartsDetailComponent({roles: ['user']}); let {componentInstance} = fixture; @@ -85,16 +86,16 @@ describe('PartsDetailComponent', () => { }); - fit('should correctly set restriction keys for publish assets', async () => { + it('should correctly set restriction keys for publish assets', async () => { let {fixture} = await renderPartsDetailComponent({roles: ['admin']}); let {componentInstance} = fixture; // publish assets success - componentInstance.isOwnPart = true; + componentInstance.partOwner = Owner.OWN; expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.publishAssets") // publish assets - not own Part - componentInstance.isOwnPart = false; + componentInstance.partOwner = Owner.CUSTOMER; expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.onlyAllowedForOwnParts"); // sucomponent investigation - not as built diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts index 04a853360c..d077f4b92d 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.ts @@ -1,22 +1,22 @@ -import { Location } from '@angular/common'; -import { Component, Input } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { ActivatedRoute, Router } from '@angular/router'; -import { RoleService } from '@core/user/role.service'; -import { TractionBatteryCode } from '@page/parts/model/aspectModels.model'; -import { Owner } from '@page/parts/model/owner.enum'; - -import { ImportState, Part, QualityType } from '@page/parts/model/parts.model'; -import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { SelectOption } from '@shared/components/select/select.component'; -import { NotificationType } from '@shared/model/notification.model'; -import { State } from '@shared/model/state'; -import { View } from '@shared/model/view.model'; -import { NotificationAction } from '@shared/modules/notification/notification-action.enum'; - -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; -import { filter, tap } from 'rxjs/operators'; +import {Location} from '@angular/common'; +import {Component, Input} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import {ActivatedRoute, Router} from '@angular/router'; +import {RoleService} from '@core/user/role.service'; +import {TractionBatteryCode} from '@page/parts/model/aspectModels.model'; +import {Owner} from '@page/parts/model/owner.enum'; + +import {ImportState, Part, QualityType} from '@page/parts/model/parts.model'; +import {PartsAssembler} from '@shared/assembler/parts.assembler'; +import {SelectOption} from '@shared/components/select/select.component'; +import {NotificationType} from '@shared/model/notification.model'; +import {State} from '@shared/model/state'; +import {View} from '@shared/model/view.model'; +import {NotificationAction} from '@shared/modules/notification/notification-action.enum'; + +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {BehaviorSubject, Observable, Subject, Subscription} from 'rxjs'; +import {filter, tap} from 'rxjs/operators'; @Component({ selector: 'app-parts-detail', @@ -39,7 +39,7 @@ export class PartsDetailComponent { public isAsPlannedPart: boolean = false; public isPersistentPart: boolean = false; - public isOwnPart: boolean = false; + public partOwner: Owner | undefined = Owner.UNKNOWN; public hasChildren: boolean = false; public investigationOnSubcomponentsTooltipMessage: string; @@ -107,9 +107,7 @@ export class PartsDetailComponent { this.isPersistentPart = true; } - if(part?.data?.owner === Owner.OWN) { - this.isOwnPart = true; - } + this.partOwner = part?.data?.owner; if(part?.data?.children?.length > 0 ) { this.hasChildren = true; @@ -182,6 +180,9 @@ export class PartsDetailComponent { else if(!this.isPersistentPart) { return 'routing.notAllowedForNonPersistentPart'; } + else if(this.partOwner === Owner.CUSTOMER || this.partOwner === Owner.UNKNOWN) { + return "routing.notAuthorizedOwner"; + } else if(this.roleService.isAdmin()) { return 'routing.unauthorized'; } else { @@ -197,7 +198,7 @@ export class PartsDetailComponent { if(!this.roleService.isAdmin()) { return 'routing.unauthorized'; } - if(!this.isOwnPart) { + if(this.partOwner !== Owner.OWN) { return 'routing.onlyAllowedForOwnParts'; } else { return 'routing.publishAssets' diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index 34e437885e..c6e7b24eda 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -23,7 +23,8 @@ "startInvestigation": "Qualitätsuntersuchung fĂ¼r Bauteile erstellen", "notAllowedForNonPersistentPart": "Qualitätsmitteilungen sind nur fĂ¼r Produkte im Importstatus \"PERSISTENT\" möglich.", "onlyAllowedForOwnParts": "Diese Funktion ist nur fĂ¼r eigene Produkte möglich.", - "publishAssets": "Produkt veröffentlichen" + "publishAssets": "Produkt veröffentlichen", + "notAuthorizedOwner": "Qualitätsmitteilungen sind nur fĂ¼r Eigene- und Lieferantenprodukte möglich." }, "pageTitle": { "dashboard": "Dashboard", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 1a6c050b13..40794c03df 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -23,7 +23,8 @@ "startInvestigation": "Start Quality investigation on subcomponents", "notAllowedForNonPersistentPart": "Quality notifications can only be created for parts that are in the import state \"PERSISTENT\".", "onlyAllowedForOwnParts": "This functionality is only possible for own parts.", - "publishAssets": "Publish asset" + "publishAssets": "Publish asset", + "notAuthorizedOwner": "This functionality is only allowed for own and supplier parts." }, "pageTitle": { "dashboard": "Dashboard", From 6ce6e29e0d0179fe62e53e8df3d3ec416673eb07 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Wed, 20 Mar 2024 16:37:09 +0100 Subject: [PATCH 16/21] chore(parts): 630 fix and add tests --- .../page/parts/detail/parts-detail.component.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts index d9e68daa8c..ff8b71f068 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts @@ -66,13 +66,14 @@ describe('PartsDetailComponent', () => { expect(componentInstance.partDetailsFacade.selectedPart).toEqual(null); }); - it('should correctly set restriction keys for actions', async () => { + it('should correctly set restriction keys for actions as user', async () => { let {fixture} = await renderPartsDetailComponent({roles: ['user']}); let {componentInstance} = fixture; // subcomponent investigation success componentInstance.isAsPlannedPart = false; componentInstance.hasChildren = true; + componentInstance.partOwner = Owner.OWN expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.startInvestigation"); @@ -80,13 +81,17 @@ describe('PartsDetailComponent', () => { componentInstance.isPersistentPart = true; expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.createIncident") + // incident creation - customer part + componentInstance.partOwner = Owner.CUSTOMER; + expect(componentInstance.setRestrictionMessageKeyForIncidentCreation()).toEqual("routing.notAuthorizedOwner"); + // publish assets - not admin expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.unauthorized"); }); - it('should correctly set restriction keys for publish assets', async () => { + fit('should correctly set restriction keys for actions as admin', async () => { let {fixture} = await renderPartsDetailComponent({roles: ['admin']}); let {componentInstance} = fixture; @@ -98,6 +103,7 @@ describe('PartsDetailComponent', () => { componentInstance.partOwner = Owner.CUSTOMER; expect(componentInstance.setRestrictionMessageKeyForPublishAssets()).toEqual("routing.onlyAllowedForOwnParts"); + componentInstance.partOwner = Owner.OWN // sucomponent investigation - not as built componentInstance.isAsPlannedPart = true; expect(componentInstance.setRestrictionMessageKeyForInvestigationOnSubcomponents()).toEqual("routing.notAllowedForAsPlanned") From 48a96e04cb67130bd42fd21fefe2e4feb68ae491 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Wed, 20 Mar 2024 16:41:22 +0100 Subject: [PATCH 17/21] chore(parts): 630 fix and add tests --- .../modules/page/parts/detail/parts-detail.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts index ff8b71f068..5b13842a69 100644 --- a/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts +++ b/frontend/src/app/modules/page/parts/detail/parts-detail.component.spec.ts @@ -91,7 +91,7 @@ describe('PartsDetailComponent', () => { }); - fit('should correctly set restriction keys for actions as admin', async () => { + it('should correctly set restriction keys for actions as admin', async () => { let {fixture} = await renderPartsDetailComponent({roles: ['admin']}); let {componentInstance} = fixture; From cfb69930f56c093695958a4a1ef6688377af98c7 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Thu, 21 Mar 2024 09:01:05 +0100 Subject: [PATCH 18/21] chore(parts): 630 documentation, code refactoring --- docs/src/docs/user/user-manual.adoc | 17 +++++++++++ .../user-guide/parts-list-detailed-view.png | Bin 174575 -> 133043 bytes .../parts/detail/parts-detail.component.scss | 27 ++++++------------ .../page/parts/detail/parts-detail.module.ts | 2 +- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/docs/src/docs/user/user-manual.adoc b/docs/src/docs/user/user-manual.adoc index 02baa0dc6e..831eb88a7e 100644 --- a/docs/src/docs/user/user-manual.adoc +++ b/docs/src/docs/user/user-manual.adoc @@ -271,6 +271,23 @@ Detailed information on the IDs for the manufactured part/batch. Information about the identifiers at the customer for the respective part/batch. +==== Traction battery code data + +If the asset has the "traction battery code" aspect model, an additional section underneath will be displayed. +In this section there are detailed information about the traction battery and a table with its subcomponents + +==== Creation of a quality incident from detailed view + +By clicking on the "announcement" icon you can create a quality incident from the detailed view, containing the part information in the currently opened detailed view. If this +functionality is disabled, a tooltip will provide information explaining the reason. You can trigger the tooltip by hovering above the button. + +==== Publish asset from detailed view + +By clicking on the "publish" icon, you can publish the currently opened part from the detailed view. If the icon is disabled, +a tooltip will provide information explaining the reason. You can trigger the tooltip by hovering above the button + + + == Other parts List view of the supplied/delivered parts and batches (Supplier parts / Customer parts). diff --git a/docs/src/images/arc42/user-guide/parts-list-detailed-view.png b/docs/src/images/arc42/user-guide/parts-list-detailed-view.png index f98da690005cf6f9790635737b49d2a1639991da..340d713d8afd3fc9f0f1f1b69a6f8a36a6890553 100644 GIT binary patch literal 133043 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

6mI@>)KTo*$k@)-V? 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 bdvvH|(gueElpE}iu79MW{jlVL^|Sv5QD%1X literal 174575 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

- - - - - - - - - - - - - - - -
{{'partDetail.position' | i18n}} 000{{i + 1}} {{'partDetail.productType' | i18n }}{{subcomponent.productType}} {{'partDetail.tractionBatteryCode' | i18n }} {{subcomponent.tractionBatteryCode}}
-
-
- -
-
-
- -
- - - - - - - - {{ 'partDetail.relations' | i18n }} - - - - - open_in_new - - - - - - - - - - - {{ 'dataLoading.error' | i18n }} - - {{ customerOrPartSiteDetails.error.message }} - - - - - - - {{ 'dataLoading.inProgress' | i18n }} - - - - - - - - - - - - {{ 'partDetail.overview' | i18n }} - - - - - - - - - -
-

{{ 'partDetail.' + item.key | i18n }}

-

{{ item.value | autoFormat }} - -

-
-
- - -
-

{{ 'partDetail.' + item.key | i18n }}

-

{{ item.value | autoFormat }}

-
-
- - -
- -

{{ 'partDetail.' + item.key | i18n }}

-
- - edit -
-
- -
- - close - -
-
-
- - - - - - - - diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.scss b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.scss deleted file mode 100644 index 30780acee4..0000000000 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.scss +++ /dev/null @@ -1,178 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2022, 2023 ZF Friedrichshafen AG - * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -.part-detail { - &--spinner { - display: flex; - justify-content: center; - align-items: center; - } - - &--container { - margin-top: 25px; - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 25px 25px; - } - - @media(max-width: 1023px) { - &--container { - grid-template-columns: repeat(1, 1fr); - } - } - - &--container > div { - min-height: 200px; - max-height: 600px; - } - - &--open { - pointer-events: unset; - } - - &--sidenav { - @apply flex bg-dustyGrayShadeWildSand; - align-items: center; - justify-content: center; - min-width: 900px; - padding: 25px; - } - - &--relation__icon { - &:hover { - cursor: pointer; - } - } - - - &-subcomponents-table-container { - flex: 1; - height: 350px; - overflow: auto; - } -} - -.part-detail--tractionBattery-content { - display: flex; - flex-wrap: wrap; - gap: 25px; -} - -.part-detail--tractionBattery-details { - flex: 1; -} - -.overview { - grid-column: span 2; - grid-row: span 1; -} - -.relation { - grid-column: span 2; - grid-row: span 2; - mat-card-content { - height: 90%; - } -} - -.import-state { - grid-column: span 2; - grid-row: span 1; -} - -.manufacturer { - grid-column: span 2; - grid-row: span 1; -} - -.specific { - grid-column: span 2; - grid-row: span 1; -} - -.tractionbattery { - grid-column: span 4; - grid-row: span 1; -} - -@media(max-width: 1023px) { - .overview { - grid-column: span 1; - grid-row: span 1; - } - - .relation { - grid-column: span 1; - grid-row: span 1; - } - - .import-state { - grid-column: span 1; - grid-row: span 1; - } - - .manufacturer { - grid-column: span 1; - grid-row: span 1; - } - - .specific { - grid-column: span 1; - grid-row: span 1; - } - - .tractionbattery { - grid-column: span 1; - grid-row: span 3; - } -} - -.card-list { - &--icon { - display: flex; - - &:hover { - @apply text-primary; - cursor: pointer; - } - } - - &--qualityType { - grid-area: c; - display: flex; - align-items: center; - width: 100%; - - & > app-select { - width: 90%; - } - - & > mat-icon { - width: 10%; - text-align: center; - cursor: pointer; - - &:hover { - @apply text-primary; - } - } - } -} diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts deleted file mode 100644 index 56e613f8ed..0000000000 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2022, 2023 ZF Friedrichshafen AG - * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -import { LayoutModule } from '@layout/layout.module'; -import { SidenavComponent } from '@layout/sidenav/sidenav.component'; -import { SidenavService } from '@layout/sidenav/sidenav.service'; -import { PartsState } from '@page/parts/core/parts.state'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { PartDetailsState } from '@shared/modules/part-details/core/partDetails.state'; -import { PartDetailsModule } from '@shared/modules/part-details/partDetails.module'; -import { screen, waitFor } from '@testing-library/angular'; -import { renderComponent } from '@tests/test-render.utils'; -import { MOCK_part_1 } from '../../../../../mocks/services/parts-mock/partsAsBuilt/partsAsBuilt.test.model'; -import { PartDetailComponent } from './part-detail.component'; - -let PartsStateMock: PartsState; -let PartDetailsStateMock: PartDetailsState; - -const part = PartsAssembler.assemblePart(MOCK_part_1, MainAspectType.AS_BUILT); - -describe('PartDetailComponent', () => { - beforeEach(() => { - PartDetailsStateMock = new PartDetailsState(); - PartDetailsStateMock.selectedPart = { data: part }; - - PartsStateMock = new PartsState(); - }); - - const renderPartDetailComponent = async ({ roles = [] } = {}) => { - return await renderComponent(``, { - declarations: [ SidenavComponent, PartDetailComponent ], - imports: [ PartDetailsModule, LayoutModule ], - providers: [ - PartDetailsFacade, - { provide: PartsState, useFactory: () => PartsStateMock }, - { provide: PartDetailsState, useFactory: () => PartDetailsStateMock }, - SidenavService, - ], - roles, - }); - }; - - it('should render side nav', async () => { - await renderPartDetailComponent(); - - const sideNavElement = await waitFor(() => screen.getByTestId('sidenav--test-id')); - expect(sideNavElement).toBeInTheDocument(); - }); - - it('should render an open sidenav with part details', async () => { - await renderPartDetailComponent(); - - const sideNavElement = await waitFor(() => screen.getByTestId('sidenav--test-id')); - const nameElement = await screen.findByText('BMW AG'); - const productionDateElement = await screen.findByText('2022-02-04T13:48:54'); - - expect(sideNavElement).toBeInTheDocument(); - await waitFor(() => expect(sideNavElement).toHaveClass('sidenav--container__open')); - - expect(nameElement).toBeInTheDocument(); - expect(productionDateElement).toBeInTheDocument(); - }); -}); diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.ts b/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.ts deleted file mode 100644 index 6d790b4db4..0000000000 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/part-detail.component.ts +++ /dev/null @@ -1,158 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022, 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2022, 2023 ZF Friedrichshafen AG - * Copyright (c) 2022, 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -import { AfterViewInit, Component, Input, OnDestroy } from '@angular/core'; -import { FormControl } from '@angular/forms'; -import { Router } from '@angular/router'; -import { RoleService } from '@core/user/role.service'; -import { TractionBatteryCode } from '@page/parts/model/aspectModels.model'; -import { Owner } from '@page/parts/model/owner.enum'; -import { Part, QualityType } from '@page/parts/model/parts.model'; -import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { SelectOption } from '@shared/components/select/select.component'; -import { State } from '@shared/model/state'; -import { View } from '@shared/model/view.model'; -import { NotificationAction } from '@shared/modules/notification/notification-action.enum'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { Observable, Subscription } from 'rxjs'; -import { filter, tap } from 'rxjs/operators'; - -@Component({ - selector: 'app-part-detail', - templateUrl: './part-detail.component.html', - styleUrls: [ '../../../components/card-list/card-list.component.scss', './part-detail.component.scss' ], -}) -export class PartDetailComponent implements AfterViewInit, OnDestroy { - @Input() showRelation = true; - @Input() showStartInvestigation = true; - - public readonly shortenPartDetails$: Observable>; - public readonly selectedPartDetails$: Observable>; - public readonly manufacturerDetails$: Observable>; - public readonly customerOrPartSiteDetails$: Observable>; - public readonly tractionBatteryDetails$: Observable>; - public readonly importStateDetails$: Observable>; - public readonly tractionBatterySubcomponents$: Observable>; - - public readonly displayedColumns: string[]; - - public isAsPlannedPart: boolean = false; - - public customerOrPartSiteDetailsHeader$: Subscription; - public customerOrPartSiteHeader: string; - - public showQualityTypeDropdown = false; - public qualityTypeOptions: SelectOption[]; - - public qualityTypeControl = new FormControl(null); - public readonly isOpen$: Observable; - - private readonly isOpenState: State = new State(false); - - public authorizationTooltipMessage: string; - - constructor(private readonly partDetailsFacade: PartDetailsFacade, private readonly router: Router, public roleService: RoleService) { - this.isOpen$ = this.isOpenState.observable; - this.selectedPartDetails$ = this.partDetailsFacade.selectedPart$; - this.shortenPartDetails$ = this.partDetailsFacade.selectedPart$.pipe( - PartsAssembler.mapPartForView(), - tap(({ data }) => { - this.qualityTypeControl.patchValue(data.qualityType, { emitEvent: false, onlySelf: true }) - }), - ); - - this.manufacturerDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForManufacturerView()); - this.customerOrPartSiteDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForCustomerOrPartSiteView()); - - this.tractionBatteryDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForTractionBatteryCodeDetailsView()); - this.tractionBatterySubcomponents$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForTractionBatteryCodeSubComponentsView()) as unknown as Observable>; - - this.importStateDetails$ = this.partDetailsFacade.selectedPart$.pipe(PartsAssembler.mapPartForAssetStateDetailsView()); - - this.customerOrPartSiteDetailsHeader$ = this.customerOrPartSiteDetails$?.subscribe(data => { - if (data?.data?.functionValidFrom) { - this.customerOrPartSiteHeader = 'partDetail.partSiteInformationData'; - } else { - this.customerOrPartSiteHeader = 'partDetail.customerData'; - } - }); - - - this.qualityTypeOptions = Object.values(QualityType).map(value => ({ - label: value, - value: value, - })); - - this.selectedPartDetails$.subscribe(part => { - - if(part?.data?.semanticDataModel) { - this.isAsPlannedPart = part.data.semanticDataModel.toString() === 'PartAsPlanned'; - } - - if(part?.data?.children?.length > 0 ) { - this.authorizationTooltipMessage = this.getRestrictionMessageKey(true); - } else { - this.authorizationTooltipMessage = this.getRestrictionMessageKey(false); - } - }); - - this.displayedColumns = [ 'position', 'productType', 'tractionBatteryCode' ]; - } - - public ngOnDestroy(): void { - this.partDetailsFacade.selectedPart = null; - } - - public ngAfterViewInit(): void { - this.partDetailsFacade.selectedPart$.pipe(filter(({ data }) => !!data)).subscribe(_ => this.setIsOpen(true)); - } - - public setIsOpen(openState: boolean) { - this.isOpenState.update(openState); - - if (!openState) { - this.partDetailsFacade.selectedPart = null; - } - } - - public openRelationPage(part: Part): void { - this.partDetailsFacade.selectedPart = null; - this.router.navigate([ `parts/relations/${ part.id }` ]).then(_ => window.location.reload()); - } - - getRestrictionMessageKey(hasChildren: boolean): string { - if(this.isAsPlannedPart) { - return 'routing.notAllowedForAsPlanned'; - } - else if(!hasChildren) { - return 'routing.noChildPartsForInvestigation'; - } - else if(this.roleService.isAdmin()) { - return 'routing.unauthorized'; - } else { - return null; - } - - } - - protected readonly NotificationAction = NotificationAction; - protected readonly Owner = Owner; -} diff --git a/frontend/src/app/modules/shared/modules/part-details/presentation/start-investigation/start-investigation.component.spec.ts b/frontend/src/app/modules/shared/modules/part-details/presentation/start-investigation/start-investigation.component.spec.ts index cd7e06ceaf..fcf221ba4f 100644 --- a/frontend/src/app/modules/shared/modules/part-details/presentation/start-investigation/start-investigation.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/part-details/presentation/start-investigation/start-investigation.component.spec.ts @@ -24,7 +24,6 @@ import { OtherPartsModule } from '@page/other-parts/other-parts.module'; import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; import { PartsModule } from '@page/parts/parts.module'; import { PartsAssembler } from '@shared/assembler/parts.assembler'; -import { PartDetailsModule } from '@shared/modules/part-details/partDetails.module'; import { StaticIdService } from '@shared/service/staticId.service'; import { fireEvent, screen, waitFor } from '@testing-library/angular'; import { getTableCheckbox, renderComponent } from '@tests/test-render.utils'; @@ -42,7 +41,7 @@ describe('StartInvestigationComponent', () => { const renderStartInvestigation = async () => { const { fixture } = await renderComponent(StartInvestigationComponent, { declarations: [ StartInvestigationComponent ], - imports: [ PartDetailsModule, PartsModule, OtherPartsModule, LayoutModule ], + imports: [ PartsModule, OtherPartsModule, LayoutModule ], providers: [ StaticIdService ], }); From fa49c9de7b678dcb8fd23429bccc880c32b4b4b5 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Thu, 21 Mar 2024 16:15:14 +0100 Subject: [PATCH 21/21] chore(parts): 630 refactoring --- .../customer-parts.component.ts | 36 ++++---- .../supplier-parts.component.ts | 38 ++++---- .../parts/presentation/parts.component.ts | 92 +++++++++---------- .../modules/shared/helper/filter-helper.ts | 10 +- 4 files changed, 91 insertions(+), 85 deletions(-) diff --git a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts index 3fd0bbccac..fb0e29730f 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/customer-parts/customer-parts.component.ts @@ -18,22 +18,22 @@ ********************************************************************************/ -import { Component, Input, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Pagination } from '@core/model/pagination.model'; -import { OtherPartsFacade } from '@page/other-parts/core/other-parts.facade'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; -import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; -import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component'; -import { TableSortingUtil } from '@shared/components/table/table-sorting.util'; -import { TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; -import { toAssetFilter, toGlobalSearchAssetFilter } from '@shared/helper/filter-helper'; -import { setMultiSorting } from '@shared/helper/table-helper'; -import { View } from '@shared/model/view.model'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { StaticIdService } from '@shared/service/staticId.service'; -import { Observable } from 'rxjs'; +import {Component, Input, OnDestroy, OnInit, QueryList, ViewChildren} from '@angular/core'; +import {ActivatedRoute, Params, Router} from '@angular/router'; +import {Pagination} from '@core/model/pagination.model'; +import {OtherPartsFacade} from '@page/other-parts/core/other-parts.facade'; +import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; +import {AssetAsBuiltFilter, AssetAsPlannedFilter, Part} from '@page/parts/model/parts.model'; +import {TableType} from '@shared/components/multi-select-autocomplete/table-type.model'; +import {PartsTableComponent} from '@shared/components/parts-table/parts-table.component'; +import {TableSortingUtil} from '@shared/components/table/table-sorting.util'; +import {TableEventConfig, TableHeaderSort} from '@shared/components/table/table.model'; +import {containsAtleastOneFilterEntry, toAssetFilter, toGlobalSearchAssetFilter} from '@shared/helper/filter-helper'; +import {setMultiSorting} from '@shared/helper/table-helper'; +import {View} from '@shared/model/view.model'; +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {StaticIdService} from '@shared/service/staticId.service'; +import {Observable} from 'rxjs'; @Component({ @@ -134,7 +134,7 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { pageSizeValue = pageSize; } - if (this.assetAsBuiltFilter && Object.keys(this.assetAsBuiltFilter).filter(key => this.assetAsBuiltFilter[key].length).length) { + if (this.assetAsBuiltFilter && containsAtleastOneFilterEntry(this.assetAsBuiltFilter)) { this.otherPartsFacade.setCustomerPartsAsBuilt(0, pageSizeValue, this.tableCustomerAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.otherPartsFacade.setCustomerPartsAsBuilt(page, pageSizeValue, this.tableCustomerAsBuiltSortList); @@ -150,7 +150,7 @@ export class CustomerPartsComponent implements OnInit, OnDestroy { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter && Object.keys(this.assetsAsPlannedFilter).filter(key => this.assetsAsPlannedFilter[key].length).length) { + if (this.assetsAsPlannedFilter && containsAtleastOneFilterEntry(this.assetsAsPlannedFilter)) { this.otherPartsFacade.setCustomerPartsAsPlanned(0, pageSizeValue, this.tableCustomerAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.otherPartsFacade.setCustomerPartsAsPlanned(page, pageSizeValue, this.tableCustomerAsPlannedSortList); diff --git a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts index bb3b9e0b53..6e28ba314c 100644 --- a/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts +++ b/frontend/src/app/modules/page/other-parts/presentation/supplier-parts/supplier-parts.component.ts @@ -18,23 +18,23 @@ ********************************************************************************/ -import { Component, Input, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Pagination } from '@core/model/pagination.model'; -import { OtherPartsFacade } from '@page/other-parts/core/other-parts.facade'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; -import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; -import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component'; -import { TableSortingUtil } from '@shared/components/table/table-sorting.util'; -import { TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; -import { toAssetFilter, toGlobalSearchAssetFilter } from '@shared/helper/filter-helper'; -import { setMultiSorting } from '@shared/helper/table-helper'; -import { NotificationType } from '@shared/model/notification.model'; -import { View } from '@shared/model/view.model'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { StaticIdService } from '@shared/service/staticId.service'; -import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import {Component, Input, OnDestroy, OnInit, QueryList, ViewChildren} from '@angular/core'; +import {ActivatedRoute, Params, Router} from '@angular/router'; +import {Pagination} from '@core/model/pagination.model'; +import {OtherPartsFacade} from '@page/other-parts/core/other-parts.facade'; +import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; +import {AssetAsBuiltFilter, AssetAsPlannedFilter, Part} from '@page/parts/model/parts.model'; +import {TableType} from '@shared/components/multi-select-autocomplete/table-type.model'; +import {PartsTableComponent} from '@shared/components/parts-table/parts-table.component'; +import {TableSortingUtil} from '@shared/components/table/table-sorting.util'; +import {TableEventConfig, TableHeaderSort} from '@shared/components/table/table.model'; +import {containsAtleastOneFilterEntry, toAssetFilter, toGlobalSearchAssetFilter} from '@shared/helper/filter-helper'; +import {setMultiSorting} from '@shared/helper/table-helper'; +import {NotificationType} from '@shared/model/notification.model'; +import {View} from '@shared/model/view.model'; +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {StaticIdService} from '@shared/service/staticId.service'; +import {BehaviorSubject, Observable, Subject} from 'rxjs'; @Component({ selector: 'app-supplier-parts', @@ -141,7 +141,7 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetAsBuiltFilter && Object.keys(this.assetAsBuiltFilter).filter(key => this.assetAsBuiltFilter[key].length).length) { + if (this.assetAsBuiltFilter && containsAtleastOneFilterEntry(this.assetAsBuiltFilter)) { this.otherPartsFacade.setSupplierPartsAsBuilt(0, pageSizeValue, this.tableSupplierAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.otherPartsFacade.setSupplierPartsAsBuilt(page, pageSizeValue, this.tableSupplierAsBuiltSortList); @@ -158,7 +158,7 @@ export class SupplierPartsComponent implements OnInit, OnDestroy { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter && Object.keys(this.assetsAsPlannedFilter).filter(key => this.assetsAsPlannedFilter[key].length).length) { + if (this.assetsAsPlannedFilter && containsAtleastOneFilterEntry(this.assetsAsPlannedFilter)) { this.otherPartsFacade.setSupplierPartsAsPlanned(0, pageSizeValue, this.tableSupplierAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.otherPartsFacade.setSupplierPartsAsPlanned(page, pageSizeValue, this.tableSupplierAsPlannedSortList); diff --git a/frontend/src/app/modules/page/parts/presentation/parts.component.ts b/frontend/src/app/modules/page/parts/presentation/parts.component.ts index 4ae5289237..59fd36680d 100644 --- a/frontend/src/app/modules/page/parts/presentation/parts.component.ts +++ b/frontend/src/app/modules/page/parts/presentation/parts.component.ts @@ -19,29 +19,29 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; -import { ActivatedRoute, Params, Router } from '@angular/router'; -import { Pagination } from '@core/model/pagination.model'; -import { RoleService } from '@core/user/role.service'; -import { PartsFacade } from '@page/parts/core/parts.facade'; -import { resetMultiSelectionAutoCompleteComponent } from '@page/parts/core/parts.helper'; -import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; -import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model'; -import { BomLifecycleSize } from '@shared/components/bom-lifecycle-activator/bom-lifecycle-activator.model'; -import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; -import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component'; -import { TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; -import { ToastService } from '@shared/components/toasts/toast.service'; -import { toAssetFilter, toGlobalSearchAssetFilter } from '@shared/helper/filter-helper'; -import { setMultiSorting } from '@shared/helper/table-helper'; -import { NotificationType } from '@shared/model/notification.model'; -import { View } from '@shared/model/view.model'; -import { PartDetailsFacade } from '@shared/modules/part-details/core/partDetails.facade'; -import { BomLifecycleSettingsService, UserSettingView } from '@shared/service/bom-lifecycle-settings.service'; -import { StaticIdService } from '@shared/service/staticId.service'; -import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; -import { map } from 'rxjs/operators'; +import {AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChildren} from '@angular/core'; +import {FormControl, FormGroup} from '@angular/forms'; +import {ActivatedRoute, Params, Router} from '@angular/router'; +import {Pagination} from '@core/model/pagination.model'; +import {RoleService} from '@core/user/role.service'; +import {PartsFacade} from '@page/parts/core/parts.facade'; +import {resetMultiSelectionAutoCompleteComponent} from '@page/parts/core/parts.helper'; +import {MainAspectType} from '@page/parts/model/mainAspectType.enum'; +import {AssetAsBuiltFilter, AssetAsPlannedFilter, Part} from '@page/parts/model/parts.model'; +import {BomLifecycleSize} from '@shared/components/bom-lifecycle-activator/bom-lifecycle-activator.model'; +import {TableType} from '@shared/components/multi-select-autocomplete/table-type.model'; +import {PartsTableComponent} from '@shared/components/parts-table/parts-table.component'; +import {TableEventConfig, TableHeaderSort} from '@shared/components/table/table.model'; +import {ToastService} from '@shared/components/toasts/toast.service'; +import {containsAtleastOneFilterEntry, toAssetFilter, toGlobalSearchAssetFilter} from '@shared/helper/filter-helper'; +import {setMultiSorting} from '@shared/helper/table-helper'; +import {NotificationType} from '@shared/model/notification.model'; +import {View} from '@shared/model/view.model'; +import {PartDetailsFacade} from '@shared/modules/part-details/core/partDetails.facade'; +import {BomLifecycleSettingsService, UserSettingView} from '@shared/service/bom-lifecycle-settings.service'; +import {StaticIdService} from '@shared/service/staticId.service'; +import {BehaviorSubject, combineLatest, Observable, Subject} from 'rxjs'; +import {map} from 'rxjs/operators'; @Component({ @@ -94,10 +94,10 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { this.tableAsPlannedSortList = []; window.addEventListener('keydown', (event) => { - this.ctrlKeyState = setMultiSorting(event); + this.ctrlKeyState = setMultiSorting(event); }); window.addEventListener('keyup', (event) => { - this.ctrlKeyState = setMultiSorting(event); + this.ctrlKeyState = setMultiSorting(event); }); } @@ -108,6 +108,7 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { assetAsBuiltFilter: AssetAsBuiltFilter; assetsAsPlannedFilter: AssetAsPlannedFilter; + public ngOnInit(): void { this.partsFacade.setPartsAsBuilt(); this.partsFacade.setPartsAsPlanned(); @@ -143,14 +144,14 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { } refreshPartsOnPublish(message: string) { - if(message) { - this.toastService.error(message); - } else { - this.toastService.success("requestPublishAssets.success") - this.partsFacade.setPartsAsBuilt(); - this.partsFacade.setPartsAsPlanned(); - this.partsTableComponents.map(component => component.clearAllRows()) - } + if (message) { + this.toastService.error(message); + } else { + this.toastService.success("requestPublishAssets.success") + this.partsFacade.setPartsAsBuilt(); + this.partsFacade.setPartsAsPlanned(); + this.partsTableComponents.map(component => component.clearAllRows()) + } } private resetFilterAndShowToast() { @@ -171,8 +172,8 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { public onSelectItem($event: Record): void { this.partDetailsFacade.selectedPart = $event as unknown as Part; let tableData = {}; - for(let component of this.partsTableComponents) { - tableData[component.tableType+"_PAGE"] = component.pageIndex; + for (let component of this.partsTableComponents) { + tableData[component.tableType + "_PAGE"] = component.pageIndex; } this.router.navigate([`parts/${$event?.id}`], {queryParams: tableData}) } @@ -184,8 +185,7 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { if (pageSize !== 0) { pageSizeValue = pageSize; } - //if any filter is applied - if (this.assetAsBuiltFilter && Object.keys(this.assetAsBuiltFilter).filter(key => this.assetAsBuiltFilter[key].length).length) { + if (this.assetAsBuiltFilter && containsAtleastOneFilterEntry(this.assetAsBuiltFilter)) { this.partsFacade.setPartsAsBuilt(0, pageSizeValue, this.tableAsBuiltSortList, toAssetFilter(this.assetAsBuiltFilter, true)); } else { this.partsFacade.setPartsAsBuilt(page, pageSizeValue, this.tableAsBuiltSortList); @@ -194,15 +194,15 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { } public onAsPlannedTableConfigChange({page, pageSize, sorting}: TableEventConfig): void { - this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); - this.currentPartTablePage['AS_PLANNED_OWN_PAGE'] = page; + this.setTableSortingList(sorting, MainAspectType.AS_PLANNED); + this.currentPartTablePage['AS_PLANNED_OWN_PAGE'] = page; let pageSizeValue = this.DEFAULT_PAGE_SIZE; if (pageSize !== 0) { pageSizeValue = pageSize; } - if (this.assetsAsPlannedFilter && Object.keys(this.assetsAsPlannedFilter).filter(key => this.assetsAsPlannedFilter[key].length).length) { + if (this.assetsAsPlannedFilter && containsAtleastOneFilterEntry(this.assetsAsPlannedFilter)) { this.partsFacade.setPartsAsPlanned(0, pageSizeValue, this.tableAsPlannedSortList, toAssetFilter(this.assetsAsPlannedFilter, true)); } else { this.partsFacade.setPartsAsPlanned(page, pageSizeValue, this.tableAsPlannedSortList); @@ -260,12 +260,12 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit { } } - private setupPageByUrlParams(params: Params ) { - if(!params) { - return; - } - this.onAsBuiltTableConfigChange({page: params['AS_BUILT_OWN_PAGE'], pageSize: 50, sorting: null}); - this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_OWN_PAGE'], pageSize: 50, sorting: null}); + private setupPageByUrlParams(params: Params) { + if (!params) { + return; + } + this.onAsBuiltTableConfigChange({page: params['AS_BUILT_OWN_PAGE'], pageSize: 50, sorting: null}); + this.onAsPlannedTableConfigChange({page: params['AS_PLANNED_OWN_PAGE'], pageSize: 50, sorting: null}); } protected readonly UserSettingView = UserSettingView; diff --git a/frontend/src/app/modules/shared/helper/filter-helper.ts b/frontend/src/app/modules/shared/helper/filter-helper.ts index 3a4b4e8a62..7b3e7ca926 100644 --- a/frontend/src/app/modules/shared/helper/filter-helper.ts +++ b/frontend/src/app/modules/shared/helper/filter-helper.ts @@ -16,14 +16,14 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import {HttpParams} from '@angular/common/http'; +import { HttpParams } from '@angular/common/http'; import { AssetAsBuiltFilter, AssetAsPlannedFilter, FilterOperator, getFilterOperatorValue, } from '@page/parts/model/parts.model'; -import {NotificationFilter} from '../../../mocks/services/investigations-mock/investigations.model'; +import { NotificationFilter } from '../../../mocks/services/investigations-mock/investigations.model'; export const DATE_FILTER_KEYS = [ 'manufacturingDate', 'functionValidFrom', 'functionValidUntil', 'validityPeriodFrom', 'validityPeriodTo', 'createdDate', 'targetDate', 'creationDate', 'endDate' ]; @@ -200,3 +200,9 @@ export function provideFilterListForNotifications( filter?: NotificationFilter, return filterList; } + +export function containsAtleastOneFilterEntry(filter: AssetAsBuiltFilter | AssetAsPlannedFilter): boolean { + return Object.keys(filter) + .filter(key => filter[key].length) + .length > 0 +}