feat: add CashboxExportSubscriptionDialog

This commit is contained in:
Bastian Wagner
2026-08-04 08:34:01 +02:00
parent 11b70b9337
commit db061a7ef7
3 changed files with 159 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
<h2 mat-dialog-title>Automatischen Versand einrichten</h2>
<mat-dialog-content>
<mat-form-field appearance="outline">
<mat-label>E-Mail-Adresse hinzufügen</mat-label>
<input matInput #recipientInput (keydown.enter)="addRecipient(recipientInput.value); recipientInput.value = ''" />
</mat-form-field>
<mat-chip-set>
@for (recipient of recipients(); track recipient) {
<mat-chip (removed)="removeRecipient(recipient)">
{{ recipient }}
<button matChipRemove><mat-icon>cancel</mat-icon></button>
</mat-chip>
}
</mat-chip-set>
<form [formGroup]="form">
<mat-form-field appearance="outline">
<mat-label>Intervall</mat-label>
<mat-select formControlName="interval">
<mat-option value="monthly">Monatlich</mat-option>
<mat-option value="quarterly">Quartalsweise</mat-option>
<mat-option value="yearly">Jährlich</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle formControlName="active">Aktiv</mat-slide-toggle>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button type="button" (click)="dialogRef.close()">Abbrechen</button>
<button mat-flat-button type="button" (click)="save()">Speichern</button>
</mat-dialog-actions>

View File

@@ -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<CashboxExportSubscriptionDialog>;
let getSubscription: ReturnType<typeof vi.fn>;
let updateSubscription: ReturnType<typeof vi.fn>;
let dialogRef: { close: ReturnType<typeof vi.fn> };
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([]);
});
});

View File

@@ -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<CashboxExportSubscriptionDialog>);
private readonly data = inject<{ teamId: number }>(MAT_DIALOG_DATA);
private readonly formBuilder = inject(FormBuilder);
private readonly api = inject(CashboxExportApi);
protected readonly recipients = signal<string[]>([]);
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());
}
}