import { DeesElement, customElement, html, css, cssManager, state, type TemplateResult } from '../plugins.js'; import { deesCatalog } from '../plugins.js'; import { appState, type IAppState, type IContact } from '../state/appstate.js'; import { appRouter } from '../router.js'; import { viewHostCss } from './shared/index.js'; import type { IStatsTile } from '@design.estate/dees-catalog'; @customElement('sipproxy-view-contacts') export class SipproxyViewContacts extends DeesElement { @state() accessor appData: IAppState = appState.getState(); @state() accessor saving = false; public static styles = [ cssManager.defaultStyles, viewHostCss, css` :host { display: block; padding: 16px; } .view-section { margin-bottom: 24px; } `, ]; connectedCallback() { super.connectedCallback(); this.rxSubscriptions.push({ unsubscribe: appState.subscribe((s) => { this.appData = s; }), } as any); } // ---------- CRUD operations ---------- private async openAddModal() { const { DeesModal } = await import('@design.estate/dees-catalog'); const formData = { name: '', number: '', company: '', notes: '' }; await DeesModal.createAndShow({ heading: 'Add Contact', content: html`
Are you sure you want to delete ${contact.name}?
This action cannot be undone.
`, menuOptions: [ { name: 'Cancel', action: async (modal) => { modal.destroy(); }, }, { name: 'Delete', action: async (modal) => { const updatedContacts = this.appData.contacts.filter((c) => c.id !== contact.id); await this.saveContacts(updatedContacts); modal.destroy(); deesCatalog.DeesToast.info('Contact deleted'); }, }, ], }); } private async toggleStar(contact: IContact) { const updatedContacts = this.appData.contacts.map((c) => c.id === contact.id ? { ...c, starred: !c.starred } : c, ); await this.saveContacts(updatedContacts); } private async saveContacts(contacts: IContact[]) { this.saving = true; try { const config = await appState.apiGetConfig(); config.contacts = contacts; await appState.apiSaveConfig(config); } catch (e: any) { deesCatalog.DeesToast.error(`Save failed: ${e.message}`, 4000); } finally { this.saving = false; } } private getColumns() { return [ { key: 'starred', header: '', sortable: false, renderer: (val: boolean | undefined, row: IContact) => { const starred = val === true; return html` { e.stopPropagation(); this.toggleStar(row); }} >${starred ? '\u2605' : '\u2606'} `; }, }, { key: 'name', header: 'Name', sortable: true, }, { key: 'number', header: 'Number', renderer: (val: string) => html`${val}`, }, { key: 'company', header: 'Company', renderer: (val: string | undefined) => val || '-', }, { key: 'notes', header: 'Notes', renderer: (val: string | undefined) => html``, }, ]; } private getDataActions() { return [ { name: 'Call', iconName: 'lucide:phone' as any, type: ['inRow'] as any, actionFunc: async ({ item }: { item: IContact }) => { appState.selectContact(item); appRouter.navigateTo('phone' as any); }, }, { name: 'Star', iconName: 'lucide:star' as any, type: ['inRow'] as any, actionFunc: async ({ item }: { item: IContact }) => { await this.toggleStar(item); }, }, { name: 'Edit', iconName: 'lucide:pencil' as any, type: ['inRow'] as any, actionFunc: async ({ item }: { item: IContact }) => { await this.openEditModal(item); }, }, { name: 'Delete', iconName: 'lucide:trash2' as any, type: ['inRow'] as any, actionFunc: async ({ item }: { item: IContact }) => { await this.deleteContact(item); }, }, { name: 'Add Contact', iconName: 'lucide:plus' as any, type: ['header'] as any, actionFunc: async () => { await this.openAddModal(); }, }, ]; } // ---------- Render ---------- public render(): TemplateResult { const contacts = this.appData.contacts || []; const companies = new Set( contacts.map((c) => c.company?.trim()).filter((c) => c && c.length > 0), ); const tiles: IStatsTile[] = [ { id: 'total', title: 'Total Contacts', value: contacts.length, type: 'number', icon: 'lucide:contactRound', description: contacts.length === 1 ? '1 contact' : `${contacts.length} contacts`, }, { id: 'starred', title: 'Starred', value: contacts.filter((c) => c.starred).length, type: 'number', icon: 'lucide:star', color: 'hsl(45 93% 47%)', description: 'Quick-dial contacts', }, { id: 'companies', title: 'Companies', value: companies.size, type: 'number', icon: 'lucide:building2', description: `${companies.size} unique`, }, ]; return html`