visual + updates

This commit is contained in:
toni
2026-03-08 11:09:52 +01:00
parent a9ff8517ff
commit 10f4750755
22 changed files with 2009 additions and 2822 deletions
@@ -1,10 +1,13 @@
<form (submit)="submit(); $event.preventDefault()">
<form [formRoot]="form">
<p>Name: {{ member.vorname }} {{ member.nachname }}</p>
<p>Passnummer: {{ member.passnummer }}</p>
<p>Geburtsdatum: {{ member.geburtsdatum | datePipe }}</p>
<mat-form-field>
<mat-label>K&uuml;ndigun zum</mat-label>
<input matInput [matDatepicker]="picker" [field]="form.kuendigungzum" />
<input
matInput
[matDatepicker]="picker"
[formField]="form.kuendigungzum" />
<mat-hint>DD.MM.YYYY</mat-hint>
<mat-datepicker-toggle
matIconSuffix
@@ -4,7 +4,12 @@ import {
inject,
signal,
} from '@angular/core';
import { Field, form } from '@angular/forms/signals';
import {
form,
FormField,
FormRoot,
TreeValidationResult,
} from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatOptionModule } from '@angular/material/core';
@@ -33,7 +38,8 @@ import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dia
MatOptionModule,
MatDatepickerModule,
DatePipePipe,
Field,
FormField,
FormRoot,
],
templateUrl: './delete-member-dialog.component.html',
styleUrl: './delete-member-dialog.component.scss',
@@ -49,16 +55,24 @@ export class DeleteMemberDialogComponent {
? dateToUTC(this.data.member.kuendigungzum)
: null,
});
protected readonly form = form(this.deletionModel);
protected readonly form = form(this.deletionModel, {
submission: {
action: async () => {
return await this.submit();
},
},
});
protected readonly member = this.data.member;
protected submit() {
protected async submit(): Promise<TreeValidationResult> {
const date = this.deletionModel().kuendigungzum;
api.scheduleDeletion(
this.member.id,
date ? dateToLocal(date).toISOString() : '',
).then(() => {
this.dialogRef.close();
});
await api
.scheduleDeletion(
this.member.id,
date ? dateToLocal(date).toISOString() : '',
)
.then(() => {
this.dialogRef.close();
});
}
}
@@ -1,23 +1,32 @@
<form (submit)="submit(); $event.preventDefault()">
<form [formRoot]="form">
<mat-form-field>
<mat-label>Vorname</mat-label>
<input matInput placeholder="Vorname" [field]="form.vorname" />
<input matInput placeholder="Vorname" [formField]="form.vorname" />
</mat-form-field>
<mat-form-field>
<mat-label>Nachname</mat-label>
<input matInput placeholder="Nachname" [field]="form.nachname" />
<input matInput placeholder="Nachname" [formField]="form.nachname" />
</mat-form-field>
<mat-form-field>
<mat-label>Geschlecht</mat-label>
<input matInput placeholder="Geschlecht" [field]="form.geschlecht" />
<input
matInput
placeholder="Geschlecht"
[formField]="form.geschlecht" />
</mat-form-field>
<mat-form-field>
<mat-label>Passnummer</mat-label>
<input matInput placeholder="Passnummer" [field]="form.passnummer" />
<input
matInput
placeholder="Passnummer"
[formField]="form.passnummer" />
</mat-form-field>
<mat-form-field>
<mat-label>Geburtsdatum</mat-label>
<input matInput [matDatepicker]="picker" [field]="form.geburtsdatum" />
<input
matInput
[matDatepicker]="picker"
[formField]="form.geburtsdatum" />
<mat-hint>DD.MM.YYYY</mat-hint>
<mat-datepicker-toggle
matIconSuffix
@@ -26,11 +35,14 @@
</mat-form-field>
<mat-form-field>
<mat-label>Geburtsort</mat-label>
<input matInput placeholder="Geburtsort" [field]="form.geburtsort" />
<input
matInput
placeholder="Geburtsort"
[formField]="form.geburtsort" />
</mat-form-field>
<mat-form-field>
<mat-label>Vereinsmitglied?</mat-label>
<mat-select matInput [field]="form.verein">
<mat-select matInput [formField]="form.verein">
<mat-option [value]="true">Ja</mat-option>
<mat-option [value]="false">Nein</mat-option>
</mat-select>
@@ -4,7 +4,12 @@ import {
inject,
signal,
} from '@angular/core';
import { Field, form } from '@angular/forms/signals';
import {
form,
FormField,
FormRoot,
TreeValidationResult,
} from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatOptionModule } from '@angular/material/core';
@@ -27,7 +32,8 @@ import { MemberDtoHelper, MemberEditForm } from '../../util/member.js';
MatSelectModule,
MatOptionModule,
MatDatepickerModule,
Field,
FormField,
FormRoot,
],
templateUrl: './edit-member-dialog.component.html',
styleUrl: './edit-member-dialog.component.scss',
@@ -43,13 +49,19 @@ export class EditMemberDialogComponent {
private readonly memberModel = signal<MemberEditForm>(
this.memberDtoHelper.editMemberForm(this.data.member),
);
protected readonly form = form(this.memberModel);
protected readonly form = form(this.memberModel, {
submission: {
action: async () => {
return await this.submit();
},
},
});
protected submit() {
protected async submit(): Promise<TreeValidationResult> {
const member: api.MemberDto = this.memberDtoHelper.memberFromSignal(
this.memberModel(),
);
api.updateMember(member).then(() => {
await api.updateMember(member).then(() => {
this.dialogRef.close();
});
}
@@ -1,23 +1,23 @@
<h1>Neues Mitglied anlegen</h1>
<form (submit)="onSubmit(); $event.preventDefault()">
<form [formRoot]="memberForm">
<mat-form-field>
<mat-label>Vorname</mat-label>
<input matInput [field]="memberForm.vorname" />
<input matInput [formField]="memberForm.vorname" />
</mat-form-field>
<mat-form-field>
<mat-label>Nachname</mat-label>
<input matInput [field]="memberForm.nachname" />
<input matInput [formField]="memberForm.nachname" />
</mat-form-field>
<mat-form-field>
<mat-label>Geschlecht</mat-label>
<input matInput [field]="memberForm.geschlecht" />
<input matInput [formField]="memberForm.geschlecht" />
</mat-form-field>
<mat-form-field>
<mat-label>Geburtsdatum</mat-label>
<input
matInput
[matDatepicker]="picker"
[field]="memberForm.geburtsdatum" />
[formField]="memberForm.geburtsdatum" />
<mat-hint>DD.MM.YYYY</mat-hint>
<mat-datepicker-toggle
matIconSuffix
@@ -26,16 +26,18 @@
</mat-form-field>
<mat-form-field>
<mat-label>Geburtsort</mat-label>
<input matInput [field]="memberForm.geburtsort" />
<input matInput [formField]="memberForm.geburtsort" />
</mat-form-field>
<mat-checkbox [field]="memberForm.verein">Verein Beigetreten?</mat-checkbox>
<mat-checkbox [formField]="memberForm.verein"
>Verein Beigetreten?</mat-checkbox
>
<mat-form-field>
<mat-label>Passnummer (falls vorhanden)</mat-label>
<input matInput [field]="memberForm.passnummer" />
<input matInput [formField]="memberForm.passnummer" />
</mat-form-field>
<mat-form-field>
<mat-label>Kontakt</mat-label>
<input matInput [field]="memberForm.kontakt" />
<input matInput [formField]="memberForm.kontakt" />
</mat-form-field>
<button mat-flat-button color="primary" type="submit">Hinzufügen</button>
</form>
@@ -4,7 +4,12 @@ import {
inject,
signal,
} from '@angular/core';
import { Field, form } from '@angular/forms/signals';
import {
form,
FormField,
FormRoot,
TreeValidationResult,
} from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDatepickerModule } from '@angular/material/datepicker';
@@ -24,7 +29,8 @@ import { MemberCreateForm, MemberDtoHelper } from '../../util/member.js';
MatInputModule,
MatCheckboxModule,
MatButtonModule,
Field,
FormField,
FormRoot,
],
templateUrl: './new-member.component.html',
styleUrl: './new-member.component.scss',
@@ -39,18 +45,21 @@ export class NewMemberComponent {
private readonly memberModel = signal<MemberCreateForm>(
this.memberDtoHelper.newMemberForm(),
);
protected readonly memberForm = form(this.memberModel);
protected onSubmit() {
if (!this.memberForm().valid()) {
return;
}
protected readonly memberForm = form(this.memberModel, {
submission: {
action: async () => {
return await this.submit();
},
},
});
protected async submit(): Promise<TreeValidationResult> {
const newMember = this.memberDtoHelper.newMemberFromSignal(
this.memberModel(),
);
api.createMember(newMember)
await api
.createMember(newMember)
.then(() => {
console.log('Successfully added Member');
this.dialogRef.close();
@@ -20,16 +20,16 @@
<label>Filter:</label>
<input
matInput
[field]="filterForm.filterString"
[formField]="filterForm.filterString"
placeholder="Filter" />
</mat-form-field>
<div class="status-filters">
<mat-radio-group [field]="filterForm.status">
<mat-radio-group [formField]="filterForm.status">
<mat-radio-button [value]="MemberStatusFilter.ACTIVE"
>Aktiv</mat-radio-button
>Aktiv zum</mat-radio-button
>
<mat-radio-button [value]="MemberStatusFilter.INACTIVE"
>Inaktiv</mat-radio-button
>Gekündigt seit</mat-radio-button
>
<mat-radio-button [value]="MemberStatusFilter.ALL"
>Alle</mat-radio-button
@@ -42,7 +42,7 @@
<input
matInput
[matDatepicker]="picker"
[field]="filterForm.statusDate"
[formField]="filterForm.statusDate"
placeholder="Aktiv bis" />
<mat-hint>DD.MM.YYYY</mat-hint>
<mat-datepicker-toggle
@@ -52,7 +52,7 @@
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
<mat-checkbox [field]="filterForm.onlyWithID"
<mat-checkbox [formField]="filterForm.onlyWithID"
>Nur mit Passnummer</mat-checkbox
>
</div>
@@ -110,7 +110,7 @@
</ng-container>
<ng-container matColumnDef="geburtsdatum">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Geburtsdatum
Geburtsdatum Geboren am
</th>
<td mat-cell *matCellDef="let element">
{{ element.geburtsdatum | datePipe }}
@@ -123,9 +123,7 @@
<td mat-cell *matCellDef="let element">{{ element.geburtsort }}</td>
</ng-container>
<ng-container matColumnDef="verein">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Vereinsmitgliedschaft
</th>
<th mat-header-cell mat-sort-header *matHeaderCellDef>Verein?</th>
<td mat-cell *matCellDef="let element">
@if(element.verein){
<mat-icon
@@ -149,12 +147,18 @@
<ng-container matColumnDef="kuendigungzum">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
K&uuml;ndigung zum
K&uuml;ndigung
</th>
<td mat-cell *matCellDef="let element">
{{ (element.kuendigungzum | datePipe) ?? "-" }}
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell mat-sort-header *matHeaderCellDef>Beitritt</th>
<td mat-cell *matCellDef="let element">
{{ element.creationDate | datePipe }}
</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef>Entf.</th>
@@ -187,7 +191,8 @@
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
class="table-row"></tr>
class="table-row"
[class.expired]="isInPast(row.kuendigungzum)"></tr>
<tr mat-footer-row *matFooterRowDef="['totalCount']; sticky: true"></tr>
</table>
</div>
@@ -49,3 +49,16 @@ td {
.table-row:nth-child(odd) {
background-color: var(--mat-sys-surface);
}
.table-row.expired {
//background-color: lightsalmon;
filter: sepia(50%);
}
.positive {
color: green;
}
.negative {
color: red;
}
@@ -9,7 +9,7 @@ import {
signal,
viewChild,
} from '@angular/core';
import { Field, form } from '@angular/forms/signals';
import { form, FormField } from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatDatepickerModule } from '@angular/material/datepicker';
@@ -30,6 +30,44 @@ 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';
class MemberFilters {
public filterString: string;
public status: MemberStatusFilter;
public statusDate: Date | null;
public onlyWithID: boolean;
public constructor(data: {
filterString: string;
status: MemberStatusFilter;
statusDate: Date | null;
onlyWithID: boolean;
}) {
this.filterString = data.filterString;
this.status = data.status;
this.statusDate = data.statusDate;
this.onlyWithID = data.onlyWithID;
}
public encode(): string {
return `${this.filterString?.replaceAll(';', '') ?? ''};${this.status};${this.statusDate?.toISOString()};${this.onlyWithID}`;
}
public static decode(encoded: string): MemberFilters {
const parts = encoded.split(';');
const statusDateStr = parts[2];
return new MemberFilters({
filterString: parts[0],
status: parts[1] as MemberStatusFilter,
statusDate:
statusDateStr && statusDateStr != ''
? new Date(statusDateStr)
: null,
onlyWithID: parts[3] === 'true',
});
}
}
enum MemberStatusFilter {
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
@@ -53,7 +91,7 @@ enum MemberStatusFilter {
MatCheckbox,
MatRadioModule,
MatSortModule,
Field,
FormField,
],
templateUrl: './view-members.html',
styleUrl: './view-members.scss',
@@ -66,7 +104,7 @@ export class ViewMembers implements AfterViewInit {
private readonly filterModel = signal({
filterString: '',
status: MemberStatusFilter.ACTIVE,
statusDate: null as Date | null,
statusDate: new Date() as Date | null,
onlyWithID: false,
});
protected readonly filterForm = form(this.filterModel);
@@ -89,6 +127,7 @@ export class ViewMembers implements AfterViewInit {
'geburtsort',
'verein',
'kontakt',
'creationDate',
'kuendigungzum',
'delete',
];
@@ -139,35 +178,41 @@ export class ViewMembers implements AfterViewInit {
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 filters = MemberFilters.decode(encodedFilter);
const currentDate =
statusDateStr && statusDateStr != ''
? new Date(statusDateStr)
if (filters.statusDate) {
const cancellationDate = data.kuendigungzum
? new Date(data.kuendigungzum)
: 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;
switch (filters.status) {
case MemberStatusFilter.ACTIVE:
const creationDate = data.creationDate
? new Date(data.creationDate)
: null;
if (
(cancellationDate &&
cancellationDate <= filters.statusDate) ||
(creationDate && creationDate > filters.statusDate)
) {
return false;
}
break;
case MemberStatusFilter.INACTIVE:
if (
!cancellationDate ||
cancellationDate < filters.statusDate
) {
return false;
}
break;
case MemberStatusFilter.ALL:
break;
}
}
if (
onlyWithID &&
filters.onlyWithID &&
(!data.passnummer || data.passnummer.trim() === '')
) {
return false;
@@ -186,14 +231,23 @@ export class ViewMembers implements AfterViewInit {
letztePruefung: this.datePipe.transform(
data.letztePruefung ?? undefined,
),
creationDate: this.datePipe.transform(
data.creationDate ?? undefined,
),
},
filter,
filters.filterString,
);
};
effect(() => {
const filters = this.filterModel();
this.dataSource.filter = `${filters.filterString?.replaceAll(';', '') ?? ''};${filters.status};${filters.statusDate?.toISOString()};${filters.onlyWithID}`;
const filtersSignal = this.filterModel();
const filters = new MemberFilters({
filterString: filtersSignal.filterString,
status: filtersSignal.status,
statusDate: filtersSignal.statusDate,
onlyWithID: filtersSignal.onlyWithID,
});
this.dataSource.filter = filters.encode();
});
this.updateData();
@@ -234,7 +288,7 @@ export class ViewMembers implements AfterViewInit {
headerRow.appendChild(clubnrHeader);
const birthdayHeader = document.createElement('th');
birthdayHeader.innerText = 'Geburtsdatum';
birthdayHeader.innerText = 'Geboren am';
headerRow.appendChild(birthdayHeader);
const geburtsortHeader = document.createElement('th');
@@ -242,7 +296,7 @@ export class ViewMembers implements AfterViewInit {
headerRow.appendChild(geburtsortHeader);
const cancelDateHeader = document.createElement('th');
cancelDateHeader.innerText = 'Kündigungsdatum';
cancelDateHeader.innerText = 'Kündigung';
headerRow.appendChild(cancelDateHeader);
table.appendChild(headerRow);
@@ -323,4 +377,11 @@ export class ViewMembers implements AfterViewInit {
document.body.appendChild(printWindow);
printWindow.contentWindow?.print();
}
protected isInPast(date: string): boolean {
if (!date) return false;
const today = new Date();
const cancellationDate = new Date(date);
return cancellationDate <= today;
}
}
@@ -116,7 +116,7 @@ export class Authentication {
return await this.keycloak.login({
redirectUri: redirectUri,
locale: 'de-DE',
//TODO loginHint:''
//TODO loginHint:''
});
}
+1 -1
View File
@@ -23,7 +23,7 @@ interface MemberForm {
export interface MemberCreateForm extends Omit<
MemberCreateArgs,
'geburtsdatum'
'geburtsdatum' | 'creationDate'
> {
geburtsdatum: Date | null;
}