Fusion UI LogoFusion UI Logo
Fusion UI LogoFusion UI Logo
/
Getting Started
Concepts
Components
Example Apps
Getting Started
Concepts
Components
Example Apps
Fusion UI LogoFusion UI Logo
Getting Started
Concepts
Components
Example Apps
Overview
Example Apps
Analytics Dashboard
Customer Management
Account Settings
Support Inbox
Subscription Checkout

Customer Management

Search, filter and edit customers in a table with a detail sheet.

Preview

/preview/customer-management

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 => (
<Status
status={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 (
<PageHeaderView
pageHeaderTitle={"Customers"}
subtitle={`${customers.length} customers`}
breadcrumbs={
<Breadcrumbs
breadcrumbs={[
{title: "Sales", href: "#sales"},
{title: "Customers", href: "/preview/customer-management"},
]}
/>
}
buttons={
<Button
size={"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>
<CustomerSheet
key={editing?.id ?? "closed"}
customer={editing}
onSave={save}
onClose={() => setEditing(null)}
/>
<Modal
open={deleting !== null}
size={"small"}
onAbort={() => setDeleting(null)}
modalTitle={
<ModalTitle
icon={
<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.

Page HeaderBreadcrumbsData TableSheetFormInputSelectSwitchModalSnackbarStatusButton
On this page
  • Preview
  • What it shows
  • Components used