import { CommonModule, DatePipe } from '@angular/common'; import { Component, OnInit, inject, signal } from '@angular/core'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { AdminService } from '../../core/services/admin.service'; import { AppSettingsService } from '../../core/services/app-settings.service'; import { ToastService } from '../../core/services/toast.service'; import { UiService } from '../../core/services/ui.service'; import type { AdminSystemInfo, AppSettings, User } from '../../shared/models'; @Component({ selector: 'app-admin', standalone: true, imports: [CommonModule, ReactiveFormsModule, DatePipe], template: ` @if (systemInfo()) {

{{ ui.t('admin.techTitle') }}

{{ ui.t('admin.techSubtitle') }}
{{ systemInfo()!.environment }}
{{ ui.t('admin.appVersion') }}
{{ systemInfo()!.suiteVersion }}
API
{{ systemInfo()!.apiVersion }}
Web
{{ systemInfo()!.webVersion }}
Node.js
{{ systemInfo()!.nodeVersion }}
{{ ui.t('admin.database') }}{{ systemInfo()!.database }}
Upload dir{{ systemInfo()!.uploadDir }}
{{ ui.t('admin.registration') }}{{ systemInfo()!.registrationEnabled ? ui.t('common.active') : ui.t('common.blocked') }}
SMTP{{ systemInfo()!.smtpConfigured ? ui.t('admin.smtpReady') : ui.t('admin.smtpNotReady') }}
API base{{ systemInfo()!.sources.apiBasePath }}
{{ ui.t('table.date') }}{{ systemInfo()!.checkedAt | date:'yyyy-MM-dd HH:mm:ss' }}
{{ ui.t('admin.kpi.users') }}{{ systemInfo()!.counters.users }}
{{ ui.t('admin.kpi.expenses') }}{{ systemInfo()!.counters.expenses }}
{{ ui.t('admin.kpi.categories') }}{{ systemInfo()!.counters.categories }}
{{ ui.t('admin.kpi.merchants') }}{{ systemInfo()!.counters.merchants }}
{{ ui.t('admin.kpi.budgets') }}{{ systemInfo()!.counters.budgets }}
{{ ui.t('admin.kpi.recurring') }}{{ systemInfo()!.counters.recurring }}
{{ ui.t('admin.kpi.integrations') }}{{ systemInfo()!.counters.shoppingIntegrations }}
}

{{ ui.t('admin.settings') }}


{{ ui.t('admin.smtp') }}

{{ ui.t('admin.users') }}

{{ users().length }}
@for (user of users(); track user.id) { } @empty { }
{{ ui.t('admin.userLabel') }}{{ ui.t('admin.role') }}{{ ui.t('admin.status') }}{{ ui.t('admin.date') }}
{{ user.fullName }}
{{ user.email }}
{{ user.role }} {{ user.isActive ? ui.t('common.active') : ui.t('common.blocked') }} {{ user.createdAt | date:'short' }}
{{ ui.t('admin.noUsers') }}
` }) export class AdminComponent implements OnInit { readonly ui = inject(UiService); private readonly fb = inject(FormBuilder); private readonly admin = inject(AdminService); private readonly appSettings = inject(AppSettingsService); private readonly toast = inject(ToastService); readonly users = signal([]); readonly settings = signal(null); readonly systemInfo = signal(null); readonly saving = signal(false); readonly form = this.fb.nonNullable.group({ appName: ['', [Validators.required, Validators.minLength(2)]], defaultCurrency: ['PLN', Validators.required], allowedProofTypes: ['RECEIPT,INVOICE,NOTE,BANK_STATEMENT,OTHER', Validators.required], registrationEnabled: [true], smtpEnabled: [false], smtpHost: [''], smtpPort: [587], smtpSecure: [false], smtpUser: [''], smtpPassword: [''], smtpFromName: [''], smtpFromEmail: [''] }); ngOnInit() { this.load(); } load() { this.admin.getSettings().subscribe({ next: (response) => { this.settings.set(response.item); this.appSettings.applySettings(response.item); this.form.reset({ appName: response.item.appName, defaultCurrency: response.item.defaultCurrency, allowedProofTypes: response.item.allowedProofTypes.join(','), registrationEnabled: response.item.registrationEnabled, smtpEnabled: response.item.smtpEnabled, smtpHost: response.item.smtpHost ?? '', smtpPort: response.item.smtpPort, smtpSecure: response.item.smtpSecure, smtpUser: response.item.smtpUser ?? '', smtpPassword: response.item.smtpPassword ?? '', smtpFromName: response.item.smtpFromName ?? '', smtpFromEmail: response.item.smtpFromEmail ?? '' }); } }); this.admin.listUsers().subscribe({ next: (response) => this.users.set(response.items) }); this.admin.getSystemInfo().subscribe({ next: (response) => this.systemInfo.set(response.item) }); } save() { if (this.form.invalid) return; this.saving.set(true); const raw = this.form.getRawValue(); this.admin.updateSettings({ appName: raw.appName, defaultCurrency: raw.defaultCurrency, registrationEnabled: raw.registrationEnabled, allowedProofTypes: raw.allowedProofTypes.split(',').map((item) => item.trim()).filter(Boolean), uiPreferences: { theme: 'dark', density: 'comfortable', defaultStatsPeriod: 'month' }, smtpEnabled: raw.smtpEnabled, smtpHost: raw.smtpHost || null, smtpPort: Number(raw.smtpPort), smtpSecure: raw.smtpSecure, smtpUser: raw.smtpUser || null, smtpPassword: raw.smtpPassword || null, smtpFromName: raw.smtpFromName || null, smtpFromEmail: raw.smtpFromEmail || null }).subscribe({ next: (response) => { this.saving.set(false); this.settings.set(response.item); this.appSettings.applySettings(response.item); this.toast.success(this.ui.t('admin.settingsSaved')); this.admin.getSystemInfo().subscribe({ next: (systemResponse) => this.systemInfo.set(systemResponse.item) }); }, error: (error) => { this.saving.set(false); this.toast.error(error.error?.message ?? this.ui.t('admin.settingsError')); } }); } sendTest() { const to = this.form.getRawValue().smtpFromEmail; if (!to) { this.toast.error(this.ui.t('admin.missingFromEmail')); return; } this.admin.testSmtp(to).subscribe({ next: () => this.toast.success(this.ui.t('admin.testSent')), error: (error) => this.toast.error(error.error?.message ?? this.ui.t('admin.testError')) }); } toggleRole(user: User) { this.admin.updateUser(user.id, { role: user.role === 'ADMIN' ? 'USER' : 'ADMIN' }).subscribe({ next: (response) => { this.users.update((items) => items.map((item) => (item.id === user.id ? response.item : item))); this.toast.success(this.ui.t('admin.roleUpdated')); }, error: (error) => this.toast.error(error.error?.message ?? this.ui.t('admin.roleError')) }); } toggleActive(user: User) { this.admin.updateUser(user.id, { isActive: !user.isActive }).subscribe({ next: (response) => { this.users.update((items) => items.map((item) => (item.id === user.id ? response.item : item))); this.toast.success(this.ui.t('admin.statusUpdated')); }, error: (error) => this.toast.error(error.error?.message ?? this.ui.t('admin.statusError')) }); } }