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.html b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html new file mode 100644 index 00000000..8da48681 --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.html @@ -0,0 +1,13 @@ + + @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..4b4e35d3 --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/components/delete-offcanvas/delete-offcanvas.component.ts @@ -0,0 +1,42 @@ +import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { DeleteWidgetComponent } from '../delete-widget/delete-widget.component'; +import { TranslateDirective } from '@ngx-translate/core'; +import { OffcanvasWrapperComponent } from '../offcanvas-wrapper/offcanvas-wrapper.component'; + +@Component({ + selector: 'tp-delete-offcanvas', + imports: [DeleteWidgetComponent, TranslateDirective, OffcanvasWrapperComponent], + templateUrl: './delete-offcanvas.component.html' +}) +export class DeleteOffcanvasComponent { + @Input() + public translationKey: string = ''; + + @Output() + public deleteClick = new EventEmitter(); + + @ViewChild('offcanvasWrapper') + protected offcanvasWrapper!: OffcanvasWrapperComponent; + + protected targetObjectName?: string; + protected targetObjectId?: string; + + public show(targetObjectId: string, targetObjectName: string): void { + if (this.offcanvasWrapper.isOpen) { + return; + } + + this.targetObjectName = targetObjectName; + this.targetObjectId = targetObjectId; + + this.offcanvasWrapper.show(); + } + + protected confirmed(): void { + this.offcanvasWrapper.close(); + + if (this.targetObjectId) { + this.deleteClick.emit(this.targetObjectId); + } + } +} 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 0307b123..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,14 +46,14 @@ [icon]="'pencil'" [type]="'outline-secondary'" [mode]="'IconOnly'" - (buttonClick)="editButtonClicked(user.id, editUserCanvas)" /> + (buttonClick)="editButtonClicked(user.id)" />
+ (buttonClick)="deleteUserOffcanvas.show(user.id, user.userName)" />
@@ -68,127 +68,97 @@ - -
-
- -
- @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')!; - - -
-
- } - - -
- -
- } -
- - - -
-
+
+ [type]="'outline-success'" + [icon]="'floppy'" + [title]="'Portal.General.Save'" + [disabled]="editUserForm.invalid" + (buttonClick)="editConfirmed(userSelectedForEditing.id)" />
- @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..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 } 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,12 +7,10 @@ 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'; -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 +21,8 @@ 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'; +import { OffcanvasWrapperComponent } from '../../components/offcanvas-wrapper/offcanvas-wrapper.component'; @Component({ templateUrl: './administration-page.component.html', @@ -34,22 +33,24 @@ import { deleteUser } from '../../../api/fn/users/delete-user'; RouterLink, BadgeComponent, TranslateDirective, - DeleteWidgetComponent, TranslatePipe, TranslateDatePipe, NgClass, ReactiveFormsModule, - AlertComponent + AlertComponent, + DeleteOffcanvasComponent, + OffcanvasWrapperComponent ] }) export class AdministrationPageComponent implements OnInit { + @ViewChild('editUserOffcanvas') + protected editUserOffcanvas!: OffcanvasWrapperComponent; + protected loadingState: LoadingState = { isLoading: true }; protected users: UserDto[] = []; protected currentUserId: string = ''; - protected userSelectedForDeletion?: UserDto; protected userSelectedForEditing?: UserDto; - protected currentOffcanvas?: NgbOffcanvasRef; protected editUserForm = new FormGroup({ userName: new FormControl('', { nonNullable: true, validators: [Validators.required] }), @@ -72,22 +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.currentOffcanvas?.close(); - } - }); } public ngOnInit(): void { @@ -104,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) { @@ -126,7 +118,7 @@ export class AdministrationPageComponent implements OnInit { this.editUserForm.get('isAdministrator')!.enable(); } - this.currentOffcanvas = this.offcanvasService.open(template, { position: 'end' }); + this.editUserOffcanvas.show(); } } @@ -143,7 +135,7 @@ export class AdministrationPageComponent implements OnInit { return; } - this.currentOffcanvas?.close(); + this.editUserOffcanvas.close(); this.loadingState = { isLoading: true }; const formValue = this.editUserForm.getRawValue(); @@ -175,20 +167,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 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..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'; @@ -46,6 +45,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', @@ -65,7 +65,6 @@ import { FileSizePipe } from '../../pipes/file-size.pipe'; NgClass, TooltipIconComponent, FormsModule, - DeleteButtonComponent, ApiKeyUsageComponent, RbacWidgetComponent, DeleteWidgetComponent, @@ -75,7 +74,8 @@ import { FileSizePipe } from '../../pipes/file-size.pipe'; IdWidgetComponent, E2eDirective, ImageManagerComponent, - FileSizePipe + FileSizePipe, + DeleteOffcanvasComponent ] }) export class ViewOrganizationComponent implements OnInit, OnDestroy { @@ -299,8 +299,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) => {