From f57333f9ada593149ed50bbdc127e6474ffe43b4 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Mon, 11 May 2026 16:16:17 +0200 Subject: [PATCH 01/35] Main screen reworked --- .../participant-import-list.component.html | 2 +- ...icipant-import-list-preview.component.html | 6 + ...icipant-import-list-preview.component.scss | 0 ...pant-import-list-preview.component.spec.ts | 0 ...rticipant-import-list-preview.component.ts | 11 + .../backend-import-list.component.html | 324 +++++++++++------- .../backend-import-list.component.scss | 47 +++ .../backend-import-list.component.ts | 34 ++ 8 files changed, 296 insertions(+), 128 deletions(-) create mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.html create mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.scss create mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.spec.ts create mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.ts diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list/participant-import-list.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list/participant-import-list.component.html index 0962d91e9f..c60c287d86 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list/participant-import-list.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list/participant-import-list.component.html @@ -15,7 +15,7 @@

Import participants

+

test

+

test1

+

test2

+

test3

+ diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.spec.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.ts new file mode 100644 index 0000000000..9d502b7d0d --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.ts @@ -0,0 +1,11 @@ +import { Component, ViewEncapsulation } from '@angular/core'; +import { _ } from '@ngx-translate/core'; + +@Component({ + selector: `os-participant-import-list-preview`, + templateUrl: `./participant-import-list-preview.component.html`, + styleUrls: [`./participant-import-list-preview.component.scss`], + encapsulation: ViewEncapsulation.None, + standalone: false +}) +export class ParticipantImportListPreview { } \ No newline at end of file diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html index 9bc80a4a3a..9175fe62ae 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html @@ -1,124 +1,159 @@ - - - @if (hasSeveralTabs()) { - - - @for (tab of importListFirstTabs; track tab) { - - {{ tab.label }} - - - } - - - - - - - - @for (tab of importListLastTabs; track tab) { - - {{ tab.label }} - +@if (!hideOldCard) { + + + @if (hasSeveralTabs()) { + + + @for (tab of importListFirstTabs; track tab) { + + {{ tab.label }} + + + } + + + + + + + @for (tab of importListLastTabs; track tab) { + + {{ tab.label }} + + + } + + } + @if (!hasSeveralTabs()) { +
+ + {{ + 'Required comma or semicolon separated values with these column header names in the first row:' + | translate + }} + +
+
+ @for (entry of possibleFields; track entry; let last = $last) { + + {{ entry }} + @if (!last) { + + } + + } + + info + +
+ @if (requiredFields.length) { +
    +
  • + @for (field of requiredFields; track field; let last = $last) { + + {{ field }} + @if (!last) { + + } + + } + @if (requiredFields.length === 1) { + {{ 'is required' | translate }}. + } + @if (requiredFields.length > 1) { + {{ 'are required' | translate }}. + } + {{ 'All other fields are optional and may be empty.' | translate }} +
  • +
  • + {{ + 'Additional columns after the required ones may be present and will not affect the import.' + | translate + }} +
  • +
} -
- } - @if (!hasSeveralTabs()) { -
- - {{ - 'Required comma or semicolon separated values with these column header names in the first row:' - | translate - }} - -
-
- @for (entry of possibleFields; track entry; let last = $last) { - - {{ entry }} - @if (!last) { - - } - + @if (additionalInfo) { +

+ +

} - - info - -
- @if (requiredFields.length) { -
    -
  • - @for (field of requiredFields; track field; let last = $last) { - - {{ field }} - @if (!last) { - - } - - } - @if (requiredFields.length === 1) { - {{ 'is required' | translate }}. - } - @if (requiredFields.length > 1) { - {{ 'are required' | translate }}. + +
    + + {{ 'Encoding of the file' | translate }} + + @for (option of encodings; track option) { + + {{ option.label | translate }} + + } + + + + {{ 'Column separator' | translate }} + + @for (option of columnSeparators; track option) { + + {{ option.label | translate }} + + } + + + + {{ 'Text separator' | translate }} + + @for (option of textSeparators; track option) { + + {{ option.label | translate }} + + } + + +
    +
    +
    + + + @if (!finishedSuccessfully && (rawFileObservable | async); as file) { +
    + {{ file.name }} + +
    } - {{ 'All other fields are optional and may be empty.' | translate }} -
  • -
  • - {{ - 'Additional columns after the required ones may be present and will not affect the import.' - | translate - }} -
  • -
- } - @if (additionalInfo) { -

- -

+ + } - -
- - {{ 'Encoding of the file' | translate }} - - @for (option of encodings; track option) { - - {{ option.label | translate }} - - } - - - - {{ 'Column separator' | translate }} - - @for (option of columnSeparators; track option) { - - {{ option.label | translate }} - - } - - - - {{ 'Text separator' | translate }} - - @for (option of textSeparators; track option) { - - {{ option.label | translate }} - - } - - -
-
-
+ + +} + + + + + +
+
+

{{ 'Import templates' | translate }}

+
- +
+
+
+

{{ 'Required column names' | translate }}

+
+ - @if (!finishedSuccessfully && (rawFileObservable | async); as file) { -
- {{ file.name }} - -
- }
+
+

{{ 'Instructions for import file' | translate }}

+ +
+
+
+
+ + + +
+ + {{ 'Drag files to this area OR click here to select files' | translate }} + +
+ @if (!finishedSuccessfully && (rawFileObservable | async); as file) { +
+ {{ file.name }} + +
} +
+ + -@if (dataSource | async) { +@if ((dataSource | async) && filePreview) {
diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.scss b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.scss index 4d41272117..e9e5ac1c19 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.scss +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.scss @@ -10,6 +10,15 @@ } } +.instructions{ + color: var(--theme-accent-400); + margin-bottom: 12px; +} + +button mat-label { + padding-right: 5px; +} + pbl-ngrid-header-cell, pbl-ngrid-cell { padding-right: 12px; @@ -82,3 +91,41 @@ pbl-ngrid-cell { display: inline-flex; margin-right: 10px; } + +.dropdiv { + width: auto; + height: 100px; + border: dotted 2px var(--theme-accent-400); + margin-bottom: 15px; + display: flex; + align-items: center; + justify-content: center; + + &:hover{ + background-color: #ccc; + } + &:not(:hover) { + background-color: none; + } + .dropdiv-label { + color: var(--theme-accent-400) + } +} + +.dropdiv.processing { + display: none; +} + +.progress { + display: none; + width: inherit; + height: inherit; + background-color: #ccc; +} + +.progress.processing { + display: flex; + height: 100px; + align-items: center; + justify-content: center; +} diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index 030322e626..a2aef3a148 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -242,6 +242,11 @@ export class BackendImportListComponent implements OnInit, OnDestroy { private _headers: Record = {}; + public hideOldCard = true; + + //REMOVE. WHEN BUTTON IS CLICKED MUST BE REDIRECTED TO FILEPREVIEW + protected filePreview: boolean = false; + public constructor( private dialog: MatDialog, private translate: TranslateService @@ -521,4 +526,33 @@ export class BackendImportListComponent implements OnInit, OnDestroy { return []; } } + + public onDragOver(event: DragEvent): void { + event.preventDefault(); + event.stopPropagation(); + } + + public onDropSuccess(event: DragEvent): void { + event.preventDefault(); + event.stopPropagation(); + const files = event.dataTransfer?.files; + if (!files || files.length === 0) { + return; + } + const droppedFile = { + target: { + files: files + } + }; + this._importer.onSelectFile(droppedFile); + } + + public onChange(event: Event): void { + this._importer.onSelectFile(event); + } + + protected showPreview(): void { + this.filePreview = !this.filePreview + + } } From 64c20cb0a8b5e68c4f234c5ea96eabe63738ba5b Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Tue, 12 May 2026 16:57:38 +0200 Subject: [PATCH 02/35] Built routing to data preview --- ...icipant-import-list-preview.component.html | 0 ...icipant-import-list-preview.component.scss | 0 ...pant-import-list-preview.component.spec.ts | 0 ...rticipant-import-list-preview.component.ts | 8 ++++ ...rticipant-import-list-preview.component.ts | 11 ----- .../participant-import-routing.module.ts | 5 +++ .../backend-import-list.component.html | 44 ++++++++++++++----- .../backend-import-list.component.scss | 8 ++-- .../backend-import-list.component.ts | 14 +++--- .../modules/import-list/import-list.module.ts | 4 +- 10 files changed, 62 insertions(+), 32 deletions(-) rename client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/{participant-import-preview => participant-import-list-preview}/participant-import-list-preview.component.html (100%) rename client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/{participant-import-preview => participant-import-list-preview}/participant-import-list-preview.component.scss (100%) rename client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/{participant-import-preview => participant-import-list-preview}/participant-import-list-preview.component.spec.ts (100%) create mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts delete mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.ts diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html similarity index 100% rename from client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.html rename to client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss similarity index 100% rename from client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.scss rename to client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.spec.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.spec.ts similarity index 100% rename from client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.spec.ts rename to client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.spec.ts diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts new file mode 100644 index 0000000000..6b9d6e9a46 --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: `os-participant-import-list-preview`, + templateUrl: `./participant-import-list-preview.component.html`, + styleUrls: [`./participant-import-list-preview.component.scss`] +}) +export class ParticipantImportListPreviewComponent {} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.ts deleted file mode 100644 index 9d502b7d0d..0000000000 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-preview/participant-import-list-preview.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component, ViewEncapsulation } from '@angular/core'; -import { _ } from '@ngx-translate/core'; - -@Component({ - selector: `os-participant-import-list-preview`, - templateUrl: `./participant-import-list-preview.component.html`, - styleUrls: [`./participant-import-list-preview.component.scss`], - encapsulation: ViewEncapsulation.None, - standalone: false -}) -export class ParticipantImportListPreview { } \ No newline at end of file diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/participant-import-routing.module.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/participant-import-routing.module.ts index 8d3e052a3f..7993cd0514 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/participant-import-routing.module.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/participant-import-routing.module.ts @@ -2,11 +2,16 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ParticipantImportListComponent } from './components/participant-import-list/participant-import-list.component'; +import { ParticipantImportListPreviewComponent } from './components/participant-import-list-preview/participant-import-list-preview.component'; const routes: Routes = [ { path: ``, component: ParticipantImportListComponent + }, + { + path: `preview`, + component: ParticipantImportListPreviewComponent } ]; diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html index 9175fe62ae..8760e4edb6 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html @@ -157,12 +157,24 @@

{{ 'Import templates' | translate }}

- @@ -171,7 +183,7 @@

{{ 'Import templates' | translate }}

{{ 'Required column names' | translate }}

- @@ -186,16 +198,27 @@

{{ 'Instructions for import file' | translate - -
- + +
+ {{ 'Drag files to this area OR click here to select files' | translate }}
+ @if (!finishedSuccessfully && (rawFileObservable | async); as file) {
{{ file.name }} -
@@ -204,8 +227,11 @@

{{ 'Instructions for import file' | translate class="mdc-button mat-mdc-button-base mat-mdc-button mat-accent" matButton outlined - (click)="showPreview()" + routerLink="/participants/import/preview" + routerLinkActive="active" translate + [disabled]="uploadButton" + (click)="showPreview()" > {{ 'Upload' | translate }} @@ -213,9 +239,7 @@

{{ 'Instructions for import file' | translate - - -@if ((dataSource | async) && filePreview) { +@if (dataSource | async) {
diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.scss b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.scss index e9e5ac1c19..57f96443ae 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.scss +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.scss @@ -10,7 +10,7 @@ } } -.instructions{ +.instructions { color: var(--theme-accent-400); margin-bottom: 12px; } @@ -100,15 +100,15 @@ pbl-ngrid-cell { display: flex; align-items: center; justify-content: center; - - &:hover{ + + &:hover { background-color: #ccc; } &:not(:hover) { background-color: none; } .dropdiv-label { - color: var(--theme-accent-400) + color: var(--theme-accent-400); } } diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index a2aef3a148..8d312fe338 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -16,6 +16,7 @@ import { import { MatDialog } from '@angular/material/dialog'; import { MatSelectChange } from '@angular/material/select'; import { MatTabChangeEvent } from '@angular/material/tabs'; +import { Router } from '@angular/router'; import { _ } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core'; import { delay, firstValueFrom, map, Observable, of } from 'rxjs'; @@ -243,13 +244,12 @@ export class BackendImportListComponent implements OnInit, OnDestroy { private _headers: Record = {}; public hideOldCard = true; - - //REMOVE. WHEN BUTTON IS CLICKED MUST BE REDIRECTED TO FILEPREVIEW - protected filePreview: boolean = false; + protected uploadButton: boolean; public constructor( private dialog: MatDialog, - private translate: TranslateService + private translate: TranslateService, + private router: Router ) {} /** @@ -257,6 +257,7 @@ export class BackendImportListComponent implements OnInit, OnDestroy { */ public ngOnInit(): void { this._importer.clearAll(); + this.uploadButton = true; this._requiredFields = this.createRequiredFields(); this._importer.currentImportPhaseObservable.subscribe(phase => { if (phase === BackendImportPhase.LOADING_PREVIEW && this.fileInput) { @@ -300,6 +301,7 @@ export class BackendImportListComponent implements OnInit, OnDestroy { * triggers the importer's onSelectFile after a file has been chosen */ public onSelectFile(event: any): void { + this.uploadButton = false; this._importer.onSelectFile(event); } @@ -309,6 +311,7 @@ export class BackendImportListComponent implements OnInit, OnDestroy { public removeSelectedFile(clearImporter = true): void { if (this.fileInput) { this.fileInput.nativeElement.value = ``; + this.uploadButton = true; } if (clearImporter) { this._importer.clearFile(); @@ -552,7 +555,6 @@ export class BackendImportListComponent implements OnInit, OnDestroy { } protected showPreview(): void { - this.filePreview = !this.filePreview - + this.router.navigateByUrl(this.router.url.concat('/preview')); } } diff --git a/client/src/app/ui/modules/import-list/import-list.module.ts b/client/src/app/ui/modules/import-list/import-list.module.ts index bc760e1bef..9e62506c20 100644 --- a/client/src/app/ui/modules/import-list/import-list.module.ts +++ b/client/src/app/ui/modules/import-list/import-list.module.ts @@ -10,6 +10,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatSelectModule } from '@angular/material/select'; import { MatTabsModule } from '@angular/material/tabs'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterOutlet } from '@angular/router'; import { EditorModule } from 'src/app/ui/modules/editor/editor.module'; import { ScrollingTableModule } from 'src/app/ui/modules/scrolling-table'; import { PipesModule } from 'src/app/ui/pipes/pipes.module'; @@ -45,7 +46,8 @@ const DECLARATIONS = [ FormsModule, PortalModule, ScrollingTableModule, - OpenSlidesTranslationModule.forChild() + OpenSlidesTranslationModule.forChild(), + RouterOutlet ] }) export class ImportListModule {} From a1643e7a8dda2e526ee4df2911a10b6df33a4ea1 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 22 May 2026 14:26:59 +0200 Subject: [PATCH 03/35] Data sent between views --- ...icipant-import-list-preview.component.html | 364 +++++++++++- ...rticipant-import-list-preview.component.ts | 517 +++++++++++++++++- .../participant-import-list.component.html | 8 - .../backend-import-list.component.html | 26 +- .../backend-import-list.component.ts | 20 +- 5 files changed, 888 insertions(+), 47 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 2c64508de3..c31cb176ab 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -1,6 +1,358 @@ -
-

test

-

test1

-

test2

-

test3

-
+ +
+

Import participants

+
+ + @if (awaitingConfirm) { + + } + +
+ + + + @if (dataSource | async) { + + + + +
+ +
+ + } + + + + + @if (entry && (entry[def] || type === 'boolean')) { +
+ @if (getHeader(def).is_list && entry[def]) { +
+ @for (item of entry[def]; track item) { + + } +
+ } + @if (!getHeader(def).is_list && entry[def] !== undefined) { + + } +
+ } +
+ + + @if (getHeader(def).is_object) { +
+ + @if (getActionIcon(entry)) { + + {{ getEntryIcon(entry) }} + + } +
+ } + @if (!getHeader(def).is_object) { + + } +
+ + + @if (type === 'string') { + {{ entry }} + } + @if (type === 'number' || type === 'integer') { + {{ entry }} + } + @if (type === 'decimal') { + {{ getShortenedDecimal(entry) }} + } + @if (type === 'date') { + {{ entry }} + } + @if (type === 'boolean') { + + } +   + + + + @if (rows?.length && (dataSource | async)) { + +
+ @if (row.state === 'error') { + + {{ getActionIcon(row) }} + + } + @if (row.state !== 'error' && row.messages.length) { + + warning + + } +
+
+ @if (row.state !== 'error') { + + {{ getActionIcon(row) }} + + } +
+
+
#
+ {{ value }} +
+ @for (column of previewColumns; track column) { +
+
{{ getColumnLabel(column.property) | translate }}
+ +
+ } +
+ } +
+ + + + +
+

The fields are defined as follows

+
+ + + @for (column of defaultColumns; track column) { + + + + + } +
{{ column.property }}: +  {{ column.label | translate }} + @if (column.customInfo) { +
+ @if (isString(column.customInfo)) { + {{ column.customInfo | translate }} + } + @if (!isString(column.customInfo)) { + {{ column.customInfo | async }} + } + } +
+
+ + + +
+
+
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 6b9d6e9a46..64376056cc 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -1,8 +1,519 @@ -import { Component } from '@angular/core'; +import { + Component, + Input, + ContentChild, + ContentChildren, + ElementRef, + EventEmitter, + Output, + QueryList, + TemplateRef, + ViewChild, + inject, + ChangeDetectionStrategy, + ChangeDetectorRef +} from '@angular/core'; +import { HeadBarModule } from 'src/app/ui/modules/head-bar'; +import { FilterListService, ListModule } from 'src/app/ui/modules/list'; +import { MatIcon } from '@angular/material/icon'; +import { MatDialog, MatDialogContent, MatDialogActions } from '@angular/material/dialog'; +import { _, TranslatePipe } from '@ngx-translate/core'; +import { TranslateService } from '@ngx-translate/core'; +import { delay, firstValueFrom, map, Observable, of } from 'rxjs'; +import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; +import { ImportListHeaderDefinition } from 'src/app/ui/modules/import-list'; +import { BackendImportPhase } from 'src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component'; +import { ImportListFirstTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-first-tab.directive'; +import { ImportListLastTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-last-tab.directive'; +import { ImportListStatusTemplateDirective } from 'src/app/ui/modules/import-list/directives/import-list-status-template.directive'; +import { + END_POSITION, + START_POSITION +} from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-position'; +import { MatSelectChange, MatFormField, MatLabel, MatSelect, MatOption } from '@angular/material/select'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { infoDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; +import { ScrollingTableCellDefConfig } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; +import { MatCard, MatCardContent } from '@angular/material/card'; +import { MatTooltip } from '@angular/material/tooltip'; +import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { + BackendImportHeader, + BackendImportSummary, + BackendImportIdentifiedRow, + BackendImportEntryObject, + BackendImportState, + BackendImportPreview +} from 'src/app/ui/modules/import-list/definitions/backend-import-preview'; @Component({ selector: `os-participant-import-list-preview`, templateUrl: `./participant-import-list-preview.component.html`, - styleUrls: [`./participant-import-list-preview.component.scss`] + styleUrls: [`./participant-import-list-preview.component.scss`], + imports: [ + HeadBarModule, + ListModule, + MatIcon, + AsyncPipe, + TranslatePipe, + NgTemplateOutlet, + MatTooltip, + MatCheckbox, + MatDialogContent, + MatDialogActions +] }) -export class ParticipantImportListPreviewComponent {} +export class ParticipantImportListPreviewComponent { + public readonly END_POSITION = END_POSITION; + public readonly START_POSITION = START_POSITION; + + @ContentChildren(ImportListFirstTabDirective) + public importListFirstTabs!: QueryList; + + @ContentChildren(ImportListLastTabDirective) + public importListLastTabs!: QueryList; + + @ContentChild(ImportListStatusTemplateDirective, { read: TemplateRef }) + public importListStateTemplate: TemplateRef; + + @Input() + public rowHeight = 50; + + public modelName = `Participant`; + + @Input() + public additionalInfo = ``; + + @Input() + public importer = inject(ParticipantImportService); + + /** + * Define extra filter properties + */ + protected get filterProps(): string[] { + return [ + `full_name`, + `groups`, + `number`, + `delegationName`, + `structure_levels`, + `member_number`, + `email`, + `username` + ]; + } + @Input() + public filterService: FilterListService | undefined; + + /** + * Defines all necessary and optional fields, that a .csv-file can contain. + */ + @Input() + public possibleFields: string[] = []; + + @Output() + public selectedTabChanged = new EventEmitter(); + + public readonly Phase = BackendImportPhase; + + /** + * Observable that allows one to monitor the currenty selected file. + */ + public get rawFileObservable(): Observable { + return this.importer?.rawFileObservable || of(null); + } + + /** + * Client-side definition of required/accepted columns. + * Ensures that the client can display information about how the import works. + */ + @Input() + public set defaultColumns(cols: ImportListHeaderDefinition[]) { + this._defaultColumns = cols; + this.setHeaders({ default: cols }); + } + + public get defaultColumns(): ImportListHeaderDefinition[] { + return this._defaultColumns; + } + + /** + * The actual headers of the preview, as they were delivered by the backend. + */ + public get previewColumns(): BackendImportHeader[] { + return this._previewColumns; + } + + /** + * The summary of the preview, as it was delivered by the backend. + */ + public get summary(): BackendImportSummary[] { + return this._summary; + } + + /** + * The rows of the preview, which were delivered by the backend. + * Affixed with fake ids for the purpose of displaying them correctly. + */ + public get rows(): BackendImportIdentifiedRow[] { + return this._rows; + } + + /** + * True if, after the first json-upload, the view is waiting for the user to confirm the import. + */ + public get awaitingConfirm(): boolean { + return this._state === BackendImportPhase.AWAITING_CONFIRM; + } + + /** + * True if the import has successfully finished. + */ + public get finishedSuccessfully(): boolean { + return this._state === BackendImportPhase.FINISHED; + } + + /** + * True if, after an attempted import failed, the view is waiting for the user to confirm the import on the new preview. + */ + public get finishedWithWarning(): boolean { + return this._state === BackendImportPhase.FINISHED_WITH_WARNING; + } + + /** + * True while an import is in progress. + */ + public get isImporting(): boolean { + return this._state === BackendImportPhase.IMPORTING; + } + + /** + * True if the preview can not be imported. + */ + public get hasErrors(): boolean { + return this._state === BackendImportPhase.ERROR; + } + + /** + * Currently selected encoding. Is set and changed by the config's available + * encodings and user mat-select input + */ + public selectedEncoding = `utf-8`; + + /** + * @returns the encodings available and their labels + */ + public get encodings(): ValueLabelCombination[] { + return this.importer.encodings; + } + + /** + * @returns the available column separators and their labels + */ + public get columnSeparators(): ValueLabelCombination[] { + return this.importer.columnSeparators; + } + + /** + * @eturns the available text separators and their labels + */ + public get textSeparators(): ValueLabelCombination[] { + return this.importer.textSeparators; + } + + /** + * If false there is something wrong with the data. + */ + public get hasRowErrors(): boolean { + return this.importer.previewHasRowErrors; + } + + /** + * Client side information on the required fields of this import. + * Generated from the information in the defaultColumns. + */ + public get requiredFields(): string[] { + return this._requiredFields; + } + + /** + * The Observable from which the views table will be calculated + */ + public get dataSource(): Observable { + return this._dataSource; + } + + private _state: BackendImportPhase = BackendImportPhase.LOADING_PREVIEW; + + private _summary: BackendImportSummary[]; + private _rows: BackendImportIdentifiedRow[]; + private _previewColumns: BackendImportHeader[]; + + private _dataSource: Observable = of([]); + private _requiredFields: string[] = []; + private _defaultColumns: ImportListHeaderDefinition[] = []; + + private _headers: Record = {}; + protected uploadButton: boolean; + + //REMOVE THIS AND HTML CODE AFTER DEVELOPMENT + public hideOldCard = true; + + public constructor( + private dialog: MatDialog, + protected translate: TranslateService, + private cd: ChangeDetectorRef + ) {} + + /** + * Starts with a clean preview (removing any previously existing import previews) + */ + public ngOnInit(): void { + this._requiredFields = this.createRequiredFields(); + this.importer.currentImportPhaseObservable.subscribe(phase => { + this._state = phase; + }); + this.importer.previewsObservable.subscribe(previews => { + this.fillPreviewData(previews); + }); + this._dataSource = this.importer.previewsObservable.pipe( + map(previews => this.calculateRows(previews)), + delay(50) + ); + } + + /** + * Resets the importer when leaving the view + */ + public ngOnDestroy(): void { + this.importer.clearAll(); + this.importer.clearFile(); + } + + /** + * Triggers a change in the tab group: Clearing the preview selection + */ + public onTabChange({ index }: MatTabChangeEvent): void { + this.importer.clearAll(); + this.selectedTabChanged.emit(index); + } + + /** + * True if there are custom tabs. + */ + public hasSeveralTabs(): boolean { + return this.importListFirstTabs.length + this.importListLastTabs.length > 0; + } + + /** + * triggers the importer's onSelectFile after a file has been chosen + */ + public onSelectFile(event: any): void { + this.uploadButton = false; + this.importer.onSelectFile(event); + } + + /** + * Gets the relevant backend header information for a property. + */ + public getHeader(propertyName: string): BackendImportHeader { + return this._headers[propertyName]?.preview; + } + + /** + * Gets the style of the column for the given property. + */ + public getColumnConfig(propertyName: string): ScrollingTableCellDefConfig { + const defaultHeader = this._headers[propertyName]?.default; + const colWidth = defaultHeader?.width ?? 50; + const def: ScrollingTableCellDefConfig = { minWidth: Math.max(150, colWidth) }; + if (!defaultHeader?.flexible) { + def.width = colWidth; + } + return def; + } + + /** + * Gets the label of the column for the given property. + */ + public getColumnLabel(propertyName: string): string { + return this._headers[propertyName]?.default?.label ?? propertyName; + } + + /** + * Get the icon for the the item + * @param item a row or an entry with a current state + * @eturn the icon for the item + */ + public getActionIcon(item: BackendImportIdentifiedRow | BackendImportEntryObject): string { + switch (item[`state`] ?? item[`info`]) { + case BackendImportState.Error: // no import possible + return `block`; + case BackendImportState.Warning: + return `warning`; + case BackendImportState.New: + return `add`; + case BackendImportState.Done: // item will be updated / has been imported + return this._state !== BackendImportPhase.FINISHED ? `merge` : `done`; + case BackendImportState.Generated: + return `autorenew`; + case BackendImportState.Remove: + return `remove`; + default: + return `block`; // fallback: Error + } + } + + public getEntryIcon(item: BackendImportEntryObject): string { + if (item.info === BackendImportState.Done || !item) { + return undefined; + } + return this.getActionIcon(item); + } + + /** + * Get the correct tooltip for the item + * @param entry a row with a current state + * @eturn the tooltip for the item + */ + public getRowTooltip(row: BackendImportIdentifiedRow): string { + switch (row.state) { + case BackendImportState.Error: // no import possible + return ( + this.getErrorDescription(row) ?? + _(`There is an unspecified error in this line, which prevents the import.`) + ); + case BackendImportState.Warning: + return this.getErrorDescription(row) ?? _(`The affected columns will not be imported.`); + case BackendImportState.New: + return this.translate.instant(this.modelName) + ` ` + this.translate.instant(`will be imported`); + case BackendImportState.Done: // item will be updated / has been imported + return ( + this.translate.instant(this.modelName) + + ` ` + + (this._state !== BackendImportPhase.FINISHED + ? this.translate.instant(`will be updated`) + : this.translate.instant(`has been imported`)) + ); + default: + return undefined; + } + } + + public getWarningRowTooltip(row: BackendImportIdentifiedRow): string { + switch (row.state) { + case BackendImportState.Error: // no import possible + return ( + this.getErrorDescription(row) ?? + _(`There is an unspecified error in this line, which prevents the import.`) + ); + default: + return this.getErrorDescription(row) ?? _(`The affected columns will not be imported.`); + } + } + + /** + * A function to trigger the csv example download. + */ + public downloadCsvExample(): void { + this.importer.downloadCsvExample(); + } + + /** + * Trigger for the column separator selection. + */ + public selectColSep(event: MatSelectChange): void { + this.importer.columnSeparator = event.value; + this.importer.refreshFile(); + } + + /** + * Trigger for the column separator selection + */ + public selectTextSep(event: MatSelectChange): void { + this.importer.textSeparator = event.value; + this.importer.refreshFile(); + } + + /** + * Trigger for the encoding selection. + */ + public selectEncoding(event: MatSelectChange): void { + this.importer.encoding = event.value; + this.importer.refreshFile(); + } + + /** + * Opens an info dialog with the given template as content. + */ + public async openDialog(dialogTemplate: TemplateRef): Promise { + const ref = this.dialog.open(dialogTemplate, infoDialogSettings); + await firstValueFrom(ref.afterClosed()); + } + + /** + * Returns the verbose title for a given summary title. + */ + public getSummaryPointTitle(title: string): string { + return this.importer.getVerboseSummaryPointTitle(title); + } + + public getShortenedDecimal(decimalString: string): string { + while (decimalString.length && [`0`, `.`].includes(decimalString.charAt(decimalString.length - 1))) { + decimalString = decimalString.substring(0, decimalString.length - 1); + } + return decimalString; + } + + public isString(value: any): value is string { + return typeof value === `string`; + } + + private setHeaders(data: { default?: ImportListHeaderDefinition[]; preview?: BackendImportHeader[] }): void { + for (const key of Object.keys(data)) { + for (const header of data[key] ?? []) { + if (!this._headers[header.property]) { + this._headers[header.property] = { [key]: header }; + } else { + this._headers[header.property][key] = header; + } + } + } + } + + private getErrorDescription(entry: BackendImportIdentifiedRow): string { + return entry.messages?.map(error => this.translate.instant(this.importer.verbose(error))).join(`\n `); + } + + private fillPreviewData(previews: BackendImportPreview[]): void { + if (!previews || !previews.length) { + this._previewColumns = undefined; + this._summary = undefined; + this._rows = undefined; + } else { + this._previewColumns = (previews[0].headers ?? this._previewColumns).filter(header => !header[`is_hidden`]); + this._summary = previews.some(preview => preview.statistics) + ? previews.flatMap(preview => preview.statistics).filter(point => point?.value) + : []; + this._rows = this.calculateRows(previews); + this.setHeaders({ preview: this._previewColumns }); + } + } + + private calculateRows(previews: BackendImportPreview[]): BackendImportIdentifiedRow[] { + return previews?.flatMap(preview => preview.rows); + } + + private createRequiredFields(): string[] { + const definitions = this.defaultColumns; + if (Array.isArray(definitions) && definitions.length > 0) { + return definitions + .filter(definition => definition.isRequired as boolean) + .map(definition => definition.property as string); + } else { + return []; + } + } +} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list/participant-import-list.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list/participant-import-list.component.html index c60c287d86..f1a56ece36 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list/participant-import-list.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list/participant-import-list.component.html @@ -3,14 +3,6 @@

Import participants

- - - @if (canImport) { - - } - @if (hasSeveralTabs()) { - - - @for (tab of importListFirstTabs; track tab) { + --> + + - + } --> + + - @for (tab of importListLastTabs; track tab) { + --> + + + diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index 8d312fe338..875d4885fb 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -27,14 +27,7 @@ import { BackendImportService } from 'src/app/ui/base/import-service'; import { ScrollingTableCellDefConfig } from '../../../scrolling-table/directives/scrolling-table-cell-config'; import { END_POSITION, START_POSITION } from '../../../scrolling-table/directives/scrolling-table-cell-position'; import { ImportListHeaderDefinition } from '../../definitions'; -import { - BackendImportEntryObject, - BackendImportHeader, - BackendImportIdentifiedRow, - BackendImportPreview, - BackendImportState, - BackendImportSummary -} from '../../definitions/backend-import-preview'; +import { BackendImportHeader, BackendImportSummary, BackendImportIdentifiedRow, BackendImportEntryObject, BackendImportState, BackendImportPreview } from '../../definitions/backend-import-preview'; import { ImportListFirstTabDirective } from '../../directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from '../../directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from '../../directives/import-list-status-template.directive'; @@ -55,7 +48,7 @@ export enum BackendImportPhase { encapsulation: ViewEncapsulation.None, standalone: false }) -export class BackendImportListComponent implements OnInit, OnDestroy { +export class BackendImportListComponent implements OnInit { public readonly END_POSITION = END_POSITION; public readonly START_POSITION = START_POSITION; @@ -250,7 +243,7 @@ export class BackendImportListComponent implements OnInit, OnDestroy { private dialog: MatDialog, private translate: TranslateService, private router: Router - ) {} + ) { } /** * Starts with a clean preview (removing any previously existing import previews) @@ -274,13 +267,6 @@ export class BackendImportListComponent implements OnInit, OnDestroy { ); } - /** - * Resets the importer when leaving the view - */ - public ngOnDestroy(): void { - this._importer.clearFile(); - } - /** * Triggers a change in the tab group: Clearing the preview selection */ From 15e9ffa23c431572da0b7d13347a9bcdd67f959b Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 22 May 2026 14:30:01 +0200 Subject: [PATCH 04/35] Run cleanup --- ...rticipant-import-list-preview.component.ts | 78 +++++++++---------- .../backend-import-list.component.ts | 12 ++- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 64376056cc..bd9fee6cc4 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -1,71 +1,70 @@ +import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { + ChangeDetectorRef, Component, - Input, ContentChild, ContentChildren, - ElementRef, EventEmitter, + inject, + Input, + OnDestroy, + OnInit, Output, QueryList, - TemplateRef, - ViewChild, - inject, - ChangeDetectionStrategy, - ChangeDetectorRef + TemplateRef } from '@angular/core'; -import { HeadBarModule } from 'src/app/ui/modules/head-bar'; -import { FilterListService, ListModule } from 'src/app/ui/modules/list'; +import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog, MatDialogActions, MatDialogContent } from '@angular/material/dialog'; import { MatIcon } from '@angular/material/icon'; -import { MatDialog, MatDialogContent, MatDialogActions } from '@angular/material/dialog'; +import { MatSelectChange } from '@angular/material/select'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { MatTooltip } from '@angular/material/tooltip'; import { _, TranslatePipe } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core'; import { delay, firstValueFrom, map, Observable, of } from 'rxjs'; +import { infoDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; +import { HeadBarModule } from 'src/app/ui/modules/head-bar'; import { ImportListHeaderDefinition } from 'src/app/ui/modules/import-list'; import { BackendImportPhase } from 'src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component'; +import { + BackendImportEntryObject, + BackendImportHeader, + BackendImportIdentifiedRow, + BackendImportPreview, + BackendImportState, + BackendImportSummary +} from 'src/app/ui/modules/import-list/definitions/backend-import-preview'; import { ImportListFirstTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from 'src/app/ui/modules/import-list/directives/import-list-status-template.directive'; +import { FilterListService, ListModule } from 'src/app/ui/modules/list'; +import { ScrollingTableCellDefConfig } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; import { END_POSITION, START_POSITION } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-position'; -import { MatSelectChange, MatFormField, MatLabel, MatSelect, MatOption } from '@angular/material/select'; -import { MatTabChangeEvent } from '@angular/material/tabs'; -import { infoDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; -import { ScrollingTableCellDefConfig } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; -import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; -import { MatCard, MatCardContent } from '@angular/material/card'; -import { MatTooltip } from '@angular/material/tooltip'; + import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; -import { MatCheckbox } from '@angular/material/checkbox'; -import { - BackendImportHeader, - BackendImportSummary, - BackendImportIdentifiedRow, - BackendImportEntryObject, - BackendImportState, - BackendImportPreview -} from 'src/app/ui/modules/import-list/definitions/backend-import-preview'; @Component({ selector: `os-participant-import-list-preview`, templateUrl: `./participant-import-list-preview.component.html`, styleUrls: [`./participant-import-list-preview.component.scss`], imports: [ - HeadBarModule, - ListModule, - MatIcon, - AsyncPipe, - TranslatePipe, - NgTemplateOutlet, - MatTooltip, - MatCheckbox, - MatDialogContent, - MatDialogActions -] + HeadBarModule, + ListModule, + MatIcon, + AsyncPipe, + TranslatePipe, + NgTemplateOutlet, + MatTooltip, + MatCheckbox, + MatDialogContent, + MatDialogActions + ] }) -export class ParticipantImportListPreviewComponent { +export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy { public readonly END_POSITION = END_POSITION; public readonly START_POSITION = START_POSITION; @@ -104,6 +103,7 @@ export class ParticipantImportListPreviewComponent { `username` ]; } + @Input() public filterService: FilterListService | undefined; @@ -258,7 +258,7 @@ export class ParticipantImportListPreviewComponent { private _headers: Record = {}; protected uploadButton: boolean; - //REMOVE THIS AND HTML CODE AFTER DEVELOPMENT + // REMOVE THIS AND HTML CODE AFTER DEVELOPMENT public hideOldCard = true; public constructor( diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index 875d4885fb..ca9e6484db 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -5,7 +5,6 @@ import { ElementRef, EventEmitter, Input, - OnDestroy, OnInit, Output, QueryList, @@ -27,7 +26,14 @@ import { BackendImportService } from 'src/app/ui/base/import-service'; import { ScrollingTableCellDefConfig } from '../../../scrolling-table/directives/scrolling-table-cell-config'; import { END_POSITION, START_POSITION } from '../../../scrolling-table/directives/scrolling-table-cell-position'; import { ImportListHeaderDefinition } from '../../definitions'; -import { BackendImportHeader, BackendImportSummary, BackendImportIdentifiedRow, BackendImportEntryObject, BackendImportState, BackendImportPreview } from '../../definitions/backend-import-preview'; +import { + BackendImportEntryObject, + BackendImportHeader, + BackendImportIdentifiedRow, + BackendImportPreview, + BackendImportState, + BackendImportSummary +} from '../../definitions/backend-import-preview'; import { ImportListFirstTabDirective } from '../../directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from '../../directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from '../../directives/import-list-status-template.directive'; @@ -243,7 +249,7 @@ export class BackendImportListComponent implements OnInit { private dialog: MatDialog, private translate: TranslateService, private router: Router - ) { } + ) {} /** * Starts with a clean preview (removing any previously existing import previews) From 391d9a69be58f22fcfae9ca44c16ebd08ce422ac Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Tue, 26 May 2026 16:11:07 +0200 Subject: [PATCH 05/35] Sort filter bar config --- ...icipant-import-list-preview.component.html | 342 +++++++++--------- ...rticipant-import-list-preview.component.ts | 46 ++- .../backend-import-list.component.html | 4 +- 3 files changed, 215 insertions(+), 177 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index c31cb176ab..f8b007eecf 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -10,11 +10,20 @@

Import participants

} - - - - @if (dataSource | async) { - + @if (dataSource | async) { + - - - + -
- -
- - } +
+ +
+ + } - - - @if (entry && (entry[def] || type === 'boolean')) { -
- @if (getHeader(def).is_list && entry[def]) { -
- @for (item of entry[def]; track item) { - - } -
- } - @if (!getHeader(def).is_list && entry[def] !== undefined) { - - } -
- } -
- - - @if (getHeader(def).is_object) { -
+ + @if (entry && (entry[def] || type === 'boolean')) { +
+ @if (getHeader(def).is_list && entry[def]) { +
+ @for (item of entry[def]; track item) { + + } +
+ } + @if (!getHeader(def).is_list && entry[def] !== undefined) { - @if (getActionIcon(entry)) { - - {{ getEntryIcon(entry) }} - - } -
- } - @if (!getHeader(def).is_object) { + } +
+ } +
+ + + @if (getHeader(def).is_object) { +
- } - + > + @if (getActionIcon(entry)) { + + {{ getEntryIcon(entry) }} + + } +
+ } + @if (!getHeader(def).is_object) { + + } +
- - @if (type === 'string') { - {{ entry }} - } - @if (type === 'number' || type === 'integer') { - {{ entry }} - } - @if (type === 'decimal') { - {{ getShortenedDecimal(entry) }} - } - @if (type === 'date') { - {{ entry }} - } - @if (type === 'boolean') { - - } -   - + + @if (type === 'string') { + {{ entry }} + } + @if (type === 'number' || type === 'integer') { + {{ entry }} + } + @if (type === 'decimal') { + {{ getShortenedDecimal(entry) }} + } + @if (type === 'date') { + {{ entry }} + } + @if (type === 'boolean') { + + } +   + - - @if (rows?.length && (dataSource | async)) { - + @if (rows?.length && (dataSource | async)) { + +
+ @if (row.state === 'error') { + + {{ getActionIcon(row) }} + + } + @if (row.state !== 'error' && row.messages.length) { + + warning + + } +
+
+ @if (row.state !== 'error') { + + {{ getActionIcon(row) }} + + } +
+
+
#
+ {{ value }} +
+ @for (column of previewColumns; track column) {
- @if (row.state === 'error') { - - {{ getActionIcon(row) }} - - } - @if (row.state !== 'error' && row.messages.length) { - - warning - - } -
-
- @if (row.state !== 'error') { - - {{ getActionIcon(row) }} - - } -
-
-
#
- {{ value }} -
- @for (column of previewColumns; track column) { -
-
{{ getColumnLabel(column.property) | translate }}
- -
- } -
- } -
+ > +
+ } + + } + - - -
-

The fields are defined as follows

-
- - - @for (column of defaultColumns; track column) { - - - + + } +
{{ column.property }}: -  {{ column.label | translate }} - @if (column.customInfo) { -
- @if (isString(column.customInfo)) { - {{ column.customInfo | translate }} - } - @if (!isString(column.customInfo)) { - {{ column.customInfo | async }} - } + +
+

The fields are defined as follows

+
+ + + @for (column of defaultColumns; track column) { + + + - - } -
{{ column.property }}: +  {{ column.label | translate }} + @if (column.customInfo) { +
+ @if (isString(column.customInfo)) { + {{ column.customInfo | translate }} } -
-
- - - -
- - + @if (!isString(column.customInfo)) { + {{ column.customInfo | async }} + } + } +
+
+ + + +
+ diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index bd9fee6cc4..4948730538 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -1,6 +1,5 @@ import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; import { - ChangeDetectorRef, Component, ContentChild, ContentChildren, @@ -38,13 +37,14 @@ import { import { ImportListFirstTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from 'src/app/ui/modules/import-list/directives/import-list-status-template.directive'; -import { FilterListService, ListModule } from 'src/app/ui/modules/list'; +import { FilterListService, ListModule, SearchService } from 'src/app/ui/modules/list'; import { ScrollingTableCellDefConfig } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; import { END_POSITION, START_POSITION } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-position'; +import { ParticipantControllerService } from '../../../../services/common/participant-controller.service'; import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; @Component({ @@ -78,7 +78,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy public importListStateTemplate: TemplateRef; @Input() - public rowHeight = 50; + public rowHeight = 20; public modelName = `Participant`; @@ -104,8 +104,41 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ]; } + /** Optional number to overwrite the display of the filtered data count, if any additional filters + * (e.g. the angular search bar) are applied on top of these filters + */ + @Input() + public filterCount = 0; + + @Input() + public filterService: FilterListService; + + @Input() + public searchService: SearchService; + + @Input() + public searchFieldInput = ``; + + @Output() + public searchFilterUpdated = new EventEmitter(); + + @Input() + public addBottomSpacer = false; + + /** + * Will show fake filter buttons with the string keys as content in bar. + * Closing them will cause the callback function to be called. + */ + @Input() + public fakeFilters: Observable void>> = null; + + protected _totalCountObservable: Observable = null; + + /** + * Wether or not to show the filter bar + */ @Input() - public filterService: FilterListService | undefined; + public showFilterBar = true; /** * Defines all necessary and optional fields, that a .csv-file can contain. @@ -258,13 +291,13 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy private _headers: Record = {}; protected uploadButton: boolean; - // REMOVE THIS AND HTML CODE AFTER DEVELOPMENT + // REMOVE THIS AND HTML CODE AFTER DEVELOPMENT #CLEANUP public hideOldCard = true; public constructor( private dialog: MatDialog, protected translate: TranslateService, - private cd: ChangeDetectorRef + protected readonly controller: ParticipantControllerService ) {} /** @@ -282,6 +315,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy map(previews => this.calculateRows(previews)), delay(50) ); + this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); } /** diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html index 983f8ce871..c358ebfc44 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.html @@ -239,7 +239,7 @@

{{ 'Instructions for import file' | translate -@if (dataSource | async) { +

The fields are defined as follows

From 2047cf8262233af7735f1b1ec016d4d00b531572 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Wed, 27 May 2026 17:13:16 +0200 Subject: [PATCH 06/35] Create participant-import-service and add filter button --- ...icipant-import-list-preview.component.html | 391 +++++------------- ...icipant-import-list-preview.component.scss | 94 +++++ ...rticipant-import-list-preview.component.ts | 60 ++- .../participant-import-filter.service.ts | 105 +++++ .../sort-filter-bar.component.ts | 1 + .../view-list/view-list.component.ts | 2 +- 6 files changed, 329 insertions(+), 324 deletions(-) create mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index f8b007eecf..c9ca26cb2f 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -21,313 +21,120 @@

Import participants

[totalCount]="_totalCountObservable | async" (searchFilterUpdated)="searchFilterUpdated.emit($event)" > - @if (dataSource | async) { - - - - -
- -
- - } - - + - - @if (entry && (entry[def] || type === 'boolean')) { -
- @if (getHeader(def).is_list && entry[def]) { -
- @for (item of entry[def]; track item) { - - } -
- } - @if (!getHeader(def).is_list && entry[def] !== undefined) { + + @if (getHeader(def).is_object) { +
- } -
- } -
- - - @if (getHeader(def).is_object) { -
+ @if (getActionIcon(entry)) { + + {{ getEntryIcon(entry) }} + + } +
+ } + @if (!getHeader(def).is_object) { - @if (getActionIcon(entry)) { - - {{ getEntryIcon(entry) }} - - } -
- } - @if (!getHeader(def).is_object) { - - } -
+ /> + } + - - @if (type === 'string') { - {{ entry }} - } - @if (type === 'number' || type === 'integer') { - {{ entry }} - } - @if (type === 'decimal') { - {{ getShortenedDecimal(entry) }} - } - @if (type === 'date') { - {{ entry }} - } - @if (type === 'boolean') { - - } -   - + + @if (type === 'string') { + {{ entry }} + } + @if (type === 'number' || type === 'integer') { + {{ entry }} + } + @if (type === 'decimal') { + {{ getShortenedDecimal(entry) }} + } + @if (type === 'date') { + {{ entry }} + } + @if (type === 'boolean') { + + } +   + - - @if (rows?.length && (dataSource | async)) { - + @if (row.state === 'error') { + + {{ getActionIcon(row) }} + + } + @if (row.state !== 'error' && row.messages.length) { + + warning + + } +
+
+ @if (row.state !== 'error') { + + {{ getActionIcon(row) }} + + } +
+
+
#
+ {{ value }} +
+ @for (column of previewColumns; track column) { +
-
- @if (row.state === 'error') { - - {{ getActionIcon(row) }} - - } - @if (row.state !== 'error' && row.messages.length) { - - warning - - } -
-
- @if (row.state !== 'error') { - - {{ getActionIcon(row) }} - - } -
-
-
#
- {{ value }} -
- @for (column of previewColumns; track column) { -
-
{{ getColumnLabel(column.property) | translate }}
- -
- } - - } - - - - + } + }

The fields are defined as follows

diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index e69de29bb2..5aa445b6c9 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -0,0 +1,94 @@ +@use 'src/assets/styles/tables.scss'; + +:host { + --os-row-height: 50px; +} + +.possible-fields-list { + span { + display: inline-flex; + } +} + +.instructions { + color: var(--theme-accent-400); + margin-bottom: 12px; +} + +button mat-label { + padding-right: 5px; +} + +pbl-ngrid-header-cell, +pbl-ngrid-cell { + padding-right: 12px; + + &:first-of-type { + padding-left: 12px !important; + } +} + +.import-list-wrapper { + display: grid; + grid-template-columns: auto auto auto; + column-gap: 12px; +} + +.import-list-summary { + .import-list-icon { + animation: rotation 1s infinite linear; + } +} + +.import-list-preview-table { + height: 500px; + + .pbl-ngrid-row { + height: var(--os-row-height); + } + + .ngrid-preview { + div { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } +} + +.import-list-preview-row { + font-size: 14px; +} + +.import-file-handle { + display: flex; + align-items: center; + margin-left: 8px; +} + +@keyframes rotation { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(359deg); + } +} + +.pointer-icon { + cursor: pointer; +} + +.bottom-offset { + margin: 0 0 5px 0; +} + +.table-title-column { + vertical-align: top; +} + +.list-field-entry { + display: inline-flex; + margin-right: 10px; +} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 4948730538..09c427044b 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -20,7 +20,7 @@ import { MatTabChangeEvent } from '@angular/material/tabs'; import { MatTooltip } from '@angular/material/tooltip'; import { _, TranslatePipe } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core'; -import { delay, firstValueFrom, map, Observable, of } from 'rxjs'; +import { firstValueFrom, map, Observable, of } from 'rxjs'; import { infoDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; import { HeadBarModule } from 'src/app/ui/modules/head-bar'; @@ -37,7 +37,7 @@ import { import { ImportListFirstTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from 'src/app/ui/modules/import-list/directives/import-list-status-template.directive'; -import { FilterListService, ListModule, SearchService } from 'src/app/ui/modules/list'; +import { ListModule, SearchService } from 'src/app/ui/modules/list'; import { ScrollingTableCellDefConfig } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; import { END_POSITION, @@ -46,6 +46,7 @@ import { import { ParticipantControllerService } from '../../../../services/common/participant-controller.service'; import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; +import { ParticipantImportFilterService } from '../../services/participant-import-filter.service'; @Component({ selector: `os-participant-import-list-preview`, @@ -104,14 +105,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ]; } - /** Optional number to overwrite the display of the filtered data count, if any additional filters - * (e.g. the angular search bar) are applied on top of these filters - */ - @Input() - public filterCount = 0; - - @Input() - public filterService: FilterListService; + public filterService = inject(ParticipantImportFilterService); @Input() public searchService: SearchService; @@ -122,22 +116,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy @Output() public searchFilterUpdated = new EventEmitter(); - @Input() - public addBottomSpacer = false; - - /** - * Will show fake filter buttons with the string keys as content in bar. - * Closing them will cause the callback function to be called. - */ - @Input() - public fakeFilters: Observable void>> = null; - protected _totalCountObservable: Observable = null; /** * Wether or not to show the filter bar */ - @Input() public showFilterBar = true; /** @@ -291,9 +274,6 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy private _headers: Record = {}; protected uploadButton: boolean; - // REMOVE THIS AND HTML CODE AFTER DEVELOPMENT #CLEANUP - public hideOldCard = true; - public constructor( private dialog: MatDialog, protected translate: TranslateService, @@ -311,10 +291,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this.importer.previewsObservable.subscribe(previews => { this.fillPreviewData(previews); }); - this._dataSource = this.importer.previewsObservable.pipe( - map(previews => this.calculateRows(previews)), - delay(50) - ); + this._dataSource = this.importer.previewsObservable.pipe(map(previews => this.calculateRows(previews))); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); } @@ -379,16 +356,16 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * Get the icon for the the item * @param item a row or an entry with a current state - * @eturn the icon for the item + * @return the icon for the item */ public getActionIcon(item: BackendImportIdentifiedRow | BackendImportEntryObject): string { switch (item[`state`] ?? item[`info`]) { case BackendImportState.Error: // no import possible - return `block`; + return `error_outline`; case BackendImportState.Warning: return `warning`; case BackendImportState.New: - return `add`; + return `add_circle_outline`; case BackendImportState.Done: // item will be updated / has been imported return this._state !== BackendImportPhase.FINISHED ? `merge` : `done`; case BackendImportState.Generated: @@ -400,6 +377,27 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } } + public getColorIcon(item: BackendImportIdentifiedRow | BackendImportEntryObject): string { + switch (item[`state`] ?? item[`info`]) { + case BackendImportState.Error: // no import possible + console.log('1'); + return `red-warning-text`; + case BackendImportState.Warning: + console.log('2'); + return 'warning'; /* `rgb(241, 227, 32)`; */ + case BackendImportState.New: + return `rgb(96, 235, 41)`; + case BackendImportState.Done: // item will be updated / has been imported + return `accent`; + case BackendImportState.Generated: + return `autorenew`; + case BackendImportState.Remove: + return `remove`; + default: + return `block`; // fallback: Error + } + } + public getEntryIcon(item: BackendImportEntryObject): string { if (item.info === BackendImportState.Done || !item) { return undefined; diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts new file mode 100644 index 0000000000..8ed415b96f --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts @@ -0,0 +1,105 @@ +import { Injectable } from '@angular/core'; +import { _ } from '@ngx-translate/core'; +import { BaseFilterListService, OsFilter } from 'src/app/site/base/base-filter.service'; +import { ViewUser } from 'src/app/site/pages/meetings/view-models/view-user'; +import { ActiveFiltersService } from 'src/app/site/services/active-filters.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ParticipantImportFilterService extends BaseFilterListService { + protected storageKey = `VotesEntry`; + + public constructor(store: ActiveFiltersService) { + super(store); + } + + protected getFilterDefinitions(): OsFilter[] { + const staticFilterOptions: OsFilter[] = [ + { + property: `isPresentInMeeting`, + label: _(`Presence`), + options: [ + { condition: true, label: _(`Is present`) }, + { condition: [false, null], label: _(`Is not present`) } + ] + }, + { + property: `isVoteWeightOne`, + label: _(`Vote weight`), + options: [ + { condition: [false, null], label: _(`Has changed vote weight`) }, + { condition: true, label: _(`Has unchanged vote weight`) } + ] + }, + { + property: `isLockedOutOfMeeting`, + label: `Locked out`, + options: [ + { condition: true, label: `Is locked out` }, + { condition: [false, null], label: `Is not locked out` } + ] + }, + { + property: `is_active`, + label: _(`Active`), + options: [ + { condition: true, label: _(`Is active`) }, + { condition: [false, null], label: _(`Is not active`) } + ] + }, + { + property: `is_physical_person`, + label: _(`Committee`), + options: [ + { condition: true, label: _(`Is not a committee`) }, + { condition: [false, null], label: _(`Is a committee`) } + ] + }, + { + property: `hasMemberNumber`, + label: _(`Membership number`), + options: [ + { condition: true, label: _(`Has a membership number`) }, + { condition: [false, null], label: _(`Has no membership number`) } + ] + }, + { + property: `isLastLogin`, + label: _(`Last login`), + options: [ + { condition: true, label: _(`Has logged in`) }, + { condition: [false, null], label: _(`Has not logged in yet`) } + ] + }, + { + property: `isLastEmailSent`, + label: _(`Last email sent`), + options: [ + { condition: true, label: _(`Got an email`) }, + { condition: [false, null], label: _(`Didn't get an email`) } + ] + }, + { + property: `hasEmail`, + label: _(`Email address`), + options: [ + { condition: true, label: _(`Has an email address`) }, + { condition: [false, null], label: _(`Has no email address`) } + ] + }, + { + property: `hasSamlId`, + label: _(`SSO`), + options: [ + { condition: true, label: _(`Has SSO identification`) }, + { condition: [false, null], label: _(`Has no SSO identification`) } + ] + } + + /* first_name last_name email member_number structure_level groups number vote_weight gender pronoun username default_password + is_active is_physical_person is_present locked_out saml_id home_committee external comment */ + ]; + return staticFilterOptions; + } +} diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts index 4896b58067..2b4e61db53 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts @@ -212,6 +212,7 @@ export class SortFilterBarComponent implements OnDestroy this.searchEdit = false; } }); + console.log('FILTERS: ', this.hasFilters, 'SEARCH BAR: ', this.showSearchIconOnly, this.searchService); } public ngOnDestroy(): void { diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts index 9c4444dac9..5054964b78 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts @@ -102,7 +102,7 @@ export class ViewListComponent implements OnInit, OnDest * Fix value for the height of the rows in the virtual-scroll-list. */ @Input() - public vScrollFixed = 110; + public vScrollFixed = 50; /** * Determines whether the table should have a fixed 100vh height or not. From 36004ec64bab5073ed4376404b6fdb9c1b219634 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 29 May 2026 11:56:25 +0200 Subject: [PATCH 07/35] Add participant-import-service, headbar to table, colors to table icons --- ...icipant-import-list-preview.component.html | 295 +++++++++--------- ...icipant-import-list-preview.component.scss | 97 +----- ...rticipant-import-list-preview.component.ts | 37 ++- .../participant-import-search.service.ts | 26 ++ .../sort-filter-bar.component.ts | 1 - .../view-list/view-list.component.html | 1 + .../view-list/view-list.component.ts | 8 +- 7 files changed, 216 insertions(+), 249 deletions(-) create mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-search.service.ts diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index c9ca26cb2f..e9cc402b8a 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -11,159 +11,166 @@

Import participants

- - @if (dataSource | async) { - - @if (entry && (entry[def] || type === 'boolean')) { -
- @if (getHeader(def).is_list && entry[def]) { -
- @for (item of entry[def]; track item) { - - } -
- } - @if (!getHeader(def).is_list && entry[def] !== undefined) { +
+ + @if (dataSource | async) { +
+ @for (column of previewColumns; track column) { +
+
{{ getColumnLabel(column.property).toUpperCase() }}
+
+ } +
+
 #
+ @if (row.state === 'error') { + + {{ getActionIcon(row) }} + + } + @if (row.state !== 'error' && row.messages.length) { + + warning + + } + @if (row.state !== 'error') { + + {{ getActionIcon(row) }} + }
- } - +
- - @if (getHeader(def).is_object) { -
+ + @if (entry && (entry[def] || type === 'boolean')) { +
+ @if (getHeader(def).is_list && entry[def]) { +
+ @for (item of entry[def]; track item) { + + } +
+ } + @if (!getHeader(def).is_list && entry[def] !== undefined) { + + } +
+ } +
+ + + @if (getHeader(def).is_object) { +
+ + @if (getActionIcon(entry)) { + + {{ getEntryIcon(entry) }} + + } +
+ } + @if (!getHeader(def).is_object) { - @if (getActionIcon(entry)) { - - {{ getEntryIcon(entry) }} - - } -
- } - @if (!getHeader(def).is_object) { - - } -
+ /> + } + - - @if (type === 'string') { - {{ entry }} - } - @if (type === 'number' || type === 'integer') { - {{ entry }} - } - @if (type === 'decimal') { - {{ getShortenedDecimal(entry) }} - } - @if (type === 'date') { - {{ entry }} - } - @if (type === 'boolean') { - - } -   - + + @if (type === 'string') { + {{ entry }} + } + @if (type === 'number' || type === 'integer') { + {{ entry }} + } + @if (type === 'decimal') { + {{ getShortenedDecimal(entry) }} + } + @if (type === 'date') { + {{ entry }} + } + @if (type === 'boolean') { + + } +   + + } -
- @if (row.state === 'error') { - - {{ getActionIcon(row) }} - - } - @if (row.state !== 'error' && row.messages.length) { - - warning - - } -
-
- @if (row.state !== 'error') { - - {{ getActionIcon(row) }} - - } -
-
-
#
- {{ value }} -
- @for (column of previewColumns; track column) { -
-
{{ getColumnLabel(column.property) | translate }}
- + +
+

The fields are defined as follows

- } - } - -
-

The fields are defined as follows

-
- - - @for (column of defaultColumns; track column) { - - - + + } +
{{ column.property }}: -  {{ column.label | translate }} - @if (column.customInfo) { -
- @if (isString(column.customInfo)) { - {{ column.customInfo | translate }} - } - @if (!isString(column.customInfo)) { - {{ column.customInfo | async }} + + + @for (column of defaultColumns; track column) { + + + - - } -
{{ column.property }}: +  {{ column.label | translate }} + @if (column.customInfo) { +
+ @if (isString(column.customInfo)) { + {{ column.customInfo | translate }} + } + @if (!isString(column.customInfo)) { + {{ column.customInfo | async }} + } } - } -
-
- - - - - +
+
+ + + +
+ +
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 5aa445b6c9..5fb62fa4c4 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -1,94 +1,17 @@ -@use 'src/assets/styles/tables.scss'; +$os-green: rgb(76, 175, 80); +$os-yellow: rgb(255, 193, 7); -:host { - --os-row-height: 50px; +.os-green { + color: $os-green; } - -.possible-fields-list { - span { - display: inline-flex; - } -} - -.instructions { - color: var(--theme-accent-400); - margin-bottom: 12px; -} - -button mat-label { - padding-right: 5px; -} - -pbl-ngrid-header-cell, -pbl-ngrid-cell { - padding-right: 12px; - - &:first-of-type { - padding-left: 12px !important; - } +.os-yellow { + color: $os-yellow; } -.import-list-wrapper { - display: grid; - grid-template-columns: auto auto auto; - column-gap: 12px; -} - -.import-list-summary { - .import-list-icon { - animation: rotation 1s infinite linear; - } -} - -.import-list-preview-table { - height: 500px; - - .pbl-ngrid-row { - height: var(--os-row-height); - } - - .ngrid-preview { - div { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - } -} - -.import-list-preview-row { - font-size: 14px; -} - -.import-file-handle { +.icon-container { display: flex; align-items: center; - margin-left: 8px; -} - -@keyframes rotation { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(359deg); - } -} - -.pointer-icon { - cursor: pointer; -} - -.bottom-offset { - margin: 0 0 5px 0; -} - -.table-title-column { - vertical-align: top; -} - -.list-field-entry { - display: inline-flex; - margin-right: 10px; + justify-content: center; + gap: 10px; + width: 100%; } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 09c427044b..fed33ed134 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -1,5 +1,6 @@ -import { AsyncPipe, NgTemplateOutlet } from '@angular/common'; +import { AsyncPipe, NgClass, NgTemplateOutlet } from '@angular/common'; import { + ChangeDetectorRef, Component, ContentChild, ContentChildren, @@ -37,7 +38,8 @@ import { import { ImportListFirstTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from 'src/app/ui/modules/import-list/directives/import-list-status-template.directive'; -import { ListModule, SearchService } from 'src/app/ui/modules/list'; +import { ListModule } from 'src/app/ui/modules/list'; +import { ListSearchService } from 'src/app/ui/modules/list/services/list-search.service'; import { ScrollingTableCellDefConfig } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; import { END_POSITION, @@ -47,6 +49,7 @@ import { import { ParticipantControllerService } from '../../../../services/common/participant-controller.service'; import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; import { ParticipantImportFilterService } from '../../services/participant-import-filter.service'; +import { ParticipantImportPreviewSearchService } from '../../services/participant-import-search.service'; @Component({ selector: `os-participant-import-list-preview`, @@ -62,7 +65,8 @@ import { ParticipantImportFilterService } from '../../services/participant-impor MatTooltip, MatCheckbox, MatDialogContent, - MatDialogActions + MatDialogActions, + NgClass ] }) export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy { @@ -106,9 +110,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } public filterService = inject(ParticipantImportFilterService); - - @Input() - public searchService: SearchService; + public alsoFilterByProperties: string[] = [`id`]; + public searchService = inject(ParticipantImportPreviewSearchService); @Input() public searchFieldInput = ``; @@ -123,6 +126,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy */ public showFilterBar = true; + /** + * Wether or not to show the header + */ + public showHeader = true; + /** * Defines all necessary and optional fields, that a .csv-file can contain. */ @@ -273,6 +281,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy private _headers: Record = {}; protected uploadButton: boolean; + protected cd = inject(ChangeDetectorRef); public constructor( private dialog: MatDialog, @@ -293,6 +302,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy }); this._dataSource = this.importer.previewsObservable.pipe(map(previews => this.calculateRows(previews))); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); + this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); + this.cd.detectChanges(); } /** @@ -367,7 +378,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.New: return `add_circle_outline`; case BackendImportState.Done: // item will be updated / has been imported - return this._state !== BackendImportPhase.FINISHED ? `merge` : `done`; + return this._state !== BackendImportPhase.FINISHED ? `autorenew` : `done`; case BackendImportState.Generated: return `autorenew`; case BackendImportState.Remove: @@ -380,19 +391,13 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy public getColorIcon(item: BackendImportIdentifiedRow | BackendImportEntryObject): string { switch (item[`state`] ?? item[`info`]) { case BackendImportState.Error: // no import possible - console.log('1'); return `red-warning-text`; case BackendImportState.Warning: - console.log('2'); - return 'warning'; /* `rgb(241, 227, 32)`; */ + return 'warning'; case BackendImportState.New: - return `rgb(96, 235, 41)`; + return 'os-green'; case BackendImportState.Done: // item will be updated / has been imported - return `accent`; - case BackendImportState.Generated: - return `autorenew`; - case BackendImportState.Remove: - return `remove`; + return this._state !== BackendImportPhase.FINISHED ? `os-yellow` : `accent`; default: return `block`; // fallback: Error } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-search.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-search.service.ts new file mode 100644 index 0000000000..800ed87d2d --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-search.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { ListSearchService } from 'src/app/ui/modules/list/services/list-search.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ParticipantImportPreviewSearchService extends ListSearchService { + public constructor() { + super( + [ + `short_name`, + `first_name`, + `username`, + `email`, + `saml_id`, + `member_number`, + `externalString`, + `homeCommitteeName`, + `gender`, + `title`, + `structure_level` + ], + [] + ); + } +} diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts index 2b4e61db53..4896b58067 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts @@ -212,7 +212,6 @@ export class SortFilterBarComponent implements OnDestroy this.searchEdit = false; } }); - console.log('FILTERS: ', this.hasFilters, 'SEARCH BAR: ', this.showSearchIconOnly, this.searchService); } public ngOnDestroy(): void { diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.html b/client/src/app/ui/modules/list/components/view-list/view-list.component.html index f44a721c27..767cb3e4ee 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.html +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.html @@ -26,6 +26,7 @@ [hiddenColumns]="hiddenColumns" [rowHeight]="vScrollFixed" [selectionMode]="multiSelect" + [showHeader]="showHeader" [tableHeight]="fullScreen ? undefined : '50vh'" (selectionChanged)="selectedRowsChange.emit($event.selectedRows)" > diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts index 5054964b78..f8a2357259 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts @@ -98,11 +98,17 @@ export class ViewListComponent implements OnInit, OnDest @Input() public showMenu = true; + /* + * To Optionally show the scrolling-table's header bar + */ + @Input() + public showHeader: boolean; + /** * Fix value for the height of the rows in the virtual-scroll-list. */ @Input() - public vScrollFixed = 50; + public vScrollFixed = 110; /** * Determines whether the table should have a fixed 100vh height or not. From 437715218b221d94b730c300449e893cd270286f Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Tue, 9 Jun 2026 17:47:00 +0200 Subject: [PATCH 08/35] Create viewParticipantImport wrapper class --- ...icipant-import-list-preview.component.html | 249 ++++++++---------- ...rticipant-import-list-preview.component.ts | 43 ++- .../participant-import-filter.service.ts | 45 ++-- .../view-models/view-participant-import.ts | 104 ++++++++ .../backend-import-list.component.ts | 3 +- .../scrolling-table.component.scss | 4 +- 6 files changed, 253 insertions(+), 195 deletions(-) create mode 100644 client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index e9cc402b8a..36b2b12784 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -3,11 +3,9 @@

Import participants

- @if (awaitingConfirm) { - - } + @@ -25,152 +23,121 @@

Import participants

(searchFilterUpdated)="searchFilterUpdated.emit($event)" > @if (dataSource | async) { -
- @for (column of previewColumns; track column) { -
-
{{ getColumnLabel(column.property).toUpperCase() }}
- -
- } + @for (column of previewColumns; track column; let i = $index) {
-
 #
- @if (row.state === 'error') { - - {{ getActionIcon(row) }} - - } - @if (row.state !== 'error' && row.messages.length) { - - warning - - } - @if (row.state !== 'error') { - - {{ getActionIcon(row) }} - - } -
-
+ @let entry = row.data; + @let type = column.type; +
+
{{ getColumnLabel(column.property) }}
+ @if (entry && (entry[def] || type === 'boolean')) { +
+ @if (getHeader(def).is_list && entry[def]) { +
+ @for (item of entry[def]; track item) { + @if (getHeader(def).is_object) { +
+ @if (getHeader(def).type === 'string') { + {{ item.value }} + } + @if (getHeader(def).type === 'number') { + {{ item.value }} + } + @if (getHeader(def).type === 'date') { + {{ item.value }} + } + @if (getHeader(def).type === 'boolean') { + + } +   - - @if (entry && (entry[def] || type === 'boolean')) { -
- @if (getHeader(def).is_list && entry[def]) { -
- @for (item of entry[def]; track item) { - + @if (getActionIcon(item)) { + + {{ getEntryIcon(item.value) }} + + } +
+ } @else { + @if (getHeader(def).type === 'string') { + {{ item.value }} + } + @if (getHeader(def).type === 'number') { + {{ item.value }} + } + @if (getHeader(def).type === 'date') { + {{ item.value }} + } + @if (getHeader(def).type === 'boolean') { + + } +   + } + } +
} -
- } - @if (!getHeader(def).is_list && entry[def] !== undefined) { - - } -
- } - + @if (!getHeader(def).is_list && entry[def] !== undefined) { + @if (getHeader(def).is_object) { +
+ @if (getHeader(def).type === 'string') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'number') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'date') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'boolean') { + + } +   - - @if (getHeader(def).is_object) { -
- - @if (getActionIcon(entry)) { - - {{ getEntryIcon(entry) }} - + @if (getActionIcon(entry[def])) { + + {{ getEntryIcon(entry[def]) }} + + } +
+ } + } +
}
+
+ } +
+
 #
+ @if (row.state === 'error') { + + {{ getActionIcon(row) }} + } - @if (!getHeader(def).is_object) { - - } - - - - @if (type === 'string') { - {{ entry }} - } - @if (type === 'number' || type === 'integer') { - {{ entry }} - } - @if (type === 'decimal') { - {{ getShortenedDecimal(entry) }} - } - @if (type === 'date') { - {{ entry }} + @if (row.state !== 'error' && row.messages.length) { + + warning + } - @if (type === 'boolean') { - + @if (row.state !== 'error') { + + {{ getActionIcon(row) }} + } -   - - } - - -
-

The fields are defined as follows

- - - @for (column of defaultColumns; track column) { - - - - - } -
{{ column.property }}: -  {{ column.label | translate }} - @if (column.customInfo) { -
- @if (isString(column.customInfo)) { - {{ column.customInfo | translate }} - } - @if (!isString(column.customInfo)) { - {{ column.customInfo | async }} - } - } -
-
- - - -
+ }
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index fed33ed134..5f8b3846b7 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -1,4 +1,4 @@ -import { AsyncPipe, NgClass, NgTemplateOutlet } from '@angular/common'; +import { AsyncPipe, NgClass } from '@angular/common'; import { ChangeDetectorRef, Component, @@ -14,13 +14,12 @@ import { TemplateRef } from '@angular/core'; import { MatCheckbox } from '@angular/material/checkbox'; -import { MatDialog, MatDialogActions, MatDialogContent } from '@angular/material/dialog'; +import { MatDialog } from '@angular/material/dialog'; import { MatIcon } from '@angular/material/icon'; import { MatSelectChange } from '@angular/material/select'; import { MatTabChangeEvent } from '@angular/material/tabs'; import { MatTooltip } from '@angular/material/tooltip'; -import { _, TranslatePipe } from '@ngx-translate/core'; -import { TranslateService } from '@ngx-translate/core'; +import { _, TranslateService } from '@ngx-translate/core'; import { firstValueFrom, map, Observable, of } from 'rxjs'; import { infoDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; @@ -50,24 +49,13 @@ import { ParticipantControllerService } from '../../../../services/common/partic import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; import { ParticipantImportFilterService } from '../../services/participant-import-filter.service'; import { ParticipantImportPreviewSearchService } from '../../services/participant-import-search.service'; +import { ViewImportedParticipant } from '../../view-models/view-participant-import'; @Component({ selector: `os-participant-import-list-preview`, templateUrl: `./participant-import-list-preview.component.html`, styleUrls: [`./participant-import-list-preview.component.scss`], - imports: [ - HeadBarModule, - ListModule, - MatIcon, - AsyncPipe, - TranslatePipe, - NgTemplateOutlet, - MatTooltip, - MatCheckbox, - MatDialogContent, - MatDialogActions, - NgClass - ] + imports: [HeadBarModule, ListModule, MatIcon, AsyncPipe, MatTooltip, MatCheckbox, NgClass] }) export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy { public readonly END_POSITION = END_POSITION; @@ -97,16 +85,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * Define extra filter properties */ protected get filterProps(): string[] { - return [ - `full_name`, - `groups`, - `number`, - `delegationName`, - `structure_levels`, - `member_number`, - `email`, - `username` - ]; + return ViewImportedParticipant.REQUESTABLE_FIELDS; } public filterService = inject(ParticipantImportFilterService); @@ -283,6 +262,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy protected uploadButton: boolean; protected cd = inject(ChangeDetectorRef); + public importedParticipants; + public constructor( private dialog: MatDialog, protected translate: TranslateService, @@ -304,6 +285,10 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); this.cd.detectChanges(); + this.importedParticipants = this._rows.map(row => { + return new ViewImportedParticipant(row.id, row); + }); + console.log(this.importedParticipants); } /** @@ -380,7 +365,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.Done: // item will be updated / has been imported return this._state !== BackendImportPhase.FINISHED ? `autorenew` : `done`; case BackendImportState.Generated: - return `autorenew`; + return `merge`; case BackendImportState.Remove: return `remove`; default: @@ -398,6 +383,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return 'os-green'; case BackendImportState.Done: // item will be updated / has been imported return this._state !== BackendImportPhase.FINISHED ? `os-yellow` : `accent`; + case BackendImportState.Generated: + return `accent`; default: return `block`; // fallback: Error } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts index 8ed415b96f..b9c51bf436 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts @@ -1,23 +1,38 @@ import { Injectable } from '@angular/core'; import { _ } from '@ngx-translate/core'; import { BaseFilterListService, OsFilter } from 'src/app/site/base/base-filter.service'; -import { ViewUser } from 'src/app/site/pages/meetings/view-models/view-user'; import { ActiveFiltersService } from 'src/app/site/services/active-filters.service'; +import { ViewImportedParticipant } from '../view-models/view-participant-import'; + @Injectable({ providedIn: 'root' }) export class ParticipantImportFilterService extends BaseFilterListService { - protected storageKey = `VotesEntry`; - + protected storageKey = `participantImportPreview`; public constructor(store: ActiveFiltersService) { super(store); } - protected getFilterDefinitions(): OsFilter[] { - const staticFilterOptions: OsFilter[] = [ + protected getFilterDefinitions(): OsFilter[] { + const staticFilterOptions: OsFilter[] = [ + { + property: `is_active`, + label: _(`Active`), + options: [ + { condition: true, label: _(`Is active`) }, + { condition: [false, null], label: _(`Is not active`) } + ] + }, { - property: `isPresentInMeeting`, + property: `is_physical_person`, + label: _(`Committee`), + options: [ + { condition: true, label: _(`Is not a committee`) }, + { condition: [false, null], label: _(`Is a committee`) } + ] + } /* ,{ + property: `is_present`, label: _(`Presence`), options: [ { condition: true, label: _(`Is present`) }, @@ -40,22 +55,6 @@ export class ParticipantImportFilterService extends BaseFilterListService { { condition: [false, null], label: `Is not locked out` } ] }, - { - property: `is_active`, - label: _(`Active`), - options: [ - { condition: true, label: _(`Is active`) }, - { condition: [false, null], label: _(`Is not active`) } - ] - }, - { - property: `is_physical_person`, - label: _(`Committee`), - options: [ - { condition: true, label: _(`Is not a committee`) }, - { condition: [false, null], label: _(`Is a committee`) } - ] - }, { property: `hasMemberNumber`, label: _(`Membership number`), @@ -95,7 +94,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { { condition: true, label: _(`Has SSO identification`) }, { condition: [false, null], label: _(`Has no SSO identification`) } ] - } + } */ /* first_name last_name email member_number structure_level groups number vote_weight gender pronoun username default_password is_active is_physical_person is_present locked_out saml_id home_committee external comment */ diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts new file mode 100644 index 0000000000..b7e35febf7 --- /dev/null +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -0,0 +1,104 @@ +import { Identifiable } from 'src/app/domain/interfaces'; +import { + BackendImportEntry, + BackendImportIdentifiedRow +} from 'src/app/ui/modules/import-list/definitions/backend-import-preview'; + +export class ViewImportedParticipant implements Identifiable /* implements Searchable */ { + public static COLLECTION = `importedParticipant`; + + public id: number; + public first_name; + public last_name; + public email; + public member_number; + public number; + public vote_weight; + public gender; + public pronoun; + public username; + public default_password; + public saml_id; + public home_committee; + public external_comment; + public title; + + public structure_level: string[]; + public groups: string[]; + + public is_active; + public is_present; + public is_locked_out; + public is_physical_person; + + public constructor(importedParticipant_id: BackendImportIdentifiedRow['id'], data?: BackendImportIdentifiedRow) { + const importedParticipant = data['data'] ?? ''; + this.id = importedParticipant_id; + + this.title = getValue(importedParticipant?.['title']); + this.first_name = getValue(importedParticipant?.['first_name']); + this.last_name = getValue(importedParticipant?.['last_name']); + this.email = getValue(importedParticipant?.['email']); + this.member_number = getValue(importedParticipant?.['member_number']); + this.structure_level = importedParticipant?.['structure_level']; + this.groups = importedParticipant?.['groups']?.[0]; + this.number = getValue(importedParticipant?.['number']); + this.vote_weight = getValue(importedParticipant?.['vote_weight']); + this.gender = getValue(importedParticipant?.['gender']); + this.pronoun = getValue(importedParticipant?.['pronoun']); + this.username = getValue(importedParticipant?.['username']); + this.default_password = getValue(importedParticipant?.['default_password']); + + this.is_active = getValue(importedParticipant?.['is_active']); + this.is_physical_person = getValue(importedParticipant?.['is_physical_person']); + this.is_present = getValue(importedParticipant?.['is_present']); + this.is_locked_out = getValue(importedParticipant?.['locked_out']); + + this.saml_id = getValue(importedParticipant?.['saml_id']); + this.home_committee = getValue(importedParticipant?.['home_committee']); + this.external_comment = getValue(importedParticipant?.['external_comment']); + } + + public static readonly REQUESTABLE_FIELDS: (keyof ViewImportedParticipant)[] = [ + 'id', + 'first_name', + 'last_name', + 'email', + 'member_number', + 'vote_weight', + 'gender', + 'pronoun', + 'username', + 'default_password', + 'saml_id', + 'home_committee', + 'external_comment', + 'title', + 'structure_level', + 'groups', + 'is_active', + 'is_present', + 'is_locked_out', + 'is_physical_person' + ]; +} + +function getValue(field: BackendImportEntry | BackendImportEntry[]): string | number | boolean { + if (!field) { + console.log('No field:', field); + return 0; + } + if (Array.isArray(field)) { + return getValue(field[0]); + } + if (typeof field === 'string') { + return field; + } + if (typeof field === 'number') { + return field; + } + if (typeof field === 'boolean') { + return field; + } + return field.value; +} diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index ca9e6484db..51a793a6b9 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -21,6 +21,7 @@ import { TranslateService } from '@ngx-translate/core'; import { delay, firstValueFrom, map, Observable, of } from 'rxjs'; import { infoDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; +import { ViewImportedParticipant } from 'src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import'; import { BackendImportService } from 'src/app/ui/base/import-service'; import { ScrollingTableCellDefConfig } from '../../../scrolling-table/directives/scrolling-table-cell-config'; @@ -507,7 +508,7 @@ export class BackendImportListComponent implements OnInit { } } - private calculateRows(previews: BackendImportPreview[]): BackendImportIdentifiedRow[] { + private calculateRows(previews: BackendImportPreview[] | ViewImportedParticipant[]): BackendImportIdentifiedRow[] { return previews?.flatMap(preview => preview.rows); } diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss index 37dbfac7da..34b4f8edac 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss @@ -43,7 +43,7 @@ .scrolling-table-header { @include scrolling-table-style(); - + overflow-x: scroll; .scrolling-table-cell { font-weight: 500; } @@ -58,7 +58,7 @@ height: 100%; .scrolling-table-row { - overflow: hidden; + overflow: scroll; @include scrolling-table-style(); } } From 9e7da3b580756c1a83b1046738a17312fb97f7cd Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Wed, 10 Jun 2026 17:36:04 +0200 Subject: [PATCH 09/35] Filters and Search bar and work, and fully integrate wrapper class --- ...rticipant-import-list-preview.component.ts | 23 ++--- .../participant-import-filter.service.ts | 76 ++++++++++---- .../view-models/view-participant-import.ts | 99 +++++++++++-------- .../definitions/backend-import-preview.ts | 3 +- .../scrolling-table.component.scss | 2 +- 5 files changed, 122 insertions(+), 81 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 5f8b3846b7..40da3dc876 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -1,6 +1,5 @@ import { AsyncPipe, NgClass } from '@angular/common'; import { - ChangeDetectorRef, Component, ContentChild, ContentChildren, @@ -260,9 +259,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy private _headers: Record = {}; protected uploadButton: boolean; - protected cd = inject(ChangeDetectorRef); - public importedParticipants; + public importedParticipants: Observable; public constructor( private dialog: MatDialog, @@ -284,11 +282,6 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this._dataSource = this.importer.previewsObservable.pipe(map(previews => this.calculateRows(previews))); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); - this.cd.detectChanges(); - this.importedParticipants = this._rows.map(row => { - return new ViewImportedParticipant(row.id, row); - }); - console.log(this.importedParticipants); } /** @@ -354,7 +347,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * @param item a row or an entry with a current state * @return the icon for the item */ - public getActionIcon(item: BackendImportIdentifiedRow | BackendImportEntryObject): string { + public getActionIcon(item: ViewImportedParticipant | BackendImportEntryObject): string { switch (item[`state`] ?? item[`info`]) { case BackendImportState.Error: // no import possible return `error_outline`; @@ -373,7 +366,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } } - public getColorIcon(item: BackendImportIdentifiedRow | BackendImportEntryObject): string { + public getColorIcon(item: ViewImportedParticipant | BackendImportEntryObject): string { switch (item[`state`] ?? item[`info`]) { case BackendImportState.Error: // no import possible return `red-warning-text`; @@ -402,7 +395,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * @param entry a row with a current state * @eturn the tooltip for the item */ - public getRowTooltip(row: BackendImportIdentifiedRow): string { + public getRowTooltip(row: ViewImportedParticipant): string { switch (row.state) { case BackendImportState.Error: // no import possible return ( @@ -426,7 +419,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } } - public getWarningRowTooltip(row: BackendImportIdentifiedRow): string { + public getWarningRowTooltip(row: ViewImportedParticipant): string { switch (row.state) { case BackendImportState.Error: // no import possible return ( @@ -507,7 +500,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } } - private getErrorDescription(entry: BackendImportIdentifiedRow): string { + private getErrorDescription(entry: ViewImportedParticipant): string { return entry.messages?.map(error => this.translate.instant(this.importer.verbose(error))).join(`\n `); } @@ -526,8 +519,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } } - private calculateRows(previews: BackendImportPreview[]): BackendImportIdentifiedRow[] { - return previews?.flatMap(preview => preview.rows); + private calculateRows(previews: BackendImportPreview[]): ViewImportedParticipant[] { + return previews?.flatMap(preview => preview.rows.map(row => new ViewImportedParticipant(row.id, row))); } private createRequiredFields(): string[] { diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts index b9c51bf436..68ba996970 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { _ } from '@ngx-translate/core'; +import { GENDER_FITLERABLE, GENDERS } from 'src/app/domain/models/users/user'; import { BaseFilterListService, OsFilter } from 'src/app/site/base/base-filter.service'; import { ActiveFiltersService } from 'src/app/site/services/active-filters.service'; @@ -31,7 +32,8 @@ export class ParticipantImportFilterService extends BaseFilterListService { { condition: true, label: _(`Is not a committee`) }, { condition: [false, null], label: _(`Is a committee`) } ] - } /* ,{ + }, + { property: `is_present`, label: _(`Presence`), options: [ @@ -40,7 +42,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `isVoteWeightOne`, + property: `vote_weight`, label: _(`Vote weight`), options: [ { condition: [false, null], label: _(`Has changed vote weight`) }, @@ -48,7 +50,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `isLockedOutOfMeeting`, + property: `is_locked_out`, label: `Locked out`, options: [ { condition: true, label: `Is locked out` }, @@ -56,7 +58,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `hasMemberNumber`, + property: `member_number`, label: _(`Membership number`), options: [ { condition: true, label: _(`Has a membership number`) }, @@ -64,23 +66,34 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `isLastLogin`, - label: _(`Last login`), + property: `title`, + label: _(`Title`), options: [ - { condition: true, label: _(`Has logged in`) }, - { condition: [false, null], label: _(`Has not logged in yet`) } + { condition: true, label: _(`Has a title`) }, + { condition: [false, null], label: _(`Has no title`) } ] }, { - property: `isLastEmailSent`, - label: _(`Last email sent`), + property: `saml_id`, + label: _(`SSO`), options: [ - { condition: true, label: _(`Got an email`) }, - { condition: [false, null], label: _(`Didn't get an email`) } + { condition: true, label: _(`Has SSO identification`) }, + { condition: [false, null], label: _(`Has no SSO identification`) } ] }, { - property: `hasEmail`, + property: `gender`, + label: _(`Gender`), + options: [ + { condition: GENDER_FITLERABLE[0], label: GENDERS[0] }, + { condition: GENDER_FITLERABLE[1], label: GENDERS[1] }, + { condition: GENDER_FITLERABLE[2], label: GENDERS[2] }, + { condition: GENDER_FITLERABLE[3], label: GENDERS[3] }, + { condition: null, label: _(`not specified`) } + ] + }, + { + property: `email`, label: _(`Email address`), options: [ { condition: true, label: _(`Has an email address`) }, @@ -88,16 +101,37 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `hasSamlId`, - label: _(`SSO`), + property: `pronoun`, + label: _(`Pronoun`), options: [ - { condition: true, label: _(`Has SSO identification`) }, - { condition: [false, null], label: _(`Has no SSO identification`) } + { condition: true, label: _(`Has pronoun`) }, + { condition: [false, null], label: _(`Has no pronoun`) } ] - } */ - - /* first_name last_name email member_number structure_level groups number vote_weight gender pronoun username default_password - is_active is_physical_person is_present locked_out saml_id home_committee external comment */ + }, + { + property: `username`, + label: _(`Username`), + options: [ + { condition: true, label: _(`Has username`) }, + { condition: [false, null], label: _(`Has no username`) } + ] + }, + { + property: `home_committee`, + label: _(`Home committee`), + options: [ + { condition: true, label: _(`Has home committee`) }, + { condition: [false, null], label: _(`Has no home committee`) } + ] + }, + { + property: `groups`, + label: _(`Username`), + options: [ + { condition: true, label: _(`Has groups`) }, + { condition: [false, null], label: _(`Has no groups`) } + ] + } ]; return staticFilterOptions; } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index b7e35febf7..a8f0b790cb 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -1,12 +1,19 @@ import { Identifiable } from 'src/app/domain/interfaces'; import { BackendImportEntry, - BackendImportIdentifiedRow + BackendImportRow, + BackendImportState } from 'src/app/ui/modules/import-list/definitions/backend-import-preview'; -export class ViewImportedParticipant implements Identifiable /* implements Searchable */ { +export class ViewImportedParticipant implements Identifiable, BackendImportRow /* implements Searchable */ { + // This class replaces BackendImportIdentifiedRow + public static COLLECTION = `importedParticipant`; + public state: BackendImportState; + public messages: string[]; + public data: Record; + public id: number; public first_name; public last_name; @@ -23,7 +30,7 @@ export class ViewImportedParticipant implements Identifiable /* implements Searc public external_comment; public title; - public structure_level: string[]; + public structure_level; public groups: string[]; public is_active; @@ -31,32 +38,35 @@ export class ViewImportedParticipant implements Identifiable /* implements Searc public is_locked_out; public is_physical_person; - public constructor(importedParticipant_id: BackendImportIdentifiedRow['id'], data?: BackendImportIdentifiedRow) { - const importedParticipant = data['data'] ?? ''; - this.id = importedParticipant_id; + public constructor(preview_id, preview: BackendImportRow) { + this.id = preview_id; - this.title = getValue(importedParticipant?.['title']); - this.first_name = getValue(importedParticipant?.['first_name']); - this.last_name = getValue(importedParticipant?.['last_name']); - this.email = getValue(importedParticipant?.['email']); - this.member_number = getValue(importedParticipant?.['member_number']); - this.structure_level = importedParticipant?.['structure_level']; - this.groups = importedParticipant?.['groups']?.[0]; - this.number = getValue(importedParticipant?.['number']); - this.vote_weight = getValue(importedParticipant?.['vote_weight']); - this.gender = getValue(importedParticipant?.['gender']); - this.pronoun = getValue(importedParticipant?.['pronoun']); - this.username = getValue(importedParticipant?.['username']); - this.default_password = getValue(importedParticipant?.['default_password']); + this.data = preview.data; + this.messages = preview.messages; + this.state = preview.state; - this.is_active = getValue(importedParticipant?.['is_active']); - this.is_physical_person = getValue(importedParticipant?.['is_physical_person']); - this.is_present = getValue(importedParticipant?.['is_present']); - this.is_locked_out = getValue(importedParticipant?.['locked_out']); + this.title = this.setValue(this.data?.['title']); + this.first_name = this.setValue(this.data?.['first_name']); + this.last_name = this.setValue(this.data?.['last_name']); + this.email = this.setValue(this.data?.['email']); + this.member_number = this.setValue(this.data?.['member_number']); + this.structure_level = this.data?.['structure_level']; + this.groups = this.data?.['groups']?.[0]; + this.number = this.setValue(this.data?.['number']); + this.vote_weight = this.setValue(this.data?.['vote_weight']); + this.gender = this.setValue(this.data?.['gender']); + this.pronoun = this.setValue(this.data?.['pronoun']); + this.username = this.setValue(this.data?.['username']); + this.default_password = this.setValue(this.data?.['default_password']); - this.saml_id = getValue(importedParticipant?.['saml_id']); - this.home_committee = getValue(importedParticipant?.['home_committee']); - this.external_comment = getValue(importedParticipant?.['external_comment']); + this.is_active = this.getBooleanValue(this.setValue(this.data?.['is_active'])); + this.is_physical_person = this.getBooleanValue(this.setValue(this.data?.['is_physical_person'])); + this.is_present = this.getBooleanValue(this.setValue(this.data?.['is_present'])); + this.is_locked_out = this.getBooleanValue(this.setValue(this.data?.['locked_out'])); + + this.saml_id = this.setValue(this.data?.['saml_id']); + this.home_committee = this.setValue(this.data?.['home_committee']); + this.external_comment = this.setValue(this.data?.['external_comment']); } public static readonly REQUESTABLE_FIELDS: (keyof ViewImportedParticipant)[] = [ @@ -81,24 +91,27 @@ export class ViewImportedParticipant implements Identifiable /* implements Searc 'is_locked_out', 'is_physical_person' ]; -} -function getValue(field: BackendImportEntry | BackendImportEntry[]): string | number | boolean { - if (!field) { - console.log('No field:', field); - return 0; + public setValue(field: BackendImportEntry | BackendImportEntry[]): string | number | boolean | undefined { + if (!field) { + return undefined; + } + if (Array.isArray(field)) { + return this.setValue(field[0]); + } + if (typeof field === 'string') { + return field; + } + if (typeof field === 'number') { + return field; + } + if (typeof field === 'boolean') { + return field; + } + return field.value; } - if (Array.isArray(field)) { - return getValue(field[0]); - } - if (typeof field === 'string') { - return field; - } - if (typeof field === 'number') { - return field; - } - if (typeof field === 'boolean') { - return field; + + public getBooleanValue(value: string | number | boolean | undefined): boolean { + return !!value; } - return field.value; } diff --git a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts index 5615278084..90d94f55fc 100644 --- a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts +++ b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts @@ -1,4 +1,5 @@ import { Identifiable } from 'src/app/domain/interfaces'; +import { ViewImportedParticipant } from 'src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import'; export enum BackendImportState { Error = `error`, @@ -51,7 +52,7 @@ export interface BackendImportPreview { id: number; // id of action_worker to import state: BackendImportState; // May be `error`, `warning` or `done` headers: BackendImportHeader[]; - rows: BackendImportIdentifiedRow[]; + rows: ViewImportedParticipant[]; statistics: BackendImportSummary[]; } diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss index 34b4f8edac..2c13c8b357 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss @@ -43,7 +43,7 @@ .scrolling-table-header { @include scrolling-table-style(); - overflow-x: scroll; + .scrolling-table-cell { font-weight: 500; } From 5dcb2c0ee2845a49c47467e39595678515b0a14f Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 12 Jun 2026 09:53:21 +0200 Subject: [PATCH 10/35] Added CSV-related buttons to sort-filter-bar. Updated filters. --- ...icipant-import-list-preview.component.html | 3 +- ...rticipant-import-list-preview.component.ts | 14 +++- .../participant-import-filter.service.ts | 20 ++--- .../view-models/view-participant-import.ts | 38 ++++++++++ .../backend-import-list.component.ts | 8 +- .../definitions/backend-import-preview.ts | 2 +- .../modules/import-list/import-list.module.ts | 4 +- .../sort-filter-bar.component.html | 74 +++++++++++++++++++ .../sort-filter-bar.component.scss | 16 ++++ .../sort-filter-bar.component.ts | 12 +++ .../view-list/view-list.component.html | 2 + .../view-list/view-list.component.ts | 6 ++ client/src/app/ui/modules/list/list.module.ts | 14 +++- 13 files changed, 190 insertions(+), 23 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 36b2b12784..1a65cab9e3 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -11,6 +11,8 @@

Import participants

Import participants

isDefault: true; config: getColumnConfig(column.property) " - class="fixed-cols" > @let entry = row.data; @let type = column.type; diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 40da3dc876..708314b738 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -100,15 +100,25 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy protected _totalCountObservable: Observable = null; /** - * Wether or not to show the filter bar + * Whether or not to show the filter bar */ public showFilterBar = true; /** - * Wether or not to show the header + * Whether or not to show the header */ public showHeader = true; + /** + * Whether or not to show the CSV-Encoding button + */ + protected csvConfiguration = true; + + /** + * Whether or not to show the CSV- + */ + protected csvReload = true; // Reload CSV file + /** * Defines all necessary and optional fields, that a .csv-file can contain. */ diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts index 68ba996970..a843d933d4 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts @@ -42,7 +42,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `vote_weight`, + property: `changedVoteWeight`, label: _(`Vote weight`), options: [ { condition: [false, null], label: _(`Has changed vote weight`) }, @@ -58,7 +58,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `member_number`, + property: `hasMemberNumber`, label: _(`Membership number`), options: [ { condition: true, label: _(`Has a membership number`) }, @@ -66,7 +66,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `title`, + property: `hasTitle`, label: _(`Title`), options: [ { condition: true, label: _(`Has a title`) }, @@ -74,7 +74,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `saml_id`, + property: `hasSamlId`, label: _(`SSO`), options: [ { condition: true, label: _(`Has SSO identification`) }, @@ -93,7 +93,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `email`, + property: `hasEmail`, label: _(`Email address`), options: [ { condition: true, label: _(`Has an email address`) }, @@ -101,7 +101,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `pronoun`, + property: `hasPronoun`, label: _(`Pronoun`), options: [ { condition: true, label: _(`Has pronoun`) }, @@ -109,7 +109,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `username`, + property: `hasUsername`, label: _(`Username`), options: [ { condition: true, label: _(`Has username`) }, @@ -117,7 +117,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `home_committee`, + property: `hasHomeCommittee`, label: _(`Home committee`), options: [ { condition: true, label: _(`Has home committee`) }, @@ -125,8 +125,8 @@ export class ParticipantImportFilterService extends BaseFilterListService { ] }, { - property: `groups`, - label: _(`Username`), + property: `hasGroups`, + label: _(`Groups`), options: [ { condition: true, label: _(`Has groups`) }, { condition: [false, null], label: _(`Has no groups`) } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index a8f0b790cb..2ce8532e35 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -114,4 +114,42 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / public getBooleanValue(value: string | number | boolean | undefined): boolean { return !!value; } + + public get changedVoteWeight(): boolean { + return this.vote_weight < 1 || this.vote_weight > 1 || !this.vote_weight ? true : false; + } + + public get hasMemberNumber(): boolean { + console.log(this.member_number); + return this.member_number ? true : false; + } + + public get hasTitle(): boolean { + return this.title?.length > 0 ? true : false; + } + + public get hasSamlId(): boolean { + return this.saml_id ? true : false; + } + + public get hasEmail(): boolean { + return this.email?.length > 0 ? true : false; + } + + public get hasUsername(): boolean { + console.log(this.username); + return this.username ? true : false; + } + + public get hasPronoun(): boolean { + return this.pronoun ? true : false; + } + + public get hasHomeCommittee(): boolean { + return this.home_committee?.length > 0 ? true : false; + } + + public get hasGroups(): boolean { + return this.groups?.length > 0 ? true : false; + } } diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index 51a793a6b9..6ebb9c4f3e 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -343,7 +343,7 @@ export class BackendImportListComponent implements OnInit { * @param item a row or an entry with a current state * @eturn the icon for the item */ - public getActionIcon(item: BackendImportIdentifiedRow | BackendImportEntryObject): string { + public getActionIcon(item: ViewImportedParticipant | BackendImportEntryObject): string { switch (item[`state`] ?? item[`info`]) { case BackendImportState.Error: // no import possible return `block`; @@ -374,7 +374,7 @@ export class BackendImportListComponent implements OnInit { * @param entry a row with a current state * @eturn the tooltip for the item */ - public getRowTooltip(row: BackendImportIdentifiedRow): string { + public getRowTooltip(row: ViewImportedParticipant): string { switch (row.state) { case BackendImportState.Error: // no import possible return ( @@ -398,7 +398,7 @@ export class BackendImportListComponent implements OnInit { } } - public getWarningRowTooltip(row: BackendImportIdentifiedRow): string { + public getWarningRowTooltip(row: ViewImportedParticipant): string { switch (row.state) { case BackendImportState.Error: // no import possible return ( @@ -489,7 +489,7 @@ export class BackendImportListComponent implements OnInit { } } - private getErrorDescription(entry: BackendImportIdentifiedRow): string { + private getErrorDescription(entry: ViewImportedParticipant): string { return entry.messages?.map(error => this.translate.instant(this._importer.verbose(error))).join(`\n `); } diff --git a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts index 90d94f55fc..34bba13bb2 100644 --- a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts +++ b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts @@ -52,7 +52,7 @@ export interface BackendImportPreview { id: number; // id of action_worker to import state: BackendImportState; // May be `error`, `warning` or `done` headers: BackendImportHeader[]; - rows: ViewImportedParticipant[]; + rows: BackendImportIdentifiedRow[] | ViewImportedParticipant[]; statistics: BackendImportSummary[]; } diff --git a/client/src/app/ui/modules/import-list/import-list.module.ts b/client/src/app/ui/modules/import-list/import-list.module.ts index 9e62506c20..bc760e1bef 100644 --- a/client/src/app/ui/modules/import-list/import-list.module.ts +++ b/client/src/app/ui/modules/import-list/import-list.module.ts @@ -10,7 +10,6 @@ import { MatIconModule } from '@angular/material/icon'; import { MatSelectModule } from '@angular/material/select'; import { MatTabsModule } from '@angular/material/tabs'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { RouterOutlet } from '@angular/router'; import { EditorModule } from 'src/app/ui/modules/editor/editor.module'; import { ScrollingTableModule } from 'src/app/ui/modules/scrolling-table'; import { PipesModule } from 'src/app/ui/pipes/pipes.module'; @@ -46,8 +45,7 @@ const DECLARATIONS = [ FormsModule, PortalModule, ScrollingTableModule, - OpenSlidesTranslationModule.forChild(), - RouterOutlet + OpenSlidesTranslationModule.forChild() ] }) export class ImportListModule {} diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html index 33ea34c07e..7225a684f1 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html @@ -77,6 +77,37 @@ } } + + @if (csvReload) { + + } + + + @if (csvConfiguration) { + + } @if (hasFilters) {

} + +
+ + @for (col of summary; track col) { +
+ + + + {{ col.value }} {{ col.name }}  +
+
+ } +

diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html index 7225a684f1..59a658052b 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html @@ -82,7 +82,7 @@ -
Import participants [listObservable]="dataSource" [searchFieldInput]="searchFieldInput" [searchService]="searchService" + [selectedColumnSeparatorOption]="selectedColumnSeparatorOption" + [selectedEncodingOption]="selectedEncodingOption" [showFilterBar]="showFilterBar" [showHeader]="showHeader" [totalCount]="_totalCountObservable | async" @@ -142,10 +143,13 @@

Import participants

} + @for (encoding of columnSeparators; track encoding) { + {{ encoding.value }} + } -
+
@let cols = shortenSummary(summary); - @for (col of cols; track col) { + @for (col of cols; track col; let i = $index) { @let colInfo = getSummaryInformation(col.name); {{ colInfo[0] }} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index fffc4d5b7f..33a1681a24 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -214,6 +214,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy */ public selectedEncoding = `utf-8`; + @Input() + protected selectedEncodingOption: string = this.selectedEncoding; + /** * @returns the encodings available and their labels */ @@ -228,6 +231,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return this.importer.columnSeparators; } + public selectedColumnSeparator = ''; + + @Input() + protected selectedColumnSeparatorOption: string = this.selectedColumnSeparator; + /** * @eturns the available text separators and their labels */ @@ -460,8 +468,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * Trigger for the column separator selection. */ - public selectColSep(event: MatSelectChange): void { - this.importer.columnSeparator = event.value; + public selectColSep(): void { + this.importer.columnSeparator = this.columnSeparators[this.selectedColumnSeparator].value; this.importer.refreshFile(); } @@ -476,8 +484,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * Trigger for the encoding selection. */ - public selectEncoding(event: MatSelectChange): void { - this.importer.encoding = event.value; + public selectEncoding(value): void { + console.log(value); + this.importer.encoding = value; this.importer.refreshFile(); } @@ -556,6 +565,10 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } protected shortenSummary(summary: BackendImportSummary[]): BackendImportSummary[] { - return summary?.filter(col => col.name !== 'structure levels created' && col.name !== 'groups created'); + return summary?.filter( + col => col.name !== 'structure levels created' && col.name !== 'groups created' && col.name !== 'warning' + ); } + + public applyFilter(): void {} } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts index 9581478826..e0be07643e 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { _ } from '@ngx-translate/core'; +import { GENDER_FITLERABLE, GENDERS } from 'src/app/domain/models/users/user'; import { BaseFilterListService, OsFilter } from 'src/app/site/base/base-filter.service'; import { ActiveFiltersService } from 'src/app/site/services/active-filters.service'; @@ -15,7 +16,17 @@ export class ParticipantImportFilterService extends BaseFilterListService { } protected getFilterDefinitions(): OsFilter[] { - /* return [ + return [ + { + property: 'state', + label: _('Participant state'), + options: [ + { condition: STATE_FITERABLE[0], label: STATES[0] }, + { condition: STATE_FITERABLE[1], label: STATES[1] }, + { condition: STATE_FITERABLE[2], label: STATES[2] }, + { condition: STATE_FITERABLE[3], label: STATES[3] } + ] + }, { property: `is_active`, label: _(`Active`), @@ -130,21 +141,7 @@ export class ParticipantImportFilterService extends BaseFilterListService { { condition: true, label: _(`Has groups`) }, { condition: [false, null], label: _(`Has no groups`) } ] - }]; - */ - - const staticFilterOptions: OsFilter[] = [ - { - property: 'state', - label: _('New participant'), - options: [ - { condition: STATE_FITERABLE[0], label: STATES[0] }, - { condition: STATE_FITERABLE[1], label: STATES[1] }, - { condition: STATE_FITERABLE[2], label: STATES[2] }, - { condition: STATE_FITERABLE[3], label: STATES[3] } - ] } ]; - return staticFilterOptions; } } diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html index 69c5ab47f8..d9962076a7 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html @@ -223,26 +223,26 @@

{{ 'File encoding' | translate }}

{{ 'UTF 8 - UNICODE' | translate }} {{ 'ISO 8859-1 - West European' | translate }} {{ 'ISO 8859-15 West European (with €)' | translate }} @@ -252,25 +252,25 @@

{{ 'File encoding' | translate }}

{{ 'Column separator' | translate }}

- + {{ 'Automatic' | translate }} {{ 'Comma' | translate }} {{ 'Semicolon' | translate }} @@ -280,25 +280,25 @@

{{ 'Column separator' | translate }}

{{ 'Text separator' | translate }}

- + {{ "Double quotes ('')" | translate }} {{ "Single quotes (')" | translate }} {{ 'Gravis (`)' | translate }} diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts index 9b135d8b9d..6ed4493e49 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts @@ -71,9 +71,11 @@ export class SortFilterBarComponent implements OnDestroy @Input() public csvConfiguration: boolean; - protected selectedEncoding = '1'; - protected selectedColumnSeparator = '1'; - protected selectedTextSeparator = '1'; + @Input() + public selectedEncodingOption = 'utf-8'; + + public selectedColumnSeparatorOption = '1'; + public selectedTextSeparatorOption = '1'; @Input() public csvReload: ParticipantImportService; @@ -232,6 +234,7 @@ export class SortFilterBarComponent implements OnDestroy this.searchEdit = false; } }); + console.log('sortfilter', this.selectedColumnSeparatorOption); } public ngOnDestroy(): void { diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.html b/client/src/app/ui/modules/list/components/view-list/view-list.component.html index 49f5c13057..79e0c1ee65 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.html +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.html @@ -10,6 +10,7 @@ [filterService]="filterService!" [searchFieldInput]="searchFieldInput" [searchService]="searchService!" + [selectedEncodingOption]="selectedEncodingOption" [showSpacer]="addBottomSpacer" [sortService]="sortService!" [totalCount]="totalCountObservable | async" @@ -20,6 +21,7 @@
} + {{ selectedColumnSeparatorOption }} implements OnInit, OnDest @Input() public csvConfiguration: boolean; + @Input() + public selectedEncodingOption = ''; + + @Input() + public selectedColumnSeparatorOption = ''; + @Input() public csvReloadButton: boolean; From f343c93bac0a22846c2b3908371b140ed3ce707a Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Wed, 17 Jun 2026 12:02:01 +0200 Subject: [PATCH 14/35] Added horizontal scroll to table --- ...icipant-import-list-preview.component.html | 5 +- ...rticipant-import-list-preview.component.ts | 14 +- .../sort-filter-bar.component.html | 49 ++++--- .../sort-filter-bar.component.ts | 32 ++++- .../view-list/view-list.component.html | 3 +- .../view-list/view-list.component.ts | 13 +- .../scrolling-table.component.html | 136 +++++++++--------- .../scrolling-table.component.scss | 1 - .../scrolling-table.component.ts | 3 + 9 files changed, 151 insertions(+), 105 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 7cfa504e9e..0cfa7020ef 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -15,11 +15,11 @@

Import participants

[csvReloadButton]="csvReloadButton" [filterProps]="filterProps" [filterService]="filterService" + [horizontalScroll]="horizontalScroll" [listObservable]="dataSource" [searchFieldInput]="searchFieldInput" [searchService]="searchService" [selectedColumnSeparatorOption]="selectedColumnSeparatorOption" - [selectedEncodingOption]="selectedEncodingOption" [showFilterBar]="showFilterBar" [showHeader]="showHeader" [totalCount]="_totalCountObservable | async" @@ -143,9 +143,6 @@

Import participants

} - @for (encoding of columnSeparators; track encoding) { - {{ encoding.value }} - }
@let cols = shortenSummary(summary); diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 33a1681a24..9037809819 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -104,6 +104,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy */ public showFilterBar = true; + /** + * Whether or not to allow horizontal scroll + */ + public horizontalScroll = true; + /** * Whether or not to show the header */ @@ -214,9 +219,6 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy */ public selectedEncoding = `utf-8`; - @Input() - protected selectedEncodingOption: string = this.selectedEncoding; - /** * @returns the encodings available and their labels */ @@ -234,7 +236,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy public selectedColumnSeparator = ''; @Input() - protected selectedColumnSeparatorOption: string = this.selectedColumnSeparator; + protected selectedColumnSeparatorOption: string; /** * @eturns the available text separators and their labels @@ -469,6 +471,10 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * Trigger for the column separator selection. */ public selectColSep(): void { + switch (this.selectedColumnSeparatorOption) { + case '1': + this.importer.columnSeparator = this.columnSeparators[0].value; + } this.importer.columnSeparator = this.columnSeparators[this.selectedColumnSeparator].value; this.importer.refreshFile(); } diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html index d9962076a7..860da64d10 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html @@ -224,6 +224,7 @@

{{ 'File encoding' | translate }}

class="radio-group" style="display: flex; flex-direction: column" [(ngModel)]="selectedEncodingOption" + (change)="sendSelectedEncoding()" > {{ 'File encoding' | translate }}

{{ 'Column separator' | translate }}

- + {{ 'Automatic' | translate }} {{ 'Comma' | translate }} {{ 'Semicolon' | translate }} @@ -280,25 +285,29 @@

{{ 'Column separator' | translate }}

{{ 'Text separator' | translate }}

- + {{ "Double quotes ('')" | translate }} {{ "Single quotes (')" | translate }} {{ 'Gravis (`)' | translate }} diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts index 6ed4493e49..8d95833ad7 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts @@ -68,14 +68,24 @@ export class SortFilterBarComponent implements OnDestroy @Input() public searchService: SearchService | undefined; + /** + * CSV options + */ @Input() public csvConfiguration: boolean; - @Input() public selectedEncodingOption = 'utf-8'; + public selectedColumnSeparatorOption = 'Automatic'; + public selectedTextSeparatorOption = "''"; + + @Output() + public selectedEncoding = new EventEmitter(); + + @Output() + public selectedColumnSeparator = new EventEmitter(); - public selectedColumnSeparatorOption = '1'; - public selectedTextSeparatorOption = '1'; + @Output() + public selectedTextSeparator = new EventEmitter(); @Input() public csvReload: ParticipantImportService; @@ -234,7 +244,6 @@ export class SortFilterBarComponent implements OnDestroy this.searchEdit = false; } }); - console.log('sortfilter', this.selectedColumnSeparatorOption); } public ngOnDestroy(): void { @@ -326,6 +335,21 @@ export class SortFilterBarComponent implements OnDestroy this._searchFieldComponent?.clear(); } + public sendSelectedEncoding(): void { + console.log(this.selectedEncodingOption); + this.selectedEncoding.emit(this.selectedEncodingOption); + } + + public sendSelectedColumnSeparator(): void { + console.log(this.selectedColumnSeparatorOption); + this.selectedColumnSeparator.emit(this.selectedColumnSeparatorOption); + } + + public sendSelectedTextSeparator(): void { + console.log(this.selectedTextSeparatorOption); + this.selectedTextSeparator.emit(this.selectedTextSeparatorOption); + } + @HostListener(`document:keydown`, [`$event`]) public onKeyDown(event: KeyboardEvent): void { if ((event.ctrlKey || event.metaKey) && event.key === `f`) { event.preventDefault(); diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.html b/client/src/app/ui/modules/list/components/view-list/view-list.component.html index 79e0c1ee65..06e0466e86 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.html +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.html @@ -10,7 +10,6 @@ [filterService]="filterService!" [searchFieldInput]="searchFieldInput" [searchService]="searchService!" - [selectedEncodingOption]="selectedEncodingOption" [showSpacer]="addBottomSpacer" [sortService]="sortService!" [totalCount]="totalCountObservable | async" @@ -21,13 +20,13 @@
} - {{ selectedColumnSeparatorOption }} implements OnInit, OnDest @Input() public csvConfiguration: boolean; - @Input() - public selectedEncodingOption = ''; + @Output() + public selectedEncoding = new EventEmitter(); @Input() - public selectedColumnSeparatorOption = ''; + public selectedColumnSeparatorOption: string; @Input() public csvReloadButton: boolean; @@ -120,6 +120,9 @@ export class ViewListComponent implements OnInit, OnDest @Input() public showHeader: boolean; + @Input() + public horizontalScroll: boolean; + /** * Fix value for the height of the rows in the virtual-scroll-list. */ @@ -287,4 +290,8 @@ export class ViewListComponent implements OnInit, OnDest public clearSearchField(): void { this._sortFilterBarComponent?.clearSearchField(); } + + public sendSelectedEncoding(): void { + this.selectedEncoding.emit(); + } } diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html index d9ccfd0c34..f3e7dbb7f5 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html @@ -7,73 +7,75 @@ [maxBufferPx]="rowHeight * 8" [minBufferPx]="rowHeight * 6" > - @if (showHeader) { -
- @for (definition of definitions; track definition; let first = $first) { -
- @if (definition.labelTemplate) { - - } @else { - {{ definition.labelString }} - } -
- } -
- } - -
- @if (isSelectionMode) { -
- -
- } - @for (definition of definitions; track definition) { -
- -
- } -
-
+
+ @if (showHeader) { +
+ @for (definition of definitions; track definition; let first = $first) { +
+ @if (definition.labelTemplate) { + + } @else { + {{ definition.labelString }} + } +
+ } +
+ } + +
+ @if (isSelectionMode) { +
+ +
+ } + @for (definition of definitions; track definition) { +
+ +
+ } +
+
+
} @else { diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss index 2c13c8b357..bb7900a5b9 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss @@ -58,7 +58,6 @@ height: 100%; .scrolling-table-row { - overflow: scroll; @include scrolling-table-style(); } } diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts index 785cc5b53e..88ce7bc727 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts @@ -53,6 +53,9 @@ export class ScrollingTableComponent>> @Input() public defaultColumnWidth: number | null = null; + @Input() + public horizontalScroll = false; + @Input() public showHeader = false; From 0d101c91eaeca784f6ce89cea98b4cd613bdce01 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Wed, 17 Jun 2026 17:05:55 +0200 Subject: [PATCH 15/35] Better HScroll support in Chrome. Minor UI Fixes --- .../participant-import-list-preview.component.html | 4 ++-- .../participant-import-list-preview.component.scss | 8 +++++++- .../participant-import-list-preview.component.ts | 7 ++++++- .../sort-filter-bar/sort-filter-bar.component.scss | 2 +- .../scrolling-table/scrolling-table.component.html | 7 ++++++- .../scrolling-table/scrolling-table.component.scss | 1 - .../scrolling-table/scrolling-table.component.ts | 2 +- 7 files changed, 23 insertions(+), 8 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 0cfa7020ef..8ff00a811e 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -8,7 +8,7 @@

Import participants

-
+
Import participants } -
+
@let cols = shortenSummary(summary); @for (col of cols; track col; let i = $index) { @let colInfo = getSummaryInformation(col.name); diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 83bb4e7003..083651a0c9 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -17,6 +17,7 @@ $os-yellow: rgb(255, 193, 7); } .footer { + padding-left: 35px; position: sticky; bottom: 0; left: 0; @@ -27,10 +28,15 @@ $os-yellow: rgb(255, 193, 7); border-top: 1px solid #aaa; display: flex; align-items: center; - padding: 0 16px; } .icon { margin-top: 5px; font-size: x-large; } + +.static-sort-filter-bar { + margin: 0; + height: 100%; + overflow: hidden; +} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 9037809819..4ed5968a12 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -1,5 +1,7 @@ import { AsyncPipe, NgClass } from '@angular/common'; import { + ChangeDetectionStrategy, + ChangeDetectorRef, Component, ContentChild, ContentChildren, @@ -54,6 +56,7 @@ import { ViewImportedParticipant } from '../../view-models/view-participant-impo selector: `os-participant-import-list-preview`, templateUrl: `./participant-import-list-preview.component.html`, styleUrls: [`./participant-import-list-preview.component.scss`], + changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeadBarModule, ListModule, MatIcon, AsyncPipe, MatTooltip, MatCheckbox, NgClass] }) export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy { @@ -283,7 +286,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy public constructor( private dialog: MatDialog, protected translate: TranslateService, - protected readonly controller: ParticipantControllerService + protected readonly controller: ParticipantControllerService, + private cd: ChangeDetectorRef ) {} /** @@ -300,6 +304,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this._dataSource = this.importer.previewsObservable.pipe(map(previews => this.calculateRows(previews))); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); + this.cd.detectChanges(); } /** diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.scss b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.scss index 266631caba..d4010ffc9b 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.scss +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.scss @@ -113,7 +113,7 @@ os-sort-filter-bar { color: gray !important; } mat-drawer { - margin-top: 2.5%; + margin-top: 41px; } .container { diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html index f3e7dbb7f5..a48a5c559c 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html @@ -7,7 +7,12 @@ [maxBufferPx]="rowHeight * 8" [minBufferPx]="rowHeight * 6" > -
+
@if (showHeader) {
>> } private buildDataTable(): void { - const source = [...this._fullSource].sort((a, b) => a.id - b.id); + const source = [...this._fullSource!].sort((a, b) => a.id - b.id); const sourceMapKeys = Object.keys(this._dataSourceMap) .map(key => Number(key)) .sort((a, b) => a - b); From 2361b057455ce0cee3fffc7f3421dd13542ed1be Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Thu, 18 Jun 2026 17:36:24 +0200 Subject: [PATCH 16/35] CSV-Options build. Import dialog prototype --- ...icipant-import-list-preview.component.html | 30 +++++++- ...icipant-import-list-preview.component.scss | 39 ++++++++++ ...rticipant-import-list-preview.component.ts | 75 ++++++++++++------- .../sort-filter-bar.component.html | 6 +- .../sort-filter-bar.component.ts | 21 +++--- .../view-list/view-list.component.html | 3 + .../view-list/view-list.component.ts | 24 ++++-- 7 files changed, 150 insertions(+), 48 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 8ff00a811e..5cd9df6909 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -3,7 +3,7 @@

Import participants

- @@ -19,12 +19,14 @@

Import participants

[listObservable]="dataSource" [searchFieldInput]="searchFieldInput" [searchService]="searchService" - [selectedColumnSeparatorOption]="selectedColumnSeparatorOption" [showFilterBar]="showFilterBar" [showHeader]="showHeader" [totalCount]="_totalCountObservable | async" [vScrollFixed]="50" (searchFilterUpdated)="searchFilterUpdated.emit($event)" + (selectedColSepOutput)="onColSepChanged($event)" + (selectedEncodingOutput)="onEncodingChanged($event)" + (selectedTextSeparatorOutput)="onTextSeparatorChanged($event)" > @if (dataSource | async) { @for (column of previewColumns; track column; let i = $index) { @@ -143,7 +145,6 @@

Import participants

} -
@let cols = shortenSummary(summary); @for (col of cols; track col; let i = $index) { @@ -155,3 +156,26 @@

Import participants

}
+ + +
+

Import participants

+ + + + +
+
Import in progress!
+ +
+ Do not close the tab if you want to see the import status! +
+
+
+
+
+ diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 083651a0c9..a7c2682ebe 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -40,3 +40,42 @@ $os-yellow: rgb(255, 193, 7); height: 100%; overflow: hidden; } + +.import-dialog { + width: 500px; + padding: 8px; +} + +.import-dialog-title { + color: var(--theme-accent-400); + font-size: 20px; + font-weight: 500; + margin-bottom: 0; +} + +.import-dialog-content { + min-height: 260px; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + color: var(--theme-accent-400); + text-align: center; +} + +.import-status { + margin-top: 32px; +} + +.import-status-title { + font-size: 28px; + font-weight: 400; + margin-bottom: 12px; +} + +.import-status-subtitle { + font-size: 18px; + line-height: 1.4; +} diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 4ed5968a12..ec67957143 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -15,9 +15,9 @@ import { TemplateRef } from '@angular/core'; import { MatCheckbox } from '@angular/material/checkbox'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatIcon } from '@angular/material/icon'; -import { MatSelectChange } from '@angular/material/select'; +import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { MatTabChangeEvent } from '@angular/material/tabs'; import { MatTooltip } from '@angular/material/tooltip'; import { _, TranslateService } from '@ngx-translate/core'; @@ -57,7 +57,17 @@ import { ViewImportedParticipant } from '../../view-models/view-participant-impo templateUrl: `./participant-import-list-preview.component.html`, styleUrls: [`./participant-import-list-preview.component.scss`], changeDetection: ChangeDetectionStrategy.OnPush, - imports: [HeadBarModule, ListModule, MatIcon, AsyncPipe, MatTooltip, MatCheckbox, NgClass] + imports: [ + HeadBarModule, + ListModule, + MatIcon, + AsyncPipe, + MatTooltip, + MatCheckbox, + NgClass, + MatDialogModule, + MatProgressSpinner + ] }) export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy { public readonly END_POSITION = END_POSITION; @@ -220,7 +230,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * Currently selected encoding. Is set and changed by the config's available * encodings and user mat-select input */ - public selectedEncoding = `utf-8`; + public selectedEncoding: string; /** * @returns the encodings available and their labels @@ -236,10 +246,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return this.importer.columnSeparators; } - public selectedColumnSeparator = ''; - - @Input() - protected selectedColumnSeparatorOption: string; + /** + * Currently selected column separator. Is set and changed by the config's available + * column separators and user mat-select input + */ + public selectedColumnSeparator; /** * @eturns the available text separators and their labels @@ -311,6 +322,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * Resets the importer when leaving the view */ public ngOnDestroy(): void { + this.importer.clearPreview(); this.importer.clearAll(); this.importer.clearFile(); } @@ -473,32 +485,24 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } /** - * Trigger for the column separator selection. + * The column separator selection. */ - public selectColSep(): void { - switch (this.selectedColumnSeparatorOption) { - case '1': - this.importer.columnSeparator = this.columnSeparators[0].value; - } - this.importer.columnSeparator = this.columnSeparators[this.selectedColumnSeparator].value; - this.importer.refreshFile(); + public onColSepChanged(label: string): void { + this.importer.columnSeparator = this.importer.columnSeparators.find(col => col.label === label)?.value; } /** - * Trigger for the column separator selection + * The text separator selection */ - public selectTextSep(event: MatSelectChange): void { - this.importer.textSeparator = event.value; - this.importer.refreshFile(); + public onTextSeparatorChanged(value: string): void { + this.importer.textSeparator = value; } /** - * Trigger for the encoding selection. + * The encoding selection. */ - public selectEncoding(value): void { - console.log(value); + public onEncodingChanged(value: string): void { this.importer.encoding = value; - this.importer.refreshFile(); } /** @@ -581,5 +585,26 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ); } - public applyFilter(): void {} + //simplify this + public DefaultOptions(): void { + if (!this.importer.encoding) { + this.importer.encoding = this.importer.encodings[0].value; + } + if (!this.importer.columnSeparator) { + this.importer.columnSeparator = this.importer.columnSeparators[0].value; + } + if (!this.importer.textSeparator) { + this.importer.textSeparator = this.importer.textSeparators[0].value; + } + } + + public async importData(dialogTemplate: TemplateRef): Promise { + const ref = this.dialog.open(dialogTemplate, { + width: `500px`, + disableClose: false, + maxWidth: `90vw`, + maxHeight: `90vh` + }); + await firstValueFrom(ref.afterClosed()); + } } diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html index 860da64d10..3c213e3643 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html @@ -224,7 +224,7 @@

{{ 'File encoding' | translate }}

class="radio-group" style="display: flex; flex-direction: column" [(ngModel)]="selectedEncodingOption" - (change)="sendSelectedEncoding()" + (change)="sendSelectedEncoding($event)" > {{ 'Column separator' | translate }} {{ 'Text separator' | translate }} implements OnDestroy public selectedTextSeparatorOption = "''"; @Output() - public selectedEncoding = new EventEmitter(); + public selectedEncodingOutput = new EventEmitter(); @Output() - public selectedColumnSeparator = new EventEmitter(); + public selectedColSepOutput = new EventEmitter(); @Output() - public selectedTextSeparator = new EventEmitter(); + public selectedTextSeparatorOutput = new EventEmitter(); @Input() public csvReload: ParticipantImportService; @@ -335,19 +335,16 @@ export class SortFilterBarComponent implements OnDestroy this._searchFieldComponent?.clear(); } - public sendSelectedEncoding(): void { - console.log(this.selectedEncodingOption); - this.selectedEncoding.emit(this.selectedEncodingOption); + public sendSelectedEncoding($event): void { + this.selectedEncodingOutput.emit($event); } - public sendSelectedColumnSeparator(): void { - console.log(this.selectedColumnSeparatorOption); - this.selectedColumnSeparator.emit(this.selectedColumnSeparatorOption); + public sendSelectedColumnSeparator($event): void { + this.selectedColSepOutput.emit($event); } - public sendSelectedTextSeparator(): void { - console.log(this.selectedTextSeparatorOption); - this.selectedTextSeparator.emit(this.selectedTextSeparatorOption); + public sendSelectedTextSeparator($event): void { + this.selectedTextSeparatorOutput.emit($event); } @HostListener(`document:keydown`, [`$event`]) public onKeyDown(event: KeyboardEvent): void { diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.html b/client/src/app/ui/modules/list/components/view-list/view-list.component.html index 06e0466e86..88c4ecaeb5 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.html +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.html @@ -14,6 +14,9 @@ [sortService]="sortService!" [totalCount]="totalCountObservable | async" (searchFieldChanged)="searchFilterUpdated.emit($event)" + (selectedColSepOutput)="sendSelectedColSep($event)" + (selectedEncodingOutput)="sendSelectedEncoding($event)" + (selectedTextSeparatorOutput)="sendSelectedTextSeparator($event)" >
diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts index 5a98419c80..b2d7c4afd6 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts @@ -66,10 +66,13 @@ export class ViewListComponent implements OnInit, OnDest public csvConfiguration: boolean; @Output() - public selectedEncoding = new EventEmitter(); + public selectedEncodingOutput = new EventEmitter(); - @Input() - public selectedColumnSeparatorOption: string; + @Output() + public selectedColSepOutput = new EventEmitter(); + + @Output() + public selectedTextSeparatorOutput = new EventEmitter(); @Input() public csvReloadButton: boolean; @@ -291,7 +294,18 @@ export class ViewListComponent implements OnInit, OnDest this._sortFilterBarComponent?.clearSearchField(); } - public sendSelectedEncoding(): void { - this.selectedEncoding.emit(); + public sendSelectedEncoding($event): void { + // FUNCIONA + this.selectedEncodingOutput.emit($event.value); + } + + public sendSelectedColSep($event): void { + // FUNCIONA + this.selectedColSepOutput.emit($event.value); + } + + public sendSelectedTextSeparator($event): void { + // FUNCIONA + this.selectedTextSeparatorOutput.emit($event.value); } } From 2c5b73d2290f5a9ccb65d17a98349c9c41e20a86 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 19 Jun 2026 16:45:48 +0200 Subject: [PATCH 17/35] Added import option. Added Loading dialog. Summary dialog prototype --- ...icipant-import-list-preview.component.html | 42 +++++--- ...icipant-import-list-preview.component.scss | 98 ++++++++++++++----- ...rticipant-import-list-preview.component.ts | 61 +++++++----- 3 files changed, 139 insertions(+), 62 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 5cd9df6909..e193b80f4a 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -3,7 +3,7 @@

Import participants

- @@ -157,16 +157,16 @@

Import participants

- +
-

Import participants

+

+ Importing participants +

- - +
Import in progress!
-
Do not close the tab if you want to see the import status!
@@ -174,8 +174,28 @@

Import participants

- + + +
+
+

Import successful

+ +
+

{{ summary[0].value }} participants have been processed

+
+ + @for (row of summary; track row) { + @let colInfo = getSummaryInformation(row.name); +
+ + {{ colInfo[0] }} + +
{{ row.value }} {{ row.name }}
+
+ } +
+
+
+
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index a7c2682ebe..8bda6ad3a0 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -4,6 +4,7 @@ $os-yellow: rgb(255, 193, 7); .os-green { color: $os-green; } + .os-yellow { color: $os-yellow; } @@ -42,40 +43,83 @@ $os-yellow: rgb(255, 193, 7); } .import-dialog { - width: 500px; padding: 8px; -} - -.import-dialog-title { color: var(--theme-accent-400); - font-size: 20px; - font-weight: 500; - margin-bottom: 0; -} -.import-dialog-content { - min-height: 260px; + .import-dialog-title { + color: var(--theme-accent-400); + font-size: 20px; + font-weight: 500; + margin-bottom: 0; + } - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + .import-dialog-content { + min-height: fit-content; - color: var(--theme-accent-400); - text-align: center; -} + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: var(--theme-accent-400); + text-align: center; + } -.import-status { - margin-top: 32px; -} + .import-status { + margin-top: 32px; + color: var(--theme-accent-400); + } + + .import-status-title { + font-size: 23px; + font-weight: 400; + margin-bottom: 12px; + } -.import-status-title { - font-size: 28px; - font-weight: 400; - margin-bottom: 12px; + .import-status-subtitle { + font-size: 18px; + } } -.import-status-subtitle { - font-size: 18px; - line-height: 1.4; +.summary-dialog { + .summary-dialog-header { + display: flex; + align-items: center; + justify-content: space-between; + } + + .close-button { + margin-right: 0.5em; + margin-top: 0.5em; + } + + .summary-dialog-title { + font-size: 20px; + font-weight: 500; + margin: 0; + } + + .summary-dialog-content { + min-height: 260px; + font-size: 17px; + color: #000000; + text-align: center; + } + + .summary-row { + display: flex; + align-items: start; + justify-content: center; + gap: 10px; + margin: 6px 0; + } + + .summary-row mat-icon { + font-size: x-large; + } + + .summary-dialog-subtitle { + font-size: 18px; + margin-left: 1.4em; + margin-top: 0; + } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index ec67957143..7292887870 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -21,8 +21,7 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { MatTabChangeEvent } from '@angular/material/tabs'; import { MatTooltip } from '@angular/material/tooltip'; import { _, TranslateService } from '@ngx-translate/core'; -import { firstValueFrom, map, Observable, of } from 'rxjs'; -import { infoDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; +import { map, Observable, of } from 'rxjs'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; import { HeadBarModule } from 'src/app/ui/modules/head-bar'; import { ImportListHeaderDefinition } from 'src/app/ui/modules/import-list'; @@ -505,14 +504,6 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this.importer.encoding = value; } - /** - * Opens an info dialog with the given template as content. - */ - public async openDialog(dialogTemplate: TemplateRef): Promise { - const ref = this.dialog.open(dialogTemplate, infoDialogSettings); - await firstValueFrom(ref.afterClosed()); - } - /** * Returns the verbose title for a given summary title. */ @@ -579,32 +570,54 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } } + /** + * Summary adapted to the footer. Displays only "created", "updated", and "error" columns. + * @param summary + * @returns BackendImportSummary[] + */ protected shortenSummary(summary: BackendImportSummary[]): BackendImportSummary[] { return summary?.filter( col => col.name !== 'structure levels created' && col.name !== 'groups created' && col.name !== 'warning' ); } - //simplify this - public DefaultOptions(): void { - if (!this.importer.encoding) { - this.importer.encoding = this.importer.encodings[0].value; - } - if (!this.importer.columnSeparator) { - this.importer.columnSeparator = this.importer.columnSeparators[0].value; - } - if (!this.importer.textSeparator) { - this.importer.textSeparator = this.importer.textSeparators[0].value; - } + private DefaultOptions(): void { + this.importer.encoding ??= this.importer.encodings[0].value; + this.importer.columnSeparator ??= this.importer.columnSeparators[0].value; + this.importer.textSeparator ??= this.importer.textSeparators[0].value; } - public async importData(dialogTemplate: TemplateRef): Promise { + public async importData(dialogTemplate: TemplateRef, summaryDialog: TemplateRef): Promise { + this.DefaultOptions(); const ref = this.dialog.open(dialogTemplate, { - width: `500px`, + width: `600px`, + disableClose: false, + maxWidth: `90vw`, + maxHeight: `90vh` + }); + + try { + if ( + await Promise.all([ + /* this.importer.doImport() */ true, + new Promise(resolve => setTimeout(resolve, 2000)) + ]) + ) { + this.importedDataSummary(summaryDialog); + ref.close(); + } + } catch (error) { + console.error('FAILED IMPORT: ', error); + ref.close(); + } + } + + private importedDataSummary(summaryDialog: TemplateRef): void { + this.dialog.open(summaryDialog, { + width: `600px`, disableClose: false, maxWidth: `90vw`, maxHeight: `90vh` }); - await firstValueFrom(ref.afterClosed()); } } From 9b3efcf11fce5986c1f636e46c5105f1a4694134 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Thu, 25 Jun 2026 09:07:49 +0200 Subject: [PATCH 18/35] Summary correctly displayed --- ...icipant-import-list-preview.component.html | 41 ++++++--- ...icipant-import-list-preview.component.scss | 26 ++++-- ...rticipant-import-list-preview.component.ts | 87 ++++++++++++------- .../view-models/view-participant-import.ts | 4 +- .../definitions/backend-import-preview.ts | 3 +- .../scrolling-table.component.scss | 8 ++ 6 files changed, 111 insertions(+), 58 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index e193b80f4a..10cd47e4d8 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -38,6 +38,7 @@

Import participants

isDefault: true; config: getColumnConfig(column.property) " + [class.error-icon]="containsError(row.data, def)" > @let entry = row.data; @let type = column.type; @@ -90,6 +91,7 @@

Import participants

} @if (!getHeader(def).is_list && entry[def] !== undefined) { @if (getHeader(def).is_object) { + @let icon = getEntryIcon(entry[def]);
@if (getHeader(def).type === 'string') { {{ entry[def].value }} @@ -106,8 +108,12 @@

Import participants

  @if (getActionIcon(entry[def])) { - - {{ getEntryIcon(entry[def]) }} + + {{ icon }} }
@@ -122,7 +128,7 @@

Import participants

*osScrollingTableCell="'errors'; row as row; config: { width: 55, position: START_POSITION }" class="flex-vertical-center icon-container" > -
 #
+
#
@if (row.state === 'error') { {{ getActionIcon(row) }} @@ -183,19 +189,26 @@

Import successfulclose

-

{{ summary[0].value }} participants have been processed

-
+

{{ summary[0]?.value }} participants have been processed

+
- @for (row of summary; track row) { - @let colInfo = getSummaryInformation(row.name); -
- - {{ colInfo[0] }} - -
{{ row.value }} {{ row.name }}
-
- } +
+ @let shortenedSummary = shortenSummary(summary); + @for (row of shortenedSummary; track row) { + @let colInfo = getSummaryInformation(row.name); +
+ + {{ colInfo[0] }} + +
{{ row.value }} {{ row.name }}
+
+ } +
+ @let extraInformation = summaryRest(summary); + @for (row of extraInformation; track row) { +
{{ row.value }} {{ row.name }}
+ }
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 8bda6ad3a0..045a161036 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -17,6 +17,10 @@ $os-yellow: rgb(255, 193, 7); width: 100%; } +.error-icon { + color: red; +} + .footer { padding-left: 35px; position: sticky; @@ -31,11 +35,6 @@ $os-yellow: rgb(255, 193, 7); align-items: center; } -.icon { - margin-top: 5px; - font-size: x-large; -} - .static-sort-filter-bar { margin: 0; height: 100%; @@ -55,7 +54,6 @@ $os-yellow: rgb(255, 193, 7); .import-dialog-content { min-height: fit-content; - display: flex; flex-direction: column; justify-content: center; @@ -99,16 +97,17 @@ $os-yellow: rgb(255, 193, 7); } .summary-dialog-content { - min-height: 260px; + display: flex; font-size: 17px; color: #000000; text-align: center; + justify-content: center; + margin-bottom: 22px; } .summary-row { display: flex; - align-items: start; - justify-content: center; + justify-content: start; gap: 10px; margin: 6px 0; } @@ -122,4 +121,13 @@ $os-yellow: rgb(255, 193, 7); margin-left: 1.4em; margin-top: 0; } + + .summary-dialog-extraInfo { + font-size: 17px; + margin-left: 1.4em; + } + + .summary-dialog-extraInfo:last-child { + margin-bottom: 16px; + } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 7292887870..63293e038a 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -21,8 +21,9 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { MatTabChangeEvent } from '@angular/material/tabs'; import { MatTooltip } from '@angular/material/tooltip'; import { _, TranslateService } from '@ngx-translate/core'; -import { map, Observable, of } from 'rxjs'; +import { map, Observable, of, Subscription } from 'rxjs'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; +import { ActiveMeetingIdService } from 'src/app/site/pages/meetings/services/active-meeting-id.service'; import { HeadBarModule } from 'src/app/ui/modules/head-bar'; import { ImportListHeaderDefinition } from 'src/app/ui/modules/import-list'; import { BackendImportPhase } from 'src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component'; @@ -72,6 +73,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy public readonly END_POSITION = END_POSITION; public readonly START_POSITION = START_POSITION; + protected activeMeetingIdService = inject(ActiveMeetingIdService); + @ContentChildren(ImportListFirstTabDirective) public importListFirstTabs!: QueryList; @@ -308,9 +311,10 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this.importer.currentImportPhaseObservable.subscribe(phase => { this._state = phase; }); - this.importer.previewsObservable.subscribe(previews => { + const tempPreviewsObservable: Subscription = this.importer.previewsObservable.subscribe(previews => { this.fillPreviewData(previews); }); + tempPreviewsObservable.unsubscribe(); this._dataSource = this.importer.previewsObservable.pipe(map(previews => this.calculateRows(previews))); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); @@ -387,12 +391,14 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return `error_outline`; case BackendImportState.Warning: return `warning`; - case BackendImportState.New: - return `add_circle_outline`; + case BackendImportState.New: // item will be imported / has been imported + return this._state !== BackendImportPhase.FINISHED ? `add_circle_outline` : `done`; case BackendImportState.Done: // item will be updated / has been imported return this._state !== BackendImportPhase.FINISHED ? `autorenew` : `done`; case BackendImportState.Generated: return `merge`; + case BackendImportState.Referenced: + return `merge`; case BackendImportState.Remove: return `remove`; default: @@ -409,9 +415,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.New: return 'os-green'; case BackendImportState.Done: // item will be updated / has been imported - return this._state !== BackendImportPhase.FINISHED ? `os-yellow` : `accent`; + return this._state !== BackendImportPhase.FINISHED ? `os-yellow` : `os-green`; case BackendImportState.Generated: return `accent`; + case BackendImportState.Referenced: + return `accent`; default: return `block`; // fallback: Error } @@ -423,7 +431,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy error: ['error_outline', 'red-warning-text'], warning: ['warning', 'warn'], created: ['add_circle_outline', 'os-green'], - updated: ['autorenew', 'os-yellow'] + updated: ['autorenew', 'os-yellow'], + // HAS TO BE ADDED TO SUMMARY IN ORDER TO WORK + referenced: ['merge', 'accent'] }[item] ?? ['group', 'accent'] ); } @@ -435,6 +445,15 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return this.getActionIcon(item); } + public containsError(entry: any, def: string): boolean { + const value = entry?.[def]; + if (!value) return false; + if (Array.isArray(value)) { + return value.some(icon => this.getEntryIcon(icon) === 'error_outline'); + } + return this.getEntryIcon(value) === 'error_outline'; + } + /** * Get the correct tooltip for the item * @param entry a row with a current state @@ -459,6 +478,14 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ? this.translate.instant(`will be updated`) : this.translate.instant(`has been imported`)) ); + case BackendImportState.Referenced: + return ( + this.translate.instant(this.modelName) + + ` ` + + (this._state !== BackendImportPhase.FINISHED + ? this.translate.instant(`will be referenced`) + : this.translate.instant(`has been imported and referenced`)) + ); default: return undefined; } @@ -581,43 +608,39 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ); } - private DefaultOptions(): void { - this.importer.encoding ??= this.importer.encodings[0].value; - this.importer.columnSeparator ??= this.importer.columnSeparators[0].value; - this.importer.textSeparator ??= this.importer.textSeparators[0].value; + protected summaryRest(summary: BackendImportSummary[]): BackendImportSummary[] { + return summary?.filter(col => col.name === 'structure levels created' || col.name === 'groups created'); } - public async importData(dialogTemplate: TemplateRef, summaryDialog: TemplateRef): Promise { - this.DefaultOptions(); - const ref = this.dialog.open(dialogTemplate, { + protected async importData(dialogTemplate: TemplateRef, summaryDialog: TemplateRef): Promise { + const customOptions = { width: `600px`, disableClose: false, maxWidth: `90vw`, maxHeight: `90vh` + }; + const sleep: (ms: number) => Promise = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + this.dialog.open(summaryDialog, { + data: this.summary, + ...customOptions + }); + const ref = this.dialog.open(dialogTemplate, { + data: this.summary, + ...customOptions, + hasBackdrop: false }); try { - if ( - await Promise.all([ - /* this.importer.doImport() */ true, - new Promise(resolve => setTimeout(resolve, 2000)) - ]) - ) { - this.importedDataSummary(summaryDialog); - ref.close(); - } - } catch (error) { - console.error('FAILED IMPORT: ', error); + await this.importer.doImport(); + await sleep(2000); + } finally { ref.close(); } } - private importedDataSummary(summaryDialog: TemplateRef): void { - this.dialog.open(summaryDialog, { - width: `600px`, - disableClose: false, - maxWidth: `90vw`, - maxHeight: `90vh` - }); - } + /* private isReferenced(row: ViewImportedParticipant): boolean { + if (!row) return false + + + } */ } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index ba4fc3e899..02b5dc4204 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -12,7 +12,7 @@ export const STATES = [ _('Referenced participant'), _(`Faulty participant`) ]; -export const STATE_FITERABLE = [`new`, `done`, `generated`, `error`]; +export const STATE_FITERABLE = [`new`, `done`, `referenced`, `error`]; export class ViewImportedParticipant implements Identifiable, BackendImportRow /* implements Searchable */ { // This class replaces BackendImportIdentifiedRow @@ -47,7 +47,7 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / public is_locked_out; public is_physical_person; - public constructor(preview_id, preview: BackendImportRow) { + public constructor(preview_id: number, preview: BackendImportRow) { this.id = preview_id; this.data = preview.data; diff --git a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts index 34bba13bb2..0133076c3b 100644 --- a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts +++ b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts @@ -7,7 +7,8 @@ export enum BackendImportState { New = `new`, Done = `done`, Generated = `generated`, - Remove = `remove` + Remove = `remove`, + Referenced = `referenced` // could be expanded later } diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss index 705ce447fc..11e9aab910 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss @@ -33,6 +33,12 @@ } } +@mixin render-all-table() { + display: flex; + width: max-content; + min-width: 100%; +} + :host, .scrolling-table-container { display: block; @@ -42,6 +48,7 @@ .scrolling-table-header { @include scrolling-table-style(); + @include render-all-table(); .scrolling-table-cell { font-weight: 500; @@ -58,6 +65,7 @@ .scrolling-table-row { @include scrolling-table-style(); + @include render-all-table(); } } From 17561426d37e320b5c1500d77df13f8dc1a5885c Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Tue, 30 Jun 2026 09:03:36 +0200 Subject: [PATCH 19/35] Referenced users displayed on table view --- ...icipant-import-list-preview.component.html | 10 +- ...icipant-import-list-preview.component.scss | 1 - ...rticipant-import-list-preview.component.ts | 151 +++++++++++------- .../view-models/view-participant-import.ts | 89 ++++++----- 4 files changed, 148 insertions(+), 103 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 10cd47e4d8..323eaa2d2d 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -65,7 +65,7 @@

Import participants

}   - @if (getActionIcon(item)) { + @if (getActionIconRow(item)) { {{ getEntryIcon(item.value) }} @@ -107,7 +107,7 @@

Import participants

}   - @if (getActionIcon(entry[def])) { + @if (getActionIconEntry(entry[def])) { Import participants
#
@if (row.state === 'error') { - {{ getActionIcon(row) }} + {{ getActionIconRow(row) }} } @if (row.state !== 'error' && row.messages.length) { @@ -145,7 +145,7 @@

Import participants

matTooltipPosition="right" [ngClass]="getColorIcon(row)" > - {{ getActionIcon(row) }} + {{ getActionIconRow(row) }}
}
@@ -190,7 +190,7 @@

Import successful

{{ summary[0]?.value }} participants have been processed

-
+
@let shortenedSummary = shortenSummary(summary); diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 045a161036..41ec89d3b6 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -102,7 +102,6 @@ $os-yellow: rgb(255, 193, 7); color: #000000; text-align: center; justify-content: center; - margin-bottom: 22px; } .summary-row { diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 63293e038a..bb68ea2961 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -24,6 +24,7 @@ import { _, TranslateService } from '@ngx-translate/core'; import { map, Observable, of, Subscription } from 'rxjs'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; import { ActiveMeetingIdService } from 'src/app/site/pages/meetings/services/active-meeting-id.service'; +import { AccountControllerService } from 'src/app/site/pages/organization/pages/accounts/services/common/account-controller.service'; import { HeadBarModule } from 'src/app/ui/modules/head-bar'; import { ImportListHeaderDefinition } from 'src/app/ui/modules/import-list'; import { BackendImportPhase } from 'src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component'; @@ -74,6 +75,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy public readonly START_POSITION = START_POSITION; protected activeMeetingIdService = inject(ActiveMeetingIdService); + protected accountsControllerService = inject(AccountControllerService); @ContentChildren(ImportListFirstTabDirective) public importListFirstTabs!: QueryList; @@ -112,6 +114,15 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy @Output() public searchFilterUpdated = new EventEmitter(); + @Output() + public selectedTabChanged = new EventEmitter(); + + /** + * Defines all necessary and optional fields, that a .csv-file can contain. + */ + @Input() + public possibleFields: string[] = []; + protected _totalCountObservable: Observable = null; /** @@ -139,14 +150,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy */ protected csvReloadButton = true; // Reload CSV file - /** - * Defines all necessary and optional fields, that a .csv-file can contain. - */ - @Input() - public possibleFields: string[] = []; - - @Output() - public selectedTabChanged = new EventEmitter(); + protected userAccounts = this.accountsControllerService.getViewModelList(); public readonly Phase = BackendImportPhase; @@ -286,7 +290,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy private _state: BackendImportPhase = BackendImportPhase.LOADING_PREVIEW; private _summary: BackendImportSummary[]; - private _rows: BackendImportIdentifiedRow[]; + private _rows: ViewImportedParticipant[]; private _previewColumns: BackendImportHeader[]; private _dataSource: Observable = of([]); @@ -333,7 +337,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * Triggers a change in the tab group: Clearing the preview selection */ - public onTabChange({ index }: MatTabChangeEvent): void { + protected onTabChange({ index }: MatTabChangeEvent): void { this.importer.clearAll(); this.selectedTabChanged.emit(index); } @@ -341,14 +345,14 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * True if there are custom tabs. */ - public hasSeveralTabs(): boolean { + protected hasSeveralTabs(): boolean { return this.importListFirstTabs.length + this.importListLastTabs.length > 0; } /** * triggers the importer's onSelectFile after a file has been chosen */ - public onSelectFile(event: any): void { + protected onSelectFile(event: any): void { this.uploadButton = false; this.importer.onSelectFile(event); } @@ -356,14 +360,14 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * Gets the relevant backend header information for a property. */ - public getHeader(propertyName: string): BackendImportHeader { + protected getHeader(propertyName: string): BackendImportHeader { return this._headers[propertyName]?.preview; } /** * Gets the style of the column for the given property. */ - public getColumnConfig(propertyName: string): ScrollingTableCellDefConfig { + protected getColumnConfig(propertyName: string): ScrollingTableCellDefConfig { const defaultHeader = this._headers[propertyName]?.default; const colWidth = defaultHeader?.width ?? 50; const def: ScrollingTableCellDefConfig = { minWidth: Math.max(150, colWidth) }; @@ -376,7 +380,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * Gets the label of the column for the given property. */ - public getColumnLabel(propertyName: string): string { + protected getColumnLabel(propertyName: string): string { return this._headers[propertyName]?.default?.label ?? propertyName; } @@ -385,19 +389,43 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * @param item a row or an entry with a current state * @return the icon for the item */ - public getActionIcon(item: ViewImportedParticipant | BackendImportEntryObject): string { - switch (item[`state`] ?? item[`info`]) { + protected getActionIconRow(item: ViewImportedParticipant): string { + switch (item[`state`]) { case BackendImportState.Error: // no import possible return `error_outline`; case BackendImportState.Warning: return `warning`; case BackendImportState.New: // item will be imported / has been imported return this._state !== BackendImportPhase.FINISHED ? `add_circle_outline` : `done`; - case BackendImportState.Done: // item will be updated / has been imported - return this._state !== BackendImportPhase.FINISHED ? `autorenew` : `done`; + case BackendImportState.Done: + if (this.isReferenced(item)) { + return 'merge'; + } else return 'autorenew'; case BackendImportState.Generated: return `merge`; - case BackendImportState.Referenced: + case BackendImportState.Remove: + return `remove`; + default: + return `block`; // fallback: Error + } + } + + /** + * Get the icon for the the entry + * @param item a row or an entry with a current state + * @return the icon for the item + */ + protected getActionIconEntry(item: BackendImportEntryObject): string { + switch (item[`info`]) { + case BackendImportState.Error: // no import possible + return `error_outline`; + case BackendImportState.Warning: + return `warning`; + case BackendImportState.New: // item will be imported / has been imported + return this._state !== BackendImportPhase.FINISHED ? `add_circle_outline` : `done`; + case BackendImportState.Done: + return this._state !== BackendImportPhase.FINISHED ? `merge` : `done`; + case BackendImportState.Generated: return `merge`; case BackendImportState.Remove: return `remove`; @@ -406,7 +434,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } } - public getColorIcon(item: ViewImportedParticipant | BackendImportEntryObject): string { + protected getColorIcon(item: ViewImportedParticipant | BackendImportEntryObject): string { switch (item[`state`] ?? item[`info`]) { case BackendImportState.Error: // no import possible return `red-warning-text`; @@ -415,37 +443,39 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.New: return 'os-green'; case BackendImportState.Done: // item will be updated / has been imported - return this._state !== BackendImportPhase.FINISHED ? `os-yellow` : `os-green`; + if (this._state === BackendImportPhase.FINISHED) { + return 'os-green'; + } + if ('state' in item && this.isReferenced(item)) { + return 'accent'; + } else return 'os-yellow'; case BackendImportState.Generated: return `accent`; - case BackendImportState.Referenced: - return `accent`; default: return `block`; // fallback: Error } } - public getSummaryInformation(item: string): string[] { + protected getSummaryInformation(item: string): string[] { return ( { error: ['error_outline', 'red-warning-text'], warning: ['warning', 'warn'], created: ['add_circle_outline', 'os-green'], updated: ['autorenew', 'os-yellow'], - // HAS TO BE ADDED TO SUMMARY IN ORDER TO WORK referenced: ['merge', 'accent'] }[item] ?? ['group', 'accent'] ); } - public getEntryIcon(item: BackendImportEntryObject): string { + protected getEntryIcon(item: BackendImportEntryObject): string { if (item.info === BackendImportState.Done || !item) { return undefined; } - return this.getActionIcon(item); + return this.getActionIconEntry(item); } - public containsError(entry: any, def: string): boolean { + protected containsError(entry: any, def: string): boolean { const value = entry?.[def]; if (!value) return false; if (Array.isArray(value)) { @@ -459,7 +489,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * @param entry a row with a current state * @eturn the tooltip for the item */ - public getRowTooltip(row: ViewImportedParticipant): string { + protected getRowTooltip(row: ViewImportedParticipant): string { switch (row.state) { case BackendImportState.Error: // no import possible return ( @@ -470,22 +500,18 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return this.getErrorDescription(row) ?? _(`The affected columns will not be imported.`); case BackendImportState.New: return this.translate.instant(this.modelName) + ` ` + this.translate.instant(`will be imported`); - case BackendImportState.Done: // item will be updated / has been imported - return ( - this.translate.instant(this.modelName) + - ` ` + - (this._state !== BackendImportPhase.FINISHED - ? this.translate.instant(`will be updated`) - : this.translate.instant(`has been imported`)) - ); - case BackendImportState.Referenced: - return ( - this.translate.instant(this.modelName) + - ` ` + - (this._state !== BackendImportPhase.FINISHED - ? this.translate.instant(`will be referenced`) - : this.translate.instant(`has been imported and referenced`)) - ); + case BackendImportState.Done: + return row.is_referenced + ? this.translate.instant(this.modelName) + + ` ` + + (this._state !== BackendImportPhase.FINISHED + ? this.translate.instant(`will be referenced`) // item will be referenced + : this.translate.instant(`has been imported`)) // item has been imported + : this.translate.instant(this.modelName) + + ` ` + + (this._state !== BackendImportPhase.FINISHED + ? this.translate.instant(`will be updated`) // item will be updated + : this.translate.instant(`has been imported`)); // item has been imported default: return undefined; } @@ -513,21 +539,21 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * The column separator selection. */ - public onColSepChanged(label: string): void { + protected onColSepChanged(label: string): void { this.importer.columnSeparator = this.importer.columnSeparators.find(col => col.label === label)?.value; } /** * The text separator selection */ - public onTextSeparatorChanged(value: string): void { + protected onTextSeparatorChanged(value: string): void { this.importer.textSeparator = value; } /** * The encoding selection. */ - public onEncodingChanged(value: string): void { + protected onEncodingChanged(value: string): void { this.importer.encoding = value; } @@ -579,11 +605,16 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy : []; this.setHeaders({ preview: this._previewColumns }); this._rows = this.calculateRows(previews); + // fix this, add referenced to footer + const count = this._rows.filter(row => row.is_referenced === true).length; + previews.some(preview => preview.statistics.push({ name: 'referenced', value: count })); } } private calculateRows(previews: BackendImportPreview[]): ViewImportedParticipant[] { - return previews?.flatMap(preview => preview.rows.map(row => new ViewImportedParticipant(row.id, row))); + return previews?.flatMap(preview => + preview.rows.map(row => new ViewImportedParticipant(row.id, row, this.activeMeetingIdService.meetingId)) + ); } private createRequiredFields(): string[] { @@ -598,7 +629,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } /** - * Summary adapted to the footer. Displays only "created", "updated", and "error" columns. + * Summary adapted to the footer. Displays only "created", "updated", "referenced" and "error" columns. * @param summary * @returns BackendImportSummary[] */ @@ -638,9 +669,17 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } } - /* private isReferenced(row: ViewImportedParticipant): boolean { - if (!row) return false - - - } */ + private isReferenced(row: ViewImportedParticipant): boolean { + for (const user of this.userAccounts) { + if ( + (user.meeting_ids.includes(row.meeting_id) && row.username && row.username === user.username) || + (row.member_number && row.member_number === user.member_number) || + (row.saml_id && row.saml_id === user.saml_id) + ) { + return false; + } + } + row.is_referenced = true; + return true; + } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index 02b5dc4204..7f255184cc 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -17,6 +17,8 @@ export const STATE_FITERABLE = [`new`, `done`, `referenced`, `error`]; export class ViewImportedParticipant implements Identifiable, BackendImportRow /* implements Searchable */ { // This class replaces BackendImportIdentifiedRow + public meeting_id: number; + public is_referenced = false; public static COLLECTION = `importedParticipant`; public state: BackendImportState; @@ -24,58 +26,59 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / public data: Record; public id: number; - public first_name; - public last_name; - public email; - public member_number; - public number; + public first_name: string; + public last_name: string; + public email: string; + public member_number: string; + public number: string; public vote_weight; - public gender; - public pronoun; - public username; - public default_password; - public saml_id; - public home_committee; - public external_comment; - public title; - - public structure_level; + public gender: string; + public pronoun: string; + public username: string; + public default_password: string; + public saml_id: string; + public home_committee: string; + public external_comment: string; + public title: string; + + public structure_level: string; public groups: string[]; - public is_active; - public is_present; - public is_locked_out; - public is_physical_person; + public is_active: boolean; + public is_present: boolean; + public is_locked_out: boolean; + public is_physical_person: boolean; - public constructor(preview_id: number, preview: BackendImportRow) { + public constructor(preview_id: number, preview: BackendImportRow, meeting_id: number) { + this.meeting_id = meeting_id; // id of the meeting to import into this.id = preview_id; this.data = preview.data; this.messages = preview.messages; this.state = preview.state; - this.title = this.setValue(this.data?.['title']); - this.first_name = this.setValue(this.data?.['first_name']); - this.last_name = this.setValue(this.data?.['last_name']); - this.email = this.setValue(this.data?.['email']); - this.member_number = this.setValue(this.data?.['member_number']); - this.structure_level = this.data?.['structure_level']; + this.title = this.setValue(this.data?.['title']) as string; + this.first_name = this.setValue(this.data?.['first_name']) as string; + this.last_name = this.setValue(this.data?.['last_name']) as string; + this.email = this.setValue(this.data?.['email']) as string; + this.member_number = this.setValue(this.data?.['member_number']) as string; + this.structure_level = this.data?.['structure_level'] as string; this.groups = this.data?.['groups']?.[0]; - this.number = this.setValue(this.data?.['number']); - this.vote_weight = this.setValue(this.data?.['vote_weight']); - this.gender = this.setValue(this.data?.['gender']); - this.pronoun = this.setValue(this.data?.['pronoun']); - this.username = this.setValue(this.data?.['username']); - this.default_password = this.setValue(this.data?.['default_password']); - - this.is_active = this.getBooleanValue(this.setValue(this.data?.['is_active'])); - this.is_physical_person = this.getBooleanValue(this.setValue(this.data?.['is_physical_person'])); - this.is_present = this.getBooleanValue(this.setValue(this.data?.['is_present'])); - this.is_locked_out = this.getBooleanValue(this.setValue(this.data?.['locked_out'])); - - this.saml_id = this.setValue(this.data?.['saml_id']); - this.home_committee = this.setValue(this.data?.['home_committee']); - this.external_comment = this.setValue(this.data?.['external_comment']); + this.number = this.setValue(this.data?.['number']) as string; + this.vote_weight = this.setValue(this.data?.['vote_weight']) as string; + this.gender = this.setValue(this.data?.['gender']) as string; + this.pronoun = this.setValue(this.data?.['pronoun']) as string; + this.username = this.setValue(this.data?.['username']) as string; + this.default_password = this.setValue(this.data?.['default_password']) as string; + + this.is_active = this.getBooleanValue(this.setValue(this.data?.['is_active']) as boolean); + this.is_physical_person = this.getBooleanValue(this.setValue(this.data?.['is_physical_person']) as boolean); + this.is_present = this.getBooleanValue(this.setValue(this.data?.['is_present']) as boolean); + this.is_locked_out = this.getBooleanValue(this.setValue(this.data?.['locked_out']) as boolean); + + this.saml_id = this.setValue(this.data?.['saml_id']) as string; + this.home_committee = this.setValue(this.data?.['home_committee']) as string; + this.external_comment = this.setValue(this.data?.['external_comment']) as string; } public static readonly REQUESTABLE_FIELDS: (keyof ViewImportedParticipant)[] = [ @@ -159,4 +162,8 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / public get hasGroups(): boolean { return this.groups?.length > 0 ? true : false; } + + public set setReferenced(value: boolean) { + this.is_referenced = value; + } } From ebdfcd7162d1c3e71b232aa32fdc89c4232a082f Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Tue, 30 Jun 2026 16:50:21 +0200 Subject: [PATCH 20/35] Referenced participants displayed in table, footer and added to filters --- ...rticipant-import-list-preview.component.ts | 92 ++++++++++++------- .../view-models/view-participant-import.ts | 5 +- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index bb68ea2961..a25adcc4c2 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -299,6 +299,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy private _headers: Record = {}; protected uploadButton: boolean; + private tempPreviewsObservable: Subscription; public constructor( private dialog: MatDialog, @@ -311,15 +312,17 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * Starts with a clean preview (removing any previously existing import previews) */ public ngOnInit(): void { + /* TODO: REMOVE THE MANUAL STATISTICS' CALCULATION */ this._requiredFields = this.createRequiredFields(); this.importer.currentImportPhaseObservable.subscribe(phase => { this._state = phase; }); - const tempPreviewsObservable: Subscription = this.importer.previewsObservable.subscribe(previews => { + this.tempPreviewsObservable = this.importer.previewsObservable.subscribe(previews => { + this._rows = this.calculateRows(previews); this.fillPreviewData(previews); }); - tempPreviewsObservable.unsubscribe(); this._dataSource = this.importer.previewsObservable.pipe(map(previews => this.calculateRows(previews))); + this.setHeaders({ preview: this._previewColumns }); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); this.cd.detectChanges(); @@ -330,8 +333,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy */ public ngOnDestroy(): void { this.importer.clearPreview(); - this.importer.clearAll(); this.importer.clearFile(); + this.importer.clearAll(); } /** @@ -398,9 +401,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.New: // item will be imported / has been imported return this._state !== BackendImportPhase.FINISHED ? `add_circle_outline` : `done`; case BackendImportState.Done: - if (this.isReferenced(item)) { - return 'merge'; - } else return 'autorenew'; + return 'autorenew'; + case BackendImportState.Referenced: + return 'merge'; case BackendImportState.Generated: return `merge`; case BackendImportState.Remove: @@ -446,9 +449,12 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy if (this._state === BackendImportPhase.FINISHED) { return 'os-green'; } - if ('state' in item && this.isReferenced(item)) { - return 'accent'; - } else return 'os-yellow'; + return 'os-yellow'; + case BackendImportState.Referenced: + if (this._state === BackendImportPhase.FINISHED) { + return 'os-green'; + } + return 'accent'; case BackendImportState.Generated: return `accent`; default: @@ -459,12 +465,13 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy protected getSummaryInformation(item: string): string[] { return ( { + total: ['group', 'accent'], error: ['error_outline', 'red-warning-text'], warning: ['warning', 'warn'], created: ['add_circle_outline', 'os-green'], updated: ['autorenew', 'os-yellow'], referenced: ['merge', 'accent'] - }[item] ?? ['group', 'accent'] + }[item] ?? ['', ''] ); } @@ -501,17 +508,21 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.New: return this.translate.instant(this.modelName) + ` ` + this.translate.instant(`will be imported`); case BackendImportState.Done: - return row.is_referenced - ? this.translate.instant(this.modelName) + - ` ` + - (this._state !== BackendImportPhase.FINISHED - ? this.translate.instant(`will be referenced`) // item will be referenced - : this.translate.instant(`has been imported`)) // item has been imported - : this.translate.instant(this.modelName) + - ` ` + - (this._state !== BackendImportPhase.FINISHED - ? this.translate.instant(`will be updated`) // item will be updated - : this.translate.instant(`has been imported`)); // item has been imported + return ( + this.translate.instant(this.modelName) + + ` ` + + (this._state !== BackendImportPhase.FINISHED + ? this.translate.instant(`will be updated`) // item will be updated + : this.translate.instant(`has been imported`)) // item has been imported + ); + case BackendImportState.Referenced: + return ( + this.translate.instant(this.modelName) + + ` ` + + (this._state !== BackendImportPhase.FINISHED + ? this.translate.instant(`will be referenced`) // item will be referenced + : this.translate.instant(`has been imported`)) + ); // item has been imported default: return undefined; } @@ -600,20 +611,35 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this._previewColumns = (previews[0]?.headers ?? this._previewColumns).filter( header => !header[`is_hidden`] ); - this._summary = previews.some(preview => preview.statistics) - ? previews.flatMap(preview => preview.statistics).filter(point => point?.value) - : []; - this.setHeaders({ preview: this._previewColumns }); - this._rows = this.calculateRows(previews); - // fix this, add referenced to footer - const count = this._rows.filter(row => row.is_referenced === true).length; - previews.some(preview => preview.statistics.push({ name: 'referenced', value: count })); + this.transformSummary(previews); + } + } + + private transformSummary(previews: BackendImportPreview[]): void { + this._summary = undefined; + this._summary = previews.some(preview => preview.statistics) + ? previews.flatMap(preview => preview.statistics).filter(point => point?.value) + : []; + const countReferenced = this._rows.filter(row => row.state === BackendImportState.Referenced).length | 0; + const countUpdated = + this._rows.filter(row => this.isReferenced(row) === false && row.state === 'done').length | 0; + if (countReferenced > 0) { + const error = this._summary.find(item => item.name === 'error'); + this._summary = this._summary.filter(item => item.name !== 'updated'); + this._summary = this._summary.filter(item => item.name !== 'error'); + this._summary.push({ name: 'updated', value: countUpdated }); + this._summary.push({ name: 'referenced', value: countReferenced }); + this._summary.push({ name: error?.name, value: error?.value }); } } private calculateRows(previews: BackendImportPreview[]): ViewImportedParticipant[] { return previews?.flatMap(preview => - preview.rows.map(row => new ViewImportedParticipant(row.id, row, this.activeMeetingIdService.meetingId)) + preview.rows.map(row => { + const participant = new ViewImportedParticipant(row.id, row, this.activeMeetingIdService.meetingId); + this.isReferenced(participant); + return participant; + }) ); } @@ -644,6 +670,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } protected async importData(dialogTemplate: TemplateRef, summaryDialog: TemplateRef): Promise { + this.tempPreviewsObservable.unsubscribe(); const customOptions = { width: `600px`, disableClose: false, @@ -679,7 +706,10 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return false; } } - row.is_referenced = true; + if (row.state !== 'done') { + return false; + } + row.setState = BackendImportState.Referenced; return true; } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index 7f255184cc..cb96715976 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -18,7 +18,6 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / // This class replaces BackendImportIdentifiedRow public meeting_id: number; - public is_referenced = false; public static COLLECTION = `importedParticipant`; public state: BackendImportState; @@ -163,7 +162,7 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / return this.groups?.length > 0 ? true : false; } - public set setReferenced(value: boolean) { - this.is_referenced = value; + public set setState(value: BackendImportState) { + this.state = value; } } From b0d8da128ed43d5c640514f782e972faf1c1f58c Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Wed, 1 Jul 2026 16:49:17 +0200 Subject: [PATCH 21/35] Correctly displayed buttons on import error. Updated summary titles --- .../base-backend-import.service.ts | 3 +- ...icipant-import-list-preview.component.scss | 1 + ...rticipant-import-list-preview.component.ts | 45 +++++++++++-------- .../participant-import.service.ts | 1 + 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/client/src/app/site/base/base-import.service/base-backend-import.service.ts b/client/src/app/site/base/base-import.service/base-backend-import.service.ts index 6ced4996bf..8a903de539 100644 --- a/client/src/app/site/base/base-import.service/base-backend-import.service.ts +++ b/client/src/app/site/base/base-import.service/base-backend-import.service.ts @@ -306,8 +306,7 @@ export abstract class BaseBackendImportService implements BackendImportService { .catch(e => e); if (typeof results !== `boolean`) { - this._currentImportPhaseSubject.next(BackendImportPhase.LOADING_PREVIEW); - this.clearAll(); + this._currentImportPhaseSubject.next(BackendImportPhase.FINISHED); this.matSnackbar.open( this.translate.instant(results.error?.message ?? results?.message ?? results), this.translate.instant(`Ok`) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 41ec89d3b6..94414325d9 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -19,6 +19,7 @@ $os-yellow: rgb(255, 193, 7); .error-icon { color: red; + font-weight: 500; } .footer { diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index a25adcc4c2..a3ccf768b1 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -1,6 +1,5 @@ import { AsyncPipe, NgClass } from '@angular/common'; import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, @@ -57,7 +56,6 @@ import { ViewImportedParticipant } from '../../view-models/view-participant-impo selector: `os-participant-import-list-preview`, templateUrl: `./participant-import-list-preview.component.html`, styleUrls: [`./participant-import-list-preview.component.scss`], - changeDetection: ChangeDetectionStrategy.OnPush, imports: [ HeadBarModule, ListModule, @@ -322,10 +320,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this.fillPreviewData(previews); }); this._dataSource = this.importer.previewsObservable.pipe(map(previews => this.calculateRows(previews))); - this.setHeaders({ preview: this._previewColumns }); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); - this.cd.detectChanges(); + this.setHeaders({ preview: this._previewColumns }); } /** @@ -395,15 +392,15 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy protected getActionIconRow(item: ViewImportedParticipant): string { switch (item[`state`]) { case BackendImportState.Error: // no import possible - return `error_outline`; + return this._state !== BackendImportPhase.FINISHED ? `error_outline` : 'close'; case BackendImportState.Warning: return `warning`; case BackendImportState.New: // item will be imported / has been imported return this._state !== BackendImportPhase.FINISHED ? `add_circle_outline` : `done`; case BackendImportState.Done: - return 'autorenew'; + return this._state !== BackendImportPhase.FINISHED ? 'autorenew' : 'done'; case BackendImportState.Referenced: - return 'merge'; + return this._state !== BackendImportPhase.FINISHED ? 'merge' : 'done'; case BackendImportState.Generated: return `merge`; case BackendImportState.Remove: @@ -483,6 +480,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } protected containsError(entry: any, def: string): boolean { + this.cd.markForCheck(); const value = entry?.[def]; if (!value) return false; if (Array.isArray(value)) { @@ -506,7 +504,13 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.Warning: return this.getErrorDescription(row) ?? _(`The affected columns will not be imported.`); case BackendImportState.New: - return this.translate.instant(this.modelName) + ` ` + this.translate.instant(`will be imported`); + return ( + this.translate.instant(this.modelName) + + ` ` + + (this._state !== BackendImportPhase.FINISHED + ? this.translate.instant(`will be imported`) // item will be updated + : this.translate.instant(`has been imported`)) // item has been imported + ); case BackendImportState.Done: return ( this.translate.instant(this.modelName) + @@ -521,8 +525,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ` ` + (this._state !== BackendImportPhase.FINISHED ? this.translate.instant(`will be referenced`) // item will be referenced - : this.translate.instant(`has been imported`)) - ); // item has been imported + : this.translate.instant(`has been imported`)) // item has been imported + ); default: return undefined; } @@ -608,6 +612,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this._summary = undefined; this._rows = undefined; } else { + this.cd.markForCheck(); this._previewColumns = (previews[0]?.headers ?? this._previewColumns).filter( header => !header[`is_hidden`] ); @@ -678,22 +683,22 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy maxHeight: `90vh` }; const sleep: (ms: number) => Promise = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - this.dialog.open(summaryDialog, { - data: this.summary, - ...customOptions - }); const ref = this.dialog.open(dialogTemplate, { data: this.summary, ...customOptions, hasBackdrop: false }); - - try { - await this.importer.doImport(); - await sleep(2000); - } finally { + const success = await this.importer.doImport(); + await sleep(2000); + if (success) { + // The close() is needed here so dialogs don't overlap if the second one opens ref.close(); + this.dialog.open(summaryDialog, { + data: this.summary, + ...customOptions + }); } + ref.close(); } private isReferenced(row: ViewImportedParticipant): boolean { @@ -712,4 +717,6 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy row.setState = BackendImportState.Referenced; return true; } + + private processErrors(): void {} } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import.service/participant-import.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import.service/participant-import.service.ts index 28f6caa749..ce91cf328f 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import.service/participant-import.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import.service/participant-import.service.ts @@ -35,6 +35,7 @@ export class ParticipantImportService extends BaseBackendImportService { total: _(`Total participants`), created: _(`Participants created`), updated: _(`Participants updated`), + referenced: _(`Participants referenced`), omitted: _(`Participants skipped`), warning: _(`Participants with warnings: affected cells will be skipped`), error: _(`Participants with errors`), From ecb5a5f524e848deff1946cadbc6cf410a13b2c9 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Thu, 2 Jul 2026 16:42:05 +0200 Subject: [PATCH 22/35] Headers correctly displayed and buttons updated on import --- ...icipant-import-list-preview.component.html | 124 +++++++++--------- ...rticipant-import-list-preview.component.ts | 28 ++-- .../backend-import-list.component.ts | 5 +- .../view-list/view-list.component.ts | 3 - .../scrolling-table-cell.directive.ts | 2 +- 5 files changed, 81 insertions(+), 81 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 323eaa2d2d..ea97e46f15 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -29,7 +29,7 @@

Import participants

(selectedTextSeparatorOutput)="onTextSeparatorChanged($event)" > @if (dataSource | async) { - @for (column of previewColumns; track column; let i = $index) { + @for (column of previewColumns; track column) {
Import participants > @let entry = row.data; @let type = column.type; -
-
{{ getColumnLabel(column.property) }}
- @if (entry && (entry[def] || type === 'boolean')) { -
- @if (getHeader(def).is_list && entry[def]) { -
- @for (item of entry[def]; track item) { - @if (getHeader(def).is_object) { -
- @if (getHeader(def).type === 'string') { - {{ item.value }} - } - @if (getHeader(def).type === 'number') { - {{ item.value }} - } - @if (getHeader(def).type === 'date') { - {{ item.value }} - } - @if (getHeader(def).type === 'boolean') { - - } -   - - @if (getActionIconRow(item)) { - - {{ getEntryIcon(item.value) }} - - } -
- } @else { - @if (getHeader(def).type === 'string') { - {{ item.value }} - } - @if (getHeader(def).type === 'number') { - {{ item.value }} - } - @if (getHeader(def).type === 'date') { - {{ item.value }} - } - @if (getHeader(def).type === 'boolean') { - - } -   - } - } -
- } - @if (!getHeader(def).is_list && entry[def] !== undefined) { +
{{ getColumnLabel(column.property) }}
+ @if (entry && (entry[def] || type === 'boolean')) { +
+ @if (getHeader(def).is_list && entry[def]) { + @for (item of entry[def]; track item) { @if (getHeader(def).is_object) { - @let icon = getEntryIcon(entry[def]);
@if (getHeader(def).type === 'string') { - {{ entry[def].value }} + {{ item.value }} } @if (getHeader(def).type === 'number') { - {{ entry[def].value }} + {{ item.value }} } @if (getHeader(def).type === 'date') { - {{ entry[def].value }} + {{ item.value }} } @if (getHeader(def).type === 'boolean') { - + }   - @if (getActionIconEntry(entry[def])) { - - {{ icon }} + @if (getActionIconRow(item)) { + + {{ getEntryIcon(item.value) }} }
+ } @else { + @if (getHeader(def).type === 'string') { + {{ item.value }} + } + @if (getHeader(def).type === 'number') { + {{ item.value }} + } + @if (getHeader(def).type === 'date') { + {{ item.value }} + } + @if (getHeader(def).type === 'boolean') { + + } +   } } -
- } -
+ } + @if (!getHeader(def).is_list && entry[def] !== undefined) { + @if (getHeader(def).is_object) { + @let icon = getEntryIcon(entry[def]); +
+ @if (getHeader(def).type === 'string') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'number') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'date') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'boolean') { + + } +   + + @if (getActionIconEntry(entry[def])) { + + {{ icon }} + + } +
+ } + } +
+ }
}
this.calculateRows(previews))); this._requiredFields = this.createRequiredFields(); this.importer.currentImportPhaseObservable.subscribe(phase => { this._state = phase; @@ -319,7 +322,6 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this._rows = this.calculateRows(previews); this.fillPreviewData(previews); }); - this._dataSource = this.importer.previewsObservable.pipe(map(previews => this.calculateRows(previews))); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); this.setHeaders({ preview: this._previewColumns }); @@ -612,11 +614,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this._summary = undefined; this._rows = undefined; } else { - this.cd.markForCheck(); this._previewColumns = (previews[0]?.headers ?? this._previewColumns).filter( header => !header[`is_hidden`] ); this.transformSummary(previews); + this.cd.markForCheck(); } } @@ -688,16 +690,18 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ...customOptions, hasBackdrop: false }); - const success = await this.importer.doImport(); - await sleep(2000); - if (success) { - // The close() is needed here so dialogs don't overlap if the second one opens - ref.close(); - this.dialog.open(summaryDialog, { - data: this.summary, - ...customOptions - }); - } + try { + await sleep(2000); + if (await this.importer.doImport()) { + // The close() is needed here so dialogs don't overlap if the second one opens + ref.close(); + this.dialog.open(summaryDialog, { + data: this.summary, + ...customOptions + }); + } + } catch {} + this.cd.detectChanges(); ref.close(); } diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index 6ebb9c4f3e..4585783222 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -1,4 +1,5 @@ import { + ChangeDetectorRef, Component, ContentChild, ContentChildren, @@ -249,7 +250,8 @@ export class BackendImportListComponent implements OnInit { public constructor( private dialog: MatDialog, private translate: TranslateService, - private router: Router + private router: Router, + private cd: ChangeDetectorRef ) {} /** @@ -272,6 +274,7 @@ export class BackendImportListComponent implements OnInit { map(previews => this.calculateRows(previews)), delay(50) ); + this.cd.detectChanges(); } /** diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts index b2d7c4afd6..303f9b7aed 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts @@ -295,17 +295,14 @@ export class ViewListComponent implements OnInit, OnDest } public sendSelectedEncoding($event): void { - // FUNCIONA this.selectedEncodingOutput.emit($event.value); } public sendSelectedColSep($event): void { - // FUNCIONA this.selectedColSepOutput.emit($event.value); } public sendSelectedTextSeparator($event): void { - // FUNCIONA this.selectedTextSeparatorOutput.emit($event.value); } } diff --git a/client/src/app/ui/modules/scrolling-table/directives/scrolling-table-cell.directive.ts b/client/src/app/ui/modules/scrolling-table/directives/scrolling-table-cell.directive.ts index 67ad878d4f..227f82cdfc 100644 --- a/client/src/app/ui/modules/scrolling-table/directives/scrolling-table-cell.directive.ts +++ b/client/src/app/ui/modules/scrolling-table/directives/scrolling-table-cell.directive.ts @@ -49,7 +49,7 @@ export class ScrollingTableCellDirective implements OnInit, OnDestroy, Scrolling } public set labelTemplate(template: any) { - setTimeout(() => (this._labelTemplate = template)); + this._labelTemplate = template; } public get labelTemplate(): any { From 0fb4ddb9b7d9b0f223833148bcfe71020de5e955 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 3 Jul 2026 11:23:26 +0200 Subject: [PATCH 23/35] Disabled import button when file contains errors --- .../participant-import-list-preview.component.html | 2 +- .../participant-import-list-preview.component.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index ea97e46f15..37df1b2d99 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -3,7 +3,7 @@

Import participants

- diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 7fd1d71f4d..3de1eadbb5 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -320,6 +320,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy }); this.tempPreviewsObservable = this.importer.previewsObservable.subscribe(previews => { this._rows = this.calculateRows(previews); + this.uploadButton = previews?.some(preview => preview.state === 'error') ? true : false; this.fillPreviewData(previews); }); this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); @@ -634,7 +635,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy const error = this._summary.find(item => item.name === 'error'); this._summary = this._summary.filter(item => item.name !== 'updated'); this._summary = this._summary.filter(item => item.name !== 'error'); - this._summary.push({ name: 'updated', value: countUpdated }); + if (countUpdated > 0) { + this._summary.push({ name: 'updated', value: countUpdated }); + } this._summary.push({ name: 'referenced', value: countReferenced }); this._summary.push({ name: error?.name, value: error?.value }); } @@ -721,6 +724,4 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy row.setState = BackendImportState.Referenced; return true; } - - private processErrors(): void {} } From 74e1b21bd3dc602f21055e524dbc4b14c1f15232 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 3 Jul 2026 17:05:42 +0200 Subject: [PATCH 24/35] Footer in darkmode, redirect after import --- ...ticipant-import-list-preview.component.scss | 6 +++--- ...articipant-import-list-preview.component.ts | 18 ++++++++++++++---- .../sort-filter-bar.component.html | 5 +++-- .../sort-filter-bar.component.ts | 5 +++++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 94414325d9..5cb0c5e9cd 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -29,9 +29,9 @@ $os-yellow: rgb(255, 193, 7); left: 0; right: 0; height: 50px; - background: white; + background-color: var(--mat-app-background-color); z-index: 1; - border-top: 1px solid #aaa; + border-top: 1px solid rgba(0, 0, 0, 0.12); display: flex; align-items: center; } @@ -100,7 +100,7 @@ $os-yellow: rgb(255, 193, 7); .summary-dialog-content { display: flex; font-size: 17px; - color: #000000; + color: var(--theme-contrast-400); text-align: center; justify-content: center; } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 3de1eadbb5..04eb1d7c4d 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -20,6 +20,7 @@ import { MatIcon } from '@angular/material/icon'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { MatTabChangeEvent } from '@angular/material/tabs'; import { MatTooltip } from '@angular/material/tooltip'; +import { Router } from '@angular/router'; import { _, TranslateService } from '@ngx-translate/core'; import { map, Observable, of, Subscription } from 'rxjs'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; @@ -303,6 +304,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy public constructor( private dialog: MatDialog, + private router: Router, protected translate: TranslateService, protected readonly controller: ParticipantControllerService, private cd: ChangeDetectorRef @@ -355,6 +357,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * triggers the importer's onSelectFile after a file has been chosen */ + // not working, fix this protected onSelectFile(event: any): void { this.uploadButton = false; this.importer.onSelectFile(event); @@ -698,16 +701,23 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy if (await this.importer.doImport()) { // The close() is needed here so dialogs don't overlap if the second one opens ref.close(); - this.dialog.open(summaryDialog, { - data: this.summary, - ...customOptions - }); + this.dialog + .open(summaryDialog, { + data: this.summary, + ...customOptions + }) + .afterClosed() + .subscribe(() => this.redirect()); } } catch {} this.cd.detectChanges(); ref.close(); } + private redirect(): void { + this.router.navigateByUrl(this.router.url.replace('/import/preview', '')); + } + private isReferenced(row: ViewImportedParticipant): boolean { for (const user of this.userAccounts) { if ( diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html index 3c213e3643..fa391de0f6 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.html @@ -77,13 +77,14 @@ } } - + @if (csvReload) { +
-

{{ 'Required column names' | translate }}

+

{{ 'Description of columns' | translate }}

@@ -558,7 +558,7 @@

{{ 'Preview' | translate }}

-->
-

The fields are defined as follows

+

Description of column names

diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index 0afec7fb82..6ca01f7a8e 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -116,6 +116,9 @@ export class BackendImportListComponent implements OnInit { */ @Input() public set defaultColumns(cols: ImportListHeaderDefinition[]) { + /**title first_name last_name email member_number structure_level groups number vote_weight gender pronoun username default_password is_active is_physical_person is_present locked_out saml_id home_committee external comment + */ + this._defaultColumns = cols; this.setHeaders({ default: cols }); } @@ -128,6 +131,7 @@ export class BackendImportListComponent implements OnInit { * The actual headers of the preview, as they were delivered by the backend. */ public get previewColumns(): BackendImportHeader[] { + console.log(this._previewColumns) return this._previewColumns; } @@ -544,7 +548,13 @@ export class BackendImportListComponent implements OnInit { files: files } }; - this._importer.onSelectFile(droppedFile); + try { + this._importer.onSelectFile(droppedFile); + this.uploadButton=false + } + catch { + e => e + } } public onChange(event: Event): void { From f682a20c9a521c3f49c393a6c9aee5b71616ba48 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Thu, 9 Jul 2026 17:23:30 +0200 Subject: [PATCH 28/35] Icon hide on unchanged state --- ...icipant-import-list-preview.component.html | 5 +- ...rticipant-import-list-preview.component.ts | 156 +++++++++++------- .../backend-import-list.component.ts | 11 +- .../definitions/backend-import-preview.ts | 3 +- .../view-list/view-list.component.ts | 2 +- 5 files changed, 105 insertions(+), 72 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 66f4e57239..3d29d96115 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -136,13 +136,14 @@

Import participants

warning } - @if (row.state !== 'error') { + @let icon = getActionIconRow(row); + @if (row.state !== 'error' && icon !== '') { - {{ getActionIconRow(row) }} + {{ icon }} } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index dbf126ffe2..232dbc93ee 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -175,6 +175,10 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this.setHeaders({ default: cols }); } + public get defaultColumns(): ImportListHeaderDefinition[] { + return this._defaultColumns; + } + /** The header's order according to how they are displayed on the template file */ private headersOrder = [ 'title', @@ -200,10 +204,6 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy 'comment' ]; - public get defaultColumns(): ImportListHeaderDefinition[] { - return this._defaultColumns; - } - /** * The actual headers of the preview, as they were delivered by the backend. */ @@ -351,14 +351,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this.tempPreviewsObservable = this.importer.previewsObservable.subscribe(previews => { this._rows = this.calculateRows(previews); this.uploadButton = previews?.some(preview => preview.state === 'error') ? true : false; - this._totalCountObservable = this._dataSource.pipe(map(items => items.length)); + this._totalCountObservable = this._dataSource?.pipe(map(items => items?.length)); this.fillPreviewData(previews); this.setHeaders({ preview: this._previewColumns }); }); this.searchService = new ListSearchService(this.filterProps, this.alsoFilterByProperties); - this.importer.previewsObservable.subscribe(d => { - console.log(d); - }); } /** @@ -420,16 +417,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return this._headers[propertyName]?.default?.label ?? propertyName; } - private hasNoChanges(item: ViewImportedParticipant): boolean { - const hasChanges = Object.values(item).some(value => - Array.isArray(value) ? value.some(val => val?.info === 'new') : value?.info === 'new' - ); - return !hasChanges; - } - /** * Get the icon for the the item - * @param item a row or an entry with a current state + * @param item a row with a current state * @return the icon for the item */ protected getActionIconRow(item: ViewImportedParticipant): string { @@ -437,22 +427,19 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.Error: // no import possible return this._state !== BackendImportPhase.FINISHED ? `error_outline` : 'close'; case BackendImportState.Warning: - return this.hasNoChanges(item) ? `warning` : ``; + return `warning`; case BackendImportState.New: // item will be imported / has been imported return this._state !== BackendImportPhase.FINISHED ? `add_circle_outline` : `done`; case BackendImportState.Done: - console.log(this.hasNoChanges(item)); - return this._state !== BackendImportPhase.FINISHED - ? this.hasNoChanges(item) - ? '' - : 'autorenew' - : 'done'; + return this._state !== BackendImportPhase.FINISHED ? 'autorenew' : 'done'; case BackendImportState.Referenced: return this._state !== BackendImportPhase.FINISHED ? 'merge' : `done`; case BackendImportState.Generated: return `merge`; case BackendImportState.Remove: return `remove`; + case BackendImportState.Unchanged: + return ``; default: return `block`; // fallback: Error } @@ -460,25 +447,50 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy /** * Get the icon for the the entry - * @param item a row or an entry with a current state + * @param item an entry with a current state * @return the icon for the item */ protected getActionIconEntry(item: BackendImportEntryObject): string { - switch (item[`info`]) { - case BackendImportState.Error: // no import possible - return `error_outline`; - case BackendImportState.Warning: - return `warning`; - case BackendImportState.New: // item will be imported / has been imported - return BackendImportPhase.FINISHED ? `add_circle_outline` : `done`; - case BackendImportState.Done: - return this._state !== BackendImportPhase.FINISHED ? `merge` : `done`; - case BackendImportState.Generated: - return `person`; - case BackendImportState.Remove: - return `remove`; + console.log('ITEM INFO INSIDE ICONENTRY', item[`info`], typeof item, item, item.info, item.value); + switch (typeof item) { + case 'string': + switch (item['info']) { + // for lists + case BackendImportState.Error: // no import possible + return `error_outline`; + case BackendImportState.Warning: + return `warning`; + case BackendImportState.New: + return `add_circle_outline`; + case BackendImportState.Done: + return `autorenew`; + case BackendImportState.Generated: + return `merge`; + case BackendImportState.Remove: + return `remove`; + default: + return `mood`; // fallback: Error + } + case 'object': + switch (item.info) { + // for objects + case BackendImportState.Error: // no import possible + return `error_outline`; + case BackendImportState.Warning: + return `warning`; + case BackendImportState.New: + return `add_circle_outline`; + case BackendImportState.Done: + return `autorenew`; + case BackendImportState.Generated: + return `merge`; + case BackendImportState.Remove: + return `remove`; + default: + return `mood_bad`; // fallback: Error + } default: - return `block`; // fallback: Error + return 'rowing'; // fallback: Error } } @@ -490,7 +502,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return 'warning'; case BackendImportState.New: return 'os-green'; - case BackendImportState.Done: // item will be updated / has been imported + case BackendImportState.Done: // has been imported / item will be updated if (this._state === BackendImportPhase.FINISHED) { return 'os-green'; } @@ -502,13 +514,14 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return 'accent'; case BackendImportState.Generated: return `accent`; + case BackendImportState.Unchanged: + return ``; default: return `block`; // fallback: Error } } protected getSummaryInformation(item: string): string[] { - // FIX FOOTER WHEN DATA IS NOT CHANGED return ( { total: ['group', 'accent'], @@ -516,7 +529,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy warning: ['warning', 'warn'], created: ['add_circle_outline', 'os-green'], updated: ['autorenew', 'os-yellow'], - referenced: ['merge', 'accent'] + referenced: ['merge', 'accent'], + unchanged: [``, ``] }[item] ?? ['', ''] ); } @@ -561,13 +575,13 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy : this.translate.instant(`has been created`)) // item has been created ); case BackendImportState.Done: - return this.hasNoChanges(row) - ? `` - : this.translate.instant(this.modelName) + - ` ` + - (this._state !== BackendImportPhase.FINISHED - ? this.translate.instant(`will be updated`) // item will be updated - : this.translate.instant(`has been updated`)); // item has been updated + return ( + this.translate.instant(this.modelName) + + ` ` + + (this._state !== BackendImportPhase.FINISHED + ? this.translate.instant(`will be updated`) // item will be updated + : this.translate.instant(`has been updated`)) + ); // item has been updated case BackendImportState.Referenced: return ( this.translate.instant(this.modelName) + @@ -576,6 +590,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ? this.translate.instant(`will be referenced`) // item will be referenced : this.translate.instant(`has been referenced`)) // item has been referenced ); + case BackendImportState.Unchanged: + return ``; default: return undefined; } @@ -685,19 +701,22 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy this._summary = previews.some(preview => preview.statistics) ? previews.flatMap(preview => preview.statistics).filter(point => point?.value) : []; - const countReferenced = this._rows.filter(row => row.state === BackendImportState.Referenced).length | 0; + const countReferenced = this._rows.filter(row => row?.state === BackendImportState.Referenced)?.length | 0; + const countUnchanged = this._rows.filter(row => row?.state === BackendImportState.Unchanged)?.length | 0; const countUpdated = - this._rows.filter(row => this.isReferenced(row) === false && row.state === 'done').length | 0; + this._summary.find(item => item?.name === 'updated')?.value - countReferenced - countUnchanged; + const error = this._summary.find(item => item.name === 'error'); + this._summary = this._summary.filter(item => item.name !== 'updated'); if (countReferenced > 0) { - const error = this._summary.find(item => item.name === 'error'); - this._summary = this._summary.filter(item => item.name !== 'updated'); - this._summary = this._summary.filter(item => item.name !== 'error'); - if (countUpdated > 0) { + this._summary.push({ name: 'referenced', value: countReferenced }); + } + if (countUpdated > 0) { + if (countUpdated - countUnchanged !== 0) { this._summary.push({ name: 'updated', value: countUpdated }); } - this._summary.push({ name: 'referenced', value: countReferenced }); - this._summary.push({ name: error?.name, value: error?.value }); } + this._summary = this._summary.filter(item => item.name !== 'error'); + this._summary.push({ name: error?.name, value: error?.value }); } private calculateRows(previews: BackendImportPreview[]): ViewImportedParticipant[] { @@ -705,6 +724,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy preview.rows.map(row => { const participant = new ViewImportedParticipant(row.id, row, this.activeMeetingIdService.meetingId); this.isReferenced(participant); + this.isUnchanged(participant); return participant; }) ); @@ -773,6 +793,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } private isReferenced(row: ViewImportedParticipant): boolean { + if (row.state !== 'done') { + return false; + } for (const user of this.userAccounts) { if ( (user.meeting_ids.includes(row.meeting_id) && row.username && row.username === user.username) || @@ -782,10 +805,25 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return false; } } - if (row.state !== 'done') { - return false; - } row.setState = BackendImportState.Referenced; return true; } + + private isUnchanged(item: ViewImportedParticipant): boolean { + if (item.state !== BackendImportState.Done) { + return false; + } + const itemValues = Object.values(item); + const someNew = itemValues.some(value => + Array.isArray(value) ? value.some(val => val?.info === 'new') : value?.info === 'new' + ); + const allNew = itemValues.every(value => + Array.isArray(value) ? value.every(val => val?.info === 'new') : value?.info === 'new' + ); + if (!someNew && !allNew) { + item.setState = BackendImportState.Unchanged; + return true; + } + return false; + } } diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index 6ca01f7a8e..db28ee2110 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -116,9 +116,6 @@ export class BackendImportListComponent implements OnInit { */ @Input() public set defaultColumns(cols: ImportListHeaderDefinition[]) { - /**title first_name last_name email member_number structure_level groups number vote_weight gender pronoun username default_password is_active is_physical_person is_present locked_out saml_id home_committee external comment - */ - this._defaultColumns = cols; this.setHeaders({ default: cols }); } @@ -131,7 +128,6 @@ export class BackendImportListComponent implements OnInit { * The actual headers of the preview, as they were delivered by the backend. */ public get previewColumns(): BackendImportHeader[] { - console.log(this._previewColumns) return this._previewColumns; } @@ -550,11 +546,8 @@ export class BackendImportListComponent implements OnInit { }; try { this._importer.onSelectFile(droppedFile); - this.uploadButton=false - } - catch { - e => e - } + this.uploadButton = false; + } catch {} } public onChange(event: Event): void { diff --git a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts index 0133076c3b..dc8c69479f 100644 --- a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts +++ b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts @@ -8,7 +8,8 @@ export enum BackendImportState { Done = `done`, Generated = `generated`, Remove = `remove`, - Referenced = `referenced` + Referenced = `referenced`, + Unchanged = `unchanged` // could be expanded later } diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts index c6327aaffd..44b80069a4 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts @@ -195,7 +195,7 @@ export class ViewListComponent implements OnInit, OnDest } public get totalCountObservable(): Observable { - return this._totalCountObservable ?? this._source.pipe(map(items => items.length)); + return this._totalCountObservable ?? this._source.pipe(map(items => items?.length)); } public get source(): V[] { From 78812f93dce7b323d47efcbecd946566947573e5 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Thu, 9 Jul 2026 17:26:42 +0200 Subject: [PATCH 29/35] merge origin --- Dockerfile | 2 +- client/package-lock.json | 803 +++++++++++------- client/package.json | 27 +- .../gateways/actions/action.service.spec.ts | 2 - .../app/gateways/actions/action.service.ts | 4 +- .../src/app/gateways/auth-adapter.service.ts | 4 +- client/src/app/gateways/http.service.ts | 14 +- .../gateways/meeting-media-adapter.service.ts | 4 +- .../get-user-editable-presenter.service.ts | 4 +- .../users/user-repository.service.ts | 13 +- .../utils/import/json-import-file-utils.ts | 10 +- .../openslides-main.component.ts | 34 +- .../interceptors/auth-token.interceptor.ts | 4 +- .../services/app-load.service.ts | 16 +- .../services/shared-worker.service.ts | 6 +- client/src/app/site/guards/auth.guard.ts | 12 +- .../src/app/site/guards/permission.guard.ts | 12 +- .../chip-select/chip-select.component.html | 12 +- .../chip-select/chip-select.component.ts | 10 +- .../account-button.component.ts | 25 +- .../account-dialog.component.ts | 22 +- .../global-search/global-search.component.ts | 34 +- ...settings-group-detail-field.component.html | 37 - ...settings-group-detail-field.component.scss | 4 - ...g-settings-group-detail-field.component.ts | 61 +- .../meeting-settings-group-detail.module.ts | 3 - .../motion-detail/motion-detail.module.ts | 4 +- .../motion-manage-timestamp.component.html | 14 +- .../motion-manage-timestamp.component.ts | 24 +- .../services/active-meeting.service.ts | 26 +- ...ing-component-service-collector.service.ts | 12 +- ...eeting-settings-definition.service.spec.ts | 18 +- .../meeting-settings-definition.service.ts | 2 - .../meeting-settings-definitions.ts | 2 - .../sequential-number-mapping.service.ts | 13 +- .../committee-meeting-preview.component.ts | 14 +- .../modules/services/meeting.service.ts | 12 +- .../committee-import-list.component.ts | 6 +- .../committee-import.service.ts | 11 +- .../organization-settings.component.ts | 3 +- .../autofocus/autofocus.directive.ts | 10 +- .../meeting-setting.directive.ts | 11 +- .../ui/directives/paper/paper.directive.ts | 4 +- .../directives/resized/resized.directive.ts | 4 +- .../trim-on-paste/trim-on-paste.directive.ts | 4 +- .../src/app/ui/modules/chip/chip.component.ts | 10 +- .../choice-dialog/services/choice.service.ts | 4 +- .../comma-separated-listing.component.ts | 2 - .../datepicker/datepicker.component.scss | 4 - .../daterangepicker.component.scss | 4 - .../editor-image-dialog.component.ts | 9 +- .../components/editor/editor.component.ts | 4 - .../directives/tab-navigation.directive.ts | 4 +- .../expandable-content-wrapper.component.ts | 6 +- .../modules/file-list/file-list.component.ts | 21 +- .../components/head-bar/head-bar.component.ts | 20 +- .../services/routing-state.service.ts | 15 +- .../head-toolbar/head-toolbar.component.ts | 10 +- .../backend-import-list.component.ts | 10 +- .../import-list-first-tab.directive.ts | 4 +- .../import-list-last-tab.directive.ts | 4 +- .../filter-menu/filter-menu.component.ts | 3 +- .../sort-filter-bar.component.ts | 9 +- .../media-upload-content.component.ts | 6 +- .../meeting-time/meeting-time.component.ts | 4 +- .../openslides-date-adapter.ts | 15 +- .../prompt-checkbox.component.ts | 4 +- .../prompt-dialog/services/prompt.service.ts | 8 +- .../ui/modules/qr-code/qr-code.component.ts | 2 - .../scrolling-table.component.ts | 10 +- .../scrolling-table-no-data.directive.ts | 10 +- .../list-search-selector.component.ts | 18 +- .../repo-search-selector.component.ts | 11 +- .../copyright-sign.component.ts | 4 +- .../components/image/image.component.ts | 4 +- .../sidenav/components/logo/logo.component.ts | 10 +- .../components/sidenav/sidenav.component.ts | 4 +- .../services/chess-challenge.service.ts | 12 +- .../sorting-tree/sorting-tree.component.ts | 14 +- .../src/app/ui/pipes/entries/entries.pipe.ts | 4 +- client/src/app/ui/pipes/time/time.pipe.ts | 4 +- client/src/app/ui/pipes/trust/trust.pipe.ts | 4 +- 82 files changed, 824 insertions(+), 840 deletions(-) diff --git a/Dockerfile b/Dockerfile index aab6e58416..8c03aa28dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:24.14-alpine AS base +FROM node:24.18-alpine AS base ## Setup ARG CONTEXT diff --git a/client/package-lock.json b/client/package-lock.json index 69f4fa2270..586a2aac45 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -22,6 +22,7 @@ "@angular/router": "^21.2.17", "@angular/service-worker": "^21.2.17", "@cantoo/pdf-lib": "^2.7.1", + "@coreui/angular-chartjs": "~5.6.25", "@coreui/chartjs": "^4.1.0", "@date-fns/tz": "^1.5.0", "@intevation/tiptap-extension-office-paste": "^0.1.2", @@ -30,20 +31,20 @@ "@ngx-translate/core": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0", "@openslides/motion-diff": "file:../packages/openslides-motion-diff", - "@tiptap/core": "^3.27.2", - "@tiptap/extension-color": "^3.27.2", - "@tiptap/extension-highlight": "^3.27.2", - "@tiptap/extension-image": "^3.27.2", + "@tiptap/core": "^3.27.3", + "@tiptap/extension-color": "^3.27.3", + "@tiptap/extension-highlight": "^3.27.3", + "@tiptap/extension-image": "^3.27.3", "@tiptap/extension-link": "^3.23.4", "@tiptap/extension-list": "^3.22.4", - "@tiptap/extension-subscript": "^3.27.2", - "@tiptap/extension-superscript": "^3.27.2", - "@tiptap/extension-table": "^3.27.2", - "@tiptap/extension-text-align": "^3.27.2", + "@tiptap/extension-subscript": "^3.27.3", + "@tiptap/extension-superscript": "^3.27.3", + "@tiptap/extension-table": "^3.27.3", + "@tiptap/extension-text-align": "^3.27.3", "@tiptap/extension-text-style": "^3.4.2", "@tiptap/extensions": "^3.22.4", - "@tiptap/pm": "^3.27.1", - "@tiptap/starter-kit": "^3.27.2", + "@tiptap/pm": "^3.27.3", + "@tiptap/starter-kit": "^3.27.3", "@tsparticles/angular": "^3.0.0", "@tsparticles/engine": "^3.9.1", "@tsparticles/plugin-emitters": "^3.9.1", @@ -63,7 +64,6 @@ "ngx-date-fns": "^12.0.1", "ngx-device-detector": "^11.0.0", "ngx-file-drop": "^16.0.0", - "ngx-material-timepicker": "^13.1.1", "ngx-papaparse": "^8.0.0", "pdfmake": "^0.3.11", "pofile": "^1.1.4", @@ -93,8 +93,8 @@ "@types/qrcode": "^1.5.6", "@types/tinycolor2": "^1.4.6", "@types/video.js": "^7.3.58", - "@typescript-eslint/eslint-plugin": "^8.62.0", - "@typescript-eslint/parser": "^8.62.0", + "@typescript-eslint/eslint-plugin": "^8.63.0", + "@typescript-eslint/parser": "^8.63.0", "@vendure/ngx-translate-extract": "^10.1.3", "angular-eslint": "^21.4.0", "eslint": "^9.39.4", @@ -116,7 +116,7 @@ "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.9.3", - "typescript-eslint": "^8.62.1" + "typescript-eslint": "^8.63.0" } }, "../packages/openslides-motion-diff": { @@ -407,6 +407,40 @@ } } }, + "node_modules/@angular-devkit/architect/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular-devkit/architect/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/schematics": { "version": "21.2.18", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.2.18.tgz", @@ -454,6 +488,40 @@ } } }, + "node_modules/@angular-devkit/schematics/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-eslint/builder": { "version": "21.4.0", "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-21.4.0.tgz", @@ -498,6 +566,40 @@ } } }, + "node_modules/@angular-eslint/builder/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular-eslint/builder/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-eslint/eslint-plugin": { "version": "21.4.0", "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-21.4.0.tgz", @@ -626,6 +728,40 @@ } } }, + "node_modules/@angular-eslint/schematics/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-eslint/template-parser": { "version": "21.4.0", "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-21.4.0.tgz", @@ -1394,6 +1530,40 @@ } } }, + "node_modules/@angular/cli/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@angular/cli/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/common": { "version": "21.2.17", "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.2.17.tgz", @@ -3831,9 +4001,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3851,9 +4018,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3871,9 +4035,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3891,9 +4052,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3911,9 +4069,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3931,9 +4086,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3951,9 +4103,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5335,6 +5484,40 @@ } } }, + "node_modules/@schematics/angular/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@schematics/angular/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sigstore/bundle": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", @@ -5459,191 +5642,219 @@ } }, "node_modules/@tiptap/core": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.27.2.tgz", - "integrity": "sha512-zRWROdDbeIghFIT05ZYc+0cCkQFBy7sN/VjFc41qJ1fuq+lfgGDotVUXLuVqVe288jSpo/VGmy/iFLIQTc9mwg==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.27.3.tgz", + "integrity": "sha512-TJj5929M96C1KlH796wS8MywfHDh49RhmakOyzyMMc9pFmRj9UXi1gj0TCXgsZtjEOG7B+m/DRvNOvnuvR9kmg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "3.27.2" + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/extension-blockquote": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.27.2.tgz", - "integrity": "sha512-pafmW5GPZnRktQ4TegU8PIP+udboPw0O0AWYKk9HVDTI+VAqGwe1Xmb9k8tPCyn3et7LO62r14pK3S8Av+dxWQ==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.27.3.tgz", + "integrity": "sha512-VGoVMcqcGkkoduzEqQ70ZK5OOHBDn5iYeHJSgkvNoSHzYHl0CaKoWxoDKJjSWC5DAM4mBU+tXTlsiUp3evRMfA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-bold": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.27.2.tgz", - "integrity": "sha512-R+HZ/symZZhhnxZf3xyGbm2mWf48K+/kYucm88TMzMUWBjXhnL1G2JwaURxXUXb0nvsz6T2IqOhPKwCvAtb7oQ==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.27.3.tgz", + "integrity": "sha512-pchbycFppBqBmvk+OxrQLIPZLNd3rNbcm7zOa+OTjluphpAcsJ6AmHBmiRhJUYWitV6/SA+0nujcaWxBp5hNcQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.27.2.tgz", - "integrity": "sha512-YYJfm3IBTOccraVUavKIPQEspZlfCmL1zlcve93OVwujo0ndgk0fy6tp7Xs2EA4dXy303fnu5xKs1H08hkqQMg==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.27.3.tgz", + "integrity": "sha512-owQW6mcgnYgQU1z5BqmslcjZFBUm1SGM/Ax++4R6XelIt/7ol6uSTjzjAzum+ASW7UlBbuJHuSgRjl8cA+RGkg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "3.27.2" + "@tiptap/extension-list": "3.27.3" } }, "node_modules/@tiptap/extension-code": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.27.2.tgz", - "integrity": "sha512-IeWL8DP6dlvSfj3hvluX+pztpNi4tZdyUvcCYG8ODj4WVHom0Vrfila88hNsL8YXbbQ0iNPLoYgcffjfWEm6aA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.27.3.tgz", + "integrity": "sha512-T7JF10Y3k7fyVujXXLVubdUh7fctgBUoiuxbvjotpltg1U/mPbOcT0UW/uif9j3H8TFvAwNApVw3x8bwwFUakA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.27.3.tgz", + "integrity": "sha512-3CVnzkpGoqqI5KaXI9l9PMwLkx34SYY63tpeo0I0QjDk4LU+JTAQDTPJFSwB4zTsHZ7ZFMU0jjasFOZ1kZRVgw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/extension-color": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.27.2.tgz", - "integrity": "sha512-X2Mo7y/49OFg/5I9B2APgXd1DsxseLXyvnvpFBGGj1AYqYGxvvJ9thvyLZr7SZVGjnAlbu3kwI2lhRQdyyIq8w==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-3.27.3.tgz", + "integrity": "sha512-tNXyK3VXPGygJP+vq1+YyPmrxLabx4vs+rYiVhGtmhIapTQeMVYcHbFH4s4v3h5m8JGVlTWnpI8n7cb5ExIhSQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-text-style": "3.27.2" + "@tiptap/extension-text-style": "3.27.3" } }, "node_modules/@tiptap/extension-document": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.27.2.tgz", - "integrity": "sha512-RkvCVKsAUEBxdc5EltXQfaiC33/PFByokRuVI08IeQDukwF9lrvba/O8vHAcHAh+XWTiy+U88m5ORLUO1MIlJA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.27.3.tgz", + "integrity": "sha512-PR25MRv56Nm4ETVLwA8nAh/0RrVdvD4D2nxnAC8kUkxz1WOYDaqTfPqqEeSWDoUUTUp1I1xzDkD4x0gEnAlmZA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-dropcursor": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.27.2.tgz", - "integrity": "sha512-PUMhuvP4zyv41iRR06m87DoeLCBbT6SFstz9QZCZcK6+17rk8cdifFE+LIfRPAoPYEO/+jRoCcJ7BhGrOJDWzA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.27.3.tgz", + "integrity": "sha512-XsL0Ziual+ynXyx5JgLb0KIOShlMN3MW7+GQvf9PGRb1vOB7IOTRFDxMPrxDWcFIZ6WWMWSrbkCLOKHStt1BWg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "3.27.2" + "@tiptap/extensions": "3.27.3" } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.27.2.tgz", - "integrity": "sha512-yT5/qzjJHVNfbXOWaQBbIqomhEWgi9ScB6gbY6TdYREqSIZiLVCEjXo9mDU2kiTFLoOznJ6p7BlhmluzKpI6NQ==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.27.3.tgz", + "integrity": "sha512-MVBqzwYrDOttkn2GOrvvKTDCymfSS2VGSKpahcKKhge5mweSDiqko2X1vBDnMF8aVW3WxIFfDMmO4VMmh8HDZA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extensions": "3.27.2" + "@tiptap/extensions": "3.27.3" } }, "node_modules/@tiptap/extension-hard-break": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.27.2.tgz", - "integrity": "sha512-nlr095C2FOOXF77gp1xKUUdpnvACckyVhIQJyurxOpHFtKFNEL/850MX/QhPHMAbGb/VdkcBqyLvQI59i0pA9A==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.27.3.tgz", + "integrity": "sha512-cySObJITR2CRrWII87mI2LJ12wEETTnxt2p1vvC6ZtUmKnB50fOu+ux8ZEt6KYNocIOtrEZthNFzTOx7R7UtrQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-heading": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.27.2.tgz", - "integrity": "sha512-AERG0GecEYhQ3j4y1VK+FsDcl1PN5IPMTFZNEbsqyo/ZZBE1bIGriTV5/dTAWPfmUFnJEfR7FYTkqu2cMvL+oA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.27.3.tgz", + "integrity": "sha512-QHXnsNic6iId8pnsFZ8z4PkX5L+HCHa/D7rAi3nNWtPlSIAOxo4nKrALcB5/tHmY+XL8kEXKH3nsNLNEDLCYPg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-highlight": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.27.2.tgz", - "integrity": "sha512-+PhUkhh8g9a005FvwtDVnxRzadLO2tBWY4plUfJJDDpFIC0lTSxc0Q6ESBEV4x/BnuClH/RKH+tZAhkyEqeTeQ==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.27.3.tgz", + "integrity": "sha512-99QYxMx8F/NhdvznM8/Zhm4XiCdMU0OEiLwPvLNObJiKV8UBqjdA68cJLX6FRuOi2nqD7xfJq2KuSsFKBldi2A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.27.3.tgz", + "integrity": "sha512-sNCuo0+Q001B5XTVWY+ULpWXQqtBNRmAqXxAexZl44bx3rDqHG8OzjwT4EM0LIj2+BYVbdqwaJ1vfQbZVEEbig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/extension-image": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.27.2.tgz", - "integrity": "sha512-xnDCj+9oNwr4Zh9B+HHi+9lXyUVYv2KmCF3QuCdGxhZA4FDNlJT3gISwYnH4LNtKSD1XHh/gSCMTpZF+8oqe3g==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.27.3.tgz", + "integrity": "sha512-3OVLRH54Xkh9yrI5oGIempuqZkhLG1plyJBRnapx0N8j9IjEImOLF78kPYoAZzaBuU78M3Zk/E7HQZgmnaNRBg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-italic": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.27.2.tgz", - "integrity": "sha512-iAnhS/UbQkk8jioeIMyn98clQGF+6eozrYqABmmzE/lxvYew0PfeNbNnj2poOCZ93USU1pYcf06Kp2Q85wpfwA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.27.3.tgz", + "integrity": "sha512-7VpVi2vn8kGFAc/HLrt96J/E6+LXOGKn8xLhJS863t150yLIG3EPQLA1h+G5O6eo+/w+9YokvJAAJoeDihj1Jg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-link": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.27.2.tgz", - "integrity": "sha512-I3VvI5/J4afMEQbjkcW2sw0ediJMy+Asz46nI2C6cK+qDtGatE9jOZo/rFMG5scTwSx3nrIZkbCcTMTJz1TFEw==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.27.3.tgz", + "integrity": "sha512-iXmu1tJ/3vP60c9d+zfO3+X64vod2EyBrM1qeufecAtWA6t0bT9tYeka0Zq6qDRGsZK5zViO9F2lsY7eHXiYuA==", "license": "MIT", "dependencies": { "linkifyjs": "^4.3.3" @@ -5653,201 +5864,201 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2", - "@tiptap/pm": "3.27.2" + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/extension-list": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.27.2.tgz", - "integrity": "sha512-SsVnh5ruM9QjgOGbhJjegHAIADuoE+9Sb0UV+lyxAeuEwO98JpVmE926Yur9abzUAA9MWZZ/XBIzoA5q0a4dBQ==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.27.3.tgz", + "integrity": "sha512-52rcaSzYjtVGUrNkdB8k1Bw/rId2lBirrWMvmnBLsieoeFd8zo8ow5zkwV057BYSgWXfnMAV7GOpDyq2IoSD9A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2", - "@tiptap/pm": "3.27.2" + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/extension-list-item": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.27.2.tgz", - "integrity": "sha512-k3D6CMZMn5GhxiCLCMizAs1uEXyQOzhR/ySiwqE3SCvz2R1Uvyg68WKcs0j5Mto5OQoD+e1H2SaLFP6+dyez4A==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.27.3.tgz", + "integrity": "sha512-UddhVaQ7+V9IfQVVE8AJLNzSmyaaNpZPQsoPQxFNlJyOFxD6RwSTT8L4rb/TwbUctxSFWpyd1J7exzf33Ony/g==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "3.27.2" + "@tiptap/extension-list": "3.27.3" } }, "node_modules/@tiptap/extension-list-keymap": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.27.2.tgz", - "integrity": "sha512-0Iy8+6/LPvD1+wpRr/RnHQ73ykoovjUf6HEw6RFyXDnUWWVnU6YLUNLxMUpuiKQ+k3d/gRRKITrCqghIuqiwJQ==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.27.3.tgz", + "integrity": "sha512-GZPVg4QCFm41yTMPEPeTlH2odNeQwRuWHY/pt3pI5LsfipQ4kDBv6h9we928vJQUnU2Gg9Y3cL5iRbmXIWWTUA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "3.27.2" + "@tiptap/extension-list": "3.27.3" } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.27.2.tgz", - "integrity": "sha512-AqPDtnwm9QR50BYTC30Pas5fQhRQZvOOBykhbbPefYta8GzSF4sD4L/dSWqitDbZCOTQgL9EbtsJdpmKGF5hJQ==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.27.3.tgz", + "integrity": "sha512-oHYoE65WfRSM5tCcXi8MtQmzPxz83E2qw0pa2yHBKwr9xCCQUu8vJHJE+EJGduWdaNImv43+6/6kVMeYYbXRKg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/extension-list": "3.27.2" + "@tiptap/extension-list": "3.27.3" } }, "node_modules/@tiptap/extension-paragraph": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.27.2.tgz", - "integrity": "sha512-On4uQqEbdmV5sgDRisIkLUqrhSEjr38QnIr7Zl2Lvnd4//RGkuJALhyJrZBkHh0VXNMbqPeJ3lL7l/beG1RXDg==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.27.3.tgz", + "integrity": "sha512-G5XPCN7lm0nULYPelJC2eUbKbt1q97j67e/HSWxwMYEXhceec5eNAtVfpDCRyXNO9osyBi2kXqLv/IQtxbch9A==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-strike": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.27.2.tgz", - "integrity": "sha512-5trECNOg49bQEWJmxmEHg1KqWaPzCS3FYwU73Cy/XsJuOllTV2MoHP3si+pmSXtDGptpPIumZ6BPE9v2/QqzXw==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.27.3.tgz", + "integrity": "sha512-dp6Rs3I4zLVJPvAw2Q9yrNuYKcX5LTONH3/oyULvGsrqq5DKz7pqscfwhwnDSzrgm/3aCz6B9r1BVHlYI/DXlA==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-subscript": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-3.27.2.tgz", - "integrity": "sha512-6HHxKOJ57A0RpLawTlCawW58bmBff6rF1+lmamxVK90J0krGLzR//GCqJm9S2lij7wZc+rJ3UoF+hCcZlBZAcw==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-3.27.3.tgz", + "integrity": "sha512-jsnoOWQ40kd7B6NMkdZxsZpcSk/+BevN6TprOVaPbo6cbThjmMCfyL/yCBRTWF/N2KMLMplz1WBhPUEG2Qk3mg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2", - "@tiptap/pm": "3.27.2" + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/extension-superscript": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-3.27.2.tgz", - "integrity": "sha512-dy0MtbbR6/4JsUhANsF5O7TXum1LoAZJkHbZK+KZSgXVGtFlfB2GCS1MUg6bBhB3LGhSvF118pR0SxeKF+HTMA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-3.27.3.tgz", + "integrity": "sha512-y37F/ckFXvCkGVx0hk1qYr0XBXUuBstbwcy1AbW7xzPh07cn8xeTa21j3ytSMaAuSaaKh/TGIj/+DhbnovK8cw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2", - "@tiptap/pm": "3.27.2" + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/extension-table": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.27.2.tgz", - "integrity": "sha512-lOuapZby/RgOmrAg5OlTIh/4li8qYhbbTJLKXGq+vCl2BlWLZBMXrhdzPnIqqCiD7mLhNoOPA4kQOohJUah+ww==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.27.3.tgz", + "integrity": "sha512-P7iQ0SkkDWZwnXzQDOVFLXktXlX67VQ3s2YxoJYKTHlc8rPZ4UCaSogFq0G2APyBlizlY3LbMNrDeZAVFOD8wQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2", - "@tiptap/pm": "3.27.2" + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/extension-text": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.27.2.tgz", - "integrity": "sha512-ZipAYrdVLtWU/YwdXkSGrW/PJnxqLGtx/GR5f0BSMO/OzxJppSc+5GSu+MEbwlFaptloSKQEHRbkACXDqm4IJg==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.27.3.tgz", + "integrity": "sha512-wSatZ/bop0pleeC18nF90zvWWEoRe/ewZncvm8LjcMrXwq41kTEs3j7nZflMYAh7X8fZvu00UIMeEyCNJl+5Yg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-text-align": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.27.2.tgz", - "integrity": "sha512-38MH2LOYzZPbJMFoL+M+NpIUpI2pMefTVRuqlHSNoAdn6wEyJoByEeceRab2vt7/S4w9lWcWxwzsXEa38G/5WQ==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.27.3.tgz", + "integrity": "sha512-yyhlwRz0Eh6xQCn+GtTDWUGqxGajT02Qf8zBN/3eZQtl3EZ2xrI8lvfLmcV9SAW0srgMd+5hX0CVV+zKeXuxeg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-text-style": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.27.2.tgz", - "integrity": "sha512-kgGg3LXYnEEttqM6CsiSLxRVU5055vON1T5xipskmbdDtV/xXTykzbISiX3ND5zYw+BG7hF8COEKLt1iLo6LbA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-3.27.3.tgz", + "integrity": "sha512-oim4RqFtMBCA4N+J8f8dTFFYZrwyfmXLxLL5SvQkn+kkpfGLv8PqJCnJ4uHxsnHXDNsWHBtVVhfTGUABZsTEHg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extension-underline": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.27.2.tgz", - "integrity": "sha512-5/1XpKxBqAkiR6ND2P/jcPQ06Opcz/Nidd6dasMkjxwuRJPU5OSz3Qy1fsfLlduRU8TzSqjT8vpQJgaqsHqbLA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.27.3.tgz", + "integrity": "sha512-YH7ka/Rp7ZwlzTuQCcs0OZ4CMSx/P/MzbseQKxKebEIZreWaKMZAaED97Fnu0dA66VlreD4/1C7xHJy84ovamg==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2" + "@tiptap/core": "3.27.3" } }, "node_modules/@tiptap/extensions": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.27.2.tgz", - "integrity": "sha512-WPmMf6+CXEgVpsxFGj20DYLt0gNU2zBATmxSPN5L+sDUx3gKXVjFtQxhKvrhaba5ubO45rIV1hJdo0AGu9TmBA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.27.3.tgz", + "integrity": "sha512-IA2QKUVJgzUPEhhkUfr6P5u5GFVfhiApiEaaHXBz07saL2c4HNoVs2RC18QlZDCtLNKrPR1mUScr72u7uYs+YQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "3.27.2", - "@tiptap/pm": "3.27.2" + "@tiptap/core": "3.27.3", + "@tiptap/pm": "3.27.3" } }, "node_modules/@tiptap/pm": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.27.2.tgz", - "integrity": "sha512-4eBV9Fx16ZZUJuO4AvHzykxdYXfF/iMjpoI8q9PHvxOe+xx8tNa4ONhZMKJLpgCKEECJiBkQmr0gOmarzB0MtA==", + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.27.3.tgz", + "integrity": "sha512-ppiG57RxM3HSHHgcHT0hP6Ib4P56Sd5itxdV4w0hIGHKPGyLLKiAkYp+htIHa9T7IvjMdaqxIlsfyV3ZKsS1sw==", "license": "MIT", "dependencies": { "prosemirror-changeset": "^2.4.1", @@ -5870,69 +6081,41 @@ } }, "node_modules/@tiptap/starter-kit": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.27.2.tgz", - "integrity": "sha512-X28UrkkJPli8f2eCRkpaxE0PDbpwKNCxKrHE6tH40hHtd7SG4zgMw0bBmNYa9o+DJGc5dXipjK6sSc+2sud3cg==", - "license": "MIT", - "dependencies": { - "@tiptap/core": "^3.27.2", - "@tiptap/extension-blockquote": "^3.27.2", - "@tiptap/extension-bold": "^3.27.2", - "@tiptap/extension-bullet-list": "^3.27.2", - "@tiptap/extension-code": "^3.27.2", - "@tiptap/extension-code-block": "^3.27.2", - "@tiptap/extension-document": "^3.27.2", - "@tiptap/extension-dropcursor": "^3.27.2", - "@tiptap/extension-gapcursor": "^3.27.2", - "@tiptap/extension-hard-break": "^3.27.2", - "@tiptap/extension-heading": "^3.27.2", - "@tiptap/extension-horizontal-rule": "^3.27.2", - "@tiptap/extension-italic": "^3.27.2", - "@tiptap/extension-link": "^3.27.2", - "@tiptap/extension-list": "^3.27.2", - "@tiptap/extension-list-item": "^3.27.2", - "@tiptap/extension-list-keymap": "^3.27.2", - "@tiptap/extension-ordered-list": "^3.27.2", - "@tiptap/extension-paragraph": "^3.27.2", - "@tiptap/extension-strike": "^3.27.2", - "@tiptap/extension-text": "^3.27.2", - "@tiptap/extension-underline": "^3.27.2", - "@tiptap/extensions": "^3.27.2", - "@tiptap/pm": "^3.27.2" + "version": "3.27.3", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.27.3.tgz", + "integrity": "sha512-xX3baFqiC30skntdhxUUvyJo755ON9c1pE83+2Wiq2g+Qnffg9knUuLCZStHoZZ318yB0aBsKAMvHWLtYDo8AA==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^3.27.3", + "@tiptap/extension-blockquote": "^3.27.3", + "@tiptap/extension-bold": "^3.27.3", + "@tiptap/extension-bullet-list": "^3.27.3", + "@tiptap/extension-code": "^3.27.3", + "@tiptap/extension-code-block": "^3.27.3", + "@tiptap/extension-document": "^3.27.3", + "@tiptap/extension-dropcursor": "^3.27.3", + "@tiptap/extension-gapcursor": "^3.27.3", + "@tiptap/extension-hard-break": "^3.27.3", + "@tiptap/extension-heading": "^3.27.3", + "@tiptap/extension-horizontal-rule": "^3.27.3", + "@tiptap/extension-italic": "^3.27.3", + "@tiptap/extension-link": "^3.27.3", + "@tiptap/extension-list": "^3.27.3", + "@tiptap/extension-list-item": "^3.27.3", + "@tiptap/extension-list-keymap": "^3.27.3", + "@tiptap/extension-ordered-list": "^3.27.3", + "@tiptap/extension-paragraph": "^3.27.3", + "@tiptap/extension-strike": "^3.27.3", + "@tiptap/extension-text": "^3.27.3", + "@tiptap/extension-underline": "^3.27.3", + "@tiptap/extensions": "^3.27.3", + "@tiptap/pm": "^3.27.3" }, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" } }, - "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-code-block": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.27.2.tgz", - "integrity": "sha512-RlrQdRLIHapi3YDYzNJa1cWnpM9aynGPoqmoEk3qdyOrXO/FFBQk/tUy+IsxZhaNSnuYPo/5cgzwG2z6Ofa9Sw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.27.2", - "@tiptap/pm": "3.27.2" - } - }, - "node_modules/@tiptap/starter-kit/node_modules/@tiptap/extension-horizontal-rule": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.27.2.tgz", - "integrity": "sha512-kuDRRmV5Hz/S6eYh5DRtG6RwbQdbThMEOs65d9MjLyNdJi+o9LMtmEm1Bd3ZLYRuIDKbdaNyXe8D6auGzsPHuQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "3.27.2", - "@tiptap/pm": "3.27.2" - } - }, "node_modules/@ts-morph/common": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.29.0.tgz", @@ -6578,13 +6761,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/luxon": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", - "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==", - "license": "MIT", - "peer": true - }, "node_modules/@types/node": { "version": "24.10.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.14.tgz", @@ -6641,17 +6817,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", - "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz", + "integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/type-utils": "8.62.1", - "@typescript-eslint/utils": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/type-utils": "8.63.0", + "@typescript-eslint/utils": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -6664,22 +6840,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.62.1", + "@typescript-eslint/parser": "^8.63.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", - "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz", + "integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3" }, "engines": { @@ -6695,14 +6871,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", - "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz", + "integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.1", - "@typescript-eslint/types": "^8.62.1", + "@typescript-eslint/tsconfig-utils": "^8.63.0", + "@typescript-eslint/types": "^8.63.0", "debug": "^4.4.3" }, "engines": { @@ -6717,14 +6893,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", - "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz", + "integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1" + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6735,9 +6911,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", - "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz", + "integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==", "dev": true, "license": "MIT", "engines": { @@ -6752,15 +6928,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", - "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz", + "integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/utils": "8.63.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -6777,9 +6953,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", - "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz", + "integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==", "dev": true, "license": "MIT", "engines": { @@ -6791,16 +6967,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", - "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz", + "integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.62.1", - "@typescript-eslint/tsconfig-utils": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/project-service": "8.63.0", + "@typescript-eslint/tsconfig-utils": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/visitor-keys": "8.63.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -6819,16 +6995,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", - "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz", + "integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1" + "@typescript-eslint/scope-manager": "8.63.0", + "@typescript-eslint/types": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6843,13 +7019,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", - "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz", + "integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/types": "8.63.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -7181,6 +7357,40 @@ } } }, + "node_modules/angular-eslint/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/angular-eslint/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -11693,16 +11903,6 @@ "yallist": "^3.0.2" } }, - "node_modules/luxon": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.0.tgz", - "integrity": "sha512-WE7p0p7W1xji9qxkLYsvcIxZyfP48GuFrWIBQZIsbjCyf65dG1rv4n83HcOyEyhvzxJCrUoObCRNFgRNIQ5KNA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - } - }, "node_modules/m3u8-parser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz", @@ -12265,19 +12465,6 @@ "@angular/core": ">=14.0.0" } }, - "node_modules/ngx-material-timepicker": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/ngx-material-timepicker/-/ngx-material-timepicker-13.1.1.tgz", - "integrity": "sha512-GST4IBFXrPSBB5VP5LVxoOk1yHbdSnaB293tjuyu+vusg+pQ/3+AtcxQEIadk6Nmsdt8zKsbXNgvLrI4nbYRKQ==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "peerDependencies": { - "@types/luxon": ">= 1.11.1", - "luxon": ">= 1.24.0" - } - }, "node_modules/ngx-papaparse": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/ngx-papaparse/-/ngx-papaparse-8.0.0.tgz", @@ -14851,16 +15038,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", - "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==", + "version": "8.63.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz", + "integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.62.1", - "@typescript-eslint/parser": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/utils": "8.62.1" + "@typescript-eslint/eslint-plugin": "8.63.0", + "@typescript-eslint/parser": "8.63.0", + "@typescript-eslint/typescript-estree": "8.63.0", + "@typescript-eslint/utils": "8.63.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/client/package.json b/client/package.json index 021df6f3dd..f09044f04d 100644 --- a/client/package.json +++ b/client/package.json @@ -54,20 +54,20 @@ "@ngx-translate/core": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0", "@openslides/motion-diff": "file:../packages/openslides-motion-diff", - "@tiptap/core": "^3.27.2", - "@tiptap/extension-color": "^3.27.2", - "@tiptap/extension-highlight": "^3.27.2", - "@tiptap/extension-image": "^3.27.2", + "@tiptap/core": "^3.27.3", + "@tiptap/extension-color": "^3.27.3", + "@tiptap/extension-highlight": "^3.27.3", + "@tiptap/extension-image": "^3.27.3", "@tiptap/extension-link": "^3.23.4", "@tiptap/extension-list": "^3.22.4", - "@tiptap/extension-subscript": "^3.27.2", - "@tiptap/extension-superscript": "^3.27.2", - "@tiptap/extension-table": "^3.27.2", - "@tiptap/extension-text-align": "^3.27.2", + "@tiptap/extension-subscript": "^3.27.3", + "@tiptap/extension-superscript": "^3.27.3", + "@tiptap/extension-table": "^3.27.3", + "@tiptap/extension-text-align": "^3.27.3", "@tiptap/extension-text-style": "^3.4.2", "@tiptap/extensions": "^3.22.4", - "@tiptap/pm": "^3.27.1", - "@tiptap/starter-kit": "^3.27.2", + "@tiptap/pm": "^3.27.3", + "@tiptap/starter-kit": "^3.27.3", "@tsparticles/angular": "^3.0.0", "@tsparticles/engine": "^3.9.1", "@tsparticles/plugin-emitters": "^3.9.1", @@ -87,7 +87,6 @@ "ngx-date-fns": "^12.0.1", "ngx-device-detector": "^11.0.0", "ngx-file-drop": "^16.0.0", - "ngx-material-timepicker": "^13.1.1", "ngx-papaparse": "^8.0.0", "pdfmake": "^0.3.11", "pofile": "^1.1.4", @@ -117,8 +116,8 @@ "@types/qrcode": "^1.5.6", "@types/tinycolor2": "^1.4.6", "@types/video.js": "^7.3.58", - "@typescript-eslint/eslint-plugin": "^8.62.0", - "@typescript-eslint/parser": "^8.62.0", + "@typescript-eslint/eslint-plugin": "^8.63.0", + "@typescript-eslint/parser": "^8.63.0", "@vendure/ngx-translate-extract": "^10.1.3", "angular-eslint": "^21.4.0", "eslint": "^9.39.4", @@ -140,7 +139,7 @@ "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.9.3", - "typescript-eslint": "^8.62.1" + "typescript-eslint": "^8.63.0" }, "browser": { "fs": false diff --git a/client/src/app/gateways/actions/action.service.spec.ts b/client/src/app/gateways/actions/action.service.spec.ts index 9a7e0e7c06..00351f2f1f 100644 --- a/client/src/app/gateways/actions/action.service.spec.ts +++ b/client/src/app/gateways/actions/action.service.spec.ts @@ -14,8 +14,6 @@ class MockHttpService { public returnType: `response` | `error` | `nothing` | `too little` | `non-response` | `no results` = `response`; - public constructor() {} - public async post(path: string, data: any, queryParams: QueryParams): Promise { this.lastPosts.push({ path, data, queryParams }); if ( diff --git a/client/src/app/gateways/actions/action.service.ts b/client/src/app/gateways/actions/action.service.ts index f2c0e7c36f..af3fc8cc9a 100644 --- a/client/src/app/gateways/actions/action.service.ts +++ b/client/src/app/gateways/actions/action.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { HttpService } from '../http.service'; import { Action } from './action'; @@ -16,7 +16,7 @@ let uniqueFnId = 0; export class ActionService { private readonly _beforeActionFnMap: Record = {}; - public constructor(private http: HttpService) {} + private http = inject(HttpService); /** * Registers a boolean function that will be used to check if actions should currently be allowed to be sent. diff --git a/client/src/app/gateways/auth-adapter.service.ts b/client/src/app/gateways/auth-adapter.service.ts index 00d9a1870b..022e566932 100644 --- a/client/src/app/gateways/auth-adapter.service.ts +++ b/client/src/app/gateways/auth-adapter.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { environment } from '../../environments/environment'; import { HttpService } from './http.service'; @@ -21,7 +21,7 @@ export class AuthAdapterService { return `${this.authUrl}/${environment.authSecurePrefix}`; } - public constructor(private http: HttpService) {} + private http = inject(HttpService); public login(user: { username: string; password: string }): Promise { return this.http.post(`${this.authUrl}/login/`, user); diff --git a/client/src/app/gateways/http.service.ts b/client/src/app/gateways/http.service.ts index 056b1ab7fd..a41f61b956 100644 --- a/client/src/app/gateways/http.service.ts +++ b/client/src/app/gateways/http.service.ts @@ -1,5 +1,5 @@ import { HttpClient, HttpErrorResponse, HttpHeaders, HttpResponse } from '@angular/common/http'; -import { Injectable, Injector } from '@angular/core'; +import { inject, Injectable, Injector } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { TranslateService } from '@ngx-translate/core'; import { firstValueFrom, Observable } from 'rxjs'; @@ -39,13 +39,11 @@ export class HttpService { return this._actionWorkerWatch; } - public constructor( - private http: HttpClient, - private errorMapper: ErrorMapService, - private injector: Injector, - private snackBar: MatSnackBar, - private translate: TranslateService - ) {} + private errorMapper = inject(ErrorMapService); + private http = inject(HttpClient); + private injector = inject(Injector); + private snackBar = inject(MatSnackBar); + private translate = inject(TranslateService); /** * Send the a http request the the given path. diff --git a/client/src/app/gateways/meeting-media-adapter.service.ts b/client/src/app/gateways/meeting-media-adapter.service.ts index 4e36f5b91f..296e6905a6 100644 --- a/client/src/app/gateways/meeting-media-adapter.service.ts +++ b/client/src/app/gateways/meeting-media-adapter.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Id } from '../domain/definitions/key-types'; import { ViewMediafile } from '../site/pages/meetings/pages/mediafiles'; @@ -13,7 +13,7 @@ type MediaAdapterActionParameters = ({ action: `set`; mediafile: ViewMediafile } providedIn: `root` }) export class MeetingMediaAdapterService { - public constructor(private actionService: ActionService) {} + private actionService = inject(ActionService); public setLogo(place: string, meetingId: Id, mediafile: ViewMediafile): Promise { return this.performAction(meetingId, { action: `set`, type: `logo`, place, mediafile }); diff --git a/client/src/app/gateways/presenter/get-user-editable-presenter.service.ts b/client/src/app/gateways/presenter/get-user-editable-presenter.service.ts index ba0674a739..a25278eb64 100644 --- a/client/src/app/gateways/presenter/get-user-editable-presenter.service.ts +++ b/client/src/app/gateways/presenter/get-user-editable-presenter.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Id } from 'src/app/domain/definitions/key-types'; import { Presenter } from './presenter'; @@ -17,7 +17,7 @@ export type GetUserEditablePresenterResult = Record { return await this.presenter.call(Presenter.GET_USER_EDITABLE, payload); diff --git a/client/src/app/gateways/repositories/users/user-repository.service.ts b/client/src/app/gateways/repositories/users/user-repository.service.ts index 14ec7413cc..7e99baad24 100644 --- a/client/src/app/gateways/repositories/users/user-repository.service.ts +++ b/client/src/app/gateways/repositories/users/user-repository.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Fqid } from 'src/app/domain/definitions/key-types'; import { MeetingUser } from 'src/app/domain/models/meeting-users/meeting-user'; import { BaseRepository } from 'src/app/gateways/repositories/base-repository'; @@ -82,12 +82,11 @@ export type FullNameInformation = ShortNameInformation & LevelAndNumberInformati providedIn: `root` }) export class UserRepositoryService extends BaseRepository { - public constructor( - repositoryServiceCollector: RepositoryServiceCollectorService, - private activeMeetingIdService: ActiveMeetingIdService, - private meetingSettingsService: MeetingSettingsService, - private meetingUserRepo: MeetingUserRepositoryService - ) { + private activeMeetingIdService = inject(ActiveMeetingIdService); + private meetingSettingsService = inject(MeetingSettingsService); + private meetingUserRepo = inject(MeetingUserRepositoryService); + + public constructor(repositoryServiceCollector: RepositoryServiceCollectorService) { super(repositoryServiceCollector, User); } diff --git a/client/src/app/infrastructure/utils/import/json-import-file-utils.ts b/client/src/app/infrastructure/utils/import/json-import-file-utils.ts index ae90bbc91f..c8cea3cc84 100644 --- a/client/src/app/infrastructure/utils/import/json-import-file-utils.ts +++ b/client/src/app/infrastructure/utils/import/json-import-file-utils.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { _ } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core'; @@ -10,16 +10,14 @@ export const WRONG_JSON_IMPORT_FORMAT_ERROR_MSG = _(`Import data needs to have t providedIn: `root` }) export class UploadFileJsonProcessorService { - public constructor( - private snackbar: MatSnackBar, - private translate: TranslateService - ) {} + private snackbar = inject(MatSnackBar); + private translate = inject(TranslateService); public async getUploadFileJson(file: FileData): Promise { const json = await new Promise(resolve => { const reader = new FileReader(); reader.addEventListener(`load`, progress => { - let result; + let result: any; try { result = JSON.parse(progress.target!.result as string); } catch (e) { diff --git a/client/src/app/openslides-main-module/components/openslides-main/openslides-main.component.ts b/client/src/app/openslides-main-module/components/openslides-main/openslides-main.component.ts index 697fafd0a6..b4b7b49316 100644 --- a/client/src/app/openslides-main-module/components/openslides-main/openslides-main.component.ts +++ b/client/src/app/openslides-main-module/components/openslides-main/openslides-main.component.ts @@ -1,4 +1,4 @@ -import { ApplicationRef, Component, DOCUMENT, Inject, OnInit, ViewContainerRef } from '@angular/core'; +import { ApplicationRef, Component, DOCUMENT, inject, OnInit, ViewContainerRef } from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; import { Router } from '@angular/router'; @@ -32,23 +32,21 @@ export class OpenSlidesMainComponent implements OnInit { public title = `OpenSlides`; - public constructor( - @Inject(DOCUMENT) private document: Document, - _viewContainer: ViewContainerRef, - _openslidesService: OpenSlidesService, - private appRef: ApplicationRef, - private lifecycleService: LifecycleService, - private domSanitizer: DomSanitizer, - private openslidesStatus: OpenSlidesStatusService, - private matIconRegistry: MatIconRegistry, - private translate: TranslateService, - private storageService: StorageService, - private config: DateFnsConfigurationService, - private updateService: UpdateService, - private router: Router, - private modelStore: ViewModelStoreService, - private ctService: CustomTranslationService - ) { + private document = inject(DOCUMENT); + private appRef = inject(ApplicationRef); + private lifecycleService = inject(LifecycleService); + private domSanitizer = inject(DomSanitizer); + private openslidesStatus = inject(OpenSlidesStatusService); + private matIconRegistry = inject(MatIconRegistry); + private translate = inject(TranslateService); + private storageService = inject(StorageService); + private config = inject(DateFnsConfigurationService); + private updateService = inject(UpdateService); + private router = inject(Router); + private modelStore = inject(ViewModelStoreService); + private ctService = inject(CustomTranslationService); + + public constructor(_viewContainer: ViewContainerRef, _openslidesService: OpenSlidesService) { overloadJsFunctions(); this.addDebugFunctions(); this.waitForAppLoaded(); diff --git a/client/src/app/openslides-main-module/interceptors/auth-token.interceptor.ts b/client/src/app/openslides-main-module/interceptors/auth-token.interceptor.ts index 74f80c99e7..c33975e728 100644 --- a/client/src/app/openslides-main-module/interceptors/auth-token.interceptor.ts +++ b/client/src/app/openslides-main-module/interceptors/auth-token.interceptor.ts @@ -6,14 +6,14 @@ import { HttpRequest, HttpResponse } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Observable, tap } from 'rxjs'; import { AuthTokenService } from '../../site/services/auth-token.service'; @Injectable() export class AuthTokenInterceptor implements HttpInterceptor { - public constructor(private authTokenService: AuthTokenService) {} + private authTokenService = inject(AuthTokenService); public intercept(request: HttpRequest, next: HttpHandler): Observable> { if (this.authTokenService.rawAccessToken) { diff --git a/client/src/app/openslides-main-module/services/app-load.service.ts b/client/src/app/openslides-main-module/services/app-load.service.ts index a428945e78..1521dcf621 100644 --- a/client/src/app/openslides-main-module/services/app-load.service.ts +++ b/client/src/app/openslides-main-module/services/app-load.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Injector, Type } from '@angular/core'; +import { inject, Injectable, Injector, Type } from '@angular/core'; import { ActionWorkerAppConfig } from 'src/app/gateways/repositories/action-worker/action-worker.config'; import { BaseRepository } from 'src/app/gateways/repositories/base-repository'; import { MeetingUserAppConfig } from 'src/app/gateways/repositories/meeting_user/meeting-user-config'; @@ -64,13 +64,13 @@ const appConfigs: AppConfig[] = [ providedIn: `root` }) export class AppLoadService { - public constructor( - private injector: Injector, - private modelMapper: CollectionMapperService, - private mainMenuService: MainMenuService, - private fallbackRoutesService: FallbackRoutesService - ) { - OpenSlidesInjector.setInjector(injector); + private injector = inject(Injector); + private modelMapper = inject(CollectionMapperService); + private mainMenuService = inject(MainMenuService); + private fallbackRoutesService = inject(FallbackRoutesService); + + public constructor() { + OpenSlidesInjector.setInjector(this.injector); } public async loadApps(): Promise { diff --git a/client/src/app/openslides-main-module/services/shared-worker.service.ts b/client/src/app/openslides-main-module/services/shared-worker.service.ts index c486ee0356..fd0c5f2bbe 100644 --- a/client/src/app/openslides-main-module/services/shared-worker.service.ts +++ b/client/src/app/openslides-main-module/services/shared-worker.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NgZone } from '@angular/core'; +import { inject, Injectable, NgZone } from '@angular/core'; import { defer, filter, @@ -51,7 +51,9 @@ export class SharedWorkerService { private messageEventSubscription: Subscription; private windowMode = false; - public constructor(private zone: NgZone) { + private zone = inject(NgZone); + + public constructor() { this.connectWorker(true); } diff --git a/client/src/app/site/guards/auth.guard.ts b/client/src/app/site/guards/auth.guard.ts index 35a3d06a1d..39abdfd2d5 100644 --- a/client/src/app/site/guards/auth.guard.ts +++ b/client/src/app/site/guards/auth.guard.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; import { AuthCheckService } from '../services/auth-check.service'; @@ -18,12 +18,10 @@ enum CannotAccessReason { export class AuthGuard { private _cannotAccessReason: CannotAccessReason | null = null; - public constructor( - private reroute: RerouteService, - private osRouter: OpenSlidesRouterService, - private operator: OperatorService, - private authCheck: AuthCheckService - ) {} + private reroute = inject(RerouteService); + private osRouter = inject(OpenSlidesRouterService); + private operator = inject(OperatorService); + private authCheck = inject(AuthCheckService); /** * Checks of the operator has the required permission to see the state. diff --git a/client/src/app/site/guards/permission.guard.ts b/client/src/app/site/guards/permission.guard.ts index 5b5b0a3581..0761855b49 100644 --- a/client/src/app/site/guards/permission.guard.ts +++ b/client/src/app/site/guards/permission.guard.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Route, Router, UrlSegment, UrlTree } from '@angular/router'; import { AuthCheckService } from '../services/auth-check.service'; @@ -9,12 +9,10 @@ import { RerouteService } from '../services/reroute.service'; providedIn: `root` }) export class PermissionGuard { - public constructor( - private router: Router, - private reroute: RerouteService, - private osRouter: OpenSlidesRouterService, - private authCheck: AuthCheckService - ) {} + private authCheck = inject(AuthCheckService); + private osRouter = inject(OpenSlidesRouterService); + private reroute = inject(RerouteService); + private router = inject(Router); public async canLoad(route: Route, segments: UrlSegment[]): Promise { const url = this.getCurrentNavigationUrl(); diff --git a/client/src/app/site/modules/chip-select/components/chip-select/chip-select.component.html b/client/src/app/site/modules/chip-select/components/chip-select/chip-select.component.html index 2c48dc2218..b1c6101110 100644 --- a/client/src/app/site/modules/chip-select/components/chip-select/chip-select.component.html +++ b/client/src/app/site/modules/chip-select/components/chip-select/chip-select.component.html @@ -1,11 +1,11 @@ @if (chips.length) { - + @for (chip of chips; track chip) { @@ -16,9 +16,9 @@ } @else { diff --git a/client/src/app/site/modules/chip-select/components/chip-select/chip-select.component.ts b/client/src/app/site/modules/chip-select/components/chip-select/chip-select.component.ts index 939fcbadc6..f42ae13355 100644 --- a/client/src/app/site/modules/chip-select/components/chip-select/chip-select.component.ts +++ b/client/src/app/site/modules/chip-select/components/chip-select/chip-select.component.ts @@ -1,4 +1,4 @@ -import { AfterContentInit, Component, ContentChildren, Input, QueryList } from '@angular/core'; +import { AfterContentInit, Component, ContentChildren, input, QueryList } from '@angular/core'; import { ChipSelectChipComponent } from '../chip-select-chip/chip-select-chip.component'; @@ -9,13 +9,9 @@ import { ChipSelectChipComponent } from '../chip-select-chip/chip-select-chip.co standalone: false }) export class ChipSelectComponent implements AfterContentInit { - public constructor() {} + public readonly chipClass = input(null); - @Input() - public chipClass: string | null; - - @Input() - public canOpen: boolean; + public readonly canOpen = input(false); @ContentChildren(ChipSelectChipComponent) public inputChips: QueryList; diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts index 18c2b5578a..1019681824 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, inject, OnInit, ViewChild } from '@angular/core'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { MatMenuTrigger } from '@angular/material/menu'; import { Router } from '@angular/router'; @@ -82,18 +82,17 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { private clickCounter = 0; private clickTimeout: number | null = null; - public constructor( - private translate: TranslateService, - private operator: OperatorService, - private userRepo: UserControllerService, - private authService: AuthService, - private dialog: MatDialog, - private router: Router, - private theme: ThemeService, - private meetingSettingsService: MeetingSettingsService, - private activeMeetingIdService: ActiveMeetingIdService, - chessChallengeService: ChessChallengeService - ) { + private translate = inject(TranslateService); + private operator = inject(OperatorService); + private userRepo = inject(UserControllerService); + private authService = inject(AuthService); + private theme = inject(ThemeService); + private meetingSettingsService = inject(MeetingSettingsService); + private activeMeetingIdService = inject(ActiveMeetingIdService); + private dialog = inject(MatDialog); + private router = inject(Router); + + public constructor(chessChallengeService: ChessChallengeService) { super(); chessChallengeService.startListening(); } diff --git a/client/src/app/site/modules/global-headbar/components/account-dialog/account-dialog.component.ts b/client/src/app/site/modules/global-headbar/components/account-dialog/account-dialog.component.ts index 0a9021c897..bb9dc92fd1 100644 --- a/client/src/app/site/modules/global-headbar/components/account-dialog/account-dialog.component.ts +++ b/client/src/app/site/modules/global-headbar/components/account-dialog/account-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, inject, OnInit, ViewChild } from '@angular/core'; import { MatDialogRef } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { TranslateService } from '@ngx-translate/core'; @@ -95,18 +95,14 @@ export class AccountDialogComponent extends BaseUiComponent implements OnInit { private _isUserInScope = false; private _isEditing = false; - public constructor( - public dialogRef: MatDialogRef, - private operator: OperatorService, - private repo: UserControllerService, - private meetingRepo: MeetingControllerService, - private userService: UserService, - private snackbar: MatSnackBar, - private authService: AuthService, - private translate: TranslateService - ) { - super(); - } + public dialogRef = inject(MatDialogRef); + private authService = inject(AuthService); + private meetingRepo = inject(MeetingControllerService); + private operator = inject(OperatorService); + private repo = inject(UserControllerService); + private snackbar = inject(MatSnackBar); + private translate = inject(TranslateService); + private userService = inject(UserService); public ngOnInit(): void { this.subscriptions.push( diff --git a/client/src/app/site/modules/global-headbar/components/global-search/global-search.component.ts b/client/src/app/site/modules/global-headbar/components/global-search/global-search.component.ts index 497376ac2d..054510e013 100644 --- a/client/src/app/site/modules/global-headbar/components/global-search/global-search.component.ts +++ b/client/src/app/site/modules/global-headbar/components/global-search/global-search.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestroy } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { _ } from '@ngx-translate/core'; import { pairwise, startWith, Subscription } from 'rxjs'; @@ -48,13 +48,6 @@ export class GlobalSearchComponent implements OnDestroy { public currentlyAvailableFilters = []; - public currentFilters = this.formBuilder.group({ - ...Object.fromEntries(Object.keys(this.availableFilters).map(field => [field, false])), - meetingFilter: this.activeMeeting.meetingId ? `current` : `all` - }); - - public inMeeting = !!this.activeMeeting.meetingId; - public get resultCount(): number { return this.results.length; } @@ -76,14 +69,23 @@ export class GlobalSearchComponent implements OnDestroy { private filterChangeSubscription: Subscription; private viewportSubscription: Subscription; - public constructor( - private activeMeeting: ActiveMeetingService, - public operator: OperatorService, - private globalSearchService: GlobalSearchService, - private formBuilder: FormBuilder, - private viewport: ViewPortService, - private cd: ChangeDetectorRef - ) { + public operator = inject(OperatorService); + private activeMeeting = inject(ActiveMeetingService); + private cd = inject(ChangeDetectorRef); + private formBuilder = inject(FormBuilder); + private globalSearchService = inject(GlobalSearchService); + private viewport = inject(ViewPortService); + + public get inMeeting(): boolean { + return !!this.activeMeeting.meetingId; + } + + public currentFilters = this.formBuilder.group({ + ...Object.fromEntries(Object.keys(this.availableFilters).map(field => [field, false])), + meetingFilter: this.activeMeeting.meetingId ? `current` : `all` + }); + + public constructor() { this.updateCurrentlyAvailableFilters(); this.filterChangeSubscription = this.currentFilters.valueChanges .pipe(startWith(this.currentFilters.value), pairwise()) diff --git a/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.html b/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.html index 9aba4033c2..f4c8c618bd 100644 --- a/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.html +++ b/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.html @@ -124,43 +124,6 @@ } - - @if (setting.type === 'datetime' || setting.type === 'date') { -
- -
- @if (setting.type === 'datetime') { - - -
- @if (updateSuccessIcon) { - error - } - - access_time - -
- @if (error) { - {{ error }} - } - -
- } -
-
- } - @if (setting.type === 'daterange') {
diff --git a/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.scss b/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.scss index 8c526026df..9a5a1d3db3 100644 --- a/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.scss +++ b/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.scss @@ -33,10 +33,6 @@ margin-bottom: 10px; } } - - .ngx-material-timepicker-toggle { - width: 28px; - } } } } diff --git a/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.ts b/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.ts index f09e2e0407..3f12ec44b6 100644 --- a/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.ts +++ b/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/components/meeting-settings-group-detail-field/meeting-settings-group-detail-field.component.ts @@ -12,7 +12,6 @@ import { import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { _ } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core'; -import { fromUnixTime, getHours, getMinutes, getUnixTime, setHours, setMinutes } from 'date-fns'; import { distinctUntilChanged, filter, map, Observable } from 'rxjs'; import { Settings } from 'src/app/domain/models/meetings/meeting'; import { BaseComponent } from 'src/app/site/base/base.component'; @@ -110,11 +109,6 @@ export class MeetingSettingsGroupDetailFieldComponent extends BaseComponent impl public get currentValue(): any { const value = this.form.get(`value`); - if (this.setting.type === `datetime` || this.setting.type === `date`) { - const date = this.form.get(`date`)!.value; - const time = this.form.get(`time`)!.value; - return this.dateAndTimeToUnix(date, time); - } return value.value; } @@ -218,7 +212,6 @@ export class MeetingSettingsGroupDetailFieldComponent extends BaseComponent impl this.form = this.formBuilder.group({ value: [``, this.setting.validators ?? []], - date: [``], time: [``], daterange: [ { @@ -231,10 +224,6 @@ export class MeetingSettingsGroupDetailFieldComponent extends BaseComponent impl this.form.disable(); } this.internalValue = this.value ?? this.meetingSettingsDefinitionProvider.getDefaultValue(this.setting); - if ((this.setting.type === `datetime` || this.setting.type === `date`) && this.value) { - const datetimeObj = this.getRestrictedValue(this.unixToDateAndTime(this.value as number)); - this.form.patchValue(datetimeObj); - } if (this.setting.type === `daterange` && this.value) { const daterangeObj = { start: this.getRestrictedValue(this.value[0] ? new Date(this.value[0] * 1000) : null), @@ -298,10 +287,6 @@ export class MeetingSettingsGroupDetailFieldComponent extends BaseComponent impl } public updateValue(newValue: any): void { - if ((this.setting.type === `datetime` || this.setting.type === `date`) && newValue) { - const datetimeObj = this.getRestrictedValue(this.unixToDateAndTime(newValue as number)); - this.form.patchValue(datetimeObj); - } if (this.setting.type === `daterange` && newValue) { const daterangeObj = { start: this.getRestrictedValue(newValue[0] ? new Date(newValue[0]) : null), @@ -321,41 +306,6 @@ export class MeetingSettingsGroupDetailFieldComponent extends BaseComponent impl }; } - /** - * Helper function to split a unix timestamp into a date as a date object and a time string in the form of HH:SS - * - * @param unix the timestamp - * - * @return an object with a date and a time field - */ - private unixToDateAndTime(unix: number): { date: Date; time: string } { - const date = fromUnixTime(unix); - const time = getHours(date) + `:` + getMinutes(date); - return { date, time }; - } - - /** - * Helper function to fuse a date object as the date part and a time string (HH:SS) as the time part. - * - * @param date the date object - * @param time the time string - * - * @return a unix timestamp - */ - private dateAndTimeToUnix(date: Date, time: string): number | null { - if (date) { - if (time) { - const timeSplit = time.split(`:`); - // + is faster than parseint and number(). ~~ would be fastest but prevented by linter... - setHours(date, +timeSplit[0]); - setMinutes(date, +timeSplit[1]); - } - return getUnixTime(date); - } else { - return null; - } - } - /** * Trigger an update of the data */ @@ -364,11 +314,6 @@ export class MeetingSettingsGroupDetailFieldComponent extends BaseComponent impl case `markupText`: // tinyMCE markuptext does not autoupdate on change, only when entering or leaving return; - case `date`: - case `datetime`: - // datetime has to be converted - value = this.dateAndTimeToUnix(this.form.get(`date`)!.value, this.form.get(`time`)!.value); - break; case `daterange`: // daterange has to be formatted value = [this.form.get(`daterange`)!.value.start, this.form.get(`daterange`)!.value.end]; @@ -401,10 +346,6 @@ export class MeetingSettingsGroupDetailFieldComponent extends BaseComponent impl this.form.controls[`value`].setValue(this.meetingSettingsDefinitionProvider.getDefaultValue(this.setting)); } - public onClearDate(): void { - this.form.controls[`date`].setValue(null); - } - /** * Sends an update request for the config item to the server. * @param value The new value to set. @@ -440,7 +381,7 @@ export class MeetingSettingsGroupDetailFieldComponent extends BaseComponent impl * @returns wheather it should be excluded or not */ public isExcludedType(type: string): boolean { - const excluded = [`boolean`, `markupText`, `text`, `translations`, `ranking`, `datetime`, `date`, `daterange`]; + const excluded = [`boolean`, `markupText`, `text`, `translations`, `ranking`, `daterange`]; return excluded.includes(type); } diff --git a/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/meeting-settings-group-detail.module.ts b/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/meeting-settings-group-detail.module.ts index 5d7d19a0aa..9cdbcbb650 100644 --- a/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/meeting-settings-group-detail.module.ts +++ b/client/src/app/site/pages/meetings/pages/meeting-settings/pages/meeting-settings-group-detail/meeting-settings-group-detail.module.ts @@ -10,8 +10,6 @@ import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; import { MatTooltipModule } from '@angular/material/tooltip'; -// time picker because angular still doesnt offer one!! -import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; import { OpenSlidesTranslationModule } from 'src/app/site/modules/translations'; import { DirectivesModule } from 'src/app/ui/directives'; import { DatepickerModule } from 'src/app/ui/modules/datepicker'; @@ -52,7 +50,6 @@ import { MeetingSettingsGroupDetailRoutingModule } from './meeting-settings-grou MatDatepickerModule, MatTooltipModule, MatInputModule, - NgxMaterialTimepickerModule, FormsModule, ReactiveFormsModule, DatepickerModule, diff --git a/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/motion-detail.module.ts b/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/motion-detail.module.ts index 3c15e70c98..44151d038d 100644 --- a/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/motion-detail.module.ts +++ b/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/motion-detail.module.ts @@ -17,9 +17,9 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSelectModule } from '@angular/material/select'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatTabsModule } from '@angular/material/tabs'; +import { MatTimepickerModule } from '@angular/material/timepicker'; import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterModule } from '@angular/router'; -import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; import { ChipSelectModule } from 'src/app/site/modules/chip-select/chip-select.module'; import { OpenSlidesTranslationModule } from 'src/app/site/modules/translations'; import { MeetingsComponentCollectorModule } from 'src/app/site/pages/meetings/modules/meetings-component-collector'; @@ -128,6 +128,7 @@ import { MotionDetailServiceModule } from './services/motion-detail-service.modu MatFormFieldModule, MatTabsModule, MatTooltipModule, + MatTimepickerModule, MatChipsModule, MatCheckboxModule, MatSelectModule, @@ -137,7 +138,6 @@ import { MotionDetailServiceModule } from './services/motion-detail-service.modu MatProgressBarModule, ParticipantSearchSelectorModule, OpenSlidesDateAdapterModule, - NgxMaterialTimepickerModule, DatepickerModule, // Detail view ScrollingModule, diff --git a/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/pages/motion-view/components/motion-manage-timestamp/motion-manage-timestamp.component.html b/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/pages/motion-view/components/motion-manage-timestamp/motion-manage-timestamp.component.html index 21d6a7b69a..73e46d1fcc 100644 --- a/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/pages/motion-view/components/motion-manage-timestamp/motion-manage-timestamp.component.html +++ b/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/pages/motion-view/components/motion-manage-timestamp/motion-manage-timestamp.component.html @@ -24,15 +24,15 @@

- + +
- - - access_time - - + + access_time +
- + +

diff --git a/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/pages/motion-view/components/motion-manage-timestamp/motion-manage-timestamp.component.ts b/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/pages/motion-view/components/motion-manage-timestamp/motion-manage-timestamp.component.ts index c7330d6a17..1d9b229b87 100644 --- a/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/pages/motion-view/components/motion-manage-timestamp/motion-manage-timestamp.component.ts +++ b/client/src/app/site/pages/meetings/pages/motions/pages/motion-detail/pages/motion-view/components/motion-manage-timestamp/motion-manage-timestamp.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; -import { fromUnixTime, getHours, getMinutes, isDate } from 'date-fns'; +import { fromUnixTime, isDate } from 'date-fns'; import { Permission } from 'src/app/domain/definitions/permission'; import { KeyOfType } from 'src/app/infrastructure/utils/keyof-type'; import { OperatorService } from 'src/app/site/services/operator.service'; @@ -73,13 +73,11 @@ export class MotionManageTimestampComponent extends BaseUiComponent implements O this.subscriptions.push( this.form.get(`date`).valueChanges.subscribe(currDate => { - if (isDate(currDate) !== !!this.form.get(`time`).value) { - this.form.get(`time`).setValue(isDate(currDate) ? `00:00` : ``); - } + this.form.get(`time`).setValue(currDate ?? null); }), this.form.get(`time`).valueChanges.subscribe(currTime => { if (!!currTime !== isDate(this.form.get(`date`).value)) { - this.form.get(`date`).setValue(currTime ? new Date() : null); + this.form.get(`date`).setValue(currTime); } }) ); @@ -95,10 +93,9 @@ export class MotionManageTimestampComponent extends BaseUiComponent implements O } public async onSave(): Promise { - const date: { date: Date | null; time: string } = this.form.value; - const [hours, minutes, ..._] = date.time.split(`:`); + const date: { date: Date | null; time: Date } = this.form.value; if (date.date) { - date.date.setHours(+hours, +minutes); + date.date.setHours(+date.time.getHours(), +date.time.getMinutes()); } await this.motionController .update({ [this.field]: date.date ? Math.floor(date.date.getTime() / 1000) : null }, this.motion) @@ -119,7 +116,10 @@ export class MotionManageTimestampComponent extends BaseUiComponent implements O public onEdit(): void { const timestamp = this.motion[this.field]; const date = timestamp - ? this.getTimes(timestamp) + ? { + date: fromUnixTime(timestamp), + time: fromUnixTime(timestamp) + } : { date: ``, time: `` @@ -128,12 +128,6 @@ export class MotionManageTimestampComponent extends BaseUiComponent implements O this.isEditMode = true; } - public getTimes(timestamp: number): { date: Date; time: string } { - const date = fromUnixTime(timestamp); - const time = getHours(date) + `:` + getMinutes(date); - return { date, time }; - } - public clearForm(): void { this.form.patchValue({ date: ``, diff --git a/client/src/app/site/pages/meetings/services/active-meeting.service.ts b/client/src/app/site/pages/meetings/services/active-meeting.service.ts index ae3ec361a4..4bfb217e6a 100644 --- a/client/src/app/site/pages/meetings/services/active-meeting.service.ts +++ b/client/src/app/site/pages/meetings/services/active-meeting.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, first, firstValueFrom, map, Observable, Subscription } from 'rxjs'; @@ -44,18 +44,18 @@ export class ActiveMeetingService { private _meetingSubject = new BehaviorSubject(null); private _meetingSubcription: Subscription | null = null; - public constructor( - private activeMeetingIdService: ActiveMeetingIdService, - private repo: MeetingControllerService, - private lifecycle: LifecycleService, - private bannerService: BannerService, - private archiveService: ArchiveStatusService, - private authService: AuthService, - private modelRequestService: ModelRequestService, - private router: Router, - private translate: TranslateService, - private meetingSettingsDefinitionService: MeetingSettingsDefinitionService - ) { + private activeMeetingIdService = inject(ActiveMeetingIdService); + private archiveService = inject(ArchiveStatusService); + private authService = inject(AuthService); + private bannerService = inject(BannerService); + private lifecycle = inject(LifecycleService); + private meetingSettingsDefinitionService = inject(MeetingSettingsDefinitionService); + private modelRequestService = inject(ModelRequestService); + private repo = inject(MeetingControllerService); + private router = inject(Router); + private translate = inject(TranslateService); + + public constructor() { this.activeMeetingIdService.meetingIdObservable.subscribe(id => { if (id !== undefined) { this.setupModelSubscription(); diff --git a/client/src/app/site/pages/meetings/services/meeting-component-service-collector.service.ts b/client/src/app/site/pages/meetings/services/meeting-component-service-collector.service.ts index c9f9671eb8..38f492fc56 100644 --- a/client/src/app/site/pages/meetings/services/meeting-component-service-collector.service.ts +++ b/client/src/app/site/pages/meetings/services/meeting-component-service-collector.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; @@ -34,10 +34,8 @@ export class MeetingComponentServiceCollectorService { return this.componentServiceCollector.router; } - public constructor( - private componentServiceCollector: ComponentServiceCollectorService, - public activeMeetingIdService: ActiveMeetingIdService, - public activeMeetingService: ActiveMeetingService, - public meetingSettingsService: MeetingSettingsService - ) {} + public activeMeetingIdService = inject(ActiveMeetingIdService); + public activeMeetingService = inject(ActiveMeetingService); + public meetingSettingsService = inject(MeetingSettingsService); + private componentServiceCollector = inject(ComponentServiceCollectorService); } diff --git a/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definition.service.spec.ts b/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definition.service.spec.ts index 377abe25f4..5eadef335d 100644 --- a/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definition.service.spec.ts +++ b/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definition.service.spec.ts @@ -36,16 +36,6 @@ const fakeSettings: SettingsGroup[] = [ label: `Setting two and three`, type: `daterange` }, - { - key: `four` as keyof Settings, - label: `Setting four`, - type: `datetime` - }, - { - key: `five` as keyof Settings, - label: `Setting five`, - type: `date` - }, { key: `a` as keyof Settings, label: `Setting a` @@ -74,7 +64,7 @@ const fakeSettings: SettingsGroup[] = [ key: `bool` as keyof Settings, label: `Boolean setting`, type: `boolean` - }, // + }, { key: `groups` as keyof Settings, label: `Group setting`, @@ -124,7 +114,7 @@ const fakeSettingsMap: Record = fakeSettings ); const fakeSettingsDefaults: Record = { - ...[`one`, `two`, `four`, `five`].mapToObject((prop, index) => ({ [prop]: index + 1 })), + ...[`one`, `two`].mapToObject((prop, index) => ({ [prop]: index + 1 })), bool: true, ...[`a`, `b`, `c`, `d`].mapToObject(letter => ({ [letter]: letter })), e: `e.mail@email.mail`, @@ -136,7 +126,7 @@ const fakeSettingsDefaults: Record = { }; const fakeBrokenSettingsDefaults: Record = { - ...[`one`, `two`, `four`, `five`].mapToObject(letter => ({ [letter]: letter })), + ...[`one`, `two`].mapToObject(letter => ({ [letter]: letter })), bool: `not a bool`, ...[`a`, `b`, `c`, `d`].mapToObject((prop, index) => ({ [prop]: index + 1 })), e: 3, @@ -154,8 +144,6 @@ const typeToDefault: Record = { translations: {}, ranking: [], choice: null, - date: null, - datetime: null, daterange: [null, null], string: ``, text: ``, diff --git a/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definition.service.ts b/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definition.service.ts index fcc9f33cc9..723d04990c 100644 --- a/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definition.service.ts +++ b/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definition.service.ts @@ -53,8 +53,6 @@ export class MeetingSettingsDefinitionService { case `ranking`: return []; case `choice`: - case `date`: - case `datetime`: return null; case `daterange`: return [null, null]; diff --git a/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definitions.ts b/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definitions.ts index d289dae50a..a85410bfbf 100644 --- a/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definitions.ts +++ b/client/src/app/site/pages/meetings/services/meeting-settings-definition.service/meeting-settings-definitions.ts @@ -26,8 +26,6 @@ export type SettingsType = | `integer` | `boolean` | `choice` - | `date` - | `datetime` | `translations` | `ranking` | `groups` diff --git a/client/src/app/site/pages/meetings/services/sequential-number-mapping.service.ts b/client/src/app/site/pages/meetings/services/sequential-number-mapping.service.ts index 739161432b..666eb51039 100644 --- a/client/src/app/site/pages/meetings/services/sequential-number-mapping.service.ts +++ b/client/src/app/site/pages/meetings/services/sequential-number-mapping.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Subscription } from 'rxjs'; import { Collection, Id } from 'src/app/domain/definitions/key-types'; import { HasMeetingId, HasSequentialNumber, isSequentialNumberHaving } from 'src/app/domain/interfaces'; @@ -52,12 +52,11 @@ export class SequentialNumberMappingService { private _subscriptions: Subscription[] = []; private _repositories: BaseMeetingRelatedRepository[] = []; - public constructor( - collectionMapperService: MeetingCollectionMapperService, - private activeMeeting: ActiveMeetingService, - private autoupdateService: AutoupdateService, - private modelRequestBuilder: ModelRequestBuilderService - ) { + private activeMeeting = inject(ActiveMeetingService); + private autoupdateService = inject(AutoupdateService); + private modelRequestBuilder = inject(ModelRequestBuilderService); + + public constructor(collectionMapperService: MeetingCollectionMapperService) { collectionMapperService.getAllRepositoriesObservable().subscribe(repositories => { this._repositories = repositories; this.updateRepositoriesSubscriptions(); diff --git a/client/src/app/site/pages/organization/pages/committees/modules/committee-meeting-preview/committee-meeting-preview.component.ts b/client/src/app/site/pages/organization/pages/committees/modules/committee-meeting-preview/committee-meeting-preview.component.ts index a23605080d..d0081e86ae 100644 --- a/client/src/app/site/pages/organization/pages/committees/modules/committee-meeting-preview/committee-meeting-preview.component.ts +++ b/client/src/app/site/pages/organization/pages/committees/modules/committee-meeting-preview/committee-meeting-preview.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { Component, inject, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { Subscription } from 'rxjs'; import { CML, OML } from 'src/app/domain/definitions/organization-permission'; @@ -76,13 +76,11 @@ export class CommitteeMeetingPreviewComponent implements OnDestroy, OnInit { private _canEditMeetingSetting = true; private _canEditMeetingSubscription: Subscription; - public constructor( - private translate: TranslateService, - private meetingRepo: MeetingControllerService, - private meetingService: MeetingService, - private promptService: PromptService, - public operator: OperatorService - ) {} + public operator = inject(OperatorService); + private meetingRepo = inject(MeetingControllerService); + private meetingService = inject(MeetingService); + private promptService = inject(PromptService); + private translate = inject(TranslateService); /** * Get the subject diff --git a/client/src/app/site/pages/organization/pages/committees/modules/services/meeting.service.ts b/client/src/app/site/pages/organization/pages/committees/modules/services/meeting.service.ts index 38a228a79e..8fe178e26a 100644 --- a/client/src/app/site/pages/organization/pages/committees/modules/services/meeting.service.ts +++ b/client/src/app/site/pages/organization/pages/committees/modules/services/meeting.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { FileExportService } from 'src/app/gateways/export/file-export.service/file-export.service'; import { ExportMeetingPresenterService } from 'src/app/gateways/presenter/export-meeting-presenter.service'; @@ -10,12 +10,10 @@ import { AccountFilterService } from '../../../accounts/services/common/account- providedIn: 'root' }) export class MeetingService { - public constructor( - private router: Router, - private accountFilterService: AccountFilterService, - private exporter: FileExportService, - private exportMeetingPresenter: ExportMeetingPresenterService - ) {} + private router = inject(Router); + private accountFilterService = inject(AccountFilterService); + private exporter = inject(FileExportService); + private exportMeetingPresenter = inject(ExportMeetingPresenterService); public async navigateToMeetingUsers(meeting: ViewMeeting): Promise { await this.router.navigate([`accounts`, `meeting`, meeting.id]); diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-import/components/committee-import-list/committee-import-list.component.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-import/components/committee-import-list/committee-import-list.component.ts index 4c88ef9062..e161849ac2 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-import/components/committee-import-list/committee-import-list.component.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-import/components/committee-import-list/committee-import-list.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; import { BaseViaBackendImportListComponent } from 'src/app/site/base/base-via-backend-import-list.component'; import { committeeHeadersAndVerboseNames } from 'src/app/site/pages/organization/pages/committees/pages/committee-import/definitions'; import { ImportListHeaderDefinition } from 'src/app/ui/modules/import-list'; @@ -32,10 +31,7 @@ export class CommitteeImportListComponent extends BaseViaBackendImportListCompon is_list: [`forward_to_committees`, `organization_tags`, `managers`, `meeting_admins`].includes(header) })); - public constructor( - protected override translate: TranslateService, - public override importer: CommitteeImportService - ) { + public constructor(public override importer: CommitteeImportService) { super(importer); } } diff --git a/client/src/app/site/pages/organization/pages/committees/pages/committee-import/services/committee-import.service/committee-import.service.ts b/client/src/app/site/pages/organization/pages/committees/pages/committee-import/services/committee-import.service/committee-import.service.ts index 63cd1426e6..5346e40282 100644 --- a/client/src/app/site/pages/organization/pages/committees/pages/committee-import/services/committee-import.service/committee-import.service.ts +++ b/client/src/app/site/pages/organization/pages/committees/pages/committee-import/services/committee-import.service/committee-import.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { _ } from '@ngx-translate/core'; import { CsvExportForBackendService } from 'src/app/gateways/export/csv-export.service/csv-export-for-backend.service'; import { BaseBackendImportService } from 'src/app/site/base/base-import.service/base-backend-import.service'; @@ -27,11 +27,10 @@ export class CommitteeImportService extends BaseBackendImportService { warning: _(`Committees with warnings: affected cells will be skipped`) }; - public constructor( - importServiceCollector: ImportServiceCollectorService, - private exporter: CsvExportForBackendService, - private repo: CommitteeControllerService - ) { + private exporter = inject(CsvExportForBackendService); + private repo = inject(CommitteeControllerService); + + public constructor(importServiceCollector: ImportServiceCollectorService) { super(importServiceCollector); } diff --git a/client/src/app/site/pages/organization/pages/settings/modules/settings-detail/components/organization-settings/organization-settings.component.ts b/client/src/app/site/pages/organization/pages/settings/modules/settings-detail/components/organization-settings/organization-settings.component.ts index 31276e66a1..38c5f2e6a3 100644 --- a/client/src/app/site/pages/organization/pages/settings/modules/settings-detail/components/organization-settings/organization-settings.component.ts +++ b/client/src/app/site/pages/organization/pages/settings/modules/settings-detail/components/organization-settings/organization-settings.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { _ } from '@ngx-translate/core'; import { availableTranslations } from 'src/app/domain/definitions/languages'; @@ -43,7 +43,6 @@ export class OrganizationSettingsComponent extends BaseComponent { private controller = inject(OrganizationControllerService); private operator = inject(OperatorService); private timeZone = inject(TimeZoneService); - private cd = inject(ChangeDetectorRef); public constructor() { super(); diff --git a/client/src/app/ui/directives/autofocus/autofocus.directive.ts b/client/src/app/ui/directives/autofocus/autofocus.directive.ts index 7d6a8923f9..73c18d5e3c 100644 --- a/client/src/app/ui/directives/autofocus/autofocus.directive.ts +++ b/client/src/app/ui/directives/autofocus/autofocus.directive.ts @@ -1,17 +1,11 @@ -import { Directive, ElementRef, OnInit } from '@angular/core'; +import { Directive, ElementRef, inject, OnInit } from '@angular/core'; @Directive({ selector: `[osAutofocus]`, standalone: false }) export class AutofocusDirective implements OnInit { - /** - * Constructor - * - * Gets the reference of the annotated element - * @param el ElementRef - */ - public constructor(private el: ElementRef) {} + private el = inject(ElementRef); /** * Executed after page init, calls the focus function after an unnoticeable timeout diff --git a/client/src/app/ui/directives/meeting-setting/meeting-setting.directive.ts b/client/src/app/ui/directives/meeting-setting/meeting-setting.directive.ts index 9c3dc3014a..70b777cbae 100644 --- a/client/src/app/ui/directives/meeting-setting/meeting-setting.directive.ts +++ b/client/src/app/ui/directives/meeting-setting/meeting-setting.directive.ts @@ -1,4 +1,4 @@ -import { Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef } from '@angular/core'; +import { Directive, EmbeddedViewRef, inject, Input, TemplateRef, ViewContainerRef } from '@angular/core'; import { Settings } from 'src/app/domain/models/meetings/meeting'; import { MeetingSettingsService } from 'src/app/site/pages/meetings/services/meeting-settings.service'; import { BaseUiComponent } from 'src/app/ui/base/base-ui-component'; @@ -57,11 +57,10 @@ export class MeetingSettingDirective extends BaseUiComponent { private _elseTemplate: TemplateRef | null = null; private _elseViewRef: EmbeddedViewRef | null = null; - public constructor( - template: TemplateRef, - private viewContainer: ViewContainerRef, - private meetingSettingsService: MeetingSettingsService - ) { + private viewContainer = inject(ViewContainerRef); + private meetingSettingsService = inject(MeetingSettingsService); + + public constructor(template: TemplateRef) { super(); this._thenTemplate = template; } diff --git a/client/src/app/ui/directives/paper/paper.directive.ts b/client/src/app/ui/directives/paper/paper.directive.ts index d128b73da2..df4b9d1223 100644 --- a/client/src/app/ui/directives/paper/paper.directive.ts +++ b/client/src/app/ui/directives/paper/paper.directive.ts @@ -1,4 +1,4 @@ -import { Directive, ElementRef, Input, OnInit } from '@angular/core'; +import { Directive, ElementRef, inject, Input, OnInit } from '@angular/core'; const DEPTH_0 = 0; const DEPTH_0_VALUE = `none`; @@ -38,7 +38,7 @@ export class PaperDirective implements OnInit { [DEPTH_5]: DEPTH_5_VALUE }; - public constructor(private container: ElementRef) {} + private container = inject(ElementRef); public ngOnInit(): void { if (!this.depthMap[this.osPaper]) { diff --git a/client/src/app/ui/directives/resized/resized.directive.ts b/client/src/app/ui/directives/resized/resized.directive.ts index 099ab7d724..781ae8a8bb 100644 --- a/client/src/app/ui/directives/resized/resized.directive.ts +++ b/client/src/app/ui/directives/resized/resized.directive.ts @@ -1,4 +1,4 @@ -import { Directive, ElementRef, EventEmitter, OnInit, Output } from '@angular/core'; +import { Directive, ElementRef, EventEmitter, inject, OnInit, Output } from '@angular/core'; export interface ElementSize { height: number; @@ -26,7 +26,7 @@ export class ResizedDirective implements OnInit { */ private oldHeight: number | null = null; - public constructor(private element: ElementRef) {} + private element = inject(ElementRef); /** * Inits the ResizeSensor. triggers initial size change. diff --git a/client/src/app/ui/directives/trim-on-paste/trim-on-paste.directive.ts b/client/src/app/ui/directives/trim-on-paste/trim-on-paste.directive.ts index 9c3ba564b3..0b88d0b54f 100644 --- a/client/src/app/ui/directives/trim-on-paste/trim-on-paste.directive.ts +++ b/client/src/app/ui/directives/trim-on-paste/trim-on-paste.directive.ts @@ -1,11 +1,11 @@ -import { Directive, ElementRef, HostListener } from '@angular/core'; +import { Directive, ElementRef, HostListener, inject } from '@angular/core'; @Directive({ selector: `[osTrimOnPaste]`, standalone: false }) export class TrimOnPasteDirective { - public constructor(private el: ElementRef) {} + private el = inject(ElementRef); @HostListener(`paste`, [`$event`]) public onPaste(event: ClipboardEvent): void { const clipboardData = event.clipboardData; diff --git a/client/src/app/ui/modules/chip/chip.component.ts b/client/src/app/ui/modules/chip/chip.component.ts index 18f32ff43c..baa33a43eb 100644 --- a/client/src/app/ui/modules/chip/chip.component.ts +++ b/client/src/app/ui/modules/chip/chip.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, ElementRef, Input } from '@angular/core'; +import { Component, ElementRef, inject, Input } from '@angular/core'; import { MatChipsModule } from '@angular/material/chips'; import { MatRippleModule } from '@angular/material/core'; import { HtmlColor } from 'src/app/domain/definitions/key-types'; @@ -31,12 +31,10 @@ export class ChipComponent { private _color: Color | null = null; private _threshold = `0.5`; - private root = this.hostElement.nativeElement; + private hostElement = inject(ElementRef); + private colorService = inject(ColorService); - public constructor( - private hostElement: ElementRef, - private colorService: ColorService - ) {} + private root = this.hostElement.nativeElement; private recalcForegroundColor(): void { this.root.style.setProperty(`--os-chip-red`, this._color!.red); diff --git a/client/src/app/ui/modules/choice-dialog/services/choice.service.ts b/client/src/app/ui/modules/choice-dialog/services/choice.service.ts index 2595c10045..8bbda6feec 100644 --- a/client/src/app/ui/modules/choice-dialog/services/choice.service.ts +++ b/client/src/app/ui/modules/choice-dialog/services/choice.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { firstValueFrom, Observable } from 'rxjs'; import { Selectable } from 'src/app/domain/interfaces'; @@ -9,7 +9,7 @@ import { ChoiceAnswer, ChoiceDialogConfig } from '../definitions'; @Injectable({ providedIn: `root` }) export class ChoiceService { - public constructor(private dialog: MatDialog) {} + private dialog = inject(MatDialog); /** * Opens the dialog. Returns the chosen value after the user accepts. diff --git a/client/src/app/ui/modules/comma-separated-listing/comma-separated-listing.component.ts b/client/src/app/ui/modules/comma-separated-listing/comma-separated-listing.component.ts index d6e836d563..0ebb659f93 100644 --- a/client/src/app/ui/modules/comma-separated-listing/comma-separated-listing.component.ts +++ b/client/src/app/ui/modules/comma-separated-listing/comma-separated-listing.component.ts @@ -33,6 +33,4 @@ export class CommaSeparatedListingComponent { public get shortenedList(): T[] { return this.ellipsed ? this.list.slice(0, this.showElementsAmount) : this.list; } - - public constructor() {} } diff --git a/client/src/app/ui/modules/datepicker/components/datepicker/datepicker.component.scss b/client/src/app/ui/modules/datepicker/components/datepicker/datepicker.component.scss index 2ebe6aaeb1..cc2821bc9a 100644 --- a/client/src/app/ui/modules/datepicker/components/datepicker/datepicker.component.scss +++ b/client/src/app/ui/modules/datepicker/components/datepicker/datepicker.component.scss @@ -20,10 +20,6 @@ margin-bottom: 10px; } } - - .ngx-material-timepicker-toggle { - width: 28px; - } } } } diff --git a/client/src/app/ui/modules/datepicker/components/daterangepicker/daterangepicker.component.scss b/client/src/app/ui/modules/datepicker/components/daterangepicker/daterangepicker.component.scss index 2ebe6aaeb1..cc2821bc9a 100644 --- a/client/src/app/ui/modules/datepicker/components/daterangepicker/daterangepicker.component.scss +++ b/client/src/app/ui/modules/datepicker/components/daterangepicker/daterangepicker.component.scss @@ -20,10 +20,6 @@ margin-bottom: 10px; } } - - .ngx-material-timepicker-toggle { - width: 28px; - } } } } diff --git a/client/src/app/ui/modules/editor/components/editor-image-dialog/editor-image-dialog.component.ts b/client/src/app/ui/modules/editor/components/editor-image-dialog/editor-image-dialog.component.ts index 8490af4e2e..f58c16d7cb 100644 --- a/client/src/app/ui/modules/editor/components/editor-image-dialog/editor-image-dialog.component.ts +++ b/client/src/app/ui/modules/editor/components/editor-image-dialog/editor-image-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject } from '@angular/core'; +import { Component, Inject, inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; interface EditorImageData { @@ -25,10 +25,9 @@ export interface EditorImageDialogOutput { export class EditorImageDialogComponent { public image: EditorImageData; - public constructor( - @Inject(MAT_DIALOG_DATA) public data: EditorImageDialogInput, - private dialogRef: MatDialogRef - ) { + private dialogRef = inject(MatDialogRef); + + public constructor(@Inject(MAT_DIALOG_DATA) public data: EditorImageDialogInput) { this.image = data.image; } diff --git a/client/src/app/ui/modules/editor/components/editor/editor.component.ts b/client/src/app/ui/modules/editor/components/editor/editor.component.ts index 4179744f01..0a3187fa70 100644 --- a/client/src/app/ui/modules/editor/components/editor/editor.component.ts +++ b/client/src/app/ui/modules/editor/components/editor/editor.component.ts @@ -165,10 +165,6 @@ export class EditorComponent extends BaseFormControlComponent implements private domParser = new DOMParser(); - public constructor() { - super(); - } - public ngAfterViewInit(): void { const editorConfig = { element: this.editorEl.nativeElement, diff --git a/client/src/app/ui/modules/editor/directives/tab-navigation.directive.ts b/client/src/app/ui/modules/editor/directives/tab-navigation.directive.ts index d932c3078a..3a3d130c3b 100644 --- a/client/src/app/ui/modules/editor/directives/tab-navigation.directive.ts +++ b/client/src/app/ui/modules/editor/directives/tab-navigation.directive.ts @@ -1,4 +1,4 @@ -import { Directive, ElementRef, HostListener, Input, QueryList } from '@angular/core'; +import { Directive, ElementRef, HostListener, inject, Input, QueryList } from '@angular/core'; @Directive({ selector: `[osArrowNavigation]`, @@ -126,7 +126,7 @@ export class ArrowNavigationDirective { firstButton.nativeElement.tabIndex = 0; } - public constructor(private button: ElementRef) {} + private button = inject(ElementRef); private findButtonIndex(): number { return this.buttonList diff --git a/client/src/app/ui/modules/expandable-content-wrapper/expandable-content-wrapper.component.ts b/client/src/app/ui/modules/expandable-content-wrapper/expandable-content-wrapper.component.ts index eb5c7934e9..c4657bae7a 100644 --- a/client/src/app/ui/modules/expandable-content-wrapper/expandable-content-wrapper.component.ts +++ b/client/src/app/ui/modules/expandable-content-wrapper/expandable-content-wrapper.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input } from '@angular/core'; import { OpenSlidesTranslationModule } from 'src/app/site/modules/translations'; import { DirectivesModule } from '../../directives'; @@ -72,7 +72,9 @@ export class ExpandableContentWrapperComponent { public _biggerHeight = false; - public constructor(private cd: ChangeDetectorRef) { + private cd = inject(ChangeDetectorRef); + + public constructor() { this.isCollapsed = false; this.update(); } diff --git a/client/src/app/ui/modules/file-list/file-list.component.ts b/client/src/app/ui/modules/file-list/file-list.component.ts index a4dc96af7f..6481d4d541 100644 --- a/client/src/app/ui/modules/file-list/file-list.component.ts +++ b/client/src/app/ui/modules/file-list/file-list.component.ts @@ -4,6 +4,7 @@ import { ChangeDetectorRef, Component, EventEmitter, + inject, Input, OnDestroy, OnInit, @@ -238,17 +239,17 @@ export class FileListComponent extends BaseUiComponent implements OnInit, OnDest private readonly _directoryBehaviorSubject = new BehaviorSubject([]); - public constructor( - private dialog: MatDialog, - private cd: ChangeDetectorRef, - private fb: UntypedFormBuilder, - private translate: TranslateService, - private repo: MediafileControllerService, - private activeMeeting: ActiveMeetingService, - private promptService: PromptService - ) { + private activeMeeting = inject(ActiveMeetingService); + private cd = inject(ChangeDetectorRef); + private dialog = inject(MatDialog); + private fb = inject(UntypedFormBuilder); + private promptService = inject(PromptService); + private repo = inject(MediafileControllerService); + private translate = inject(TranslateService); + + public constructor() { super(); - this.moveForm = fb.group({ directory_id: [] }); + this.moveForm = this.fb.group({ directory_id: [] }); this.subscriptions.push( this.moveForm.get(`directory_id`).valueChanges.subscribe(id => { this.movingToPublicFolder = id && this.repo.getViewModel(id).isPublishedOrganizationWide; diff --git a/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts b/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts index 490d898771..f7ae2d95d2 100644 --- a/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts +++ b/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts @@ -3,6 +3,7 @@ import { AfterViewInit, Component, EventEmitter, + inject, Input, OnInit, Output, @@ -206,18 +207,13 @@ export class HeadBarComponent implements OnInit, AfterViewInit { private _isSavingSubject = new BehaviorSubject(false); private _isSaveButtonEnabled = true; - /** - * Empty constructor - */ - public constructor( - public vp: ViewPortService, - private menu: MainMenuService, - private router: Router, - private route: ActivatedRoute, - private routingState: RoutingStateService, - private headbarService: GlobalHeadbarService, - private _viewContainerRef: ViewContainerRef - ) {} + public vp = inject(ViewPortService); + private headbarService = inject(GlobalHeadbarService); + private menu = inject(MainMenuService); + private route = inject(ActivatedRoute); + private router = inject(Router); + private routingState = inject(RoutingStateService); + private _viewContainerRef = inject(ViewContainerRef); /** * Detect if the cancel edit event was used diff --git a/client/src/app/ui/modules/head-bar/services/routing-state.service.ts b/client/src/app/ui/modules/head-bar/services/routing-state.service.ts index cde686bf4e..7132d33f01 100644 --- a/client/src/app/ui/modules/head-bar/services/routing-state.service.ts +++ b/client/src/app/ui/modules/head-bar/services/routing-state.service.ts @@ -1,5 +1,5 @@ import { Location } from '@angular/common'; -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { Router, RoutesRecognized } from '@angular/router'; import { filter, pairwise, startWith } from 'rxjs'; @@ -48,6 +48,9 @@ export class RoutingStateService { return this._customOrigin; } + private router = inject(Router); + private location = inject(Location); + private _currentUrl: string = this.location.path(); /** @@ -55,10 +58,7 @@ export class RoutingStateService { * * @param router Angular Router */ - public constructor( - private router: Router, - private location: Location - ) { + public constructor() { this.router.events .pipe( filter(e => e instanceof RoutesRecognized), @@ -67,9 +67,10 @@ export class RoutingStateService { ) .subscribe((event: any[]) => { this.skipUnsafeRouteCheck = - router.currentNavigation()?.extras?.state && router.currentNavigation()?.extras?.state[`canGoBack`]; + this.router.currentNavigation()?.extras?.state && + this.router.currentNavigation()?.extras?.state[`canGoBack`]; this._previousUrl = event[0]?.urlAfterRedirects ?? this._currentUrl; - const currentNavigationExtras = router.currentNavigation()?.extras; + const currentNavigationExtras = this.router.currentNavigation()?.extras; if (currentNavigationExtras && currentNavigationExtras.state && currentNavigationExtras.state[`back`]) { this._customOrigin = this._previousUrl; } else if ( diff --git a/client/src/app/ui/modules/head-toolbar/head-toolbar.component.ts b/client/src/app/ui/modules/head-toolbar/head-toolbar.component.ts index 236ed00de8..dc22c53fe7 100644 --- a/client/src/app/ui/modules/head-toolbar/head-toolbar.component.ts +++ b/client/src/app/ui/modules/head-toolbar/head-toolbar.component.ts @@ -2,6 +2,7 @@ import { TemplatePortal } from '@angular/cdk/portal'; import { AfterViewInit, Component, + inject, OnDestroy, TemplateRef, ViewChild, @@ -28,13 +29,8 @@ import { GlobalHeadbarService } from 'src/app/site/modules/global-headbar/global export class HeadToolbarComponent implements AfterViewInit, OnDestroy { @ViewChild(`toolbarContent`) public toolbarContent: TemplateRef; - /** - * Empty constructor - */ - public constructor( - private headbarService: GlobalHeadbarService, - private _viewContainerRef: ViewContainerRef - ) {} + private headbarService = inject(GlobalHeadbarService); + private _viewContainerRef = inject(ViewContainerRef); public ngAfterViewInit(): void { this.headbarService.additionalHeadbar = new TemplatePortal(this.toolbarContent, this._viewContainerRef); diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index db28ee2110..8aed83e056 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -5,6 +5,7 @@ import { ContentChildren, ElementRef, EventEmitter, + inject, Input, OnInit, Output, @@ -248,11 +249,12 @@ export class BackendImportListComponent implements OnInit { protected uploadButton: boolean; protected selectedNewFile; + private dialog = inject(MatDialog); + private translate = inject(TranslateService); + public constructor( - private dialog: MatDialog, - private translate: TranslateService, - private router: Router, - private cd: ChangeDetectorRef + private cd: ChangeDetectorRef, + private router: Router ) {} /** diff --git a/client/src/app/ui/modules/import-list/directives/import-list-first-tab.directive.ts b/client/src/app/ui/modules/import-list/directives/import-list-first-tab.directive.ts index 5bfcb8ae08..476ef29f41 100644 --- a/client/src/app/ui/modules/import-list/directives/import-list-first-tab.directive.ts +++ b/client/src/app/ui/modules/import-list/directives/import-list-first-tab.directive.ts @@ -1,4 +1,4 @@ -import { Directive, Input, TemplateRef } from '@angular/core'; +import { Directive, inject, Input, TemplateRef } from '@angular/core'; @Directive({ selector: `[osImportListFirstTab]`, @@ -8,5 +8,5 @@ export class ImportListFirstTabDirective { @Input() public label: string; - public constructor(public templateRef: TemplateRef) {} + public templateRef = inject(TemplateRef); } diff --git a/client/src/app/ui/modules/import-list/directives/import-list-last-tab.directive.ts b/client/src/app/ui/modules/import-list/directives/import-list-last-tab.directive.ts index c2a49e49aa..ed74b8a83a 100644 --- a/client/src/app/ui/modules/import-list/directives/import-list-last-tab.directive.ts +++ b/client/src/app/ui/modules/import-list/directives/import-list-last-tab.directive.ts @@ -1,4 +1,4 @@ -import { Directive, Input, TemplateRef } from '@angular/core'; +import { Directive, inject, Input, TemplateRef } from '@angular/core'; @Directive({ selector: `[osImportListLastTab]`, @@ -8,5 +8,5 @@ export class ImportListLastTabDirective { @Input() public label: string; - public constructor(public templateRef: TemplateRef) {} + public templateRef = inject(TemplateRef); } diff --git a/client/src/app/ui/modules/list/components/filter-menu/filter-menu.component.ts b/client/src/app/ui/modules/list/components/filter-menu/filter-menu.component.ts index a53bdfcd9b..1f8304cfca 100644 --- a/client/src/app/ui/modules/list/components/filter-menu/filter-menu.component.ts +++ b/client/src/app/ui/modules/list/components/filter-menu/filter-menu.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, EventEmitter, + inject, Input, OnInit, Output, @@ -58,7 +59,7 @@ export class FilterMenuComponent implements OnInit { @Input() public showSpacer = false; - public constructor(private cd: ChangeDetectorRef) {} + private cd = inject(ChangeDetectorRef); /** * Directly closes again if no sorting is available diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts index 72ced2db83..9baa3aab50 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts @@ -4,6 +4,7 @@ import { ElementRef, EventEmitter, HostListener, + inject, Input, OnDestroy, OnInit, @@ -239,11 +240,9 @@ export class SortFilterBarComponent implements OnDestroy private mobileSubscription: Subscription | null; - public constructor( - protected translate: TranslateService, - public vp: ViewPortService, - private bottomSheet: MatBottomSheet - ) {} + public vp = inject(ViewPortService); + protected translate = inject(TranslateService); + private bottomSheet = inject(MatBottomSheet); public ngOnInit(): void { this.mobileSubscription = this.vp.isMobileSubject.subscribe(v => { diff --git a/client/src/app/ui/modules/media-upload-content/media-upload-content.component.ts b/client/src/app/ui/modules/media-upload-content/media-upload-content.component.ts index b21a6a4b3e..2acfe9a862 100644 --- a/client/src/app/ui/modules/media-upload-content/media-upload-content.component.ts +++ b/client/src/app/ui/modules/media-upload-content/media-upload-content.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, inject, Input, OnInit, Output } from '@angular/core'; import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; @@ -80,9 +80,7 @@ export class MediaUploadContentComponent extends BaseUiComponent implements OnIn public directorySelectionForm: UntypedFormGroup; - public constructor(private formBuilder: UntypedFormBuilder) { - super(); - } + private formBuilder = inject(UntypedFormBuilder); /** * Init diff --git a/client/src/app/ui/modules/meeting-time/meeting-time.component.ts b/client/src/app/ui/modules/meeting-time/meeting-time.component.ts index e44b417003..48413eeed0 100644 --- a/client/src/app/ui/modules/meeting-time/meeting-time.component.ts +++ b/client/src/app/ui/modules/meeting-time/meeting-time.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, Input } from '@angular/core'; +import { Component, inject, Input } from '@angular/core'; import { ViewMeeting } from 'src/app/site/pages/meetings/view-models/view-meeting'; import { TimeZoneService } from 'src/app/site/services/time-zone.service'; @@ -12,7 +12,7 @@ import { PipesModule } from '../../pipes'; imports: [CommonModule, PipesModule] }) export class MeetingTimeComponent { - public constructor(public timeZoneService: TimeZoneService) {} + public timeZoneService = inject(TimeZoneService); @Input() public meeting?: ViewMeeting; diff --git a/client/src/app/ui/modules/openslides-date-adapter/openslides-date-adapter.ts b/client/src/app/ui/modules/openslides-date-adapter/openslides-date-adapter.ts index b620ac12f3..19339f489e 100644 --- a/client/src/app/ui/modules/openslides-date-adapter/openslides-date-adapter.ts +++ b/client/src/app/ui/modules/openslides-date-adapter/openslides-date-adapter.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Optional } from '@angular/core'; +import { Inject, inject, Injectable, Optional } from '@angular/core'; import { MAT_DATE_LOCALE } from '@angular/material/core'; import { DateFnsAdapter } from '@angular/material-date-fns-adapter'; import { TranslateService } from '@ngx-translate/core'; @@ -10,16 +10,15 @@ import { langToTimeLocale } from 'src/app/infrastructure/utils'; */ @Injectable() export class OpenSlidesDateAdapter extends DateFnsAdapter { - public constructor( - protected translate: TranslateService, - @Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string - ) { + private translate = inject(TranslateService); + + public constructor(@Optional() @Inject(MAT_DATE_LOCALE) dateLocale: string) { super(dateLocale); // subscribe to language changes to change localisation of dates accordingly // DateAdapter seems not to be a singleton so we do that in this subclass instead of app.component - this.updateLocaleByName(translate.getCurrentLang()); - translate.onLangChange.subscribe(() => { - this.updateLocaleByName(translate.getCurrentLang()); + this.updateLocaleByName(this.translate.getCurrentLang()); + this.translate.onLangChange.subscribe(() => { + this.updateLocaleByName(this.translate.getCurrentLang()); }); } diff --git a/client/src/app/ui/modules/prompt-checkbox/prompt-checkbox.component.ts b/client/src/app/ui/modules/prompt-checkbox/prompt-checkbox.component.ts index eefafe2068..4949dc89e0 100644 --- a/client/src/app/ui/modules/prompt-checkbox/prompt-checkbox.component.ts +++ b/client/src/app/ui/modules/prompt-checkbox/prompt-checkbox.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, EventEmitter, inject, Input, Output } from '@angular/core'; import { MAT_CHECKBOX_DEFAULT_OPTIONS, MatCheckboxDefaultOptions, MatCheckboxModule } from '@angular/material/checkbox'; import { PromptService } from '../prompt-dialog'; @@ -34,7 +34,7 @@ export class PromptCheckboxComponent { */ @Output() public selected = new EventEmitter(); - public constructor(public prompt: PromptService) {} + public prompt = inject(PromptService); public async openPrompt(): Promise { if (!this.disabled) { diff --git a/client/src/app/ui/modules/prompt-dialog/services/prompt.service.ts b/client/src/app/ui/modules/prompt-dialog/services/prompt.service.ts index 9a8c6ba826..2de141347f 100644 --- a/client/src/app/ui/modules/prompt-dialog/services/prompt.service.ts +++ b/client/src/app/ui/modules/prompt-dialog/services/prompt.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { firstValueFrom } from 'rxjs'; @@ -11,10 +11,8 @@ import { PromptDialogComponent } from '../prompt-dialog.component'; export class PromptService { private dialogRef: MatDialogRef | null = null; - public constructor( - private dialog: MatDialog, - private translate: TranslateService - ) {} + private dialog = inject(MatDialog); + private translate = inject(TranslateService); /** * Opens the dialog. Returns true, if the user accepts. diff --git a/client/src/app/ui/modules/qr-code/qr-code.component.ts b/client/src/app/ui/modules/qr-code/qr-code.component.ts index 96259795b5..3c83f90f70 100644 --- a/client/src/app/ui/modules/qr-code/qr-code.component.ts +++ b/client/src/app/ui/modules/qr-code/qr-code.component.ts @@ -21,8 +21,6 @@ export class QrCodeComponent implements AfterViewInit { private _textSubject = new BehaviorSubject(``); - public constructor() {} - public ngAfterViewInit(): void { this._textSubject.pipe(distinctUntilChanged()).subscribe(text => this.createCanvas(text)); } diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts index 233b19acdc..788bd6332c 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts @@ -1,6 +1,6 @@ import { TemplatePortal } from '@angular/cdk/portal'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; -import { AfterViewInit, Component, ElementRef, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, inject, Input, OnInit, Output, ViewChild } from '@angular/core'; import { ChangeDetectorRef, EventEmitter, HostListener, OnDestroy } from '@angular/core'; import { BehaviorSubject, map, Observable } from 'rxjs'; import { Identifiable } from 'src/app/domain/interfaces'; @@ -145,12 +145,8 @@ export class ScrollingTableComponent>> private _oldDistTop = 0; - public constructor( - private manageService: ScrollingTableManageService, - private cd: ChangeDetectorRef - ) { - super(); - } + private manageService = inject(ScrollingTableManageService); + private cd = inject(ChangeDetectorRef); public ngOnInit(): void { this.manageService.currentScrollingTableComponent = this; diff --git a/client/src/app/ui/modules/scrolling-table/directives/scrolling-table-no-data.directive.ts b/client/src/app/ui/modules/scrolling-table/directives/scrolling-table-no-data.directive.ts index 98510a806c..e5c5b3125b 100644 --- a/client/src/app/ui/modules/scrolling-table/directives/scrolling-table-no-data.directive.ts +++ b/client/src/app/ui/modules/scrolling-table/directives/scrolling-table-no-data.directive.ts @@ -1,5 +1,5 @@ import { CdkPortal } from '@angular/cdk/portal'; -import { Directive, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; +import { Directive, inject, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; import { ScrollingTableManageService } from '../services'; @@ -8,11 +8,9 @@ import { ScrollingTableManageService } from '../services'; standalone: false }) export class ScrollingTableNoDataDirective extends CdkPortal implements OnInit { - public constructor( - template: TemplateRef, - viewContainer: ViewContainerRef, - private manageService: ScrollingTableManageService - ) { + private manageService = inject(ScrollingTableManageService); + + public constructor(template: TemplateRef, viewContainer: ViewContainerRef) { super(template, viewContainer); } diff --git a/client/src/app/ui/modules/search-selector/components/list-search-selector/list-search-selector.component.ts b/client/src/app/ui/modules/search-selector/components/list-search-selector/list-search-selector.component.ts index 776463671e..05a007f002 100644 --- a/client/src/app/ui/modules/search-selector/components/list-search-selector/list-search-selector.component.ts +++ b/client/src/app/ui/modules/search-selector/components/list-search-selector/list-search-selector.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, Optional, Self, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, Optional, Self, signal, ViewEncapsulation } from '@angular/core'; import { NgControl } from '@angular/forms'; import { MatFormFieldControl } from '@angular/material/form-field'; import { auditTime, distinctUntilChanged, Observable, Subscription } from 'rxjs'; @@ -18,6 +18,8 @@ import { BaseSearchSelectorComponent } from '../base-search-selector/base-search export class ListSearchSelectorComponent extends BaseSearchSelectorComponent { private _inputListValuesSubscription: Subscription; + private isListEmpty = signal(true); + /** * The inputlist subject. Subscribes to it and updates the selector, if the subject * changes its values. @@ -35,10 +37,7 @@ export class ListSearchSelectorComponent extends BaseSearchSelectorComponent { } else { this._inputListValuesSubscription = value.pipe(auditTime(10), distinctUntilChanged()).subscribe(items => { this.selectableItems = items; - if (this.contentForm) { - this.disabled = - (this.disabled || !items || (!!items && !items.length)) && !this.clickNotFound.observed; - } + this.isListEmpty.set(!items || (!!items && !items.length)); }); this.subscriptions.push(this._inputListValuesSubscription); } @@ -49,4 +48,13 @@ export class ListSearchSelectorComponent extends BaseSearchSelectorComponent { public constructor(@Optional() @Self() ngControl: NgControl) { super(ngControl); } + + public override get disabled(): boolean { + return super.disabled || (this.isListEmpty() && !this.clickNotFound.observed); + } + + @Input() + public override set disabled(value: boolean) { + super.disabled = value; + } } diff --git a/client/src/app/ui/modules/search-selector/components/repo-search-selector/repo-search-selector.component.ts b/client/src/app/ui/modules/search-selector/components/repo-search-selector/repo-search-selector.component.ts index 36fa62e1c4..d7794bb788 100644 --- a/client/src/app/ui/modules/search-selector/components/repo-search-selector/repo-search-selector.component.ts +++ b/client/src/app/ui/modules/search-selector/components/repo-search-selector/repo-search-selector.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnDestroy, OnInit, Optional, Self, ViewEncapsulation } from '@angular/core'; +import { Component, inject, Input, OnDestroy, OnInit, Optional, Self, ViewEncapsulation } from '@angular/core'; import { NgControl } from '@angular/forms'; import { MatFormFieldControl } from '@angular/material/form-field'; import { map, OperatorFunction } from 'rxjs'; @@ -45,11 +45,10 @@ export class RepoSearchSelectorComponent extends BaseSearchSelectorComponent imp private subscriptionName: string; - public constructor( - @Optional() @Self() ngControl: NgControl, - private meetingSettingsService: MeetingSettingsService, - private modelRequestService: ModelRequestService - ) { + private meetingSettingsService = inject(MeetingSettingsService); + private modelRequestService = inject(ModelRequestService); + + public constructor(@Optional() @Self() ngControl: NgControl) { super(ngControl); this.shouldPropagateOnRegistering = false; } diff --git a/client/src/app/ui/modules/sidenav/components/copyright-sign/copyright-sign.component.ts b/client/src/app/ui/modules/sidenav/components/copyright-sign/copyright-sign.component.ts index a0993296ee..89f32849b7 100644 --- a/client/src/app/ui/modules/sidenav/components/copyright-sign/copyright-sign.component.ts +++ b/client/src/app/ui/modules/sidenav/components/copyright-sign/copyright-sign.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { EasterEggContentPlatformService } from '../../modules/easter-egg/services/easter-egg-content-platform.service'; @@ -12,7 +12,7 @@ export class CopyrightSignComponent { private clickTimeout: number | null = null; private clickCounter = 0; - public constructor(private dialog: EasterEggContentPlatformService) {} + private dialog = inject(EasterEggContentPlatformService); public launchC4(event: Event): void { event.stopPropagation(); diff --git a/client/src/app/ui/modules/sidenav/components/image/image.component.ts b/client/src/app/ui/modules/sidenav/components/image/image.component.ts index 0763808d78..a5d5ae91b0 100644 --- a/client/src/app/ui/modules/sidenav/components/image/image.component.ts +++ b/client/src/app/ui/modules/sidenav/components/image/image.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, inject, Input } from '@angular/core'; import { HttpService } from 'src/app/gateways/http.service'; @Component({ @@ -39,7 +39,7 @@ export class ImageComponent { private _source: string | null = null; - public constructor(private http: HttpService) {} // Maybe the wrong place here! + private http = inject(HttpService); private onChange(): void { this.loaded = false; diff --git a/client/src/app/ui/modules/sidenav/components/logo/logo.component.ts b/client/src/app/ui/modules/sidenav/components/logo/logo.component.ts index 82a3e638c4..d814c5af87 100644 --- a/client/src/app/ui/modules/sidenav/components/logo/logo.component.ts +++ b/client/src/app/ui/modules/sidenav/components/logo/logo.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, inject, Input, OnInit } from '@angular/core'; import { MediaManageService } from 'src/app/site/pages/meetings/services/media-manage.service'; import { ThemeService } from 'src/app/site/services/theme.service'; import { BaseUiComponent } from 'src/app/ui/base/base-ui-component'; @@ -38,12 +38,8 @@ export class LogoComponent extends BaseUiComponent implements OnInit { private _path: string | null = null; private _isDarkMode = false; - public constructor( - private themeService: ThemeService, - private mediaManageService: MediaManageService - ) { - super(); - } + private themeService = inject(ThemeService); + private mediaManageService = inject(MediaManageService); public ngOnInit(): void { this.subscriptions.push( diff --git a/client/src/app/ui/modules/sidenav/components/sidenav/sidenav.component.ts b/client/src/app/ui/modules/sidenav/components/sidenav/sidenav.component.ts index 3d50d8d171..f4f27e2994 100644 --- a/client/src/app/ui/modules/sidenav/components/sidenav/sidenav.component.ts +++ b/client/src/app/ui/modules/sidenav/components/sidenav/sidenav.component.ts @@ -1,4 +1,4 @@ -import { Component, ContentChild, Input, TemplateRef, ViewChild } from '@angular/core'; +import { Component, ContentChild, inject, Input, TemplateRef, ViewChild } from '@angular/core'; import { MatSidenav } from '@angular/material/sidenav'; import { ViewPortService } from 'src/app/site/services/view-port.service'; @@ -34,7 +34,7 @@ export class SidenavComponent { return this.isMobile ? (this.sideNav?.opened ?? false) : this.desktopOpen; } - public constructor(private vp: ViewPortService) {} + private vp = inject(ViewPortService); public close(): void { if (this.isMobile) { diff --git a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service.ts b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service.ts index 4a4400926e..c6440a2aef 100644 --- a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service.ts +++ b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { TranslateService } from '@ngx-translate/core'; import { NotifyResponse, NotifyService } from 'src/app/gateways/notify.service'; @@ -11,12 +11,10 @@ import { ChessDialogModule } from '../chess-dialog.module'; providedIn: 'root' }) export class ChessChallengeService { - public constructor( - private notifyService: NotifyService, - private dialog: MatDialog, - private translate: TranslateService, - private prompt: PromptService - ) {} + private notifyService = inject(NotifyService); + private dialog = inject(MatDialog); + private translate = inject(TranslateService); + private prompt = inject(PromptService); public startListening(): void { this.notifyService diff --git a/client/src/app/ui/modules/sorting/modules/sorting-tree/components/sorting-tree/sorting-tree.component.ts b/client/src/app/ui/modules/sorting/modules/sorting-tree/components/sorting-tree/sorting-tree.component.ts index c343358b7f..6a5216165f 100644 --- a/client/src/app/ui/modules/sorting/modules/sorting-tree/components/sorting-tree/sorting-tree.component.ts +++ b/client/src/app/ui/modules/sorting/modules/sorting-tree/components/sorting-tree/sorting-tree.component.ts @@ -1,7 +1,7 @@ import { ArrayDataSource } from '@angular/cdk/collections'; import { CdkDragMove, CdkDragSortEvent, CdkDragStart } from '@angular/cdk/drag-drop'; import { FlatTreeControl } from '@angular/cdk/tree'; -import { Component, ContentChild, EventEmitter, Input, OnDestroy, Output, TemplateRef } from '@angular/core'; +import { Component, ContentChild, EventEmitter, inject, Input, OnDestroy, Output, TemplateRef } from '@angular/core'; import { auditTime, Observable, Subscription } from 'rxjs'; import { Displayable, Identifiable } from 'src/app/domain/interfaces'; import { FlatNode, TreeIdNode } from 'src/app/infrastructure/definitions/tree'; @@ -223,16 +223,8 @@ export class SortingTreeComponent implemen @ContentChild(TemplateRef, { static: true }) public innerNode!: TemplateRef; - /** - * Constructor - * - * @param treeService Service to get data from store and build the tree nodes. - * @param sortService Service to sort tree nodes by their given items. - */ - public constructor( - private treeService: TreeService, - private sortService: TreeSortService - ) {} + private treeService = inject(TreeService); + private sortService = inject(TreeSortService); /** * On destroy - unsubscribe the subscription diff --git a/client/src/app/ui/pipes/entries/entries.pipe.ts b/client/src/app/ui/pipes/entries/entries.pipe.ts index 6aeaa2b4a0..2e2bdbe785 100644 --- a/client/src/app/ui/pipes/entries/entries.pipe.ts +++ b/client/src/app/ui/pipes/entries/entries.pipe.ts @@ -1,5 +1,5 @@ import { KeyValue } from '@angular/common'; -import { KeyValueDiffer, KeyValueDiffers, Pipe, PipeTransform } from '@angular/core'; +import { inject, KeyValueDiffer, KeyValueDiffers, Pipe, PipeTransform } from '@angular/core'; interface IterableMap { [Symbol.iterator](): IterableIterator<[K, V]>; @@ -14,7 +14,7 @@ export class EntriesPipe implements PipeTransform { private keyValues: KeyValue[] = []; private compareFn: (a: KeyValue, b: KeyValue) => number = DefaultCompareFn; - public constructor(private readonly differs: KeyValueDiffers) {} + private readonly differs = inject(KeyValueDiffers); public transform( instance: IterableMap | any, diff --git a/client/src/app/ui/pipes/time/time.pipe.ts b/client/src/app/ui/pipes/time/time.pipe.ts index 88c3b0dcf1..68be3613fd 100644 --- a/client/src/app/ui/pipes/time/time.pipe.ts +++ b/client/src/app/ui/pipes/time/time.pipe.ts @@ -1,4 +1,4 @@ -import { Pipe, PipeTransform } from '@angular/core'; +import { inject, Pipe, PipeTransform } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; @Pipe({ @@ -7,7 +7,7 @@ import { TranslateService } from '@ngx-translate/core'; standalone: false }) export class TimePipe implements PipeTransform { - public constructor(private translate: TranslateService) {} + private translate = inject(TranslateService); public transform(timestamp: number): string { return new Date(timestamp * 1000).toLocaleString(this.translate.getCurrentLang()); diff --git a/client/src/app/ui/pipes/trust/trust.pipe.ts b/client/src/app/ui/pipes/trust/trust.pipe.ts index 7ebee3a6e1..579c46e5c6 100644 --- a/client/src/app/ui/pipes/trust/trust.pipe.ts +++ b/client/src/app/ui/pipes/trust/trust.pipe.ts @@ -1,4 +1,4 @@ -import { Pipe, PipeTransform } from '@angular/core'; +import { inject, Pipe, PipeTransform } from '@angular/core'; import { DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl } from '@angular/platform-browser'; @Pipe({ @@ -6,7 +6,7 @@ import { DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl standalone: false }) export class TrustPipe implements PipeTransform { - public constructor(protected sanitizer: DomSanitizer) {} + protected sanitizer = inject(DomSanitizer); public transform(value: any, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl { switch (type) { From b30df3f1b0dbbc984ab1d98b624b21cdf63fb523 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Tue, 14 Jul 2026 17:06:51 +0200 Subject: [PATCH 30/35] Added icons to updated rows. Changed titles. Fixed minor UI issues --- ...icipant-import-list-preview.component.html | 16 +- ...icipant-import-list-preview.component.scss | 4 + ...rticipant-import-list-preview.component.ts | 179 ++++++++++++------ .../view-models/view-participant-import.ts | 36 +++- .../definitions/backend-import-preview.ts | 2 +- 5 files changed, 170 insertions(+), 67 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 3d29d96115..510c7c8f2c 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -65,8 +65,8 @@

Import participants

  @if (getActionIconRow(item)) { - - {{ getEntryIcon(item.value) }} + + {{ getEntryIcon(item) }} }
@@ -77,6 +77,9 @@

Import participants

@if (getHeader(def).type === 'number') { {{ item.value }} } + @if (getHeader(def).type === 'decimal') { + {{ this.getShortenedDecimal(entry[def].value) }} + } @if (getHeader(def).type === 'date') { {{ item.value }} } @@ -89,7 +92,7 @@

Import participants

} @if (!getHeader(def).is_list && entry[def] !== undefined) { @if (getHeader(def).is_object) { - @let icon = getEntryIcon(entry[def]); + @let icon = getActionIconEntry(entry[def]);
@if (getHeader(def).type === 'string') { {{ entry[def].value }} @@ -97,6 +100,9 @@

Import participants

@if (getHeader(def).type === 'number') { {{ entry[def].value }} } + @if (getHeader(def).type === 'decimal') { + {{ this.getShortenedDecimal(entry[def].value) }} + } @if (getHeader(def).type === 'date') { {{ entry[def].value }} } @@ -105,7 +111,7 @@

Import participants

}   - @if (getActionIconEntry(entry[def])) { + @if (getEntryIcon(entry[def]) || checkChanges(row, column.property)) { Import participants
}
Action
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 842d7d61fd..53e16e4c70 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -22,6 +22,10 @@ $os-yellow: rgb(255, 193, 7); font-weight: 500; } +.lower-margin-top { + margin-top: -20px; +} + .footer { padding-left: 35px; position: sticky; diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 232dbc93ee..31de6b19e6 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -23,8 +23,10 @@ import { MatTooltip } from '@angular/material/tooltip'; import { Router } from '@angular/router'; import { _, TranslateService } from '@ngx-translate/core'; import { map, Observable, of, Subscription } from 'rxjs'; +import { StructureLevel } from 'src/app/domain/models/structure-levels'; import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; import { ActiveMeetingIdService } from 'src/app/site/pages/meetings/services/active-meeting-id.service'; +import { ViewUser } from 'src/app/site/pages/meetings/view-models/view-user'; import { AccountControllerService } from 'src/app/site/pages/organization/pages/accounts/services/common/account-controller.service'; import { HeadBarModule } from 'src/app/ui/modules/head-bar'; import { ImportListHeaderDefinition } from 'src/app/ui/modules/import-list'; @@ -49,6 +51,7 @@ import { } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-position'; import { ParticipantControllerService } from '../../../../services/common/participant-controller.service'; +import { ViewStructureLevel } from '../../../structure-levels/view-models'; import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; import { ParticipantImportFilterService } from '../../services/participant-import-filter.service'; import { ParticipantImportPreviewSearchService } from '../../services/participant-import-search.service'; @@ -451,46 +454,21 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy * @return the icon for the item */ protected getActionIconEntry(item: BackendImportEntryObject): string { - console.log('ITEM INFO INSIDE ICONENTRY', item[`info`], typeof item, item, item.info, item.value); - switch (typeof item) { - case 'string': - switch (item['info']) { - // for lists - case BackendImportState.Error: // no import possible - return `error_outline`; - case BackendImportState.Warning: - return `warning`; - case BackendImportState.New: - return `add_circle_outline`; - case BackendImportState.Done: - return `autorenew`; - case BackendImportState.Generated: - return `merge`; - case BackendImportState.Remove: - return `remove`; - default: - return `mood`; // fallback: Error - } - case 'object': - switch (item.info) { - // for objects - case BackendImportState.Error: // no import possible - return `error_outline`; - case BackendImportState.Warning: - return `warning`; - case BackendImportState.New: - return `add_circle_outline`; - case BackendImportState.Done: - return `autorenew`; - case BackendImportState.Generated: - return `merge`; - case BackendImportState.Remove: - return `remove`; - default: - return `mood_bad`; // fallback: Error - } + switch (item.info) { + case BackendImportState.Error: // no import possible + return `error_outline`; + case BackendImportState.Warning: + return `warning`; + case BackendImportState.New: + return `add_circle_outline`; + case BackendImportState.Done: + return 'autorenew'; + case BackendImportState.Generated: + return `merge`; + case BackendImportState.Remove: + return `remove`; default: - return 'rowing'; // fallback: Error + return `mood_bad`; // fallback: Error } } @@ -651,8 +629,8 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } public getShortenedDecimal(decimalString: string): string { - while (decimalString.length && [`0`, `.`].includes(decimalString.charAt(decimalString.length - 1))) { - decimalString = decimalString.substring(0, decimalString.length - 1); + while (decimalString?.length && [`0`, `.`].includes(decimalString?.charAt(decimalString?.length - 1))) { + decimalString = decimalString?.substring(0, decimalString?.length - 1); } return decimalString; } @@ -704,16 +682,14 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy const countReferenced = this._rows.filter(row => row?.state === BackendImportState.Referenced)?.length | 0; const countUnchanged = this._rows.filter(row => row?.state === BackendImportState.Unchanged)?.length | 0; const countUpdated = - this._summary.find(item => item?.name === 'updated')?.value - countReferenced - countUnchanged; + (this._summary.find(item => item?.name === 'updated')?.value - countReferenced - countUnchanged) | 0; const error = this._summary.find(item => item.name === 'error'); this._summary = this._summary.filter(item => item.name !== 'updated'); if (countReferenced > 0) { this._summary.push({ name: 'referenced', value: countReferenced }); } if (countUpdated > 0) { - if (countUpdated - countUnchanged !== 0) { - this._summary.push({ name: 'updated', value: countUpdated }); - } + this._summary.push({ name: 'updated', value: countUpdated }); } this._summary = this._summary.filter(item => item.name !== 'error'); this._summary.push({ name: error?.name, value: error?.value }); @@ -813,17 +789,114 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy if (item.state !== BackendImportState.Done) { return false; } - const itemValues = Object.values(item); - const someNew = itemValues.some(value => - Array.isArray(value) ? value.some(val => val?.info === 'new') : value?.info === 'new' - ); - const allNew = itemValues.every(value => - Array.isArray(value) ? value.every(val => val?.info === 'new') : value?.info === 'new' - ); - if (!someNew && !allNew) { + if (this.checkChanges(item) === false) { item.setState = BackendImportState.Unchanged; return true; } return false; } + + protected checkChanges(item: ViewImportedParticipant, headerName?: string): false | '' | 'autorenew' { + for (const user of this.userAccounts) { + if ( + (user.meeting_ids.includes(item.meeting_id) && item.username && item.username === user.username) || + (item.member_number && item.member_number === user.member_number) || + (item.saml_id && item.saml_id === user.saml_id) + ) { + const updatedUser = user.getModel(); + const changes: Partial> = {}; + for (const key of Object.keys(updatedUser) as (keyof ViewUser)[]) { + if (key in item) { + const importedValue = item[key as keyof ViewImportedParticipant]; + if (key === 'id') { + continue; + } + if (updatedUser[key] !== importedValue) { + changes[key] = { + old: updatedUser[key], + new: importedValue + }; + } + } + if (item.is_locked_out !== user.is_locked_out) { + changes['locked_out'] = { + old: user.is_locked_out, + new: item.is_locked_out + }; + } + if (item.is_present !== user.isPresentInMeeting()) { + changes['is_present'] = { + old: user.isPresentInMeeting(), + new: item.is_present + }; + } + if (item.saml_id !== user.saml_id) { + changes['saml_id'] = { + old: user.saml_id, + new: item.saml_id + }; + } + if (item.number !== user.number()) { + changes['number'] = { + old: user.number(), + new: item.number + }; + } + if (item.comment !== user.comment(this.activeMeetingIdService.meetingId)) { + changes['comment'] = { + old: user.comment(), + new: item.comment + }; + } + if (item.gender !== user.gender_name) { + changes['gender'] = { + old: user.gender_name, + new: item.gender + }; + } + if ( + this.getShortenedDecimal(item.voteWeight) !== + this.getShortenedDecimal(user.voteWeight.toString()) + ) { + changes['vote_weight'] = { + old: user.voteWeight, + new: item.voteWeight + }; + } + if (item.external !== user.external) { + changes['external'] = { + old: user.external, + new: item.external + }; + } + const structureLevels = this.createStructureLevelModels( + user.structure_levels(this.activeMeetingIdService.meetingId) + ); + const userSLevels = new Set(structureLevels); + const userSLevelsIds = new Set(structureLevels.map(level => level.id)); + const itemSLevels = new Set(item.getStructureLevels); + console.log('LEVELS', itemSLevels, userSLevels); + itemSLevels.forEach(itemSLevel => { + if (itemSLevel['id'] && !userSLevelsIds.has(itemSLevel['id'])) { + changes['structure_level'] = { + old: userSLevels['value'], + new: itemSLevel['value'] + }; + } + }); + } + if (Object.keys(changes).includes(headerName)) { + return 'autorenew'; + } + if (Object.keys(changes).length === 0) { + return false; + } + } + } + return ''; + } + + private createStructureLevelModels(structureLevels: ViewStructureLevel[]): StructureLevel[] { + return structureLevels.map(structureLevel => structureLevel.getModel()); + } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index cb96715976..d441d17c22 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -1,4 +1,5 @@ import { _ } from '@ngx-translate/core'; +import { Decimal } from 'src/app/domain/definitions/key-types'; import { Identifiable } from 'src/app/domain/interfaces'; import { BackendImportEntry, @@ -30,19 +31,20 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / public email: string; public member_number: string; public number: string; - public vote_weight; + public vote_weight: Decimal; public gender: string; public pronoun: string; public username: string; public default_password: string; public saml_id: string; public home_committee: string; - public external_comment: string; + public comment: string; public title: string; - public structure_level: string; + public structure_levels: string[]; public groups: string[]; + public external: boolean; public is_active: boolean; public is_present: boolean; public is_locked_out: boolean; @@ -61,7 +63,7 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / this.last_name = this.setValue(this.data?.['last_name']) as string; this.email = this.setValue(this.data?.['email']) as string; this.member_number = this.setValue(this.data?.['member_number']) as string; - this.structure_level = this.data?.['structure_level'] as string; + this.structure_levels = this.data?.['structure_level'] as string[]; this.groups = this.data?.['groups']?.[0]; this.number = this.setValue(this.data?.['number']) as string; this.vote_weight = this.setValue(this.data?.['vote_weight']) as string; @@ -74,10 +76,11 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / this.is_physical_person = this.getBooleanValue(this.setValue(this.data?.['is_physical_person']) as boolean); this.is_present = this.getBooleanValue(this.setValue(this.data?.['is_present']) as boolean); this.is_locked_out = this.getBooleanValue(this.setValue(this.data?.['locked_out']) as boolean); + this.external = this.getBooleanValue(this.setValue(this.data?.['external'] as boolean)); this.saml_id = this.setValue(this.data?.['saml_id']) as string; this.home_committee = this.setValue(this.data?.['home_committee']) as string; - this.external_comment = this.setValue(this.data?.['external_comment']) as string; + this.comment = this.setValue(this.data?.['comment']) as string; } public static readonly REQUESTABLE_FIELDS: (keyof ViewImportedParticipant)[] = [ @@ -93,9 +96,10 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / 'default_password', 'saml_id', 'home_committee', - 'external_comment', + 'comment', + 'external', 'title', - 'structure_level', + 'structure_levels', 'groups', 'is_active', 'is_present', @@ -122,12 +126,28 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / return field.value; } + protected getDecimalFields(): (keyof ViewImportedParticipant)[] { + return [`vote_weight`]; + } + + public get voteWeight(): Decimal { + return this.vote_weight; + } + + public get getGroups(): string[] { + return [this.groups['value']]; + } + + public get getStructureLevels(): string[] { + return this.structure_levels; + } + public getBooleanValue(value: string | number | boolean | undefined): boolean { return !!value; } public get changedVoteWeight(): boolean { - return this.vote_weight < 1 || this.vote_weight > 1 || !this.vote_weight ? true : false; + return !this.vote_weight ? true : false; } public get hasMemberNumber(): boolean { diff --git a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts index dc8c69479f..72a52f1b1b 100644 --- a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts +++ b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts @@ -15,7 +15,7 @@ export enum BackendImportState { export interface BackendImportHeader { property: string; // field name/column title - type: `boolean` | `number` | `string` | `date`; // date must be in format yyyy-mm-dd + type: `boolean` | `number` | `string` | `decimal` | `date`; // date must be in format yyyy-mm-dd is_list: boolean; // optional, if not given defaults to `false` is_object: boolean; // optional, if not given defaults to `false` is_hidden: boolean; // optional, if not given defaults to `false` From b6186e33e0fe3b6842b42cf038ef128fed89060c Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Wed, 15 Jul 2026 09:15:45 +0200 Subject: [PATCH 31/35] Solved conflicts --- ...rticipant-import-list-preview.component.ts | 43 ++++++++----------- .../participant-import-filter.service.ts | 6 +-- .../participant-import-search.service.ts | 2 +- .../view-models/view-participant-import.ts | 11 ++--- .../components/head-bar/head-bar.component.ts | 2 +- .../backend-import-list.component.ts | 8 +--- .../definitions/backend-import-preview.ts | 2 +- .../sort-filter-bar.component.ts | 8 +--- .../view-list/view-list.component.ts | 5 +-- 9 files changed, 32 insertions(+), 55 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 31de6b19e6..9f629629a6 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -23,32 +23,7 @@ import { MatTooltip } from '@angular/material/tooltip'; import { Router } from '@angular/router'; import { _, TranslateService } from '@ngx-translate/core'; import { map, Observable, of, Subscription } from 'rxjs'; -import { StructureLevel } from 'src/app/domain/models/structure-levels'; -import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; -import { ActiveMeetingIdService } from 'src/app/site/pages/meetings/services/active-meeting-id.service'; -import { ViewUser } from 'src/app/site/pages/meetings/view-models/view-user'; -import { AccountControllerService } from 'src/app/site/pages/organization/pages/accounts/services/common/account-controller.service'; -import { HeadBarModule } from 'src/app/ui/modules/head-bar'; -import { ImportListHeaderDefinition } from 'src/app/ui/modules/import-list'; -import { BackendImportPhase } from 'src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component'; -import { - BackendImportEntryObject, - BackendImportHeader, - BackendImportIdentifiedRow, - BackendImportPreview, - BackendImportState, - BackendImportSummary -} from 'src/app/ui/modules/import-list/definitions/backend-import-preview'; -import { ImportListFirstTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-first-tab.directive'; -import { ImportListLastTabDirective } from 'src/app/ui/modules/import-list/directives/import-list-last-tab.directive'; -import { ImportListStatusTemplateDirective } from 'src/app/ui/modules/import-list/directives/import-list-status-template.directive'; -import { ListModule } from 'src/app/ui/modules/list'; -import { ListSearchService } from 'src/app/ui/modules/list/services/list-search.service'; -import { ScrollingTableCellDefConfig } from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; -import { - END_POSITION, - START_POSITION -} from 'src/app/ui/modules/scrolling-table/directives/scrolling-table-cell-position'; + import { ParticipantControllerService } from '../../../../services/common/participant-controller.service'; import { ViewStructureLevel } from '../../../structure-levels/view-models'; @@ -56,6 +31,22 @@ import { ParticipantImportService } from '../../services/participant-import.serv import { ParticipantImportFilterService } from '../../services/participant-import-filter.service'; import { ParticipantImportPreviewSearchService } from '../../services/participant-import-search.service'; import { ViewImportedParticipant } from '../../view-models/view-participant-import'; +import { StructureLevel } from '@app/domain/models/structure-levels'; +import { ValueLabelCombination } from '@app/infrastructure/utils/import/import-utils'; +import { ActiveMeetingIdService } from '@app/site/pages/meetings/services/active-meeting-id.service'; +import { ViewUser } from '@app/site/pages/meetings/view-models/view-user'; +import { AccountControllerService } from '@app/site/pages/organization/pages/accounts/services/common/account-controller.service'; +import { HeadBarModule } from '@app/ui/modules/head-bar'; +import { ImportListHeaderDefinition } from '@app/ui/modules/import-list'; +import { BackendImportPhase } from '@app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component'; +import { BackendImportHeader, BackendImportSummary, BackendImportIdentifiedRow, BackendImportState, BackendImportEntryObject, BackendImportPreview } from '@app/ui/modules/import-list/definitions/backend-import-preview'; +import { ImportListFirstTabDirective } from '@app/ui/modules/import-list/directives/import-list-first-tab.directive'; +import { ImportListLastTabDirective } from '@app/ui/modules/import-list/directives/import-list-last-tab.directive'; +import { ImportListStatusTemplateDirective } from '@app/ui/modules/import-list/directives/import-list-status-template.directive'; +import { ListModule } from '@app/ui/modules/list'; +import { ListSearchService } from '@app/ui/modules/list/services/list-search.service'; +import { ScrollingTableCellDefConfig } from '@app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; +import { END_POSITION, START_POSITION } from '@app/ui/modules/scrolling-table/directives/scrolling-table-cell-position'; @Component({ selector: `os-participant-import-list-preview`, diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts index be1411030e..2b3b570e54 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts @@ -1,10 +1,10 @@ import { Injectable } from '@angular/core'; import { _ } from '@ngx-translate/core'; -import { GENDER_FITLERABLE, GENDERS } from 'src/app/domain/models/users/user'; -import { BaseFilterListService, OsFilter } from 'src/app/site/base/base-filter.service'; -import { ActiveFiltersService } from 'src/app/site/services/active-filters.service'; import { STATE_FITERABLE, STATES, ViewImportedParticipant } from '../view-models/view-participant-import'; +import { GENDER_FITLERABLE, GENDERS } from '@app/domain/models/users/user'; +import { BaseFilterListService, OsFilter } from '@app/site/base/base-filter.service'; +import { ActiveFiltersService } from '@app/site/services/active-filters.service'; @Injectable({ providedIn: 'root' diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-search.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-search.service.ts index 800ed87d2d..328a8a4077 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-search.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-search.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { ListSearchService } from 'src/app/ui/modules/list/services/list-search.service'; +import { ListSearchService } from '@app/ui/modules/list/services/list-search.service'; @Injectable({ providedIn: 'root' diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index d441d17c22..3355ee6f69 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -1,11 +1,8 @@ +import { Decimal } from '@app/domain/definitions/key-types'; +import { Identifiable } from '@app/domain/interfaces'; +import { BackendImportRow, BackendImportState, BackendImportEntry } from '@app/ui/modules/import-list/definitions/backend-import-preview'; import { _ } from '@ngx-translate/core'; -import { Decimal } from 'src/app/domain/definitions/key-types'; -import { Identifiable } from 'src/app/domain/interfaces'; -import { - BackendImportEntry, - BackendImportRow, - BackendImportState -} from 'src/app/ui/modules/import-list/definitions/backend-import-preview'; + export const STATES = [ _(`New participant`), diff --git a/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts b/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts index 59996f1082..91b1c230dc 100644 --- a/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts +++ b/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts @@ -73,7 +73,7 @@ export const HEAD_BAR_HEIGHT = 50; // height of the head-bar in px. templateUrl: `./head-bar.component.html`, styleUrls: [`./head-bar.component.scss`], encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.Eager, + // changeDetection: ChangeDetectionStrategy.Eager, standalone: false }) export class HeadBarComponent implements OnInit, AfterViewInit { diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index c099fa79f8..34821ef918 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -1,6 +1,6 @@ import { - ChangeDetectorRef, ChangeDetectionStrategy, + ChangeDetectorRef, Component, ContentChild, ContentChildren, @@ -25,10 +25,6 @@ import { BackendImportService } from '@app/ui/base/import-service'; import { _ } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core'; import { delay, firstValueFrom, map, Observable, of } from 'rxjs'; -import { infoDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; -import { ValueLabelCombination } from 'src/app/infrastructure/utils/import/import-utils'; -import { ViewImportedParticipant } from 'src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import'; -import { BackendImportService } from 'src/app/ui/base/import-service'; import { ScrollingTableCellDefConfig } from '../../../scrolling-table/directives/scrolling-table-cell-config'; import { END_POSITION, START_POSITION } from '../../../scrolling-table/directives/scrolling-table-cell-position'; @@ -44,6 +40,7 @@ import { import { ImportListFirstTabDirective } from '../../directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from '../../directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from '../../directives/import-list-status-template.directive'; +import { ViewImportedParticipant } from '@app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import'; export enum BackendImportPhase { LOADING_PREVIEW, @@ -59,7 +56,6 @@ export enum BackendImportPhase { templateUrl: `./backend-import-list.component.html`, styleUrls: [`./backend-import-list.component.scss`], encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.Eager, standalone: false }) export class BackendImportListComponent implements OnInit { diff --git a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts index 1dc728c139..b28ca7fa90 100644 --- a/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts +++ b/client/src/app/ui/modules/import-list/definitions/backend-import-preview.ts @@ -1,5 +1,5 @@ import { Identifiable } from '@app/domain/interfaces'; -import { ViewImportedParticipant } from 'src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import'; +import { ViewImportedParticipant } from '@app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import'; export enum BackendImportState { Error = `error`, diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts index ece797147c..1f55bffea8 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts @@ -22,17 +22,11 @@ import { FilterListService } from '@app/ui/modules/list/definitions/filter-servi import { OsSortOption, SortListService } from '@app/ui/modules/list/definitions/sort-service'; import { TranslateService } from '@ngx-translate/core'; import { Observable, Subscription } from 'rxjs'; -import { Identifiable } from 'src/app/domain/interfaces'; -import { OsFilterIndicator } from 'src/app/site/base/base-filter.service'; -import { OsSortingOption } from 'src/app/site/base/base-sort.service'; -import { ParticipantImportService } from 'src/app/site/pages/meetings/pages/participants/pages/participant-import/services'; -import { ViewPortService } from 'src/app/site/services/view-port.service'; -import { FilterListService } from 'src/app/ui/modules/list/definitions/filter-service'; -import { OsSortOption, SortListService } from 'src/app/ui/modules/list/definitions/sort-service'; import { RoundedInputComponent } from '../../../input/components/rounded-input/rounded-input.component'; import { SearchService } from '../../definitions/search-service'; import { SortBottomSheetComponent } from '../sort-bottom-sheet/sort-bottom-sheet.component'; +import { ParticipantImportService } from '@app/site/pages/meetings/pages/participants/pages/participant-import/services'; /** * Reusable bar for list views, offering sorting and filter options. diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts index b3abd8107d..3d24b525cd 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts @@ -13,13 +13,12 @@ import { import { Identifiable } from '@app/domain/interfaces'; import { ViewModelListProvider } from '@app/ui/base/view-model-list-provider'; import { BehaviorSubject, delay, find, map, Observable, of } from 'rxjs'; -import { Identifiable } from 'src/app/domain/interfaces'; -import { ParticipantImportService } from 'src/app/site/pages/meetings/pages/participants/pages/participant-import/services'; -import { ViewModelListProvider } from 'src/app/ui/base/view-model-list-provider'; + import { ScrollingTableComponent } from '../../../scrolling-table/components/scrolling-table/scrolling-table.component'; import { FilterListService, SearchService, SortListService } from '../../definitions'; import { SortFilterBarComponent } from '../sort-filter-bar/sort-filter-bar.component'; +import { ParticipantImportService } from '@app/site/pages/meetings/pages/participants/pages/participant-import/services'; @Component({ selector: `os-view-list`, From d1b4db9bb3dfec482c17d78a669e95bc8262d3f8 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Wed, 15 Jul 2026 17:06:00 +0200 Subject: [PATCH 32/35] Fixed minor dependency errors --- ...icipant-import-list-preview.component.html | 7 +-- ...rticipant-import-list-preview.component.ts | 59 +++++++++---------- .../participant-import-filter.service.ts | 6 +- .../view-models/view-participant-import.ts | 7 ++- .../components/head-bar/head-bar.component.ts | 1 - .../backend-import-list.component.ts | 7 ++- .../sort-filter-bar.component.ts | 2 +- .../view-list/view-list.component.ts | 3 +- 8 files changed, 45 insertions(+), 47 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 510c7c8f2c..dfef4314cc 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -110,14 +110,13 @@

Import participants

}   - - @if (getEntryIcon(entry[def]) || checkChanges(row, column.property)) { + @if (getEntryIcon(entry[def])) { - {{ icon }} + {{ icon === 'merge' ? icon : checkChanges(row, column.property) }} }
@@ -128,7 +127,7 @@

Import participants

}
Action
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 9f629629a6..5d910d030d 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -21,17 +21,6 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { MatTabChangeEvent } from '@angular/material/tabs'; import { MatTooltip } from '@angular/material/tooltip'; import { Router } from '@angular/router'; -import { _, TranslateService } from '@ngx-translate/core'; -import { map, Observable, of, Subscription } from 'rxjs'; - - -import { ParticipantControllerService } from '../../../../services/common/participant-controller.service'; -import { ViewStructureLevel } from '../../../structure-levels/view-models'; -import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; -import { ParticipantImportFilterService } from '../../services/participant-import-filter.service'; -import { ParticipantImportPreviewSearchService } from '../../services/participant-import-search.service'; -import { ViewImportedParticipant } from '../../view-models/view-participant-import'; -import { StructureLevel } from '@app/domain/models/structure-levels'; import { ValueLabelCombination } from '@app/infrastructure/utils/import/import-utils'; import { ActiveMeetingIdService } from '@app/site/pages/meetings/services/active-meeting-id.service'; import { ViewUser } from '@app/site/pages/meetings/view-models/view-user'; @@ -39,7 +28,14 @@ import { AccountControllerService } from '@app/site/pages/organization/pages/acc import { HeadBarModule } from '@app/ui/modules/head-bar'; import { ImportListHeaderDefinition } from '@app/ui/modules/import-list'; import { BackendImportPhase } from '@app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component'; -import { BackendImportHeader, BackendImportSummary, BackendImportIdentifiedRow, BackendImportState, BackendImportEntryObject, BackendImportPreview } from '@app/ui/modules/import-list/definitions/backend-import-preview'; +import { + BackendImportEntryObject, + BackendImportHeader, + BackendImportIdentifiedRow, + BackendImportPreview, + BackendImportState, + BackendImportSummary +} from '@app/ui/modules/import-list/definitions/backend-import-preview'; import { ImportListFirstTabDirective } from '@app/ui/modules/import-list/directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from '@app/ui/modules/import-list/directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from '@app/ui/modules/import-list/directives/import-list-status-template.directive'; @@ -47,6 +43,14 @@ import { ListModule } from '@app/ui/modules/list'; import { ListSearchService } from '@app/ui/modules/list/services/list-search.service'; import { ScrollingTableCellDefConfig } from '@app/ui/modules/scrolling-table/directives/scrolling-table-cell-config'; import { END_POSITION, START_POSITION } from '@app/ui/modules/scrolling-table/directives/scrolling-table-cell-position'; +import { _, TranslateService } from '@ngx-translate/core'; +import { map, Observable, of, Subscription } from 'rxjs'; + +import { ParticipantControllerService } from '../../../../services/common/participant-controller.service'; +import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; +import { ParticipantImportFilterService } from '../../services/participant-import-filter.service'; +import { ParticipantImportPreviewSearchService } from '../../services/participant-import-search.service'; +import { ViewImportedParticipant } from '../../view-models/view-participant-import'; @Component({ selector: `os-participant-import-list-preview`, @@ -795,7 +799,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy (item.saml_id && item.saml_id === user.saml_id) ) { const updatedUser = user.getModel(); - const changes: Partial> = {}; + const changes: Partial> = {}; for (const key of Object.keys(updatedUser) as (keyof ViewUser)[]) { if (key in item) { const importedValue = item[key as keyof ViewImportedParticipant]; @@ -860,25 +864,22 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy new: item.external }; } - const structureLevels = this.createStructureLevelModels( - user.structure_levels(this.activeMeetingIdService.meetingId) + const structureLevels = user.structureLevels(this.activeMeetingIdService.meetingId).split(', '); + const itemSLevels = item.getStructureLevels.map(level => level['value']); + console.log( + 'LEVELS', + itemSLevels, + structureLevels, + item.structure_levels.difference(structureLevels) ); - const userSLevels = new Set(structureLevels); - const userSLevelsIds = new Set(structureLevels.map(level => level.id)); - const itemSLevels = new Set(item.getStructureLevels); - console.log('LEVELS', itemSLevels, userSLevels); - itemSLevels.forEach(itemSLevel => { - if (itemSLevel['id'] && !userSLevelsIds.has(itemSLevel['id'])) { - changes['structure_level'] = { - old: userSLevels['value'], - new: itemSLevel['value'] - }; - } - }); } if (Object.keys(changes).includes(headerName)) { return 'autorenew'; } + /* if (Object.keys(changes).includes('structure_levels')) { + console.log('autorenew'); + return 'autorenew'; + } */ if (Object.keys(changes).length === 0) { return false; } @@ -886,8 +887,4 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } return ''; } - - private createStructureLevelModels(structureLevels: ViewStructureLevel[]): StructureLevel[] { - return structureLevels.map(structureLevel => structureLevel.getModel()); - } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts index 2b3b570e54..727858d2e6 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/services/participant-import-filter.service.ts @@ -1,10 +1,10 @@ import { Injectable } from '@angular/core'; -import { _ } from '@ngx-translate/core'; - -import { STATE_FITERABLE, STATES, ViewImportedParticipant } from '../view-models/view-participant-import'; import { GENDER_FITLERABLE, GENDERS } from '@app/domain/models/users/user'; import { BaseFilterListService, OsFilter } from '@app/site/base/base-filter.service'; import { ActiveFiltersService } from '@app/site/services/active-filters.service'; +import { _ } from '@ngx-translate/core'; + +import { STATE_FITERABLE, STATES, ViewImportedParticipant } from '../view-models/view-participant-import'; @Injectable({ providedIn: 'root' diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index 3355ee6f69..5d92386fa7 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -1,9 +1,12 @@ import { Decimal } from '@app/domain/definitions/key-types'; import { Identifiable } from '@app/domain/interfaces'; -import { BackendImportRow, BackendImportState, BackendImportEntry } from '@app/ui/modules/import-list/definitions/backend-import-preview'; +import { + BackendImportEntry, + BackendImportRow, + BackendImportState +} from '@app/ui/modules/import-list/definitions/backend-import-preview'; import { _ } from '@ngx-translate/core'; - export const STATES = [ _(`New participant`), _(`Updated participant`), diff --git a/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts b/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts index 91b1c230dc..5f88731a9c 100644 --- a/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts +++ b/client/src/app/ui/modules/head-bar/components/head-bar/head-bar.component.ts @@ -1,7 +1,6 @@ import { TemplatePortal } from '@angular/cdk/portal'; import { AfterViewInit, - ChangeDetectionStrategy, Component, EventEmitter, inject, diff --git a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts index 34821ef918..9df2f0dc53 100644 --- a/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts +++ b/client/src/app/ui/modules/import-list/components/via-backend-import-list/backend-import-list.component.ts @@ -1,5 +1,4 @@ import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, @@ -21,6 +20,7 @@ import { MatTabChangeEvent } from '@angular/material/tabs'; import { Router } from '@angular/router'; import { infoDialogSettings } from '@app/infrastructure/utils/dialog-settings'; import { ValueLabelCombination } from '@app/infrastructure/utils/import/import-utils'; +import { ViewImportedParticipant } from '@app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import'; import { BackendImportService } from '@app/ui/base/import-service'; import { _ } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core'; @@ -40,7 +40,6 @@ import { import { ImportListFirstTabDirective } from '../../directives/import-list-first-tab.directive'; import { ImportListLastTabDirective } from '../../directives/import-list-last-tab.directive'; import { ImportListStatusTemplateDirective } from '../../directives/import-list-status-template.directive'; -import { ViewImportedParticipant } from '@app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import'; export enum BackendImportPhase { LOADING_PREVIEW, @@ -550,7 +549,9 @@ export class BackendImportListComponent implements OnInit { try { this._importer.onSelectFile(droppedFile); this.uploadButton = false; - } catch {} + } catch { + this.uploadButton = false; + } } public onChange(event: Event): void { diff --git a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts index 1f55bffea8..41d8a8667e 100644 --- a/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts +++ b/client/src/app/ui/modules/list/components/sort-filter-bar/sort-filter-bar.component.ts @@ -17,6 +17,7 @@ import { MatDrawer } from '@angular/material/sidenav'; import { Identifiable } from '@app/domain/interfaces'; import { OsFilterIndicator } from '@app/site/base/base-filter.service'; import { OsSortingOption } from '@app/site/base/base-sort.service'; +import { ParticipantImportService } from '@app/site/pages/meetings/pages/participants/pages/participant-import/services'; import { ViewPortService } from '@app/site/services/view-port.service'; import { FilterListService } from '@app/ui/modules/list/definitions/filter-service'; import { OsSortOption, SortListService } from '@app/ui/modules/list/definitions/sort-service'; @@ -26,7 +27,6 @@ import { Observable, Subscription } from 'rxjs'; import { RoundedInputComponent } from '../../../input/components/rounded-input/rounded-input.component'; import { SearchService } from '../../definitions/search-service'; import { SortBottomSheetComponent } from '../sort-bottom-sheet/sort-bottom-sheet.component'; -import { ParticipantImportService } from '@app/site/pages/meetings/pages/participants/pages/participant-import/services'; /** * Reusable bar for list views, offering sorting and filter options. diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts index 3d24b525cd..2f314eb02b 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts @@ -11,14 +11,13 @@ import { ViewEncapsulation } from '@angular/core'; import { Identifiable } from '@app/domain/interfaces'; +import { ParticipantImportService } from '@app/site/pages/meetings/pages/participants/pages/participant-import/services'; import { ViewModelListProvider } from '@app/ui/base/view-model-list-provider'; import { BehaviorSubject, delay, find, map, Observable, of } from 'rxjs'; - import { ScrollingTableComponent } from '../../../scrolling-table/components/scrolling-table/scrolling-table.component'; import { FilterListService, SearchService, SortListService } from '../../definitions'; import { SortFilterBarComponent } from '../sort-filter-bar/sort-filter-bar.component'; -import { ParticipantImportService } from '@app/site/pages/meetings/pages/participants/pages/participant-import/services'; @Component({ selector: `os-view-list`, From b012418dcfe04473144c274a8a4ff20c3d27c33e Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 17 Jul 2026 10:51:25 +0200 Subject: [PATCH 33/35] Fixed transparent header on hover. Increased 'Email' column width --- ...icipant-import-list-preview.component.html | 58 ++++++++++--------- ...icipant-import-list-preview.component.scss | 24 +++++++- ...rticipant-import-list-preview.component.ts | 15 +---- .../scrolling-table.component.html | 10 +++- .../scrolling-table.component.scss | 2 +- 5 files changed, 65 insertions(+), 44 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index dfef4314cc..000cfaaae4 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -49,7 +49,7 @@

Import participants

@if (getHeader(def).is_list && entry[def]) { @for (item of entry[def]; track item) { @if (getHeader(def).is_object) { -
+
@if (getHeader(def).type === 'string') { {{ item.value }} } @@ -93,32 +93,36 @@

Import participants

@if (!getHeader(def).is_list && entry[def] !== undefined) { @if (getHeader(def).is_object) { @let icon = getActionIconEntry(entry[def]); -
- @if (getHeader(def).type === 'string') { - {{ entry[def].value }} - } - @if (getHeader(def).type === 'number') { - {{ entry[def].value }} - } - @if (getHeader(def).type === 'decimal') { - {{ this.getShortenedDecimal(entry[def].value) }} - } - @if (getHeader(def).type === 'date') { - {{ entry[def].value }} - } - @if (getHeader(def).type === 'boolean') { - - } -   - @if (getEntryIcon(entry[def])) { - - {{ icon === 'merge' ? icon : checkChanges(row, column.property) }} - - } +
+
+ @if (getHeader(def).type === 'string') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'number') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'decimal') { + {{ this.getShortenedDecimal(entry[def].value) }} + } + @if (getHeader(def).type === 'date') { + {{ entry[def].value }} + } + @if (getHeader(def).type === 'boolean') { + + } +
+
+ @if (getEntryIcon(entry[def]) || checkChanges(row, column.property)) { + + {{ icon }} + + } +
} } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index 53e16e4c70..cd7130052a 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -23,7 +23,29 @@ $os-yellow: rgb(255, 193, 7); } .lower-margin-top { - margin-top: -20px; + margin-top: -4px; + padding-left: 2px; +} + +.list-field-entry { + display: inline-flex; + margin-right: 10px; +} + +.cell-wrapper { + display: flex; + align-items: center; + width: 100%; + max-width: 300px; + overflow: hidden; + + .email-cell { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } .footer { diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 5d910d030d..3fd6d11f99 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -791,7 +791,7 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy return false; } - protected checkChanges(item: ViewImportedParticipant, headerName?: string): false | '' | 'autorenew' { + protected checkChanges(item: ViewImportedParticipant, headerName?: string): false | string { for (const user of this.userAccounts) { if ( (user.meeting_ids.includes(item.meeting_id) && item.username && item.username === user.username) || @@ -864,22 +864,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy new: item.external }; } - const structureLevels = user.structureLevels(this.activeMeetingIdService.meetingId).split(', '); - const itemSLevels = item.getStructureLevels.map(level => level['value']); - console.log( - 'LEVELS', - itemSLevels, - structureLevels, - item.structure_levels.difference(structureLevels) - ); } + console.log(changes); if (Object.keys(changes).includes(headerName)) { return 'autorenew'; } - /* if (Object.keys(changes).includes('structure_levels')) { - console.log('autorenew'); - return 'autorenew'; - } */ if (Object.keys(changes).length === 0) { return false; } diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html index 061b2fb1d3..8d232d4529 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html @@ -41,7 +41,10 @@ [ngStyle]="{ flexBasis: definition.width || defaultColumnWidth, width: definition.width || defaultColumnWidth, - minWidth: definition.minWidth || defaultColumnWidth, + minWidth: + definition.property === 'email' && horizontalScroll + ? '300px' + : definition.minWidth || defaultColumnWidth, maxWidth: definition.maxWidth || defaultColumnWidth }" > @@ -87,7 +90,10 @@ [ngStyle]="{ flexBasis: definition.width || defaultColumnWidth, width: definition.width || defaultColumnWidth, - minWidth: definition.minWidth || defaultColumnWidth, + minWidth: + definition.property === 'email' && horizontalScroll + ? '300px' + : definition.minWidth || defaultColumnWidth, maxWidth: definition.maxWidth || defaultColumnWidth }" > diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss index b99a37f562..a43541ca05 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss @@ -2,7 +2,7 @@ box-sizing: border-box; &:hover { - background-color: rgba(0, 0, 0, 0.025); + background-color: var(--mat-app-background-color); } .scrolling-table-cell { From 46cbeba501bbba4f6ceaeca85e95a0ced87fd7e5 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Fri, 17 Jul 2026 14:39:40 +0200 Subject: [PATCH 34/35] Fixed footer stepping on filter mat-card --- ...icipant-import-list-preview.component.html | 5 ++-- ...icipant-import-list-preview.component.scss | 14 ----------- ...rticipant-import-list-preview.component.ts | 1 - .../view-list/view-list.component.html | 1 + .../view-list/view-list.component.ts | 13 ++++++++++ .../scrolling-table.component.html | 16 ++++++++++++ .../scrolling-table.component.scss | 25 +++++++++++++++++++ .../scrolling-table.component.ts | 25 +++++++++++++++++++ .../scrolling-table/scrolling-table.module.ts | 4 ++- 9 files changed, 86 insertions(+), 18 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html index 000cfaaae4..28c680e3f8 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.html @@ -19,6 +19,7 @@

Import participants

[listObservable]="dataSource" [searchFieldInput]="searchFieldInput" [searchService]="searchService" + [shortenedSummary]="shortenSummary(summary)" [showFilterBar]="showFilterBar" [showHeader]="showHeader" [totalCount]="_totalCountObservable | async" @@ -158,7 +159,7 @@

Import participants

} -
+
diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss index cd7130052a..869097fe61 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.scss @@ -48,20 +48,6 @@ $os-yellow: rgb(255, 193, 7); } } -.footer { - padding-left: 35px; - position: sticky; - bottom: 0; - left: 0; - right: 0; - height: 50px; - background-color: var(--mat-app-background-color); - z-index: 1; - border-top: 1px solid rgba(0, 0, 0, 0.12); - display: flex; - align-items: center; -} - .static-sort-filter-bar { margin: 0; height: 100%; diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index 3fd6d11f99..d577b9e1f3 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -865,7 +865,6 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy }; } } - console.log(changes); if (Object.keys(changes).includes(headerName)) { return 'autorenew'; } diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.html b/client/src/app/ui/modules/list/components/view-list/view-list.component.html index 9b2c1ffc4a..06f230d3fe 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.html +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.html @@ -33,6 +33,7 @@ [horizontalScroll]="horizontalScroll" [rowHeight]="vScrollFixed" [selectionMode]="multiSelect" + [shortenedSummary]="shortenedSummary" [showHeader]="showHeader" [tableHeight]="fullScreen ? undefined : '50vh'" (selectionChanged)="selectedRowsChange.emit($event.selectedRows)" diff --git a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts index 2f314eb02b..bb2d572ecb 100644 --- a/client/src/app/ui/modules/list/components/view-list/view-list.component.ts +++ b/client/src/app/ui/modules/list/components/view-list/view-list.component.ts @@ -13,6 +13,7 @@ import { import { Identifiable } from '@app/domain/interfaces'; import { ParticipantImportService } from '@app/site/pages/meetings/pages/participants/pages/participant-import/services'; import { ViewModelListProvider } from '@app/ui/base/view-model-list-provider'; +import { BackendImportSummary } from '@app/ui/modules/import-list/definitions/backend-import-preview'; import { BehaviorSubject, delay, find, map, Observable, of } from 'rxjs'; import { ScrollingTableComponent } from '../../../scrolling-table/components/scrolling-table/scrolling-table.component'; @@ -177,6 +178,18 @@ export class ViewListComponent implements OnInit, OnDest } } + /** + * Summary adapted to the footer. Displays only "created", "updated", "referenced" and "error" columns. + */ + @Input() + public shortenedSummary: BackendImportSummary[]; + + /** + * Information to display the correct icons in footer + */ + @Input() + public GetSummaryInformation; + private _totalCountObservable: Observable = null; /** diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html index 8d232d4529..8374b48904 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.html @@ -64,6 +64,9 @@
+ @if (horizontalScroll) { + + }
} @else { @@ -106,4 +109,17 @@ } + + +
+ @let cols = shortenedSummary; + @for (col of cols; track col; let i = $index) { + @let colInfo = getSummaryInformation(col.name); + + {{ colInfo[0] }} + +
 {{ col.value }} {{ col.name }}   
+ } +
+
} diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss index a43541ca05..df966100dd 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.scss @@ -1,3 +1,6 @@ +$os-green: rgb(15, 228, 15); +$os-yellow: rgb(255, 193, 7); + @mixin scrolling-table-style() { box-sizing: border-box; @@ -95,6 +98,28 @@ width: 100%; } +.footer { + padding-left: 35px; + position: sticky; + bottom: 0; + left: 0; + right: 0; + height: 50px; + background-color: var(--mat-app-background-color); + z-index: 2; + border-top: 1px solid rgba(0, 0, 0, 0.12); + display: flex; + align-items: center; +} + +.os-green { + color: $os-green; +} + +.os-yellow { + color: $os-yellow; +} + .table-body { width: max-content; min-width: 100%; diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts index b42e7ec910..e39c054645 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts @@ -16,6 +16,7 @@ import { Identifiable } from '@app/domain/interfaces'; import { Mapable, Mutable } from '@app/infrastructure/utils'; import { KeyCode } from '@app/infrastructure/utils/key-code'; import { BaseUiComponent } from '@app/ui/base/base-ui-component'; +import { BackendImportSummary } from '@app/ui/modules/import-list/definitions/backend-import-preview'; import { BehaviorSubject, map, Observable } from 'rxjs'; import { SCROLLING_TABLE } from '../../definitions/index'; @@ -117,6 +118,12 @@ export class ScrollingTableComponent>> @Input() public addBottomSpacer = false; + /** + * Summary adapted to the footer. Displays only "created", "updated", "referenced" and "error" columns. + */ + @Input() + public shortenedSummary: BackendImportSummary[]; + @Output() public selectionChanged = new EventEmitter>(); @@ -317,4 +324,22 @@ export class ScrollingTableComponent>> this.cd.markForCheck(); this.scrollViewport?.checkViewportSize(); } + + /** + * Information to display the correct icons on footer when importing participants + */ + protected getSummaryInformation(item: string): string[] { + console.log(item); + return ( + { + total: ['group', 'accent'], + error: ['error_outline', 'red-warning-text'], + warning: ['warning', 'warn'], + created: ['add_circle_outline', 'os-green'], + updated: ['autorenew', 'os-yellow'], + referenced: ['merge', 'accent'], + unchanged: [``, ``] + }[item] ?? ['', ''] + ); + } } diff --git a/client/src/app/ui/modules/scrolling-table/scrolling-table.module.ts b/client/src/app/ui/modules/scrolling-table/scrolling-table.module.ts index 0d597321e6..4648b26a93 100644 --- a/client/src/app/ui/modules/scrolling-table/scrolling-table.module.ts +++ b/client/src/app/ui/modules/scrolling-table/scrolling-table.module.ts @@ -4,6 +4,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatIcon } from '@angular/material/icon'; import { MatTableModule } from '@angular/material/table'; import { ScrollingTableComponent } from './components/scrolling-table/scrolling-table.component'; @@ -28,7 +29,8 @@ const DECLARATIONS = [ PortalModule, MatTableModule, MatCheckboxModule, - ScrollingTableServiceModule + ScrollingTableServiceModule, + MatIcon ], exports: DECLARATIONS }) From c320d078d232a7afb97fd94169ac5081c2b332c7 Mon Sep 17 00:00:00 2001 From: Javier Borrego Date: Mon, 20 Jul 2026 16:46:49 +0200 Subject: [PATCH 35/35] Structure level and Group icons displayed according to import state --- ...rticipant-import-list-preview.component.ts | 64 +++++++++++++++++-- .../view-models/view-participant-import.ts | 4 +- .../scrolling-table.component.ts | 1 - 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts index d577b9e1f3..3c8a978c7a 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/components/participant-import-list-preview/participant-import-list-preview.component.ts @@ -21,6 +21,7 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { MatTabChangeEvent } from '@angular/material/tabs'; import { MatTooltip } from '@angular/material/tooltip'; import { Router } from '@angular/router'; +import { Group } from '@app/domain/models/users/group'; import { ValueLabelCombination } from '@app/infrastructure/utils/import/import-utils'; import { ActiveMeetingIdService } from '@app/site/pages/meetings/services/active-meeting-id.service'; import { ViewUser } from '@app/site/pages/meetings/view-models/view-user'; @@ -46,6 +47,7 @@ import { END_POSITION, START_POSITION } from '@app/ui/modules/scrolling-table/di import { _, TranslateService } from '@ngx-translate/core'; import { map, Observable, of, Subscription } from 'rxjs'; +import { ViewGroup } from '../../../../modules'; import { ParticipantControllerService } from '../../../../services/common/participant-controller.service'; import { ParticipantImportService } from '../../services/participant-import.service/participant-import.service'; import { ParticipantImportFilterService } from '../../services/participant-import-filter.service'; @@ -463,7 +465,11 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy case BackendImportState.Remove: return `remove`; default: - return `mood_bad`; // fallback: Error + // ad hoc check for updated structure levels and groups + if ((item.info as string) === 'updated') { + return 'autorenew'; + } + return 'mood_bad'; } } @@ -800,6 +806,9 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy ) { const updatedUser = user.getModel(); const changes: Partial> = {}; + this.compareStructureLevel(item?.structure_levels, user?.structure_level().split(',')); + const userGroups: ViewGroup[] = user?.groups(this.activeMeetingIdService.meetingId) || []; + const changedGroups = this.compareGroups(item?.groups, userGroups); for (const key of Object.keys(updatedUser) as (keyof ViewUser)[]) { if (key in item) { const importedValue = item[key as keyof ViewImportedParticipant]; @@ -813,13 +822,13 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy }; } } - if (item.is_locked_out !== user.is_locked_out) { + if (item.is_locked_out !== user.is_locked_out && user.is_locked_out !== undefined) { changes['locked_out'] = { old: user.is_locked_out, new: item.is_locked_out }; } - if (item.is_present !== user.isPresentInMeeting()) { + if (item.is_present !== user.isPresentInMeeting() && user.isPresentInMeeting() !== undefined) { changes['is_present'] = { old: user.isPresentInMeeting(), new: item.is_present @@ -858,12 +867,18 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy new: item.voteWeight }; } - if (item.external !== user.external) { + if (item.external !== user.external && user.external !== undefined) { changes['external'] = { old: user.external, new: item.external }; } + if (changedGroups.new.length) { + changes['groups'] = { + old: changedGroups.old, + new: changedGroups.new + }; + } } if (Object.keys(changes).includes(headerName)) { return 'autorenew'; @@ -875,4 +890,45 @@ export class ParticipantImportListPreviewComponent implements OnInit, OnDestroy } return ''; } + + private compareStructureLevel( + newSL: string[], + oldSL: string[] + ): { + old: string[]; + new: string[]; + } { + // No need to check new items as they are calculated by the backend + const commonSL = newSL.filter(sl => oldSL.includes(sl['value'])).sort(); + const diffSL = newSL.filter(sl => !oldSL.includes(sl['value']) && sl['id']).sort(); + diffSL.filter(sl => { + sl['info'] = 'updated'; + }); + return { + old: oldSL, + new: commonSL.concat(diffSL) + }; + } + + private compareGroups( + newGroups: string[], + oldGroups: ViewGroup[] + ): { + old: string[]; + new: string[]; + } { + const oldGroupNames = oldGroups.map((g: Group) => g.name).sort(); + const addedGroups = newGroups + .filter(newgroup => !oldGroupNames.includes(newgroup['value'] as string) && newgroup['id']) + .sort(); + addedGroups.filter(g => { + if (g['info'] === 'done') { + g['info'] = 'updated'; + } + }); + return { + old: oldGroupNames, + new: addedGroups + }; + } } diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts index 5d92386fa7..8e2af5bea7 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-import/view-models/view-participant-import.ts @@ -64,7 +64,7 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / this.email = this.setValue(this.data?.['email']) as string; this.member_number = this.setValue(this.data?.['member_number']) as string; this.structure_levels = this.data?.['structure_level'] as string[]; - this.groups = this.data?.['groups']?.[0]; + this.groups = this.data?.['groups'] as string[]; this.number = this.setValue(this.data?.['number']) as string; this.vote_weight = this.setValue(this.data?.['vote_weight']) as string; this.gender = this.setValue(this.data?.['gender']) as string; @@ -179,7 +179,7 @@ export class ViewImportedParticipant implements Identifiable, BackendImportRow / } public get hasGroups(): boolean { - return this.groups?.length > 0 ? true : false; + return this.getGroups.length > 0 ? true : false; } public set setState(value: BackendImportState) { diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts index e39c054645..81f0bd22e0 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts @@ -329,7 +329,6 @@ export class ScrollingTableComponent>> * Information to display the correct icons on footer when importing participants */ protected getSummaryInformation(item: string): string[] { - console.log(item); return ( { total: ['group', 'accent'],