Customer Management
Search, filter and edit customers in a table with a detail sheet.
Preview
File: app.tsx
"use client";import React, {useMemo, useState} from "react";import {Breadcrumbs,Button,ColumnConfig,Container,DataTable,IconAdd,IconDeleteBin5,IconEdit,IconErrorWarning,IconMore2Alt,Modal,ModalActions,ModalTitle,PageHeaderView,RowConfig,Status,Tag,useSimpleTable,useSnackbar,} from "@brynlabs/fusion-ui";import {Customer, initialCustomers} from "./data";import {CustomerSheet} from "./customer-sheet";import {tableText} from "../shared/table-text";const currency = (value: number) =>value.toLocaleString("en", {style: "currency", currency: "EUR", maximumFractionDigits: 0});const newCustomer = (id: number): Customer => ({id,name: "",email: "",company: "",plan: "Starter",active: true,newsletter: false,revenue: 0,});export const CustomerManagementApp = () => {const {showSnackbar} = useSnackbar();const [customers, setCustomers] = useState(initialCustomers);const [editing, setEditing] = useState<Customer | null>(null);const [deleting, setDeleting] = useState<Customer | null>(null);const options = useMemo(() => {const columns: ColumnConfig<Customer>[] = [{key: "name", label: "Name", render: customer => customer.name},{key: "email", label: "Email", render: customer => customer.email, initialWidth: 230},{key: "company", label: "Company", render: customer => customer.company},{key: "plan",label: "Plan",render: customer => (<Tag tagStyle={customer.plan === "Enterprise" ? "purple" : "primary"} emphasis={"low"}>{customer.plan}</Tag>),filter: {type: "multiSelect", getValue: customer => customer.plan},initialWidth: 130,},{key: "status",label: "Status",render: customer => (<Statusstatus={customer.active ? "Active" : "Inactive"}statusStyle={customer.active ? "success" : "neutral"}/>),filter: {type: "multiSelect",getValue: customer => (customer.active ? "Active" : "Inactive"),},initialWidth: 130,},{key: "revenue",label: "Revenue",render: customer => currency(customer.revenue),sortFunction: (a, b) => a.revenue - b.revenue,filter: {type: "range", min: 0, max: 70000, getValue: customer => customer.revenue},initialWidth: 120,},];return {columns,initialColumns: columns.map(column => column.key),searchIndexRow: (customer: Customer) => [customer.name, customer.email, customer.company],};}, []);const table = useSimpleTable<Customer>(customers, options);const rows: RowConfig<Customer>[] = table.rows.map(row => {if (row.loading) {return row;}return {...row,buttons: [{icon: <IconEdit />, action: () => setEditing(row.data)},{icon: <IconMore2Alt />,contextMenu: [{items: [{title: "Edit", icon: <IconEdit />, callback: () => setEditing(row.data)},{title: "Delete", icon: <IconDeleteBin5 />, callback: () => setDeleting(row.data)},],},],},],};});const save = (customer: Customer) => {setCustomers(current =>current.some(c => c.id === customer.id)? current.map(c => (c.id === customer.id ? customer : c)): [customer, ...current],);setEditing(null);showSnackbar({message: `${customer.name} was saved`});};const remove = (customer: Customer) => {setCustomers(current => current.filter(c => c.id !== customer.id));setDeleting(null);showSnackbar({message: `${customer.name} was deleted`,actionLabel: "Undo",onAction: () => setCustomers(current => [customer, ...current]),});};return (<PageHeaderViewpageHeaderTitle={"Customers"}subtitle={`${customers.length} customers`}breadcrumbs={<Breadcrumbsbreadcrumbs={[{title: "Sales", href: "#sales"},{title: "Customers", href: "/preview/customer-management"},]}/>}buttons={<Buttonsize={"small"}primaryIcon={<IconAdd />}onClick={() => setEditing(newCustomer(Math.max(0, ...customers.map(c => c.id)) + 1))}>New customer</Button>}><Container direction={"both"}><div style={{height: "calc(100dvh - 12rem)", minHeight: "26rem"}}><DataTable<Customer>{...table}rows={rows}hasCheckboxes={false}onRowClick={customer => setEditing(customer)}modalText={tableText}/></div></Container><CustomerSheetkey={editing?.id ?? "closed"}customer={editing}onSave={save}onClose={() => setEditing(null)}/><Modalopen={deleting !== null}size={"small"}onAbort={() => setDeleting(null)}modalTitle={<ModalTitleicon={<span style={{color: "var(--color--critical-500)"}}><IconErrorWarning /></span>}onAbort={() => setDeleting(null)}>Delete {deleting?.name}?</ModalTitle>}actions={<ModalActions><Button color={"critical"} onClick={() => deleting && remove(deleting)}>Delete customer</Button><Button color={"neutral"} onClick={() => setDeleting(null)}>Keep customer</Button></ModalActions>}>The customer and their history are removed. You can undo this right after deleting.</Modal></PageHeaderView>);};
What it shows
- A searchable data table with a row action menu
- A sheet with a form to edit the selected customer next to the table
- A modal to confirm deleting, and a snackbar confirming the change
- Breadcrumbs and page header actions
Components used
Read more about each component and when to use it.

