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 -1
View File
@@ -12,7 +12,7 @@
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a",
"type": "module",
"devDependencies": {
"nodemon": "^3.1.11",
"nodemon": "^3.1.14",
"tsoa": "^6.6.0"
}
}
+3
View File
@@ -5,3 +5,6 @@ generated/
.env
keycloak/realm-export
src/cert.pem
src/key.pem
+13 -13
View File
@@ -12,27 +12,27 @@
"devDependencies": {
"@types/connect-pg-simple": "^7.0.3",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.5",
"@types/express": "^5.0.6",
"@types/express-session": "^1.18.2",
"@types/node": "^24.9.2",
"@types/pg": "^8.15.6",
"prisma": "^7.0.1",
"tsx": "^4.20.6",
"@types/node": "^25.3.5",
"@types/pg": "^8.18.0",
"prisma": "^7.4.2",
"tsx": "^4.21.0",
"typescript": "~5.9.3"
},
"dependencies": {
"@keycloak/keycloak-admin-client": "^26.4.4",
"@prisma/adapter-pg": "^7.0.1",
"@prisma/client": "^7.0.1",
"@keycloak/keycloak-admin-client": "^26.5.5",
"@prisma/adapter-pg": "^7.4.2",
"@prisma/client": "^7.4.2",
"@tsoa/runtime": "^6.6.0",
"connect-pg-simple": "^10.0.0",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.1.0",
"express-session": "^1.18.2",
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"express-session": "^1.19.0",
"helmet": "^8.1.0",
"keycloak-connect": "^26.1.1",
"pg": "^8.16.3",
"pg": "^8.20.0",
"rxjs": "~7.8.2",
"tsoa": "^6.6.0"
}
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Member" ADD COLUMN "creationDate" DATE;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Member" ALTER COLUMN "creationDate" SET DEFAULT CURRENT_TIMESTAMP;
+1
View File
@@ -21,6 +21,7 @@ model Member {
kontakt String? @db.VarChar
kuendigungzum DateTime? @db.Date
marker Boolean?
creationDate DateTime? @db.Date @default(now())
}
model session {
+6 -1
View File
@@ -14,11 +14,12 @@ export interface MemberDto {
kontakt: string;
kuendigungzum: string;
marker: boolean;
creationDate: string;
}
export class MemberCreateArgs implements Omit<
MemberDto,
"id" | "kuendigungzum" | "letztePruefung"
"id" | "kuendigungzum" | "letztePruefung" | "creationDate"
> {
vorname!: string;
nachname!: string;
@@ -47,6 +48,7 @@ export function mapDbMemberToMember(dbMember: DbMember): MemberDto {
passnummer: dbMember.passnummer ?? "",
verein: dbMember.verein ?? false,
marker: dbMember.marker ?? false,
creationDate: dbMember.creationDate?.toDateString() ?? "",
};
}
@@ -71,5 +73,8 @@ export function mapMemberDtoToDbMember(member: MemberDto): DbMember {
? new Date(member.kuendigungzum)
: null,
marker: member.marker,
creationDate: member.creationDate
? new Date(member.creationDate)
: null,
};
}
+29 -14
View File
@@ -66,25 +66,40 @@ async function run() {
},
);
if (environment.isDev()) {
//TODO create self-signed for localhost
const port = 3000;
app.listen(port, () => {
const isDev = environment.isDev();
let cert: https.ServerOptions["cert"];
let key: https.ServerOptions["key"];
let SSL_KEY_PASSWORD: string | undefined;
let port: number;
if (isDev) {
// cert = fs.readFileSync("src/cert.pem");
// key = fs.readFileSync("src/key.pem");
port = 3000;
app.listen(port, error => {
if (error) {
console.error("Startup crashed:", error);
}
console.log(`Server is running at http://localhost:${port}`);
});
} else {
//TODO start with HTTPS in production
const port = 5443;
port = 5443;
const SSL_CERT_PATH = process.env["SSL_CERT_PATH"];
const SSL_KEY_PATH = process.env["SSL_KEY_PATH"];
const SSL_KEY_PASSWORD = process.env["SSL_KEY_PASSWORD"];
SSL_KEY_PASSWORD = process.env["SSL_KEY_PASSWORD"];
if (!SSL_CERT_PATH || !SSL_KEY_PATH || !SSL_KEY_PASSWORD) {
throw new Error("Missing SSL configuration");
}
cert = fs.readFileSync(SSL_CERT_PATH);
key = fs.readFileSync(SSL_KEY_PATH);
const sslOptions: https.ServerOptions = {
cert: fs.readFileSync(SSL_CERT_PATH),
key: fs.readFileSync(SSL_KEY_PATH),
cert: cert,
key: key,
passphrase: SSL_KEY_PASSWORD,
minVersion: "TLSv1.3",
maxVersion: "TLSv1.3",
@@ -100,14 +115,14 @@ async function run() {
https.createServer(sslOptions, app).listen(port, () => {
console.log(`Server is running on port ${port}`);
});
// const h2SslOptions={};
// http2.createServer(h2SslOptions, app).listen(port, ()=>{
// console.log(`HTTP/2 server is running on port ${port}`);
// })
}
console.log("done");
// const h2SslOptions={};
// http2.createServer(h2SslOptions, app).listen(port, ()=>{
// console.log(`HTTP/2 server is running on port ${port}`);
// })
console.log("setup done");
}
run();
@@ -82,6 +82,9 @@ export class MembersService {
kuendigungzum: member.kuendigungzum
? member.kuendigungzum
: null,
creationDate: member.creationDate
? member.creationDate
: null,
},
where: { id: id },
}),
+21 -21
View File
@@ -25,37 +25,37 @@
},
"private": true,
"dependencies": {
"@angular/cdk": "21.0.5",
"@angular/common": "^21.0.6",
"@angular/compiler": "^21.0.6",
"@angular/core": "^21.0.6",
"@angular/forms": "^21.0.6",
"@angular/material": "21.0.5",
"@angular/material-date-fns-adapter": "^21.0.5",
"@angular/platform-browser": "^21.0.6",
"@angular/router": "^21.0.6",
"@angular/service-worker": "^21.0.6",
"@angular/cdk": "^21.2.1",
"@angular/common": "^21.2.1",
"@angular/compiler": "^21.2.1",
"@angular/core": "^21.2.1",
"@angular/forms": "^21.2.1",
"@angular/material": "^21.2.1",
"@angular/material-date-fns-adapter": "^21.2.1",
"@angular/platform-browser": "^21.2.1",
"@angular/router": "^21.2.1",
"@angular/service-worker": "^21.2.1",
"@date-fns/tz": "^1.4.1",
"@date-fns/utc": "^2.1.1",
"@oazapfts/runtime": "^1.0.4",
"@oazapfts/runtime": "^1.2.0",
"date-fns": "^4.1.0",
"keycloak-angular": "^20.0.0",
"keycloak-js": "^26.2.1",
"rxjs": "~7.8.2",
"keycloak-angular": "^21.0.0",
"keycloak-js": "^26.2.3",
"rxjs": "^7.8.2",
"tslib": "^2.8.1"
},
"devDependencies": {
"@angular/build": "^21.0.4",
"@angular/cli": "^21.0.4",
"@angular/compiler-cli": "^21.0.6",
"@types/jasmine": "~5.1.13",
"jasmine-core": "~5.9.0",
"@angular/build": "^21.2.1",
"@angular/cli": "^21.2.1",
"@angular/compiler-cli": "^21.2.1",
"@types/jasmine": "~6.0.0",
"jasmine-core": "~6.1.0",
"karma": "~6.4.4",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.1",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"oazapfts": "^6.3.1",
"karma-jasmine-html-reporter": "~2.2.0",
"oazapfts": "^7.4.2",
"typescript": "~5.9.3"
}
}
@@ -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;
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ interface MemberForm {
export interface MemberCreateForm extends Omit<
MemberCreateArgs,
'geburtsdatum'
'geburtsdatum' | 'creationDate'
> {
geburtsdatum: Date | null;
}
+1707 -2682
View File
File diff suppressed because it is too large Load Diff