diff --git a/packages/api/src/controllers/membersController.ts b/packages/api/src/controllers/membersController.ts index 9dd55dc..c756a9b 100644 --- a/packages/api/src/controllers/membersController.ts +++ b/packages/api/src/controllers/membersController.ts @@ -103,7 +103,7 @@ export class MembersContoller extends Controller { ) { await this.membersService.scheduleDeletion( memberId, - cancelDate, + cancelDate ? cancelDate : null, request, ); } diff --git a/packages/api/src/dtos/member.ts b/packages/api/src/dtos/member.ts index c98f83a..5d781f7 100644 --- a/packages/api/src/dtos/member.ts +++ b/packages/api/src/dtos/member.ts @@ -1,51 +1,52 @@ import { type MemberModel as DbMember } from "../generated/prisma/models.js"; export interface MemberDto { - id: string | null; - vorname: string | null; - nachname: string | null; - geburtsdatum: string | null; - geburtsort: string | null; - geschlecht: string | null; - passnummer: string | null; - verein: boolean | null; - graduierung: string | null; - letztePruefung: string | null; - kontakt: string | null; - kuendigungzum: string | null; - marker: boolean | null; + id: string; + vorname: string; + nachname: string; + geburtsdatum: string; + geburtsort: string; + geschlecht: string; + passnummer: string; + verein: boolean; + graduierung: string; + letztePruefung: string; + kontakt: string; + kuendigungzum: string; + marker: boolean; } -export class MemberCreateArgs implements Omit { - vorname!: string | null; - nachname!: string | null; - geburtsdatum!: string | null; - geburtsort!: string | null; - geschlecht!: string | null; - passnummer!: string | null; - verein!: boolean | null; - graduierung!: string | null; - letztePruefung!: string | null; - kontakt!: string | null; - kuendigungzum!: string | null; - marker!: boolean | null; +export class MemberCreateArgs implements Omit< + MemberDto, + "id" | "kuendigungzum" | "letztePruefung" +> { + vorname!: string; + nachname!: string; + geburtsdatum!: string; + geburtsort!: string; + geschlecht!: string; + passnummer!: string; + verein!: boolean; + graduierung!: string; + kontakt!: string; + marker!: boolean; } export function mapDbMemberToMember(dbMember: DbMember): MemberDto { return { - geburtsdatum: dbMember.geburtsdatum?.toDateString() ?? null, + geburtsdatum: dbMember.geburtsdatum?.toDateString() ?? "", id: dbMember.id, - nachname: dbMember.nachname, - geburtsort: dbMember.geburtsort, - geschlecht: dbMember.geschlecht, - graduierung: dbMember.graduierung, - vorname: dbMember.vorname, - kontakt: dbMember.kontakt, - kuendigungzum: dbMember.kuendigungzum?.toDateString() ?? null, - letztePruefung: dbMember.letztePruefung?.toDateString() ?? null, - passnummer: dbMember.passnummer, - verein: dbMember.verein, - marker: dbMember.marker, + nachname: dbMember.nachname ?? "", + geburtsort: dbMember.geburtsort ?? "", + geschlecht: dbMember.geschlecht ?? "", + graduierung: dbMember.graduierung ?? "", + vorname: dbMember.vorname ?? "", + kontakt: dbMember.kontakt ?? "", + kuendigungzum: dbMember.kuendigungzum?.toDateString() ?? "", + letztePruefung: dbMember.letztePruefung?.toDateString() ?? "", + passnummer: dbMember.passnummer ?? "", + verein: dbMember.verein ?? false, + marker: dbMember.marker ?? false, }; } diff --git a/packages/api/src/services/membersService.ts b/packages/api/src/services/membersService.ts index 62c4cd8..fe0b5d6 100644 --- a/packages/api/src/services/membersService.ts +++ b/packages/api/src/services/membersService.ts @@ -68,7 +68,19 @@ export class MembersService { const modifiedMember = await this.database.doRequest( async prisma => await prisma.member.update({ - data: { ...member, id: id }, + data: { + ...member, + id: id, + letztePruefung: member.letztePruefung + ? member.letztePruefung + : null, + geburtsdatum: member.geburtsdatum + ? member.geburtsdatum + : null, + kuendigungzum: member.kuendigungzum + ? member.kuendigungzum + : null, + }, where: { id: id }, }), request, @@ -79,7 +91,7 @@ export class MembersService { public async scheduleDeletion( memberId: string, - cancelDate: Date, + cancelDate: Date | null, request: express.Request, ) { await this.database.doRequest( diff --git a/packages/frontend/src/app/components/delete-member-dialog/delete-member-dialog.component.html b/packages/frontend/src/app/components/delete-member-dialog/delete-member-dialog.component.html index 863d6f4..62dee46 100644 --- a/packages/frontend/src/app/components/delete-member-dialog/delete-member-dialog.component.html +++ b/packages/frontend/src/app/components/delete-member-dialog/delete-member-dialog.component.html @@ -1,13 +1,17 @@ -
-

Name: {{ member.vorname }} {{ member.nachname }}

-

Passnummer: {{ member.passnummer }}

-

Geburtsdatum: {{ member.geburtsdatum??undefined | datePipe }}

- - Kündigun zum - - DD.MM.YYYY - - - - + +

Name: {{ member.vorname }} {{ member.nachname }}

+

Passnummer: {{ member.passnummer }}

+

Geburtsdatum: {{ member.geburtsdatum | datePipe }}

+ + Kündigun zum + + DD.MM.YYYY + + + +
diff --git a/packages/frontend/src/app/components/delete-member-dialog/delete-member-dialog.component.ts b/packages/frontend/src/app/components/delete-member-dialog/delete-member-dialog.component.ts index b3fe500..8a91551 100644 --- a/packages/frontend/src/app/components/delete-member-dialog/delete-member-dialog.component.ts +++ b/packages/frontend/src/app/components/delete-member-dialog/delete-member-dialog.component.ts @@ -1,5 +1,5 @@ -import { Component, Inject } from '@angular/core'; -import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { Component, inject, signal } from '@angular/core'; +import { Field, form } from '@angular/forms/signals'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatOptionModule } from '@angular/material/core'; @@ -9,18 +9,17 @@ 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, dateToLocal } from '../../pipes/date-pipe.pipe.js'; +import { + DatePipePipe, + dateToLocal, + dateToUTC, +} from '../../pipes/date-pipe.pipe.js'; import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js'; -type DeletionForm = { - kuendigungzum: FormControl; -}; - @Component({ selector: 'app-delete-member-dialog', standalone: true, imports: [ - ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatCheckboxModule, @@ -29,32 +28,31 @@ type DeletionForm = { MatOptionModule, MatDatepickerModule, DatePipePipe, + Field, ], templateUrl: './delete-member-dialog.component.html', styleUrl: './delete-member-dialog.component.scss', }) export class DeleteMemberDialogComponent { - form: FormGroup; - member: api.MemberDto; - constructor( - @Inject(MAT_DIALOG_DATA) public data: { member: api.MemberDto }, - private dialogRef: MatDialogRef, - ) { - this.member = data.member; - this.form = new FormGroup({ - kuendigungzum: new FormControl(null), - }); - } + private readonly data = inject<{ member: api.MemberDto }>(MAT_DIALOG_DATA); + private readonly dialogRef = inject( + MatDialogRef, + ); + private readonly deletionModel = signal({ + kuendigungzum: this.data.member.kuendigungzum + ? dateToUTC(this.data.member.kuendigungzum) + : null, + }); + protected readonly form = form(this.deletionModel); + protected readonly member = this.data.member; - async submit() { - const date = this.form.controls.kuendigungzum.value; - if (date) { - await api.scheduleDeletion( - this.member.id!, - dateToLocal(date).toISOString(), - ); + protected async submit() { + const date = this.deletionModel().kuendigungzum; + await api.scheduleDeletion( + this.member.id, + date ? dateToLocal(date).toISOString() : '', + ); - this.dialogRef.close(); - } + this.dialogRef.close(); } } diff --git a/packages/frontend/src/app/components/edit-member-dialog/edit-member-dialog.component.html b/packages/frontend/src/app/components/edit-member-dialog/edit-member-dialog.component.html index bdb1335..5c2a035 100644 --- a/packages/frontend/src/app/components/edit-member-dialog/edit-member-dialog.component.html +++ b/packages/frontend/src/app/components/edit-member-dialog/edit-member-dialog.component.html @@ -1,44 +1,41 @@ -
- - Vorname - - - - Nachname - - - - Geschlecht - - - - Passnummer - - - - Geburtsdatum - - DD.MM.YYYY - - - - - Geburtsort - - - - Unterrichtsvertrag? - - Ja - Nein - - - - Vereinsmitglied? - - Ja - Nein - - - + + + Vorname + + + + Nachname + + + + Geschlecht + + + + Passnummer + + + + Geburtsdatum + + DD.MM.YYYY + + + + + Geburtsort + + + + Vereinsmitglied? + + Ja + Nein + + +
diff --git a/packages/frontend/src/app/components/edit-member-dialog/edit-member-dialog.component.ts b/packages/frontend/src/app/components/edit-member-dialog/edit-member-dialog.component.ts index 277532e..9455d96 100644 --- a/packages/frontend/src/app/components/edit-member-dialog/edit-member-dialog.component.ts +++ b/packages/frontend/src/app/components/edit-member-dialog/edit-member-dialog.component.ts @@ -1,5 +1,5 @@ -import { Component, Inject } from '@angular/core'; -import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { Component, inject, signal } from '@angular/core'; +import { Field, form } from '@angular/forms/signals'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatOptionModule } from '@angular/material/core'; @@ -9,13 +9,12 @@ 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 { MemberDtoHelper } from '../../util/member.js'; +import { MemberDtoHelper, MemberEditForm } from '../../util/member.js'; @Component({ selector: 'app-edit-member-dialog', standalone: true, imports: [ - ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatCheckboxModule, @@ -23,25 +22,28 @@ import { MemberDtoHelper } from '../../util/member.js'; MatSelectModule, MatOptionModule, MatDatepickerModule, + Field, ], templateUrl: './edit-member-dialog.component.html', styleUrl: './edit-member-dialog.component.scss', }) export class EditMemberDialogComponent { - form: FormGroup; - constructor( - @Inject(MAT_DIALOG_DATA) public data: { member: api.MemberDto }, - private memberDtoHelper: MemberDtoHelper, - private dialogRef: MatDialogRef, - ) { - this.form = this.memberDtoHelper.newMemberForm(data.member); - } + private readonly data = inject<{ member: api.MemberDto }>(MAT_DIALOG_DATA); + private readonly memberDtoHelper = inject(MemberDtoHelper); + private readonly dialogRef = inject( + MatDialogRef, + ); - submit() { - const member: api.MemberDto = this.memberDtoHelper.memberFromForm( - this.form, + private readonly memberModel = signal( + this.memberDtoHelper.editMemberForm(this.data.member), + ); + protected readonly form = form(this.memberModel); + + protected async submit() { + const member: api.MemberDto = this.memberDtoHelper.memberFromSignal( + this.memberModel(), ); - api.updateMember(member); + await api.updateMember(member); this.dialogRef.close(); } } diff --git a/packages/frontend/src/app/components/new-member/new-member.component.html b/packages/frontend/src/app/components/new-member/new-member.component.html index a89fec7..205c0c1 100644 --- a/packages/frontend/src/app/components/new-member/new-member.component.html +++ b/packages/frontend/src/app/components/new-member/new-member.component.html @@ -1,23 +1,23 @@

Neues Mitglied anlegen

-
+ Vorname - + Nachname - + Geschlecht - + Geburtsdatum + [field]="memberForm.geburtsdatum" /> DD.MM.YYYY Geburtsort - + - Verein Beigetreten? + Verein Beigetreten? Passnummer (falls vorhanden) - + Kontakt - + - + diff --git a/packages/frontend/src/app/components/new-member/new-member.component.ts b/packages/frontend/src/app/components/new-member/new-member.component.ts index 52372ca..395049e 100644 --- a/packages/frontend/src/app/components/new-member/new-member.component.ts +++ b/packages/frontend/src/app/components/new-member/new-member.component.ts @@ -1,5 +1,5 @@ -import { Component, inject } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; +import { Component, inject, signal } from '@angular/core'; +import { Field, form } from '@angular/forms/signals'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDatepickerModule } from '@angular/material/datepicker'; @@ -7,43 +7,49 @@ import { MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import * as api from '../../generated-api/api.js'; -import { MemberDtoHelper } from '../../util/member.js'; +import { MemberCreateForm, MemberDtoHelper } from '../../util/member.js'; @Component({ selector: 'app-new-member', standalone: true, providers: [], imports: [ - ReactiveFormsModule, MatFormFieldModule, MatDatepickerModule, MatInputModule, MatCheckboxModule, MatButtonModule, + Field, ], templateUrl: './new-member.component.html', styleUrl: './new-member.component.scss', }) export class NewMemberComponent { private readonly memberDtoHelper = inject(MemberDtoHelper); - - constructor() {} - memberForm = this.memberDtoHelper.newMemberForm(); - private dialogRef: MatDialogRef = inject( + private readonly dialogRef: MatDialogRef = inject( MatDialogRef, ); - onSubmit() { - const newMember: api.MemberCreateArgs = - this.memberDtoHelper.newMemberFromForm(this.memberForm); + private readonly memberModel = signal( + this.memberDtoHelper.newMemberForm(), + ); + protected readonly memberForm = form(this.memberModel); - api.createMember(newMember) - .then(() => { - console.log('Successfully added Member'); - this.dialogRef.close(); - }) - .catch(e => { - console.error('Error adding member', e); - }); + protected async onSubmit(): Promise { + if (!this.memberForm().valid()) { + return; + } + + const newMember = this.memberDtoHelper.newMemberFromSignal( + this.memberModel(), + ); + + try { + await api.createMember(newMember); + console.log('Successfully added Member'); + this.dialogRef.close(); + } catch (e) { + console.error('Error adding member', e); + } } } diff --git a/packages/frontend/src/app/components/view-members/view-members.html b/packages/frontend/src/app/components/view-members/view-members.html index 087765a..c1fe5ef 100644 --- a/packages/frontend/src/app/components/view-members/view-members.html +++ b/packages/frontend/src/app/components/view-members/view-members.html @@ -9,10 +9,13 @@
- +
- + Aktiv @@ -30,20 +33,21 @@ DD.MM.YYYY
- Nur mit Passnummer + Nur mit Passnummer
- (''); - protected readonly statusControl = new FormControl( - MemberStatusFilter.ACTIVE, + private readonly changeDetectorRef = inject(ChangeDetectorRef); + + protected readonly MemberStatusFilter = MemberStatusFilter; + private readonly filterModel = signal({ + filterString: '', + status: MemberStatusFilter.ACTIVE, + statusDate: null as Date | null, + onlyWithID: false, + }); + protected readonly filterForm = form(this.filterModel); + + protected readonly statusDateDisabled = computed( + () => this.filterForm.status().value() === MemberStatusFilter.ALL, ); - protected readonly statusDateControl = new FormControl(null); - protected readonly onlyWithID = new FormControl(false); - protected dataSource = new MatTableDataSource(); + + + protected readonly dataSource = new MatTableDataSource(); private readonly datePipe = new DatePipePipe(); protected readonly displayedColumns: string[] = [ @@ -159,7 +175,7 @@ export class ViewMembers implements AfterViewInit { return originalFilterPredicate( { ...data, - id: null, + id: '', geburtsdatum: this.datePipe.transform( data.geburtsdatum ?? undefined, ), @@ -173,42 +189,11 @@ export class ViewMembers implements AfterViewInit { 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}`; - }); - this.statusControl.valueChanges - .pipe(takeUntilDestroyed()) - .subscribe(value => { - if ( - value === MemberStatusFilter.ALL && - this.statusDateControl.enabled - ) { - return this.statusDateControl.disable(); - } else if ( - value !== MemberStatusFilter.ALL && - this.statusDateControl.disabled - ) { - return this.statusDateControl.enable(); - } - }); + effect(() => { + const filters = this.filterModel(); + this.dataSource.filter = `${filters.filterString?.replaceAll(';', '') ?? ''};${filters.status};${filters.statusDate?.toISOString()};${filters.onlyWithID}`; + }); this.updateData(); } @@ -221,5 +206,6 @@ export class ViewMembers implements AfterViewInit { private async updateData(): Promise { const members = await api.getMembers(); this.dataSource.data = members; + this.changeDetectorRef.detectChanges(); } } diff --git a/packages/frontend/src/app/util/member.ts b/packages/frontend/src/app/util/member.ts index a18714f..669f732 100644 --- a/packages/frontend/src/app/util/member.ts +++ b/packages/frontend/src/app/util/member.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; +import { FormControl } from '@angular/forms'; import { MAT_DATE_LOCALE } from '@angular/material/core'; -import { Locale, parse } from 'date-fns'; +import { Locale } from 'date-fns'; import { MemberCreateArgs, MemberDto } from '../generated-api/api'; import { dateToLocal, dateToUTC } from '../pipes/date-pipe.pipe'; @@ -21,86 +21,78 @@ interface MemberForm { marker: FormControl; } +export interface MemberCreateForm extends Omit< + MemberCreateArgs, + 'geburtsdatum' +> { + geburtsdatum: Date | null; +} + +export interface MemberEditForm extends Omit< + MemberDto, + 'geburtsdatum' | 'kuendigungzum' | 'letztePruefung' +> { + geburtsdatum: Date | null; + kuendigungzum: Date | null; + letztePruefung: Date | null; +} + @Injectable({ providedIn: 'root', }) export class MemberDtoHelper { constructor(@Inject(MAT_DATE_LOCALE) private locale: Locale) {} - public newMemberForm(member?: MemberDto): FormGroup { - const geburtsdatum = member?.geburtsdatum; - let enDate: Date | null = null; - if (geburtsdatum) { - enDate = parse(geburtsdatum, 'dd.MM.yyyy', new Date(), { - //TODO get dd.MM.yyyy from MAT_DATE_LOCALE or LOCALE_ID - locale: this.locale, - }); - } - - return new FormGroup({ - id: new FormControl(member?.id), - vorname: new FormControl(member?.vorname), - nachname: new FormControl(member?.nachname), - geschlecht: new FormControl(member?.geschlecht), - geburtsdatum: new FormControl( - member?.geburtsdatum ? dateToUTC(member?.geburtsdatum) : null, - ), - geburtsort: new FormControl(member?.geburtsort), - passnummer: new FormControl(member?.passnummer), - verein: new FormControl(member?.verein), - kontakt: new FormControl(member?.kontakt), - graduierung: new FormControl(member?.graduierung), - 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): MemberDto { + public newMemberForm(): MemberCreateForm { 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 - ? dateToLocal(form.controls.geburtsdatum.value).toISOString() - : null, - geburtsort: form.controls.geburtsort.value ?? null, - passnummer: form.controls.passnummer.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 - ? 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, + vorname: '', + nachname: '', + geschlecht: '', + geburtsdatum: null, + geburtsort: '', + passnummer: '', + verein: false, + kontakt: '', + graduierung: '', + marker: false, }; } - newMemberFromForm(form: FormGroup): MemberCreateArgs { + public newMemberFromSignal(sig: MemberCreateForm): 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 - ? dateToLocal(form.controls.geburtsdatum.value).toISOString() + ...sig, + geburtsdatum: sig.geburtsdatum + ? dateToLocal(sig.geburtsdatum).toISOString() + : '', + }; + } + + public editMemberForm(member: MemberDto): MemberEditForm { + return { + ...member, + geburtsdatum: member.geburtsdatum + ? dateToUTC(member.geburtsdatum) : null, - geburtsort: form.controls.geburtsort.value ?? null, - passnummer: form.controls.passnummer.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, + kuendigungzum: member.kuendigungzum + ? dateToUTC(member.kuendigungzum) + : null, + letztePruefung: member.letztePruefung + ? dateToUTC(member.letztePruefung) + : null, + }; + } + + public memberFromSignal(sig: MemberEditForm): MemberDto { + return { + ...sig, + geburtsdatum: sig.geburtsdatum + ? dateToLocal(sig.geburtsdatum).toISOString() + : '', + kuendigungzum: sig.kuendigungzum + ? dateToLocal(sig.kuendigungzum).toISOString() + : '', + letztePruefung: sig.letztePruefung + ? dateToLocal(sig.letztePruefung).toISOString() + : '', }; } }