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());
}
}