import React, { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Mail, Package, Pencil, Phone, Plus, Users } from 'lucide-react'; import { Avatar, Button, DataTable, EmptyState, Field, Grid, IconButton, Input, KpiCard, Modal, PageHeader, Stack, } from '@/components/ds'; import { ListToolbar } from '@/components/doormile/ListToolbar'; import { useTenantCustomers, useUpdateTenantCustomer } from '@/lib/doormileHooks'; import { matchesQuery, number, percentOf, useDebouncedValue } from '@/lib/doormileFormat'; /** * Customers — the end customers a client's bookings are delivered to. * * The identifier and name fields differ between the two customer resources * this list can be fed from, so both are read through the accessors below * rather than assumed: a row that arrives as `firstname`/`lastname` and one * that arrives as `name` have to render identically. */ const custId = (row) => row.appcustomerid ?? row.tenantcustomerid ?? row.customerid ?? row.id; const custName = (row) => row.name || [row.firstname, row.lastname].filter(Boolean).join(' ') || '—'; const custPhone = (row) => row.phone || row.contactno || ''; const custEmail = (row) => row.email || ''; /** Tenant customers carry no booking count; 0 is honest rather than blank. */ const custBookings = (row) => row.totalbookings ?? 0; export default function Customers({ embedded = false }) { const navigate = useNavigate(); const { data: customers = [], isLoading, isFetching } = useTenantCustomers(); const updateCustomer = useUpdateTenantCustomer(); const [search, setSearch] = useState(''); const debouncedSearch = useDebouncedValue(search); const [activeFilter, setActiveFilter] = useState('all'); const [editRow, setEditRow] = useState(null); const [form, setForm] = useState({ name: '', phone: '', email: '' }); const stats = useMemo(() => { const total = customers.length; return { total, withEmail: customers.filter((row) => Boolean(row.email)).length, withPhone: customers.filter((row) => Boolean(row.phone)).length, totalBookings: customers.reduce((sum, row) => sum + (Number(row.totalbookings) || 0), 0), }; }, [customers]); const rows = useMemo(() => { let list = customers; if (activeFilter === 'withEmail') list = list.filter((row) => Boolean(row.email)); else if (activeFilter === 'withPhone') list = list.filter((row) => Boolean(row.phone)); return list.filter((row) => matchesQuery(row, [custName, custPhone, custEmail], debouncedSearch)); }, [customers, activeFilter, debouncedSearch]); const toggleFilter = (key) => setActiveFilter((current) => (current === key ? 'all' : key)); const openEdit = (row) => { setEditRow(row); setForm({ name: custName(row) === '—' ? '' : custName(row), phone: custPhone(row), email: custEmail(row) }); }; const save = () => { /* The record stores firstname/lastname but the dialog edits one name field. Splitting on the first space keeps the surname through a round trip instead of blanking it. */ const [firstname, ...rest] = String(form.name || '').trim().split(/\s+/); updateCustomer.mutate( { id: custId(editRow), data: { firstname: firstname || '', lastname: rest.join(' '), phone: form.phone || '', email: form.email || '', }, }, { onSuccess: (res) => res?.success !== false && setEditRow(null) } ); }; const columns = useMemo( () => [ { key: 'customer', header: 'Customer', sortable: true, accessor: custName, cell: (row) => (

{custName(row)}

ID #{custId(row) ?? '—'}

), }, { key: 'contact', header: 'Contact', sortable: true, accessor: custPhone, cell: (row) => (

{custEmail(row) ? (

) : null}
), }, { key: 'bookings', header: 'Bookings', sortValue: custBookings, accessor: (row) => number(custBookings(row), '0'), align: 'right', hideBelow: 'md', }, { key: 'actions', header: 'Actions', align: 'right', cell: (row) => (
openEdit(row)} />
), }, ], [] ); return ( setActiveFilter('all')} /> toggleFilter('withEmail')} /> toggleFilter('withPhone')} /> navigate('/doormile/customer/create')}> New customer } /> custId(row)} loading={isLoading} refreshing={isFetching && !isLoading} pageSize={10} defaultSort={{ key: 'customer', direction: 'asc' }} isFiltered={Boolean(debouncedSearch) || activeFilter !== 'all'} onClearFilters={() => { setSearch(''); setActiveFilter('all'); }} emptyState={ debouncedSearch || activeFilter !== 'all' ? undefined : ( navigate('/doormile/customer/create') }} /> ) } /> !open && setEditRow(null)} title="Edit customer" description={`ID #${custId(editRow || {}) ?? ''}`} icon={Users} busy={updateCustomer.isPending} footer={ <> } > setForm({ ...form, name: e.target.value })} placeholder="Anitha Raman" /> setForm({ ...form, phone: e.target.value })} placeholder="9876543210" /> setForm({ ...form, email: e.target.value })} placeholder="anitha@example.com" /> ); }