feat: add CashboxExportSubscriptionDialog
This commit is contained in:
@@ -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>
|
||||||
@@ -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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user