From 32042cd4e683510918c16c99d054b1ce2fa96baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 20 Feb 2026 14:41:31 +0100 Subject: [PATCH 1/8] Refactor offcanvas --- .../delete-offcanvas.component.html | 18 ++++++ .../delete-offcanvas.component.ts | 60 +++++++++++++++++++ .../administration-page.component.html | 30 ++-------- .../administration-page.component.ts | 34 ++++------- 4 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html create mode 100644 src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts diff --git a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html new file mode 100644 index 00000000..2848f11f --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html @@ -0,0 +1,18 @@ + +
+
+ +
+ @if (targetObjectName && targetObjectId) { + +
+
+ {{ targetObjectId }} +
+ } +
+
diff --git a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts new file mode 100644 index 00000000..ebacec29 --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts @@ -0,0 +1,60 @@ +import { Component, EventEmitter, inject, Input, Output, TemplateRef, ViewChild } from '@angular/core'; +import { ActionButtonComponent } from '../action-button/action-button.component'; +import { DeleteWidgetComponent } from '../delete-widget/delete-widget.component'; +import { TranslateDirective } from '@ngx-translate/core'; +import { NgbOffcanvas, NgbOffcanvasRef } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'tp-delete-offcanvas', + imports: [ActionButtonComponent, DeleteWidgetComponent, TranslateDirective], + templateUrl: './delete-offcanvas.component.html', + styleUrl: './delete-offcanvas.component.scss' +}) +export class DeleteOffcanvasComponent { + @Input() + public translationKey: string = ''; + + @Output() + public deleteClick = new EventEmitter(); + + @ViewChild('offcanvasTemplate') + protected offcanvasTemplate!: TemplateRef; + + protected targetObjectName?: string; + protected targetObjectId?: string; + + private readonly offcanvasService = inject(NgbOffcanvas); + private currentOffcanvas?: NgbOffcanvasRef; + + public show(targetObjectId: string, targetObjectName: string): void { + if (this.currentOffcanvas) { + return; + } + + this.targetObjectName = targetObjectName; + this.targetObjectId = targetObjectId; + + this.currentOffcanvas = this.offcanvasService.open(this.offcanvasTemplate, { position: 'end' }); + } + + public close(): void { + this.currentOffcanvas?.close(); + this.currentOffcanvas = undefined; + this.targetObjectId = undefined; + this.targetObjectName = undefined; + } + + protected confirmed(): void { + // Remember the id because close() will reset the field + const id = this.targetObjectId; + + this.close(); + + if (!id) { + return; + } + this.deleteClick.emit(id); + } + + // TODO: Close currently open offcanvas on confirm/abort +} diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html index 0307b123..1bcdbbee 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html @@ -53,7 +53,7 @@ [type]="'outline-danger'" [mode]="'IconOnly'" [disabled]="isCurrentUser" - (buttonClick)="deleteButtonClicked(user.id, deleteConfirmationCanvas)" /> + (buttonClick)="deleteConfirmationCanvas.show(user.id, user.userName)" /> @@ -75,7 +75,7 @@ [title]="'Portal.General.Cancel'" [icon]="'x-circle'" [type]="'outline-secondary'" - (buttonClick)="currentOffcanvas?.close()" /> + (buttonClick)="currentEditOffcanvas?.close()" /> @if (userSelectedForEditing) {
@@ -170,25 +170,7 @@ - -
-
- -
- @if (userSelectedForDeletion) { - -
-
- {{ userSelectedForDeletion.id }} -
- } -
-
+ diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts index cf92bace..6800251e 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, TemplateRef } from '@angular/core'; +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NgbOffcanvas, NgbOffcanvasRef } from '@ng-bootstrap/ng-bootstrap'; import { switchMap } from 'rxjs'; @@ -13,7 +13,6 @@ import { filter } from 'rxjs/operators'; import { ActionButtonComponent } from '../../components/action-button/action-button.component'; import { BadgeComponent } from '../../components/badge/badge.component'; import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'; -import { DeleteWidgetComponent } from '../../components/delete-widget/delete-widget.component'; import { TranslateDatePipe } from '../../pipes/translate-date.pipe'; import { NgClass } from '@angular/common'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; @@ -24,6 +23,7 @@ import { getUsers } from '../../../api/fn/users/get-users'; import { UpdateUserEndpointRequest } from '../../../api/models/update-user-endpoint-request'; import { updateUser } from '../../../api/fn/users/update-user'; import { deleteUser } from '../../../api/fn/users/delete-user'; +import { DeleteOffcanvasComponent } from '../../components/delete-offcanvas/delete-offcanvas.component'; @Component({ templateUrl: './administration-page.component.html', @@ -34,22 +34,24 @@ import { deleteUser } from '../../../api/fn/users/delete-user'; RouterLink, BadgeComponent, TranslateDirective, - DeleteWidgetComponent, TranslatePipe, TranslateDatePipe, NgClass, ReactiveFormsModule, - AlertComponent + AlertComponent, + DeleteOffcanvasComponent ] }) export class AdministrationPageComponent implements OnInit { + @ViewChild('deleteConfirmationCanvas') + protected deleteConfirmationCanvas?: DeleteOffcanvasComponent; + protected loadingState: LoadingState = { isLoading: true }; protected users: UserDto[] = []; protected currentUserId: string = ''; - protected userSelectedForDeletion?: UserDto; protected userSelectedForEditing?: UserDto; - protected currentOffcanvas?: NgbOffcanvasRef; + protected currentEditOffcanvas?: NgbOffcanvasRef; protected editUserForm = new FormGroup({ userName: new FormControl('', { nonNullable: true, validators: [Validators.required] }), @@ -85,7 +87,8 @@ export class AdministrationPageComponent implements OnInit { ) .subscribe({ next: () => { - this.currentOffcanvas?.close(); + this.currentEditOffcanvas?.close(); + this.deleteConfirmationCanvas?.close(); } }); } @@ -126,7 +129,7 @@ export class AdministrationPageComponent implements OnInit { this.editUserForm.get('isAdministrator')!.enable(); } - this.currentOffcanvas = this.offcanvasService.open(template, { position: 'end' }); + this.currentEditOffcanvas = this.offcanvasService.open(template, { position: 'end' }); } } @@ -143,7 +146,7 @@ export class AdministrationPageComponent implements OnInit { return; } - this.currentOffcanvas?.close(); + this.currentEditOffcanvas?.close(); this.loadingState = { isLoading: true }; const formValue = this.editUserForm.getRawValue(); @@ -175,20 +178,7 @@ export class AdministrationPageComponent implements OnInit { }); } - protected deleteButtonClicked(id: string, template: TemplateRef): void { - if (id === this.currentUserId) { - return; - } - - this.userSelectedForDeletion = this.users.find((x) => x.id === id); - - if (this.userSelectedForDeletion) { - this.currentOffcanvas = this.offcanvasService.open(template, { position: 'end' }); - } - } - protected deleteConfirmed(userId: string): void { - this.currentOffcanvas?.close(); this.loadingState = { isLoading: true }; this.turnierplanApi From b9ad24a629844e4a4b82f1bfa1fb010f9c739171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 20 Feb 2026 14:48:59 +0100 Subject: [PATCH 2/8] Use widget for api keys also --- src/Turnierplan.App/Client/src/app/i18n/de.ts | 13 +++++++++---- .../delete-offcanvas/delete-offcanvas.component.ts | 3 +-- .../administration-page.component.html | 7 ++----- .../administration-page.component.ts | 2 +- .../view-organization.component.html | 11 ++++++++++- .../view-organization.component.ts | 8 +++++--- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/Turnierplan.App/Client/src/app/i18n/de.ts b/src/Turnierplan.App/Client/src/app/i18n/de.ts index e7e70a71..4a322f07 100644 --- a/src/Turnierplan.App/Client/src/app/i18n/de.ts +++ b/src/Turnierplan.App/Client/src/app/i18n/de.ts @@ -131,7 +131,7 @@ export const de = { }, DeleteUser: { Title: 'Benutzer löschen', - Info: 'Wenn Sie eine Benutzer löschen, werden die Organisationen des Benutzers nicht mitgelöscht und bleiben weiterhin für alle Administratoren sichtbar.', + Info: 'Wenn Sie einen Benutzer löschen, werden die Organisationen des Benutzers nicht mitgelöscht und bleiben weiterhin für alle Administratoren sichtbar.', IdConfirmation: 'Benutzer-ID:', SuccessToast: { Title: 'Benutzer wurde gelöscht', @@ -245,9 +245,14 @@ export const de = { Expired: 'Dieser API-Schlüssel ist abgelaufen', NoApiKeys: 'Keine API-Schlüssel vorhanden', ViewCharts: 'Aufrufstatistik', - DeleteToast: { - Title: 'API-Schlüssel wurde gelöscht', - Message: 'Der API-Schlüssel wurde gelöscht und kann nun nicht mehr für Anfragen verwendet werden.' + Delete: { + Title: 'API-Schlüssel löschen', + Info: 'Wenn Sie eine API-Schlüssel löschen, kann dieser Schlüssel nicht mehr für neue Anfragen verwendet werden. Außerdem sind alle vorherigen Anfragen, welche mit diesem Schlüssel getätigt wurden, nicht mehr nachvollziehbar.', + IdConfirmation: 'API-Schlüssel ID:', + SuccessToast: { + Title: 'API-Schlüssel wurde gelöscht', + Message: 'Der API-Schlüssel wurde gelöscht und kann nun nicht mehr für Anfragen verwendet werden.' + } } }, RbacWidget: { diff --git a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts index ebacec29..50d1ea1c 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts @@ -7,8 +7,7 @@ import { NgbOffcanvas, NgbOffcanvasRef } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'tp-delete-offcanvas', imports: [ActionButtonComponent, DeleteWidgetComponent, TranslateDirective], - templateUrl: './delete-offcanvas.component.html', - styleUrl: './delete-offcanvas.component.scss' + templateUrl: './delete-offcanvas.component.html' }) export class DeleteOffcanvasComponent { @Input() diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html index 1bcdbbee..0f87ec99 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html @@ -53,7 +53,7 @@ [type]="'outline-danger'" [mode]="'IconOnly'" [disabled]="isCurrentUser" - (buttonClick)="deleteConfirmationCanvas.show(user.id, user.userName)" /> + (buttonClick)="deleteUserOffcanvas.show(user.id, user.userName)" /> @@ -170,7 +170,4 @@ - + diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts index 6800251e..e78b9392 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts @@ -43,7 +43,7 @@ import { DeleteOffcanvasComponent } from '../../components/delete-offcanvas/dele ] }) export class AdministrationPageComponent implements OnInit { - @ViewChild('deleteConfirmationCanvas') + @ViewChild('deleteUserOffcanvas') protected deleteConfirmationCanvas?: DeleteOffcanvasComponent; protected loadingState: LoadingState = { isLoading: true }; diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.html b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.html index d79f4e34..b17db870 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.html @@ -217,7 +217,11 @@ @if (writeAllowed) { - + } @@ -267,3 +271,8 @@ } + + diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts index 41634a98..e7a4b76f 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts @@ -46,6 +46,7 @@ import { ImageManagerComponent } from '../../components/image-manager/image-mana import { getImages } from '../../../api/fn/images/get-images'; import { GetImagesEndpointResponse } from '../../../api/models/get-images-endpoint-response'; import { FileSizePipe } from '../../pipes/file-size.pipe'; +import { DeleteOffcanvasComponent } from '../../components/delete-offcanvas/delete-offcanvas.component'; @Component({ templateUrl: './view-organization.component.html', @@ -75,7 +76,8 @@ import { FileSizePipe } from '../../pipes/file-size.pipe'; IdWidgetComponent, E2eDirective, ImageManagerComponent, - FileSizePipe + FileSizePipe, + DeleteOffcanvasComponent ] }) export class ViewOrganizationComponent implements OnInit, OnDestroy { @@ -299,8 +301,8 @@ export class ViewOrganizationComponent implements OnInit, OnDestroy { next: () => { this.notificationService.showNotification( 'info', - 'Portal.ViewOrganization.ApiKeys.DeleteToast.Title', - 'Portal.ViewOrganization.ApiKeys.DeleteToast.Message' + 'Portal.ViewOrganization.ApiKeys.Delete.SuccessToast.Title', + 'Portal.ViewOrganization.ApiKeys.Delete.SuccessToast.Message' ); }, error: (error) => { From c02ea93086e4e04941ab54103ec88514dd15d6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 20 Feb 2026 14:49:18 +0100 Subject: [PATCH 3/8] Cleanup --- .../pages/view-organization/view-organization.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts index e7a4b76f..2d177bff 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts @@ -20,7 +20,6 @@ import { VenueTileComponent } from '../../components/venue-tile/venue-tile.compo import { NgClass, AsyncPipe } from '@angular/common'; import { TooltipIconComponent } from '../../components/tooltip-icon/tooltip-icon.component'; import { FormsModule } from '@angular/forms'; -import { DeleteButtonComponent } from '../../components/delete-button/delete-button.component'; import { ApiKeyUsageComponent } from '../../components/api-key-usage/api-key-usage.component'; import { RbacWidgetComponent } from '../../components/rbac-widget/rbac-widget.component'; import { DeleteWidgetComponent } from '../../components/delete-widget/delete-widget.component'; @@ -66,7 +65,6 @@ import { DeleteOffcanvasComponent } from '../../components/delete-offcanvas/dele NgClass, TooltipIconComponent, FormsModule, - DeleteButtonComponent, ApiKeyUsageComponent, RbacWidgetComponent, DeleteWidgetComponent, From 4cd6409193097d39323b41c14f632222d41cda33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 20 Feb 2026 15:01:22 +0100 Subject: [PATCH 4/8] Fix --- .../components/delete-offcanvas/delete-offcanvas.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts index 50d1ea1c..f3dccf17 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts @@ -34,11 +34,11 @@ export class DeleteOffcanvasComponent { this.targetObjectId = targetObjectId; this.currentOffcanvas = this.offcanvasService.open(this.offcanvasTemplate, { position: 'end' }); + this.currentOffcanvas.hidden.subscribe(() => (this.currentOffcanvas = undefined)); } public close(): void { this.currentOffcanvas?.close(); - this.currentOffcanvas = undefined; this.targetObjectId = undefined; this.targetObjectName = undefined; } @@ -52,6 +52,7 @@ export class DeleteOffcanvasComponent { if (!id) { return; } + this.deleteClick.emit(id); } From 2629caa2a644cbf7ff912169861ce940c327d43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 20 Feb 2026 17:13:31 +0100 Subject: [PATCH 5/8] Remove old todo --- .../components/delete-offcanvas/delete-offcanvas.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts index f3dccf17..54218eeb 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts @@ -55,6 +55,4 @@ export class DeleteOffcanvasComponent { this.deleteClick.emit(id); } - - // TODO: Close currently open offcanvas on confirm/abort } From 3f847681e2c38bb471a55983f196f609b9743349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Fri, 20 Feb 2026 17:14:54 +0100 Subject: [PATCH 6/8] Add todo for later --- .../pages/view-organization/view-organization.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts index 2d177bff..cf164bea 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts @@ -79,6 +79,7 @@ import { DeleteOffcanvasComponent } from '../../components/delete-offcanvas/dele ] }) export class ViewOrganizationComponent implements OnInit, OnDestroy { + // TODO: Close api key delete offcanvas on route change public static readonly imagesPageId = 5; private static readonly venuesPageId = 1; From fa3c27f5d17c2ffd57cf389ff03a88bd9d649f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Feb 2026 09:13:10 +0100 Subject: [PATCH 7/8] Refactor into offcanvas wrapper component --- .../delete-offcanvas.component.html | 29 ++- .../delete-offcanvas.component.ts | 36 +--- .../offcanvas-wrapper.component.html | 8 + .../offcanvas-wrapper.component.ts | 48 +++++ .../administration-page.component.html | 177 +++++++++--------- .../administration-page.component.ts | 41 ++-- 6 files changed, 177 insertions(+), 162 deletions(-) create mode 100644 src/Turnierplan.App/Client/src/app/portal/components/offcanvas-wrapper/offcanvas-wrapper.component.html create mode 100644 src/Turnierplan.App/Client/src/app/portal/components/offcanvas-wrapper/offcanvas-wrapper.component.ts diff --git a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html index 2848f11f..8da48681 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html @@ -1,18 +1,13 @@ - -
-
- + + @if (targetObjectName && targetObjectId) { + +
+
+ {{ targetObjectId }}
- @if (targetObjectName && targetObjectId) { - -
-
- {{ targetObjectId }} -
- } -
- + } + diff --git a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts index 54218eeb..4b4e35d3 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts @@ -1,12 +1,11 @@ -import { Component, EventEmitter, inject, Input, Output, TemplateRef, ViewChild } from '@angular/core'; -import { ActionButtonComponent } from '../action-button/action-button.component'; +import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { DeleteWidgetComponent } from '../delete-widget/delete-widget.component'; import { TranslateDirective } from '@ngx-translate/core'; -import { NgbOffcanvas, NgbOffcanvasRef } from '@ng-bootstrap/ng-bootstrap'; +import { OffcanvasWrapperComponent } from '../offcanvas-wrapper/offcanvas-wrapper.component'; @Component({ selector: 'tp-delete-offcanvas', - imports: [ActionButtonComponent, DeleteWidgetComponent, TranslateDirective], + imports: [DeleteWidgetComponent, TranslateDirective, OffcanvasWrapperComponent], templateUrl: './delete-offcanvas.component.html' }) export class DeleteOffcanvasComponent { @@ -16,43 +15,28 @@ export class DeleteOffcanvasComponent { @Output() public deleteClick = new EventEmitter(); - @ViewChild('offcanvasTemplate') - protected offcanvasTemplate!: TemplateRef; + @ViewChild('offcanvasWrapper') + protected offcanvasWrapper!: OffcanvasWrapperComponent; protected targetObjectName?: string; protected targetObjectId?: string; - private readonly offcanvasService = inject(NgbOffcanvas); - private currentOffcanvas?: NgbOffcanvasRef; - public show(targetObjectId: string, targetObjectName: string): void { - if (this.currentOffcanvas) { + if (this.offcanvasWrapper.isOpen) { return; } this.targetObjectName = targetObjectName; this.targetObjectId = targetObjectId; - this.currentOffcanvas = this.offcanvasService.open(this.offcanvasTemplate, { position: 'end' }); - this.currentOffcanvas.hidden.subscribe(() => (this.currentOffcanvas = undefined)); - } - - public close(): void { - this.currentOffcanvas?.close(); - this.targetObjectId = undefined; - this.targetObjectName = undefined; + this.offcanvasWrapper.show(); } protected confirmed(): void { - // Remember the id because close() will reset the field - const id = this.targetObjectId; + this.offcanvasWrapper.close(); - this.close(); - - if (!id) { - return; + if (this.targetObjectId) { + this.deleteClick.emit(this.targetObjectId); } - - this.deleteClick.emit(id); } } diff --git a/src/Turnierplan.App/Client/src/app/portal/components/offcanvas-wrapper/offcanvas-wrapper.component.html b/src/Turnierplan.App/Client/src/app/portal/components/offcanvas-wrapper/offcanvas-wrapper.component.html new file mode 100644 index 00000000..1b5c427b --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/components/offcanvas-wrapper/offcanvas-wrapper.component.html @@ -0,0 +1,8 @@ + +
+
+ +
+ +
+
diff --git a/src/Turnierplan.App/Client/src/app/portal/components/offcanvas-wrapper/offcanvas-wrapper.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/offcanvas-wrapper/offcanvas-wrapper.component.ts new file mode 100644 index 00000000..e4f79bb9 --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/components/offcanvas-wrapper/offcanvas-wrapper.component.ts @@ -0,0 +1,48 @@ +import { Component, inject, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; +import { ActionButtonComponent } from '../action-button/action-button.component'; +import { NgbOffcanvas, NgbOffcanvasRef } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'tp-offcanvas-wrapper', + imports: [ActionButtonComponent], + templateUrl: './offcanvas-wrapper.component.html' +}) +export class OffcanvasWrapperComponent implements OnDestroy { + @ViewChild('offcanvasTemplate') + protected offcanvasTemplate!: TemplateRef; + + private readonly offcanvasService = inject(NgbOffcanvas); + private offcanvasRef?: NgbOffcanvasRef; + + public get isOpen(): boolean { + return this.offcanvasRef !== undefined; + } + + public ngOnDestroy(): void { + // Ensure the offcanvas is closed when this component is destroyed + this.close(); + } + + public show(): void { + if (this.offcanvasRef) { + return; + } + + this.offcanvasRef = this.offcanvasService.open(this.offcanvasTemplate, { position: 'end' }); + + this.offcanvasRef.hidden.subscribe({ + next: () => { + this.offcanvasRef = undefined; + } + }); + } + + public close(): void { + if (!this.offcanvasRef) { + return; + } + + // This will cause the 'hidden' observable to emit which will set the variable to undefined + this.offcanvasRef.close(); + } +} diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html index 0f87ec99..2bca99bb 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.html @@ -46,7 +46,7 @@ [icon]="'pencil'" [type]="'outline-secondary'" [mode]="'IconOnly'" - (buttonClick)="editButtonClicked(user.id, editUserCanvas)" /> + (buttonClick)="editButtonClicked(user.id)" />
- -
-
- -
- @if (userSelectedForEditing) { -
-
+ + @if (userSelectedForEditing) { +
+
-
-
- @let userNameControl = editUserForm.get('userName')!; - - -
-
+ +
+ @let userNameControl = editUserForm.get('userName')!; + + +
+
-
- @let fullNameControl = editUserForm.get('fullName')!; - - -
-
+
+ @let fullNameControl = editUserForm.get('fullName')!; + + +
+
-
- @let eMailNameControl = editUserForm.get('eMail')!; - - -
-
+
+ @let eMailNameControl = editUserForm.get('eMail')!; + + +
+
-
- - -
+
+ + +
- @let isAdministratorControl = editUserForm.get('isAdministrator')!; - @if (!userSelectedForEditing.isAdministrator && isAdministratorControl.value) { - - } + @let isAdministratorControl = editUserForm.get('isAdministrator')!; + @if (!userSelectedForEditing.isAdministrator && isAdministratorControl.value) { + + } -
+
+ + +
+ + @let updatePasswordControl = editUserForm.get('updatePassword')!; + @if (updatePasswordControl.value) { +
+ @let passwordControl = editUserForm.get('password')!; + - + class="form-control" + id="password" + type="password" + formControlName="password" + [ngClass]="passwordControl.dirty ? (passwordControl.invalid ? 'is-invalid' : 'is-valid') : ''" /> +
+ } + - @let updatePasswordControl = editUserForm.get('updatePassword')!; - @if (updatePasswordControl.value) { -
- @let passwordControl = editUserForm.get('password')!; - - -
-
- } - - -
- -
- } -
- +
+ +
+ } +
diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts index e78b9392..aeee1981 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/administration-page/administration-page.component.ts @@ -1,6 +1,5 @@ -import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { NgbOffcanvas, NgbOffcanvasRef } from '@ng-bootstrap/ng-bootstrap'; import { switchMap } from 'rxjs'; import { AuthenticationService } from '../../../core/services/authentication.service'; @@ -8,8 +7,7 @@ import { NotificationService } from '../../../core/services/notification.service import { PageFrameNavigationTab, PageFrameComponent } from '../../components/page-frame/page-frame.component'; import { LoadingState, LoadingStateDirective } from '../../directives/loading-state.directive'; import { TitleService } from '../../services/title.service'; -import { NavigationStart, Router, RouterLink } from '@angular/router'; -import { filter } from 'rxjs/operators'; +import { RouterLink } from '@angular/router'; import { ActionButtonComponent } from '../../components/action-button/action-button.component'; import { BadgeComponent } from '../../components/badge/badge.component'; import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'; @@ -24,6 +22,7 @@ import { UpdateUserEndpointRequest } from '../../../api/models/update-user-endpo import { updateUser } from '../../../api/fn/users/update-user'; import { deleteUser } from '../../../api/fn/users/delete-user'; import { DeleteOffcanvasComponent } from '../../components/delete-offcanvas/delete-offcanvas.component'; +import { OffcanvasWrapperComponent } from '../../components/offcanvas-wrapper/offcanvas-wrapper.component'; @Component({ templateUrl: './administration-page.component.html', @@ -39,19 +38,19 @@ import { DeleteOffcanvasComponent } from '../../components/delete-offcanvas/dele NgClass, ReactiveFormsModule, AlertComponent, - DeleteOffcanvasComponent + DeleteOffcanvasComponent, + OffcanvasWrapperComponent ] }) export class AdministrationPageComponent implements OnInit { - @ViewChild('deleteUserOffcanvas') - protected deleteConfirmationCanvas?: DeleteOffcanvasComponent; + @ViewChild('editUserOffcanvas') + protected editUserOffcanvas!: OffcanvasWrapperComponent; protected loadingState: LoadingState = { isLoading: true }; protected users: UserDto[] = []; protected currentUserId: string = ''; protected userSelectedForEditing?: UserDto; - protected currentEditOffcanvas?: NgbOffcanvasRef; protected editUserForm = new FormGroup({ userName: new FormControl('', { nonNullable: true, validators: [Validators.required] }), @@ -74,23 +73,9 @@ export class AdministrationPageComponent implements OnInit { private readonly turnierplanApi: TurnierplanApi, private readonly titleService: TitleService, private readonly authenticationService: AuthenticationService, - private readonly offcanvasService: NgbOffcanvas, - private readonly notificationService: NotificationService, - private readonly router: Router + private readonly notificationService: NotificationService ) { this.authenticationService.authentication$.pipe(takeUntilDestroyed()).subscribe((userInfo) => (this.currentUserId = userInfo.id)); - - this.router.events - .pipe( - takeUntilDestroyed(), - filter((event) => event instanceof NavigationStart) - ) - .subscribe({ - next: () => { - this.currentEditOffcanvas?.close(); - this.deleteConfirmationCanvas?.close(); - } - }); } public ngOnInit(): void { @@ -107,7 +92,11 @@ export class AdministrationPageComponent implements OnInit { }); } - protected editButtonClicked(id: string, template: TemplateRef): void { + protected editButtonClicked(id: string): void { + if (this.editUserOffcanvas.isOpen) { + return; + } + this.userSelectedForEditing = this.users.find((x) => x.id === id); if (this.userSelectedForEditing) { @@ -129,7 +118,7 @@ export class AdministrationPageComponent implements OnInit { this.editUserForm.get('isAdministrator')!.enable(); } - this.currentEditOffcanvas = this.offcanvasService.open(template, { position: 'end' }); + this.editUserOffcanvas.show(); } } @@ -146,7 +135,7 @@ export class AdministrationPageComponent implements OnInit { return; } - this.currentEditOffcanvas?.close(); + this.editUserOffcanvas.close(); this.loadingState = { isLoading: true }; const formValue = this.editUserForm.getRawValue(); From 82ce7a8220a73dd8b20a810b2a4a3a76722a9bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sat, 21 Feb 2026 09:23:08 +0100 Subject: [PATCH 8/8] Delete resolved todo --- .../pages/view-organization/view-organization.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts index cf164bea..2d177bff 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-organization/view-organization.component.ts @@ -79,7 +79,6 @@ import { DeleteOffcanvasComponent } from '../../components/delete-offcanvas/dele ] }) export class ViewOrganizationComponent implements OnInit, OnDestroy { - // TODO: Close api key delete offcanvas on route change public static readonly imagesPageId = 5; private static readonly venuesPageId = 1;