import { CommonModule, CurrencyPipe, DatePipe } from '@angular/common'; import { Component, OnInit, inject, signal } from '@angular/core'; import { FormArray, FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { CategoriesService } from '../../core/services/categories.service'; import { RecurringExpensesService } from '../../core/services/recurring-expenses.service'; import { ToastService } from '../../core/services/toast.service'; import { UiService } from '../../core/services/ui.service'; import type { RecurringExpense } from '../../shared/models'; const today = () => new Date().toISOString().slice(0, 10); @Component({ selector: 'app-recurring', standalone: true, imports: [CommonModule, ReactiveFormsModule, CurrencyPipe, DatePipe], templateUrl: './recurring.component.html' }) export class RecurringComponent implements OnInit { readonly ui = inject(UiService); private readonly fb = inject(FormBuilder); private readonly categoriesService = inject(CategoriesService); private readonly recurringService = inject(RecurringExpensesService); private readonly toast = inject(ToastService); readonly categories = this.categoriesService.items; readonly items = signal([]); readonly editingId = signal(null); readonly form = this.fb.nonNullable.group({ title: ['', [Validators.required, Validators.minLength(2)]], amount: [0, [Validators.required, Validators.min(0.01)]], categoryId: ['', Validators.required], defaultStatus: ['PENDING' as RecurringExpense['defaultStatus']], frequency: ['MONTHLY' as RecurringExpense['frequency']], intervalValue: [1, [Validators.required, Validators.min(1)]], startDate: [today(), Validators.required], nextRunDate: [today(), Validators.required], endDate: [''], maxOccurrences: [null as number | null], merchant: [''], description: [''], tagsText: [''], isActive: [true], customFields: this.fb.array([]) }); get customFields() { return this.form.controls.customFields as FormArray; } ngOnInit() { this.categoriesService.ensureLoaded(true); this.load(); } load() { this.recurringService.list().subscribe({ next: (response) => this.items.set(response.items) }); } addCustomField(key = '', value = '') { this.customFields.push(this.fb.group({ key: [key], value: [value] })); } removeCustomField(index: number) { this.customFields.removeAt(index); } save() { if (this.form.invalid) return; const raw = this.form.getRawValue(); const customEntries = this.customFields.getRawValue().map((item: { key: string; value: string }) => [item.key, item.value] as [string, string]).filter(([key, value]) => Boolean(key && value)); const payload = { ...raw, categoryId: raw.categoryId, endDate: raw.endDate || null, maxOccurrences: raw.maxOccurrences ? Number(raw.maxOccurrences) : null, tags: raw.tagsText.split(',').map((item) => item.trim()).filter(Boolean), customFields: Object.fromEntries(customEntries) }; const request = this.editingId() ? this.recurringService.update(this.editingId()!, payload) : this.recurringService.create(payload); request.subscribe({ next: () => { this.toast.success(this.ui.t('recurring.saved')); this.cancelEdit(); this.load(); }, error: (error) => this.toast.error(error.error?.message ?? this.ui.t('recurring.saveError')) }); } edit(item: RecurringExpense) { this.editingId.set(item.id); this.customFields.clear(); Object.entries(item.customFields || {}).forEach(([key, value]) => this.addCustomField(key, value)); this.form.reset({ title: item.title, amount: item.amount, categoryId: item.category.id, defaultStatus: item.defaultStatus, frequency: item.frequency, intervalValue: item.intervalValue, startDate: item.startDate, nextRunDate: item.nextRunDate, endDate: item.endDate || '', maxOccurrences: item.maxOccurrences, merchant: item.merchant || '', description: item.description || '', tagsText: item.tags.join(', '), isActive: item.isActive, customFields: [] as never[] }); } cancelEdit() { this.editingId.set(null); this.customFields.clear(); this.form.reset({ title: '', amount: 0, categoryId: '', defaultStatus: 'PENDING', frequency: 'MONTHLY', intervalValue: 1, startDate: today(), nextRunDate: today(), endDate: '', maxOccurrences: null, merchant: '', description: '', tagsText: '', isActive: true, customFields: [] as never[] }); } remove(item: RecurringExpense) { this.recurringService.delete(item.id).subscribe({ next: () => { this.toast.success(this.ui.t('recurring.deleted')); this.load(); }, error: (error) => this.toast.error(error.error?.message ?? this.ui.t('recurring.deleteError')) }); } runNow() { this.recurringService.runNow().subscribe({ next: () => { this.toast.success(this.ui.t('recurring.ran')); this.load(); }, error: (error) => this.toast.error(error.error?.message ?? this.ui.t('toast.error')) }); } }