diff --git a/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.html b/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.html new file mode 100644 index 0000000..0f470ff --- /dev/null +++ b/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.html @@ -0,0 +1,30 @@ +

Automatischen Versand einrichten

+ + + E-Mail-Adresse hinzufügen + + + + @for (recipient of recipients(); track recipient) { + + {{ recipient }} + + + } + +
+ + Intervall + + Monatlich + Quartalsweise + Jährlich + + + Aktiv +
+
+ + + + diff --git a/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.spec.ts b/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.spec.ts new file mode 100644 index 0000000..45d7910 --- /dev/null +++ b/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.spec.ts @@ -0,0 +1,62 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { of } from 'rxjs'; +import { CashboxExportApi } from '../../../../core/team/cashbox-export-api'; +import { CashboxExportSubscriptionDialog } from './cashbox-export-subscription-dialog'; + +describe('CashboxExportSubscriptionDialog', () => { + let fixture: ComponentFixture; + let getSubscription: ReturnType; + let updateSubscription: ReturnType; + let dialogRef: { close: ReturnType }; + + beforeEach(async () => { + getSubscription = vi.fn(() => + of({ recipients: ['a@example.com'], interval: 'monthly', active: true, nextRunDate: '2026-09-01T00:00:00.000Z' }), + ); + updateSubscription = vi.fn(() => + of({ recipients: ['a@example.com', 'b@example.com'], interval: 'monthly', active: true, nextRunDate: '2026-09-01T00:00:00.000Z' }), + ); + dialogRef = { close: vi.fn() }; + + await TestBed.configureTestingModule({ + imports: [CashboxExportSubscriptionDialog], + providers: [ + { provide: MAT_DIALOG_DATA, useValue: { teamId: 5 } }, + { provide: MatDialogRef, useValue: dialogRef }, + { provide: CashboxExportApi, useValue: { getSubscription, updateSubscription } }, + ], + }).compileComponents(); + fixture = TestBed.createComponent(CashboxExportSubscriptionDialog); + fixture.detectChanges(); + }); + + it('loads the existing subscription into the form', () => { + expect(getSubscription).toHaveBeenCalledWith(5); + expect(fixture.componentInstance['recipients']()).toEqual(['a@example.com']); + }); + + it('rejects adding an invalid email', () => { + fixture.componentInstance['addRecipient']('not-an-email'); + expect(fixture.componentInstance['recipients']()).toEqual(['a@example.com']); + }); + + it('adds a valid email and saves the updated recipient list', () => { + fixture.componentInstance['addRecipient']('b@example.com'); + expect(fixture.componentInstance['recipients']()).toEqual(['a@example.com', 'b@example.com']); + + fixture.componentInstance['save'](); + + expect(updateSubscription).toHaveBeenCalledWith(5, { + recipients: ['a@example.com', 'b@example.com'], + interval: 'monthly', + active: true, + }); + expect(dialogRef.close).toHaveBeenCalled(); + }); + + it('removes a recipient', () => { + fixture.componentInstance['removeRecipient']('a@example.com'); + expect(fixture.componentInstance['recipients']()).toEqual([]); + }); +}); diff --git a/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.ts b/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.ts new file mode 100644 index 0000000..b052c07 --- /dev/null +++ b/myteamwallet_frontend_modern/src/app/features/team/cashbox/cashbox-export-subscription-dialog/cashbox-export-subscription-dialog.ts @@ -0,0 +1,67 @@ +import { Component, inject, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatChipsModule } from '@angular/material/chips'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatIconModule } from '@angular/material/icon'; +import { CashboxExportApi } from '../../../../core/team/cashbox-export-api'; +import { RecurringTransactionInterval } from '../../../../models/recurring-transaction.model'; + +const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + +@Component({ + selector: 'app-cashbox-export-subscription-dialog', + imports: [ + ReactiveFormsModule, + MatButtonModule, + MatChipsModule, + MatDialogModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatSelectModule, + MatSlideToggleModule, + ], + templateUrl: './cashbox-export-subscription-dialog.html', +}) +export class CashboxExportSubscriptionDialog { + protected readonly dialogRef = inject(MatDialogRef); + private readonly data = inject<{ teamId: number }>(MAT_DIALOG_DATA); + private readonly formBuilder = inject(FormBuilder); + private readonly api = inject(CashboxExportApi); + + protected readonly recipients = signal([]); + protected readonly form = this.formBuilder.nonNullable.group({ + interval: ['monthly' as RecurringTransactionInterval, Validators.required], + active: [false], + }); + + constructor() { + this.api.getSubscription(this.data.teamId).subscribe((subscription) => { + this.recipients.set(subscription.recipients); + this.form.setValue({ interval: subscription.interval, active: subscription.active }); + }); + } + + protected addRecipient(value: string): void { + const trimmed = value.trim(); + if (!EMAIL_PATTERN.test(trimmed) || this.recipients().includes(trimmed)) return; + this.recipients.set([...this.recipients(), trimmed]); + } + + protected removeRecipient(value: string): void { + this.recipients.set(this.recipients().filter((entry) => entry !== value)); + } + + protected save(): void { + if (this.form.invalid) return; + const { interval, active } = this.form.getRawValue(); + this.api + .updateSubscription(this.data.teamId, { recipients: this.recipients(), interval, active }) + .subscribe(() => this.dialogRef.close()); + } +}