import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; import { A11yModule } from '@angular/cdk/a11y'; import { MatIconModule } from '@angular/material/icon'; import { JtGridBulkEvent, JtGridBulkService, JtGridCellDirective, JtGridColumn, JtGridComponent, JtGridRowEvent, RowAction } from '@jambotron/aria-grid'; import { ApiService } from '../../core/api.service'; import { Contact, errorText } from '../../core/models'; import { DialogService } from '../../ui/dialog.service'; import { ToastService } from '../../ui/toast.service'; /** Address book: grid, add/edit form, write to a contact. */ @Component({ selector: 'app-contacts', imports: [FormsModule, A11yModule, MatIconModule, JtGridComponent, JtGridCellDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: `
contacts

CONTACTS

{{ contacts().length }} contacts · suggested while you type an address

{{ row.name || '—' }} {{ row.email }} {{ row.phone || '' }} {{ row.company || '' }}
@if (editing(); as c) {
} ` }) export class ContactsPage { private api = inject(ApiService); private bulk = inject(JtGridBulkService); private dialogs = inject(DialogService); private toasts = inject(ToastService); private router = inject(Router); readonly contacts = signal([]); readonly loading = signal(true); readonly editing = signal(null); readonly formError = signal(''); readonly columns: JtGridColumn[] = [ { key: 'name', label: 'Name', sort: c => (c.name || '').toLowerCase() }, { key: 'email', label: 'Email', sort: c => c.email.toLowerCase() }, { key: 'phone', label: 'Phone', sort: c => c.phone || '' }, { key: 'company', label: 'Company', sort: c => (c.company || '').toLowerCase() } ]; readonly bulkActions: RowAction[] = [ { value: 'write', label: 'Write to them', icon: 'edit' }, { value: 'delete', label: 'Delete', icon: 'delete', danger: true } ]; readonly rowActions = (): RowAction[] => [ { value: 'write', label: 'Write', icon: 'edit', inline: true }, { value: 'edit', label: 'Edit', icon: 'manage_accounts', inline: true }, { value: 'delete', label: 'Delete', icon: 'delete', danger: true } ]; readonly byEmail = (c: Contact) => c.id ?? c.email; readonly nameOf = (c: Contact) => c.name || c.email; constructor() { this.load(); } load(): void { this.api.contacts().subscribe({ next: list => { this.contacts.set(list); this.loading.set(false); }, error: e => { this.loading.set(false); this.toasts.error(errorText(e)); } }); } edit(c: Contact): void { this.formError.set(''); this.editing.set({ ...c }); } save(): void { const c = this.editing(); if (!c) return; if (!/^[^@\s]+@[^@\s]+$/.test((c.email || '').trim())) { this.formError.set('Enter a valid email'); return; } const body = { name: (c.name || '').trim(), email: c.email.trim(), phone: (c.phone || '').trim(), company: (c.company || '').trim() }; (c.id ? this.api.updateContact(c.id, body) : this.api.addContact(body)).subscribe({ next: () => { this.editing.set(null); this.toasts.ok(c.id ? 'Contact saved' : 'Contact added'); this.load(); }, error: e => this.formError.set(errorText(e).includes('duplicate') ? 'This email is already in contacts' : errorText(e)) }); } onRow(e: JtGridRowEvent): void { if (e.action === 'write') this.router.navigate(['/compose', { to: e.row.email }]); if (e.action === 'edit') this.edit(e.row); if (e.action === 'delete') { this.dialogs.confirm(`Delete ${this.nameOf(e.row)}?`, 'The contact is removed from the address book.', 'Delete', true) .subscribe(ok => ok !== null && this.api.deleteContact(e.row.id!).subscribe({ next: () => { this.toasts.ok('Contact deleted'); this.load(); }, error: err => this.toasts.error(errorText(err)) })); } } onBulk(e: JtGridBulkEvent): void { if (e.action === 'write') { this.router.navigate(['/compose', { to: e.rows.map(c => c.email).join(', ') }]); return; } this.bulk.run({ rows: e.rows, label: 'Delete', danger: true, confirm: `Delete ${e.rows.length} contacts?`, run: c => this.api.deleteContact(c.id!) }).subscribe(() => this.load()); } }