From 2af80bf3a74d651c7f7ca201450b37efd55f4cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sun, 31 May 2026 19:42:50 +0200 Subject: [PATCH 01/11] Add endpoint & button for export --- src/Turnierplan.App/Client/src/app/i18n/de.ts | 1 + .../view-planning-realm.component.html | 7 ++++ .../view-planning-realm.component.ts | 18 ++++++++ .../ExportApplicationsEndpoint.cs | 41 +++++++++++++++++++ ...mer.cs => ResponseOperationTransformer.cs} | 38 +++++++++++++++-- src/Turnierplan.App/Program.cs | 2 +- 6 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 src/Turnierplan.App/Endpoints/Applications/ExportApplicationsEndpoint.cs rename src/Turnierplan.App/OpenApi/{PdfResponseOperationTransformer.cs => ResponseOperationTransformer.cs} (54%) diff --git a/src/Turnierplan.App/Client/src/app/i18n/de.ts b/src/Turnierplan.App/Client/src/app/i18n/de.ts index 954eacee..dac09be0 100644 --- a/src/Turnierplan.App/Client/src/app/i18n/de.ts +++ b/src/Turnierplan.App/Client/src/app/i18n/de.ts @@ -1040,6 +1040,7 @@ export const de = { CheckboxLabel: 'Ich habe den Hinweis gelesen und möchte die Anmeldung erstellen' } }, + ExportApplications:"Export (.csv)", TournamentClasses: { Name: 'Name', InvitationLinkCount: 'Anmeldelinks', diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.html b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.html index 3d99d48c..c2cd6ef3 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.html @@ -39,6 +39,13 @@ } @case (2) { + + + @let canAddApplications = !_hasUnsavedChanges && planningRealm.tournamentClasses.length > 0; @if (!canAddApplications) { diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts index 87c7f4f0..6375ec3b 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts @@ -36,6 +36,7 @@ import { createApplication } from '../../../api/fn/applications/create-applicati import { updatePlanningRealm } from '../../../api/fn/planning-realms/update-planning-realm'; import { deletePlanningRealm } from '../../../api/fn/planning-realms/delete-planning-realm'; import { LabelsManagerComponent } from '../../components/labels-manager/labels-manager.component'; +import { exportApplications } from '../../../api/fn/applications/export-applications'; export type UpdatePlanningRealmFunc = (modifyFunc: (planningRealm: PlanningRealmDto) => boolean) => void; @@ -303,6 +304,23 @@ export class ViewPlanningRealmComponent implements OnInit, OnDestroy, DiscardCha }); } + protected exportApplications(): void { + if (!this.planningRealm) { + return; + } + + // TODO: Add dialog with option to include teams and to apply the current filter + + this.turnierplanApi.invoke(exportApplications, { planningRealmId: this.planningRealm.id }).subscribe({ + next: (result) => { + const a = document.createElement('a'); + a.href = URL.createObjectURL(new Blob([result])); + a.download = 'asdf.csv'; // TODO: Add proper translated file name + a.click(); + } + }); + } + protected renamePlanningRealm(name: string): void { if (!this.planningRealm) { return; diff --git a/src/Turnierplan.App/Endpoints/Applications/ExportApplicationsEndpoint.cs b/src/Turnierplan.App/Endpoints/Applications/ExportApplicationsEndpoint.cs new file mode 100644 index 00000000..5eac89e6 --- /dev/null +++ b/src/Turnierplan.App/Endpoints/Applications/ExportApplicationsEndpoint.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Mvc; +using Turnierplan.App.OpenApi; +using Turnierplan.App.Security; +using Turnierplan.Core.PublicId; +using Turnierplan.Dal.Repositories; + +namespace Turnierplan.App.Endpoints.Applications; + +internal sealed class ExportApplicationsEndpoint : EndpointBase +{ + protected override HttpMethod Method => HttpMethod.Get; + + protected override string Route => "/api/planning-realms/{planningRealmId}/applications-export"; + + protected override Delegate Handler => Handle; + + protected override void ConfigureMetadata(RouteHandlerBuilder builder) + { + builder.ProducesCsv(); + } + + private static async Task Handle( + [FromRoute] PublicId planningRealmId, + IPlanningRealmRepository planningRealmRepository, + IAccessValidator accessValidator) + { + var planningRealm = await planningRealmRepository.GetByPublicIdAsync(planningRealmId, IPlanningRealmRepository.Includes.TournamentClasses | IPlanningRealmRepository.Includes.ApplicationsWithTeamsAndTournamentLinks); + + if (planningRealm is null) + { + return Results.NotFound(); + } + + if (!accessValidator.IsActionAllowed(planningRealm, Actions.ApplicationsRead)) + { + return Results.Forbid(); + } + + return Results.Text("csvcontent"); + } +} diff --git a/src/Turnierplan.App/OpenApi/PdfResponseOperationTransformer.cs b/src/Turnierplan.App/OpenApi/ResponseOperationTransformer.cs similarity index 54% rename from src/Turnierplan.App/OpenApi/PdfResponseOperationTransformer.cs rename to src/Turnierplan.App/OpenApi/ResponseOperationTransformer.cs index 6c7e4241..c07d6e9f 100644 --- a/src/Turnierplan.App/OpenApi/PdfResponseOperationTransformer.cs +++ b/src/Turnierplan.App/OpenApi/ResponseOperationTransformer.cs @@ -8,13 +8,36 @@ namespace Turnierplan.App.OpenApi; /// content type in such a way that the caller cannot retrieve the Blob returned by the server. This can be /// fixed by modifying the format property and setting it to binary. /// -internal sealed class PdfResponseOperationTransformer : IOpenApiOperationTransformer +internal sealed class ResponseOperationTransformer : IOpenApiOperationTransformer { public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) { + var csvResponseType = typeof(ICsvResponse); var pdfResponseType = typeof(IPdfResponse); - if (context.Description.SupportedResponseTypes.Any(x => x.Type == pdfResponseType) && operation.Responses is not null) + if (operation.Responses is null) + { + return Task.CompletedTask; + } + + if (context.Description.SupportedResponseTypes.Any(x => x.Type == csvResponseType)) + { + operation.Responses["200"] = new OpenApiResponse + { + Content = new Dictionary + { + ["text/csv"] = new() + { + Schema = new OpenApiSchema + { + Type = JsonSchemaType.String + } + } + } + }; + } + + if (context.Description.SupportedResponseTypes.Any(x => x.Type == pdfResponseType)) { operation.Responses["200"] = new OpenApiResponse { @@ -35,13 +58,20 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform return Task.CompletedTask; } + public interface ICsvResponse; + public interface IPdfResponse; } -internal static class PdfResponseOperationTransformerExtensions +internal static class ResponseOperationTransformerExtensions { + public static void ProducesCsv(this RouteHandlerBuilder builder) + { + builder.Produces(); + } + public static void ProducesPdf(this RouteHandlerBuilder builder) { - builder.Produces(); + builder.Produces(); } } diff --git a/src/Turnierplan.App/Program.cs b/src/Turnierplan.App/Program.cs index c92aeec8..c02b7439 100644 --- a/src/Turnierplan.App/Program.cs +++ b/src/Turnierplan.App/Program.cs @@ -49,7 +49,7 @@ builder.Services.AddOpenApi("turnierplan", options => { - options.AddOperationTransformer(); + options.AddOperationTransformer(); options.AddSchemaTransformer(); options.AddSchemaTransformer(); }); From 41db7d824386ed72da418008ce29571a0c8f5cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Thu, 4 Jun 2026 08:23:45 +0200 Subject: [PATCH 02/11] Refactor into dialog component --- src/Turnierplan.App/Client/src/app/i18n/de.ts | 6 ++- .../export-applications-dialog.component.html | 17 +++++++ .../export-applications-dialog.component.ts | 45 +++++++++++++++++++ .../view-planning-realm.component.html | 2 +- .../view-planning-realm.component.ts | 17 +++---- 5 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.html create mode 100644 src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.ts diff --git a/src/Turnierplan.App/Client/src/app/i18n/de.ts b/src/Turnierplan.App/Client/src/app/i18n/de.ts index dac09be0..8971d2b4 100644 --- a/src/Turnierplan.App/Client/src/app/i18n/de.ts +++ b/src/Turnierplan.App/Client/src/app/i18n/de.ts @@ -1040,7 +1040,11 @@ export const de = { CheckboxLabel: 'Ich habe den Hinweis gelesen und möchte die Anmeldung erstellen' } }, - ExportApplications:"Export (.csv)", + ExportApplications: { + Button: 'Export (.csv)', + DialogTitle: 'asdf', + Download: 'Herunterladen' + }, TournamentClasses: { Name: 'Name', InvitationLinkCount: 'Anmeldelinks', diff --git a/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.html b/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.html new file mode 100644 index 00000000..d47cdf39 --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.html @@ -0,0 +1,17 @@ + + + diff --git a/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.ts new file mode 100644 index 00000000..ef5e47be --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.ts @@ -0,0 +1,45 @@ +import { Component } from '@angular/core'; +import { TranslateDirective } from '@ngx-translate/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { ActionButtonComponent } from '../action-button/action-button.component'; +import { SmallSpinnerComponent } from '../../../core/components/small-spinner/small-spinner.component'; +import { TurnierplanApi } from '../../../api/turnierplan-api'; +import { exportApplications } from '../../../api/fn/applications/export-applications'; + +@Component({ + selector: 'tp-export-applications-dialog', + imports: [TranslateDirective, ActionButtonComponent, SmallSpinnerComponent], + templateUrl: './export-applications-dialog.component.html' +}) +export class ExportApplicationsDialogComponent { + protected isDownloading = false; + private planningRealmId?: string; + + constructor( + protected readonly modal: NgbActiveModal, + private readonly turnierplanApi: TurnierplanApi + ) {} + + public initialize(planningRealmId: string) { + this.planningRealmId = planningRealmId; + } + + protected exportApplications(): void { + if (!this.planningRealmId) { + return; + } + + this.isDownloading = true; + + this.turnierplanApi.invoke(exportApplications, { planningRealmId: this.planningRealmId }).subscribe({ + next: (result) => { + const a = document.createElement('a'); + a.href = URL.createObjectURL(new Blob([result])); + a.download = 'asdf.csv'; // TODO: Add proper translated file name + a.click(); + + this.modal.close(); + } + }); + } +} diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.html b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.html index c2cd6ef3..d4f83e26 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.html @@ -41,7 +41,7 @@ diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts index 6375ec3b..75c4280d 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts @@ -36,7 +36,7 @@ import { createApplication } from '../../../api/fn/applications/create-applicati import { updatePlanningRealm } from '../../../api/fn/planning-realms/update-planning-realm'; import { deletePlanningRealm } from '../../../api/fn/planning-realms/delete-planning-realm'; import { LabelsManagerComponent } from '../../components/labels-manager/labels-manager.component'; -import { exportApplications } from '../../../api/fn/applications/export-applications'; +import { ExportApplicationsDialogComponent } from '../../components/export-applications-dialog/export-applications-dialog.component'; export type UpdatePlanningRealmFunc = (modifyFunc: (planningRealm: PlanningRealmDto) => boolean) => void; @@ -309,16 +309,13 @@ export class ViewPlanningRealmComponent implements OnInit, OnDestroy, DiscardCha return; } - // TODO: Add dialog with option to include teams and to apply the current filter - - this.turnierplanApi.invoke(exportApplications, { planningRealmId: this.planningRealm.id }).subscribe({ - next: (result) => { - const a = document.createElement('a'); - a.href = URL.createObjectURL(new Blob([result])); - a.download = 'asdf.csv'; // TODO: Add proper translated file name - a.click(); - } + const ref = this.modalService.open(ExportApplicationsDialogComponent, { + centered: true, + size: 'md', + fullscreen: 'md' }); + + (ref.componentInstance as ExportApplicationsDialogComponent).initialize(this.planningRealm.id); } protected renamePlanningRealm(name: string): void { From 6a11cf34cdbab7fd5292a9bee7b5e3a6843b8bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Thu, 4 Jun 2026 08:30:04 +0200 Subject: [PATCH 03/11] Add dynamic file name --- src/Turnierplan.App/Client/src/app/i18n/de.ts | 3 ++- .../document-manager.component.ts | 3 ++- .../export-applications-dialog.component.ts | 25 +++++++++++++------ .../src/app/portal/helpers/file-name.ts | 3 +++ .../view-planning-realm.component.ts | 2 +- 5 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 src/Turnierplan.App/Client/src/app/portal/helpers/file-name.ts diff --git a/src/Turnierplan.App/Client/src/app/i18n/de.ts b/src/Turnierplan.App/Client/src/app/i18n/de.ts index 8971d2b4..55a3a612 100644 --- a/src/Turnierplan.App/Client/src/app/i18n/de.ts +++ b/src/Turnierplan.App/Client/src/app/i18n/de.ts @@ -1043,7 +1043,8 @@ export const de = { ExportApplications: { Button: 'Export (.csv)', DialogTitle: 'asdf', - Download: 'Herunterladen' + Download: 'Herunterladen', + FileName: 'Anmeldungen {{planningRealmName}}' }, TournamentClasses: { Name: 'Name', diff --git a/src/Turnierplan.App/Client/src/app/portal/components/document-manager/document-manager.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/document-manager/document-manager.component.ts index 91065b77..07581d75 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/document-manager/document-manager.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/document-manager/document-manager.component.ts @@ -27,6 +27,7 @@ import { setMatchPlanDocumentConfiguration } from '../../../api/fn/documents/set import { MatchPlanDocumentConfiguration } from '../../../api/models/match-plan-document-configuration'; import { setReceiptsDocumentConfiguration } from '../../../api/fn/documents/set-receipts-document-configuration'; import { ReceiptsDocumentConfiguration } from '../../../api/models/receipts-document-configuration'; +import { makeSafeFileName } from '../../helpers/file-name'; @Component({ selector: 'tp-document-manager', @@ -244,7 +245,7 @@ export class DocumentManagerComponent { } private getDocumentFileName(name: string): string { - return `${name} - ${this.tournamentName}.pdf`.replaceAll(/[^.A-Za-z0-9Ä-Öä-öß _-]/g, '_'); + return `${makeSafeFileName(`${name} - ${this.tournamentName}`)}.pdf`; } private getDocumentConfig(document: DocumentDto): Observable { diff --git a/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.ts b/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.ts index ef5e47be..95ceebc3 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.ts @@ -1,10 +1,12 @@ import { Component } from '@angular/core'; -import { TranslateDirective } from '@ngx-translate/core'; +import { TranslateDirective, TranslateService } from '@ngx-translate/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { ActionButtonComponent } from '../action-button/action-button.component'; import { SmallSpinnerComponent } from '../../../core/components/small-spinner/small-spinner.component'; import { TurnierplanApi } from '../../../api/turnierplan-api'; import { exportApplications } from '../../../api/fn/applications/export-applications'; +import { PlanningRealmDto } from '../../../api/models/planning-realm-dto'; +import { makeSafeFileName } from '../../helpers/file-name'; @Component({ selector: 'tp-export-applications-dialog', @@ -13,29 +15,36 @@ import { exportApplications } from '../../../api/fn/applications/export-applicat }) export class ExportApplicationsDialogComponent { protected isDownloading = false; - private planningRealmId?: string; + private planningRealm?: PlanningRealmDto; constructor( protected readonly modal: NgbActiveModal, - private readonly turnierplanApi: TurnierplanApi + private readonly turnierplanApi: TurnierplanApi, + private readonly translateService: TranslateService ) {} - public initialize(planningRealmId: string) { - this.planningRealmId = planningRealmId; + public initialize(planningRealm: PlanningRealmDto) { + this.planningRealm = planningRealm; } protected exportApplications(): void { - if (!this.planningRealmId) { + if (!this.planningRealm) { return; } + const fileName = `${makeSafeFileName( + this.translateService.instant('Portal.ViewPlanningRealm.ExportApplications.FileName', { + planningRealmName: this.planningRealm?.name + }) as string + )}.csv`; + this.isDownloading = true; - this.turnierplanApi.invoke(exportApplications, { planningRealmId: this.planningRealmId }).subscribe({ + this.turnierplanApi.invoke(exportApplications, { planningRealmId: this.planningRealm.id }).subscribe({ next: (result) => { const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([result])); - a.download = 'asdf.csv'; // TODO: Add proper translated file name + a.download = fileName; a.click(); this.modal.close(); diff --git a/src/Turnierplan.App/Client/src/app/portal/helpers/file-name.ts b/src/Turnierplan.App/Client/src/app/portal/helpers/file-name.ts new file mode 100644 index 00000000..5e652313 --- /dev/null +++ b/src/Turnierplan.App/Client/src/app/portal/helpers/file-name.ts @@ -0,0 +1,3 @@ +export const makeSafeFileName = (input: string): string => { + return input.replaceAll(/[^.A-Za-z0-9Ä-Öä-öß _-]/g, '_'); +}; diff --git a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts index 75c4280d..7ff686e7 100644 --- a/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts +++ b/src/Turnierplan.App/Client/src/app/portal/pages/view-planning-realm/view-planning-realm.component.ts @@ -315,7 +315,7 @@ export class ViewPlanningRealmComponent implements OnInit, OnDestroy, DiscardCha fullscreen: 'md' }); - (ref.componentInstance as ExportApplicationsDialogComponent).initialize(this.planningRealm.id); + (ref.componentInstance as ExportApplicationsDialogComponent).initialize(this.planningRealm); } protected renamePlanningRealm(name: string): void { From ff953be0846f8c3f50a9e861ed60366c27dec5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Thu, 4 Jun 2026 08:30:24 +0200 Subject: [PATCH 04/11] Add translated title --- src/Turnierplan.App/Client/src/app/i18n/de.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turnierplan.App/Client/src/app/i18n/de.ts b/src/Turnierplan.App/Client/src/app/i18n/de.ts index 55a3a612..6bfb626f 100644 --- a/src/Turnierplan.App/Client/src/app/i18n/de.ts +++ b/src/Turnierplan.App/Client/src/app/i18n/de.ts @@ -1042,7 +1042,7 @@ export const de = { }, ExportApplications: { Button: 'Export (.csv)', - DialogTitle: 'asdf', + DialogTitle: 'Anmeldungen exportieren', Download: 'Herunterladen', FileName: 'Anmeldungen {{planningRealmName}}' }, From 0c81ebf009e289b73e1ec1c257589e41342ddf3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Thu, 4 Jun 2026 08:40:01 +0200 Subject: [PATCH 05/11] Dialog is finished --- src/Turnierplan.App/Client/src/app/i18n/de.ts | 3 ++ .../export-applications-dialog.component.html | 18 ++++++++++- .../export-applications-dialog.component.ts | 30 ++++++++++++------- .../ExportApplicationsEndpoint.cs | 1 + 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/Turnierplan.App/Client/src/app/i18n/de.ts b/src/Turnierplan.App/Client/src/app/i18n/de.ts index 6bfb626f..dfbec679 100644 --- a/src/Turnierplan.App/Client/src/app/i18n/de.ts +++ b/src/Turnierplan.App/Client/src/app/i18n/de.ts @@ -1043,6 +1043,9 @@ export const de = { ExportApplications: { Button: 'Export (.csv)', DialogTitle: 'Anmeldungen exportieren', + InfoTest: + 'Nachfolgend können Sie eine CSV-Tabelle mit allen Anmeldungen in diesem Turnierplaner herunterladen. Wahlweise können die Mannschaften jeder Anmeldung ebenfalls aufgelistet werden.', + IncludeApplicationTeams: 'Mannschaften auflisten', Download: 'Herunterladen', FileName: 'Anmeldungen {{planningRealmName}}' }, diff --git a/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.html b/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.html index d47cdf39..0a58922d 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/export-applications-dialog/export-applications-dialog.component.html @@ -2,7 +2,23 @@ - +