finalize migration (yay)
This commit is contained in:
@@ -7,7 +7,8 @@
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"generate": "oazapfts --optimistic --useEnumType ./src/app/generated-api/swagger.json ./src/app/generated-api/api.ts"
|
||||
"generate": "oazapfts --optimistic --useEnumType ./src/app/generated-api/swagger.json ./src/app/generated-api/api.ts",
|
||||
"dev": "pnpm -w run dev"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
@@ -29,7 +30,7 @@
|
||||
"@angular/core": "^20.3.15",
|
||||
"@angular/forms": "^20.3.15",
|
||||
"@angular/material": "20.2.11",
|
||||
"@angular/material-date-fns-adapter": "^21.0.2",
|
||||
"@angular/material-date-fns-adapter": "^20.2.14",
|
||||
"@angular/platform-browser": "^20.3.15",
|
||||
"@angular/router": "^20.3.15",
|
||||
"@date-fns/tz": "^1.4.1",
|
||||
@@ -57,4 +58,4 @@
|
||||
"oazapfts": "^6.3.1",
|
||||
"typescript": "~5.9.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
ApplicationConfig,
|
||||
isDevMode,
|
||||
LOCALE_ID,
|
||||
provideAppInitializer,
|
||||
provideBrowserGlobalErrorListeners,
|
||||
provideZoneChangeDetection,
|
||||
} from '@angular/core';
|
||||
@@ -33,9 +35,11 @@ import {
|
||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||
import { de } from 'date-fns/locale';
|
||||
import { routes } from './app.routes';
|
||||
import { initializeApi } from './util/api';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideAppInitializer(initializeApi),
|
||||
provideRouter(
|
||||
routes,
|
||||
withPreloading(PreloadAllModules),
|
||||
@@ -45,7 +49,7 @@ export const appConfig: ApplicationConfig = {
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideKeycloak({
|
||||
config: {
|
||||
url: true
|
||||
url: isDevMode()
|
||||
? 'http://localhost:8080/'
|
||||
: 'https://keycloak.toni714.de:8443/',
|
||||
realm: 'taekwondo',
|
||||
@@ -55,6 +59,9 @@ export const appConfig: ApplicationConfig = {
|
||||
onLoad: 'check-sso',
|
||||
silentCheckSsoRedirectUri:
|
||||
window.location.origin + '/silent-check-sso.html',
|
||||
pkceMethod: 'S256',
|
||||
flow: 'standard',
|
||||
locale: 'de',
|
||||
},
|
||||
features: [withAutoRefreshToken({ sessionTimeout: 600000 })],
|
||||
providers: [AutoRefreshTokenService, UserActivityService],
|
||||
@@ -63,7 +70,7 @@ export const appConfig: ApplicationConfig = {
|
||||
provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
|
||||
useValue: [
|
||||
createInterceptorCondition<IncludeBearerTokenCondition>({
|
||||
urlPattern: true
|
||||
urlPattern: isDevMode()
|
||||
? /^http:\/\/localhost:(3000|8080)\/.*$/
|
||||
: /^https:\/\/tkd-api\.toni714\.de\/.*$/,
|
||||
}),
|
||||
|
||||
+19
-11
@@ -14,9 +14,13 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import * as api from '../../generated-api/api';
|
||||
import { DatePipePipe } from '../../pipes/date-pipe.pipe.js';
|
||||
import { DatePipePipe, dateToLocal } from '../../pipes/date-pipe.pipe.js';
|
||||
import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js';
|
||||
|
||||
type DeletionForm = {
|
||||
kuendigungzum: FormControl<Date | null>;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-member-dialog',
|
||||
standalone: true,
|
||||
@@ -35,23 +39,27 @@ import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dia
|
||||
styleUrl: './delete-member-dialog.component.scss',
|
||||
})
|
||||
export class DeleteMemberDialogComponent {
|
||||
form: FormGroup;
|
||||
member: api.Member;
|
||||
form: FormGroup<DeletionForm>;
|
||||
member: api.MemberDto;
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: { member: api.Member },
|
||||
@Inject(MAT_DIALOG_DATA) public data: { member: api.MemberDto },
|
||||
private dialogRef: MatDialogRef<EditMemberDialogComponent>,
|
||||
) {
|
||||
this.member = data.member;
|
||||
this.form = new FormGroup({
|
||||
kuendigungzum: new FormControl('', [Validators.required]),
|
||||
this.form = new FormGroup<DeletionForm>({
|
||||
kuendigungzum: new FormControl(null, [Validators.required]),
|
||||
});
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await api.scheduleDeletion(
|
||||
this.member.id!,
|
||||
this.form.controls['kuendigungzum'].value,
|
||||
);
|
||||
this.dialogRef.close();
|
||||
const date = this.form.controls.kuendigungzum.value;
|
||||
if (date) {
|
||||
await api.scheduleDeletion(
|
||||
this.member.id!,
|
||||
dateToLocal(date).toISOString(),
|
||||
);
|
||||
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ import { MemberDtoHelper } from '../../util/member.js';
|
||||
export class EditMemberDialogComponent {
|
||||
form: FormGroup;
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data: { member: api.Member },
|
||||
@Inject(MAT_DIALOG_DATA) public data: { member: api.MemberDto },
|
||||
private memberDtoHelper: MemberDtoHelper,
|
||||
private dialogRef: MatDialogRef<EditMemberDialogComponent>,
|
||||
) {
|
||||
@@ -38,7 +38,7 @@ export class EditMemberDialogComponent {
|
||||
}
|
||||
|
||||
submit() {
|
||||
const member: api.Member = this.memberDtoHelper.memberFromForm(
|
||||
const member: api.MemberDto = this.memberDtoHelper.memberFromForm(
|
||||
this.form,
|
||||
);
|
||||
api.updateMember(member);
|
||||
|
||||
@@ -1,39 +1,43 @@
|
||||
<h1>Neues Mitglied anlegen</h1>
|
||||
<form [formGroup]="memberForm" (ngSubmit)="onSubmit()">
|
||||
<mat-form-field>
|
||||
<mat-label>Vorname</mat-label>
|
||||
<input matInput formControlName="vorname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Nachname</mat-label>
|
||||
<input matInput formControlName="nachname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geschlecht</mat-label>
|
||||
<input matInput formControlName="geschlecht" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geburtsdatum</mat-label>
|
||||
<input matInput [matDatepicker]="picker" formControlName="geburtsdatum" />
|
||||
<mat-hint>DD.MM.YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geburtsort</mat-label>
|
||||
<input matInput formControlName="geburtsort" />
|
||||
</mat-form-field>
|
||||
<mat-checkbox formControlName="uvertrag"
|
||||
>Unterrichtsvertrag Abgegeben?</mat-checkbox
|
||||
>
|
||||
<mat-checkbox formControlName="uvertrag">Verein Beigetreten?</mat-checkbox>
|
||||
<mat-form-field>
|
||||
<mat-label>Passnummer (falls vorhanden)</mat-label>
|
||||
<input matInput formControlName="passnummer" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Kontakt</mat-label>
|
||||
<input matInput formControlName="kontakt" />
|
||||
</mat-form-field>
|
||||
<button mat-flat-button color="primary" type="submit">Hinzufügen</button>
|
||||
<mat-form-field>
|
||||
<mat-label>Vorname</mat-label>
|
||||
<input matInput formControlName="vorname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Nachname</mat-label>
|
||||
<input matInput formControlName="nachname" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geschlecht</mat-label>
|
||||
<input matInput formControlName="geschlecht" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geburtsdatum</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="picker"
|
||||
formControlName="geburtsdatum" />
|
||||
<mat-hint>DD.MM.YYYY</mat-hint>
|
||||
<mat-datepicker-toggle
|
||||
matIconSuffix
|
||||
[for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Geburtsort</mat-label>
|
||||
<input matInput formControlName="geburtsort" />
|
||||
</mat-form-field>
|
||||
<mat-checkbox [formControl]="memberForm.controls.verein"
|
||||
>Verein Beigetreten?</mat-checkbox
|
||||
>
|
||||
<mat-form-field>
|
||||
<mat-label>Passnummer (falls vorhanden)</mat-label>
|
||||
<input matInput formControlName="passnummer" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Kontakt</mat-label>
|
||||
<input matInput formControlName="kontakt" />
|
||||
</mat-form-field>
|
||||
<button mat-flat-button color="primary" type="submit">Hinzufügen</button>
|
||||
</form>
|
||||
|
||||
@@ -34,7 +34,7 @@ export class NewMemberComponent {
|
||||
);
|
||||
|
||||
onSubmit() {
|
||||
const newMember: api.PickMemberExcludeKeyofMemberIdOrLoeschenam =
|
||||
const newMember: api.MemberCreateArgs =
|
||||
this.memberDtoHelper.newMemberFromForm(this.memberForm);
|
||||
|
||||
api.createMember(newMember)
|
||||
|
||||
@@ -1,125 +1,142 @@
|
||||
<h1>Mitglieder & Sportlerübersicht</h1>
|
||||
<button mat-raised-button color="accent" (click)="newMember()">
|
||||
Neues Mitglied
|
||||
Neues Mitglied
|
||||
</button>
|
||||
<div class="filter-container mat-elevation-z8">
|
||||
<div class="filter-header">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
(keyup)="applyFilter($event)"
|
||||
[value]="filter$ | async"
|
||||
placeholder="Filter"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="dataSource$ | async as dataSource">
|
||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="edit">
|
||||
<th mat-header-cell *matHeaderCellDef>Bearb.</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button
|
||||
mat-mini-fab-button
|
||||
(click)="editMember(element.id)"
|
||||
aria-label="Edit Member"
|
||||
<div class="filter-header">
|
||||
<mat-form-field>
|
||||
<label>Filter:</label>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="filterControl"
|
||||
placeholder="Filter" />
|
||||
</mat-form-field>
|
||||
<div class="status-filters">
|
||||
<mat-radio-group [formControl]="statusControl">
|
||||
<mat-radio-button [value]="MemberStatusFilter.ACTIVE"
|
||||
>Aktiv</mat-radio-button
|
||||
>
|
||||
<mat-radio-button [value]="MemberStatusFilter.INACTIVE"
|
||||
>Inaktiv</mat-radio-button
|
||||
>
|
||||
<mat-radio-button [value]="MemberStatusFilter.ALL"
|
||||
>Alle</mat-radio-button
|
||||
>
|
||||
</mat-radio-group>
|
||||
<mat-form-field
|
||||
matTooltip="Zeigt nur Mitglieder, die zum angegebenen Datum den ausgewählten status hatten"
|
||||
matTooltipPosition="above">
|
||||
<label>Zum Datum:</label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="picker"
|
||||
[formControl]="statusDateControl"
|
||||
[disabled]="statusControl.value===MemberStatusFilter.ALL"
|
||||
placeholder="Aktiv bis" />
|
||||
<mat-hint>DD.MM.YYYY</mat-hint>
|
||||
<mat-datepicker-toggle
|
||||
matIconSuffix
|
||||
[disabled]="statusControl.value===MemberStatusFilter.ALL"
|
||||
[for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-checkbox [formControl]="onlyWithID"
|
||||
>Nur mit Passnummer</mat-checkbox
|
||||
>
|
||||
<mat-icon fontIcon="edit"></mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--TODO Sorting doesnt work anymore-->
|
||||
<table mat-table matSort [dataSource]="dataSource" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="edit">
|
||||
<th mat-header-cell *matHeaderCellDef>Bearb.</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button
|
||||
mat-mini-fab-button
|
||||
(click)="editMember(element.id)"
|
||||
aria-label="Edit Member">
|
||||
<mat-icon fontIcon="edit"></mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="delete">
|
||||
<th mat-header-cell *matHeaderCellDef>Entf.</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button
|
||||
mat-mini-fab-button
|
||||
(click)="deleteMember(element.id)"
|
||||
aria-label="Delete Member"
|
||||
>
|
||||
<mat-icon fontIcon="delete"></mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
<th mat-header-cell *matHeaderCellDef>Entf.</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button
|
||||
mat-mini-fab-button
|
||||
(click)="deleteMember(element.id)"
|
||||
aria-label="Delete Member">
|
||||
<mat-icon fontIcon="delete"></mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="vorname">
|
||||
<th mat-header-cell *matHeaderCellDef>Vorname</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<span [innerHTML]="element.vorname | stylizeNames"></span>
|
||||
</td>
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>Vorname</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<span [innerHTML]="element.vorname | stylizeNames"></span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="nachname">
|
||||
<th mat-header-cell *matHeaderCellDef>Nachname</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<span [innerHTML]="element.nachname | stylizeNames"></span>
|
||||
</td>
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>Nachname</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<span [innerHTML]="element.nachname | stylizeNames"></span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="geschlecht">
|
||||
<th mat-header-cell *matHeaderCellDef>Geschlecht</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.geschlecht }}</td>
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>Geschlecht</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.geschlecht }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="passnummer">
|
||||
<th mat-header-cell *matHeaderCellDef>Passnummer</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ element.passnummer | nobreakHyphen }}
|
||||
</td>
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>Passnummer</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ element.passnummer | nobreakHyphen }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="geburtsdatum">
|
||||
<th mat-header-cell *matHeaderCellDef>Geburtsdatum</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ element.geburtsdatum | datePipe }}
|
||||
</td>
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>Geburtsdatum</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ element.geburtsdatum | datePipe }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="geburtsort">
|
||||
<th mat-header-cell *matHeaderCellDef>Geburtsort</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.geburtsort }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="uvertrag">
|
||||
<th mat-header-cell *matHeaderCellDef>Unterrichtsvertrag</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<mat-icon
|
||||
*ngIf="element.uvertrag"
|
||||
class="positive"
|
||||
aria-label="Unterrichtsvertrag Check"
|
||||
fontIcon="check"
|
||||
></mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="!element.uvertrag"
|
||||
class="negative"
|
||||
aria-label="Unterrichtsvertrag Cross"
|
||||
fontIcon="close"
|
||||
></mat-icon>
|
||||
</td>
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>Geburtsort</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.geburtsort }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="verein">
|
||||
<th mat-header-cell *matHeaderCellDef>Vereinsmitgliedschaft</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<mat-icon
|
||||
*ngIf="element.verein"
|
||||
class="positive"
|
||||
aria-label="Verein Check"
|
||||
fontIcon="check"
|
||||
></mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="!element.verein"
|
||||
class="negative"
|
||||
aria-label="Verein Cross"
|
||||
fontIcon="close"
|
||||
></mat-icon>
|
||||
</td>
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>
|
||||
Vereinsmitgliedschaft
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
@if(element.verein){
|
||||
<mat-icon
|
||||
class="positive"
|
||||
aria-label="Verein Check"
|
||||
fontIcon="check"></mat-icon>
|
||||
}@else {
|
||||
|
||||
<mat-icon
|
||||
class="negative"
|
||||
aria-label="Verein Cross"
|
||||
fontIcon="close"></mat-icon>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="kontakt">
|
||||
<th mat-header-cell *matHeaderCellDef>Kontakt</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.kontakt }}</td>
|
||||
<th mat-header-cell *matHeaderCellDef>Kontakt</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.kontakt }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="kuendigungzum">
|
||||
<th mat-header-cell *matHeaderCellDef>Kündigung zum</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ (element.kuendigungzum | datePipe) ?? "-" }}
|
||||
</td>
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>
|
||||
Kündigung zum
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ (element.kuendigungzum | datePipe) ?? "-" }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</ng-container>
|
||||
</table>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
mat-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.filter-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.status-filters {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { AfterViewInit, Component, viewChild } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { BehaviorSubject, firstValueFrom, from, Observable } from 'rxjs';
|
||||
import { combineLatest, startWith } from 'rxjs';
|
||||
import * as api from '../../generated-api/api';
|
||||
import { DatePipePipe } from '../../pipes/date-pipe.pipe.js';
|
||||
import { NobreakHyphenPipe } from '../../pipes/nobreak-hyphen.pipe.js';
|
||||
@@ -16,6 +21,12 @@ import { DeleteMemberDialogComponent } from '../delete-member-dialog/delete-memb
|
||||
import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js';
|
||||
import { NewMemberComponent } from '../new-member/new-member.component';
|
||||
|
||||
enum MemberStatusFilter {
|
||||
ACTIVE = 'ACTIVE',
|
||||
INACTIVE = 'INACTIVE',
|
||||
ALL = 'ALL',
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-view-members',
|
||||
imports: [
|
||||
@@ -23,60 +34,33 @@ import { NewMemberComponent } from '../new-member/new-member.component';
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
NgIf,
|
||||
StylizeNamesPipe,
|
||||
NobreakHyphenPipe,
|
||||
RouterModule,
|
||||
MatButtonModule,
|
||||
AsyncPipe,
|
||||
DatePipePipe,
|
||||
ReactiveFormsModule,
|
||||
MatDatepickerModule,
|
||||
MatTooltipModule,
|
||||
MatCheckbox,
|
||||
MatRadioModule,
|
||||
MatSortModule,
|
||||
],
|
||||
templateUrl: './view-members.html',
|
||||
styleUrl: './view-members.scss',
|
||||
})
|
||||
export class ViewMembers {
|
||||
async deleteMember(memberId: any) {
|
||||
const dataSource = await firstValueFrom(this.dataSource$);
|
||||
export class ViewMembers implements AfterViewInit {
|
||||
protected MemberStatusFilter = MemberStatusFilter;
|
||||
protected readonly filterControl = new FormControl<string>('');
|
||||
protected readonly statusControl = new FormControl<MemberStatusFilter>(
|
||||
MemberStatusFilter.ACTIVE,
|
||||
);
|
||||
protected readonly statusDateControl = new FormControl<Date | null>(null);
|
||||
protected readonly onlyWithID = new FormControl<boolean>(false);
|
||||
protected dataSource = new MatTableDataSource<api.MemberDto>();
|
||||
private readonly datePipe = new DatePipePipe();
|
||||
|
||||
const dialogRef = this.dialog.open(DeleteMemberDialogComponent, {
|
||||
data: {
|
||||
member: dataSource.data.find(member => member.id === memberId),
|
||||
},
|
||||
hasBackdrop: true,
|
||||
width: '90%',
|
||||
height: '90%',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(async result => {
|
||||
this.dataSource$ = this.updateData();
|
||||
});
|
||||
}
|
||||
newMember() {
|
||||
const dialogRef = this.dialog.open(NewMemberComponent, {});
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
this.dataSource$ = this.updateData();
|
||||
});
|
||||
}
|
||||
async editMember(memberId: any) {
|
||||
const dataSource = await firstValueFrom(this.dataSource$);
|
||||
|
||||
const dialogRef = this.dialog.open(EditMemberDialogComponent, {
|
||||
data: {
|
||||
member: dataSource.data.find(member => member.id === memberId),
|
||||
},
|
||||
hasBackdrop: true,
|
||||
width: '90%',
|
||||
height: '90%',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(async result => {
|
||||
this.dataSource$ = this.updateData();
|
||||
});
|
||||
}
|
||||
dataSource$: Observable<MatTableDataSource<api.Member>> =
|
||||
new BehaviorSubject(new MatTableDataSource([] as api.Member[]));
|
||||
filter$: BehaviorSubject<string> = new BehaviorSubject('');
|
||||
displayedColumns: string[] = [
|
||||
protected readonly displayedColumns: string[] = [
|
||||
'edit',
|
||||
'delete',
|
||||
'vorname',
|
||||
@@ -85,31 +69,141 @@ export class ViewMembers {
|
||||
'passnummer',
|
||||
'geburtsdatum',
|
||||
'geburtsort',
|
||||
'uvertrag',
|
||||
'verein',
|
||||
'kontakt',
|
||||
'kuendigungzum',
|
||||
];
|
||||
|
||||
applyFilter(event: KeyboardEvent) {
|
||||
let filterValue = (event.target as HTMLInputElement).value ?? '';
|
||||
filterValue = filterValue.trim();
|
||||
filterValue = filterValue.toLowerCase();
|
||||
this.filter$.next(filterValue);
|
||||
protected deleteMember(memberId: any) {
|
||||
const dialogRef = this.dialog.open(DeleteMemberDialogComponent, {
|
||||
data: {
|
||||
member: this.dataSource.data.find(
|
||||
member => member.id === memberId,
|
||||
),
|
||||
},
|
||||
hasBackdrop: true,
|
||||
width: '90%',
|
||||
height: '90%',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(async result => {
|
||||
this.updateData();
|
||||
});
|
||||
}
|
||||
constructor(private dialog: MatDialog) {
|
||||
this.dataSource$ = this.updateData();
|
||||
protected newMember() {
|
||||
const dialogRef = this.dialog.open(NewMemberComponent, {});
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
this.updateData();
|
||||
});
|
||||
}
|
||||
protected editMember(memberId: any) {
|
||||
const dialogRef = this.dialog.open(EditMemberDialogComponent, {
|
||||
data: {
|
||||
member: this.dataSource.data.find(
|
||||
member => member.id === memberId,
|
||||
),
|
||||
},
|
||||
hasBackdrop: true,
|
||||
width: '90%',
|
||||
height: '90%',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(async result => {
|
||||
//TODO do upsert instead
|
||||
this.updateData();
|
||||
});
|
||||
}
|
||||
|
||||
updateData() {
|
||||
return from(
|
||||
api.getMembers().then(members => {
|
||||
let dataSource = new MatTableDataSource<api.Member>(members);
|
||||
this.filter$.subscribe(filter => {
|
||||
dataSource.filter = filter;
|
||||
});
|
||||
return dataSource;
|
||||
}),
|
||||
);
|
||||
public constructor(private dialog: MatDialog) {
|
||||
const originalFilterPredicate = this.dataSource.filterPredicate;
|
||||
this.dataSource.filterPredicate = (
|
||||
data: api.MemberDto,
|
||||
encodedFilter: string,
|
||||
) => {
|
||||
const parts = encodedFilter.split(';');
|
||||
const filter = parts[0];
|
||||
const status = parts[1] as MemberStatusFilter;
|
||||
const statusDateStr = parts[2];
|
||||
const onlyWithID = parts[3] === 'true';
|
||||
|
||||
const currentDate =
|
||||
statusDateStr && statusDateStr != ''
|
||||
? new Date(statusDateStr)
|
||||
: null;
|
||||
const cancellationDate = data.kuendigungzum
|
||||
? new Date(data.kuendigungzum)
|
||||
: null;
|
||||
if (
|
||||
currentDate &&
|
||||
cancellationDate &&
|
||||
((status === MemberStatusFilter.ACTIVE &&
|
||||
cancellationDate <= currentDate) ||
|
||||
(status === MemberStatusFilter.INACTIVE &&
|
||||
cancellationDate > currentDate))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (status === MemberStatusFilter.INACTIVE && !cancellationDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
onlyWithID &&
|
||||
(!data.passnummer || data.passnummer.trim() === '')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return originalFilterPredicate(
|
||||
{
|
||||
...data,
|
||||
geburtsdatum: this.datePipe.transform(
|
||||
data.geburtsdatum ?? undefined,
|
||||
),
|
||||
kuendigungzum: this.datePipe.transform(
|
||||
data.kuendigungzum ?? undefined,
|
||||
),
|
||||
letztePruefung: this.datePipe.transform(
|
||||
data.letztePruefung ?? undefined,
|
||||
),
|
||||
},
|
||||
filter,
|
||||
);
|
||||
};
|
||||
combineLatest([
|
||||
this.filterControl.valueChanges.pipe(
|
||||
startWith(this.filterControl.value),
|
||||
),
|
||||
this.statusControl.valueChanges.pipe(
|
||||
startWith(this.statusControl.value),
|
||||
),
|
||||
this.statusDateControl.valueChanges.pipe(
|
||||
startWith(this.statusDateControl.value),
|
||||
),
|
||||
this.onlyWithID.valueChanges.pipe(startWith(this.onlyWithID.value)),
|
||||
])
|
||||
// .pipe(takeUntilDestroyed())
|
||||
.subscribe(combined => {
|
||||
const filter = combined[0];
|
||||
const status = combined[1];
|
||||
const date = combined[2];
|
||||
const onlyWithID = combined[3];
|
||||
this.dataSource.filter = `${filter?.replaceAll(';', '') ?? ''};${status};${date?.toISOString()};${onlyWithID}`;
|
||||
console.log('filter', this.dataSource.filter);
|
||||
});
|
||||
|
||||
this.updateData();
|
||||
}
|
||||
|
||||
protected readonly sort = viewChild.required(MatSort);
|
||||
public ngAfterViewInit(): void {
|
||||
this.dataSource.sort = this.sort();
|
||||
}
|
||||
|
||||
private async updateData(): Promise<void> {
|
||||
const members = await api.getMembers();
|
||||
console.log('Members loaded');
|
||||
console.dir(members);
|
||||
this.dataSource.data = members;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
import { Inject, Pipe, PipeTransform } from '@angular/core';
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core';
|
||||
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
||||
import * as tz from '@date-fns/tz';
|
||||
import { format, Locale } from 'date-fns';
|
||||
|
||||
@Pipe({
|
||||
name: 'datePipe',
|
||||
standalone: true
|
||||
name: 'datePipe',
|
||||
standalone: true,
|
||||
})
|
||||
export class DatePipePipe implements PipeTransform {
|
||||
constructor(@Inject(MAT_DATE_LOCALE) private locale: Locale){}
|
||||
private readonly locale = inject<Locale>(MAT_DATE_LOCALE);
|
||||
|
||||
transform(value?: string): string {
|
||||
if(!value) return "-";
|
||||
const date=new tz.TZDate(value!, "UTC");
|
||||
const timeZone=Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const zonedDate=date.withTimeZone(timeZone);
|
||||
return format(zonedDate, 'P', { locale: this.locale });
|
||||
}
|
||||
public transform(value?: string): string {
|
||||
if (!value) return '-';
|
||||
const zonedDate = dateToUTC(value);
|
||||
return format(zonedDate, 'P', { locale: this.locale });
|
||||
}
|
||||
}
|
||||
|
||||
export function dateToLocal(date: Date): Date {
|
||||
return new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
||||
}
|
||||
|
||||
export function dateToUTC(value: string): Date {
|
||||
const date = new tz.TZDate(value!, 'UTC');
|
||||
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
return date.withTimeZone(timeZone);
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ export class Authentication {
|
||||
return await this.keycloak.login({
|
||||
redirectUri: redirectUri,
|
||||
locale: 'de-DE',
|
||||
//TODO loginHint:''
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { isDevMode } from '@angular/core';
|
||||
|
||||
import * as api from '../generated-api/api';
|
||||
|
||||
export function initializeApi() {
|
||||
if (isDevMode()) {
|
||||
// override api url in dev mode
|
||||
api.defaults.baseUrl = 'http://localhost:3000';
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,8 @@ import { Inject, Injectable } from '@angular/core';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
||||
import { Locale, parse } from 'date-fns';
|
||||
import {
|
||||
Member,
|
||||
PickMemberExcludeKeyofMemberIdOrLoeschenam,
|
||||
} from '../generated-api/api';
|
||||
import { MemberCreateArgs, MemberDto } from '../generated-api/api';
|
||||
import { dateToLocal, dateToUTC } from '../pipes/date-pipe.pipe';
|
||||
|
||||
interface MemberForm {
|
||||
id: FormControl<string | null | undefined>;
|
||||
@@ -15,12 +13,12 @@ interface MemberForm {
|
||||
geburtsdatum: FormControl<Date | null>;
|
||||
geburtsort: FormControl<string | null | undefined>;
|
||||
passnummer: FormControl<string | null | undefined>;
|
||||
uvertrag: FormControl<boolean | null | undefined>;
|
||||
verein: FormControl<boolean | null | undefined>;
|
||||
kontakt: FormControl<string | null | undefined>;
|
||||
graduierung: FormControl<string | null | undefined>;
|
||||
kuendigungzum: FormControl<string | null | undefined>;
|
||||
letztePruefung: FormControl<string | null | undefined>;
|
||||
kuendigungzum: FormControl<Date | null | undefined>;
|
||||
letztePruefung: FormControl<Date | null | undefined>;
|
||||
marker: FormControl<boolean | null | undefined>;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
@@ -28,7 +26,7 @@ interface MemberForm {
|
||||
})
|
||||
export class MemberDtoHelper {
|
||||
constructor(@Inject(MAT_DATE_LOCALE) private locale: Locale) {}
|
||||
public newMemberForm(member?: Member): FormGroup<MemberForm> {
|
||||
public newMemberForm(member?: MemberDto): FormGroup<MemberForm> {
|
||||
const geburtsdatum = member?.geburtsdatum;
|
||||
let enDate: Date | null = null;
|
||||
if (geburtsdatum) {
|
||||
@@ -38,62 +36,71 @@ export class MemberDtoHelper {
|
||||
});
|
||||
}
|
||||
|
||||
return new FormGroup({
|
||||
return new FormGroup<MemberForm>({
|
||||
id: new FormControl(member?.id),
|
||||
vorname: new FormControl(member?.vorname),
|
||||
nachname: new FormControl(member?.nachname),
|
||||
geschlecht: new FormControl(member?.geschlecht),
|
||||
geburtsdatum: new FormControl(enDate),
|
||||
geburtsdatum: new FormControl(
|
||||
member?.geburtsdatum ? dateToUTC(member?.geburtsdatum) : null,
|
||||
),
|
||||
geburtsort: new FormControl(member?.geburtsort),
|
||||
passnummer: new FormControl(member?.passnummer),
|
||||
uvertrag: new FormControl(member?.uvertrag),
|
||||
verein: new FormControl(member?.verein),
|
||||
kontakt: new FormControl(member?.kontakt),
|
||||
graduierung: new FormControl(member?.graduierung),
|
||||
kuendigungzum: new FormControl(member?.kuendigungzum),
|
||||
letztePruefung: new FormControl(member?.letztePruefung),
|
||||
kuendigungzum: new FormControl(
|
||||
member?.kuendigungzum ? dateToUTC(member?.kuendigungzum) : null,
|
||||
),
|
||||
letztePruefung: new FormControl(
|
||||
member?.letztePruefung
|
||||
? dateToUTC(member?.letztePruefung)
|
||||
: null,
|
||||
),
|
||||
marker: new FormControl(false),
|
||||
});
|
||||
}
|
||||
|
||||
memberFromForm(form: FormGroup<MemberForm>): Member {
|
||||
memberFromForm(form: FormGroup<MemberForm>): MemberDto {
|
||||
return {
|
||||
id: form.controls.id.value ?? null,
|
||||
vorname: form.controls.vorname.value ?? null,
|
||||
nachname: form.controls.nachname.value ?? null,
|
||||
geschlecht: form.controls.geschlecht.value ?? null,
|
||||
geburtsdatum: form.controls.geburtsdatum.value
|
||||
? JSON.stringify(form.controls.geburtsdatum.value)
|
||||
? dateToLocal(form.controls.geburtsdatum.value).toISOString()
|
||||
: null,
|
||||
geburtsort: form.controls.geburtsort.value ?? null,
|
||||
passnummer: form.controls.passnummer.value ?? null,
|
||||
uvertrag: form.controls.uvertrag.value ?? null,
|
||||
verein: form.controls.verein.value ?? null,
|
||||
kontakt: form.controls.kontakt.value ?? null,
|
||||
graduierung: form.controls.graduierung?.value ?? null,
|
||||
kuendigungzum: form.controls.kuendigungzum.value ?? null, //TODO not for new member
|
||||
letztePruefung: form.controls.letztePruefung?.value ?? null, //TODO not for new member
|
||||
loeschenam: null, //TODO? not client settable
|
||||
kuendigungzum: form.controls.kuendigungzum.value
|
||||
? dateToLocal(form.controls.kuendigungzum.value).toISOString()
|
||||
: null,
|
||||
letztePruefung: form.controls.letztePruefung.value
|
||||
? dateToLocal(form.controls.letztePruefung.value).toISOString()
|
||||
: null,
|
||||
marker: form.controls.marker.value ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
newMemberFromForm(
|
||||
form: FormGroup<MemberForm>,
|
||||
): PickMemberExcludeKeyofMemberIdOrLoeschenam {
|
||||
newMemberFromForm(form: FormGroup<MemberForm>): MemberCreateArgs {
|
||||
return {
|
||||
vorname: form.controls.vorname.value ?? null,
|
||||
nachname: form.controls.nachname.value ?? null,
|
||||
geschlecht: form.controls.geschlecht.value ?? null,
|
||||
geburtsdatum: form.controls.geburtsdatum.value
|
||||
? JSON.stringify(form.controls.geburtsdatum.value)
|
||||
? dateToLocal(form.controls.geburtsdatum.value).toISOString()
|
||||
: null,
|
||||
geburtsort: form.controls.geburtsort.value ?? null,
|
||||
passnummer: form.controls.passnummer.value ?? null,
|
||||
uvertrag: form.controls.uvertrag.value ?? null,
|
||||
verein: form.controls.verein.value ?? null,
|
||||
kontakt: form.controls.kontakt.value ?? null,
|
||||
graduierung: form.controls.graduierung?.value ?? null,
|
||||
kuendigungzum: null, //TODO not for new member
|
||||
letztePruefung: null, //TODO not for new member
|
||||
marker: form.controls.marker.value ?? false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user