/// import { Cell, computed, Default, ifElse, NAME, navigateTo, pattern, UI, } from "commontools"; import ContactDetail, { type Contact } from "./contact-detail.tsx"; interface Relationship { fromName: string; toName: string; label: Default; } interface Input { contacts: Cell>; relationships: Cell>; } interface Output { contacts: Contact[]; relationships: Relationship[]; } export default pattern(({ contacts, relationships }) => { const searchQuery = Cell.of(""); const newRelationFrom = Cell.of(""); const newRelationTo = Cell.of(""); const newRelationLabel = Cell.of(""); const contactCount = computed(() => contacts.get().length); const contactSelectItems = computed( () => contacts.map((c) => ({ label: c.name || "(unnamed)", value: c.name })), ); const matchesSearch = (contact: Contact, query: string): boolean => { if (!query) return true; const q = query.toLowerCase(); const name = (contact.name || "").toLowerCase(); const email = (contact.email || "").toLowerCase(); const company = (contact.company || "").toLowerCase(); return name.includes(q) || email.includes(q) || company.includes(q); }; return { [NAME]: "Contact Book", [UI]: ( Contacts ({contactCount}) {contacts.map((contact) => { const isVisible = computed(() => matchesSearch(contact, searchQuery.get()) ); const contactRelations = computed(() => { const name = contact.name; return relationships .get() .filter( (r: Relationship) => r.fromName === name || r.toName === name, ); }); return ifElse( isVisible, { const detail = ContactDetail({ contact }); return navigateTo(detail); }} > {contact.name || "(unnamed)"} {contact.email && ( {contact.email} )} {contact.company && ( {contact.company} )} {/* Show relationships */} {contactRelations.map((rel: Relationship) => ( ↔ {rel.fromName === contact.name ? rel.toName : rel.fromName} {rel.label && ({rel.label})} ))} { const current = contacts.get(); const idx = current.findIndex((c) => Cell.equals(contact, c) ); if (idx >= 0) { contacts.set(current.toSpliced(idx, 1)); } }} > × , null, ); })} {ifElse( computed(() => contacts.get().length === 0),
No contacts yet. Add one below!
, null, )}
{ contacts.push({ name: "", email: "", phone: "", company: "", tags: [], notes: "", createdAt: Date.now(), }); }} > Add Contact { const from = newRelationFrom.get(); const to = newRelationTo.get(); if (from && to && from !== to) { relationships.push({ fromName: from, toName: to, label: newRelationLabel.get(), }); newRelationFrom.set(""); newRelationTo.set(""); newRelationLabel.set(""); } }} > Link
), contacts, relationships, }; });