Data Grid

11 blocks

Data Grid 01

Preview
npx nateui@latest add DataGridOrderHistory
Dark
import { useMemo, useState } from 'react'
import Avatar from '@/components/ui/Avatar'
import Button from '@/components/ui/Button'
import Card from '@/components/ui/Card'
import Dropdown from '@/components/ui/Dropdown'
import Input from '@/components/ui/Input'
import Tag from '@/components/ui/Tag'
import Container from '@/components/composites/Container'
import DataTable from '@/components/composites/DataTable'
import type { ColumnDef } from '@/components/composites/DataTable'
import {
    PiBuildings,
    PiCalendarBlank,
    PiCaretDown,
    PiCheck,
    PiCheckCircleFill,
    PiCheckSquare,
    PiDotsThreeVerticalBold,
    PiGlobe,
    PiMagnifyingGlass,
    PiPackage,
    PiStorefront,
    PiXCircleFill,
} from 'react-icons/pi'

const assetBase = 'https://statics.nateui.com/img'

type PaymentStatus = 'Paid' | 'Canceled'
type FulfillmentStatus = 'Fulfilled' | 'Unfulfilled'
type Channel = 'Storefront' | 'Marketplace' | 'Wholesale'
type PaymentFilter = PaymentStatus | 'All'
type FulfillmentFilter = FulfillmentStatus | 'All'
type DateRangeOption = {
    id: string
    label: string
    start: Date | null
    end: Date | null
}
type SortKey =
    | 'orderId'
    | 'total'
    | 'paymentStatus'
    | 'customer'
    | 'orderDate'
    | 'channel'

type OrderRow = {
    orderId: string
    total: number
    paymentStatus: PaymentStatus
    fulfillmentStatus: FulfillmentStatus | null
    customer: string
    customerAvatar: string
    orderDate: string
    orderDateLabel: string
    channel: Channel
}

const orderRows: OrderRow[] = [
    { orderId: 'ORD-8401', total: 482.75, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Mara Voss', customerAvatar: `${assetBase}/avatars/thumb-1.jpg`, orderDate: '2026-04-29', orderDateLabel: 'Wed 29 Apr, 2026', channel: 'Storefront' },
    { orderId: 'ORD-8400', total: 1260.4, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Jon Bellamy', customerAvatar: `${assetBase}/avatars/thumb-2.jpg`, orderDate: '2026-04-28', orderDateLabel: 'Tue 28 Apr, 2026', channel: 'Wholesale' },
    { orderId: 'ORD-8399', total: 94.5, paymentStatus: 'Canceled', fulfillmentStatus: null, customer: 'Talia Nunez', customerAvatar: `${assetBase}/avatars/thumb-3.jpg`, orderDate: '2026-04-27', orderDateLabel: 'Mon 27 Apr, 2026', channel: 'Marketplace' },
    { orderId: 'ORD-8398', total: 739.2, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Evan Rios', customerAvatar: `${assetBase}/avatars/thumb-4.jpg`, orderDate: '2026-04-25', orderDateLabel: 'Sat 25 Apr, 2026', channel: 'Storefront' },
    { orderId: 'ORD-8397', total: 215.99, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Nina Carver', customerAvatar: `${assetBase}/avatars/thumb-5.jpg`, orderDate: '2026-04-24', orderDateLabel: 'Fri 24 Apr, 2026', channel: 'Marketplace' },
    { orderId: 'ORD-8396', total: 0, paymentStatus: 'Canceled', fulfillmentStatus: null, customer: 'Lena Ortiz', customerAvatar: `${assetBase}/avatars/thumb-6.jpg`, orderDate: '2026-04-23', orderDateLabel: 'Thu 23 Apr, 2026', channel: 'Wholesale' },
    { orderId: 'ORD-8395', total: 368.65, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Owen Hale', customerAvatar: `${assetBase}/avatars/thumb-7.jpg`, orderDate: '2026-04-22', orderDateLabel: 'Wed 22 Apr, 2026', channel: 'Storefront' },
    { orderId: 'ORD-8394', total: 1098, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Aria Moss', customerAvatar: `${assetBase}/avatars/thumb-8.jpg`, orderDate: '2026-04-21', orderDateLabel: 'Tue 21 Apr, 2026', channel: 'Marketplace' },
    { orderId: 'ORD-8393', total: 56.25, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Calder Reed', customerAvatar: `${assetBase}/avatars/thumb-9.jpg`, orderDate: '2026-04-20', orderDateLabel: 'Mon 20 Apr, 2026', channel: 'Wholesale' },
    { orderId: 'ORD-8392', total: 824.1, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Iris Vale', customerAvatar: `${assetBase}/avatars/thumb-10.jpg`, orderDate: '2026-04-18', orderDateLabel: 'Sat 18 Apr, 2026', channel: 'Storefront' },
    { orderId: 'ORD-8391', total: 175.8, paymentStatus: 'Canceled', fulfillmentStatus: null, customer: 'Micah Stone', customerAvatar: `${assetBase}/avatars/thumb-11.jpg`, orderDate: '2026-04-17', orderDateLabel: 'Fri 17 Apr, 2026', channel: 'Marketplace' },
    { orderId: 'ORD-8390', total: 432.6, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Sora Kim', customerAvatar: `${assetBase}/avatars/thumb-12.jpg`, orderDate: '2026-04-16', orderDateLabel: 'Thu 16 Apr, 2026', channel: 'Wholesale' },
    { orderId: 'ORD-8389', total: 267.35, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Drew Palmer', customerAvatar: `${assetBase}/avatars/thumb-13.jpg`, orderDate: '2026-04-15', orderDateLabel: 'Wed 15 Apr, 2026', channel: 'Storefront' },
    { orderId: 'ORD-8388', total: 911.5, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Rae Sullivan', customerAvatar: `${assetBase}/avatars/thumb-14.jpg`, orderDate: '2026-04-14', orderDateLabel: 'Tue 14 Apr, 2026', channel: 'Marketplace' },
    { orderId: 'ORD-8387', total: 0, paymentStatus: 'Canceled', fulfillmentStatus: null, customer: 'Theo Wren', customerAvatar: `${assetBase}/avatars/thumb-15.jpg`, orderDate: '2026-04-13', orderDateLabel: 'Mon 13 Apr, 2026', channel: 'Wholesale' },
    { orderId: 'ORD-8386', total: 148.75, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Cora Bennett', customerAvatar: `${assetBase}/avatars/thumb-16.jpg`, orderDate: '2026-04-11', orderDateLabel: 'Sat 11 Apr, 2026', channel: 'Storefront' },
    { orderId: 'ORD-8385', total: 689.9, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Noel Avery', customerAvatar: `${assetBase}/avatars/thumb-17.jpg`, orderDate: '2026-04-10', orderDateLabel: 'Fri 10 Apr, 2026', channel: 'Marketplace' },
    { orderId: 'ORD-8384', total: 321.4, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Gia Hart', customerAvatar: `${assetBase}/avatars/thumb-18.jpg`, orderDate: '2026-04-09', orderDateLabel: 'Thu 9 Apr, 2026', channel: 'Wholesale' },
    { orderId: 'ORD-8383', total: 74.2, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Milo Grant', customerAvatar: `${assetBase}/avatars/thumb-19.jpg`, orderDate: '2026-04-08', orderDateLabel: 'Wed 8 Apr, 2026', channel: 'Storefront' },
    { orderId: 'ORD-8382', total: 546.25, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Asha Ford', customerAvatar: `${assetBase}/avatars/thumb-20.jpg`, orderDate: '2026-04-07', orderDateLabel: 'Tue 7 Apr, 2026', channel: 'Marketplace' },
    { orderId: 'ORD-8381', total: 193.6, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Luca Byrne', customerAvatar: `${assetBase}/avatars/thumb-21.jpg`, orderDate: '2026-04-06', orderDateLabel: 'Mon 6 Apr, 2026', channel: 'Wholesale' },
    { orderId: 'ORD-8380', total: 808.4, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Rina Cole', customerAvatar: `${assetBase}/avatars/thumb-22.jpg`, orderDate: '2026-04-04', orderDateLabel: 'Sat 4 Apr, 2026', channel: 'Storefront' },
    { orderId: 'ORD-8379', total: 129.99, paymentStatus: 'Canceled', fulfillmentStatus: null, customer: 'Pia Rowe', customerAvatar: `${assetBase}/avatars/thumb-23.jpg`, orderDate: '2026-04-03', orderDateLabel: 'Fri 3 Apr, 2026', channel: 'Marketplace' },
    { orderId: 'ORD-8378', total: 258.3, paymentStatus: 'Paid', fulfillmentStatus: 'Unfulfilled', customer: 'Sam Delaney', customerAvatar: `${assetBase}/avatars/thumb-24.jpg`, orderDate: '2026-04-02', orderDateLabel: 'Thu 2 Apr, 2026', channel: 'Wholesale' },
    { orderId: 'ORD-8377', total: 675.45, paymentStatus: 'Paid', fulfillmentStatus: 'Fulfilled', customer: 'Jules Park', customerAvatar: `${assetBase}/avatars/thumb-25.jpg`, orderDate: '2026-04-01', orderDateLabel: 'Wed 1 Apr, 2026', channel: 'Storefront' },
]

const sortableKeys: SortKey[] = [
    'orderId',
    'total',
    'paymentStatus',
    'customer',
    'orderDate',
    'channel',
]

const dateRangeOptions: DateRangeOption[] = [
    {
        id: 'april',
        label: '1 Apr – 30 Apr',
        start: new Date(2026, 3, 1),
        end: new Date(2026, 3, 30),
    },
    {
        id: 'late-april',
        label: '15 Apr – 30 Apr',
        start: new Date(2026, 3, 15),
        end: new Date(2026, 3, 30),
    },
    {
        id: 'early-april',
        label: '1 Apr – 14 Apr',
        start: new Date(2026, 3, 1),
        end: new Date(2026, 3, 14),
    },
    {
        id: 'all-dates',
        label: 'All dates',
        start: null,
        end: null,
    },
]

const formatCurrency = (value: number) =>
    new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD',
    }).format(value)

const getChannelIcon = (channel: Channel) => {
    switch (channel) {
        case 'Storefront':
            return PiStorefront
        case 'Marketplace':
            return PiGlobe
        case 'Wholesale':
            return PiBuildings
    }
}

const renderPaymentTag = (paymentStatus: PaymentStatus) => {
    const isPaid = paymentStatus === 'Paid'

    return (
        <Tag
            className={
                isPaid
                    ? 'border-0 bg-success-soft text-success'
                    : 'border-0 bg-destructive-soft text-destructive'
            }
            prefix={
                isPaid ? (
                    <PiCheckCircleFill className="mr-1 text-sm" />
                ) : (
                    <PiXCircleFill className="mr-1 text-sm" />
                )
            }
        >
            {paymentStatus}
        </Tag>
    )
}

const renderFulfillmentTag = (fulfillmentStatus: FulfillmentStatus | null) => {
    if (!fulfillmentStatus) {
        return null
    }

    const isFulfilled = fulfillmentStatus === 'Fulfilled'

    return (
        <Tag
            className="gap-1 bg-card"
        >
            {
                isFulfilled ? (
                    <PiCheckSquare />
                ) : (
                    <PiPackage />
                )
            }
            {fulfillmentStatus}
        </Tag>
    )
}

const columns: ColumnDef<OrderRow>[] = [
    {
        header: 'Order ID',
        accessorKey: 'orderId',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="truncate font-medium">{row.original.orderId}</span>
        ),
    },
    {
        header: 'Total',
        accessorKey: 'total',
        enableSorting: true,
        cell: ({ row }) => (
            <span>
                {formatCurrency(row.original.total)}
            </span>
        ),
    },
    {
        header: 'Status',
        accessorKey: 'paymentStatus',
        enableSorting: true,
        cell: ({ row }) => (
            <div className="flex flex-nowrap items-center gap-2">
                {renderPaymentTag(row.original.paymentStatus)}
                {renderFulfillmentTag(row.original.fulfillmentStatus)}
            </div>
        ),
    },
    {
        header: 'Customer',
        accessorKey: 'customer',
        enableSorting: true,
        cell: ({ row }) => (
            <div className="flex min-w-0 items-center gap-2">
                <Avatar
                    alt={row.original.customer}
                    size={24}
                    src={row.original.customerAvatar}
                />
                <span className="truncate font-medium">{row.original.customer}</span>
            </div>
        ),
    },
    {
        header: 'Order Date',
        accessorKey: 'orderDate',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="whitespace-nowrap text-muted-foreground">
                {row.original.orderDateLabel}
            </span>
        ),
    },
    {
        header: 'Channel',
        accessorKey: 'channel',
        enableSorting: true,
        cell: ({ row }) => {
            const ChannelIcon = getChannelIcon(row.original.channel)

            return (
                <span className="flex max-w-40 min-w-0 items-center gap-2">
                    <ChannelIcon className="shrink-0 text-base text-muted-foreground" />
                    <span className="truncate">{row.original.channel}</span>
                </span>
            )
        },
    },
    {
        id: 'actions',
        header: '',
        enableSorting: false,
        size: 48,
        minSize: 48,
        maxSize: 48,
        cell: ({ row }) => (
            <div className="flex justify-end">
                <Dropdown
                    placement="bottom-end"
                    renderTitle={
                        <Button
                            aria-label={`More actions for ${row.original.orderId}`}
                            icon={<PiDotsThreeVerticalBold />}
                            shape="circle"
                            size="sm"
                            variant="ghost"
                        />
                    }
                >
                    <Dropdown.Item eventKey="view">View order</Dropdown.Item>
                    <Dropdown.Item eventKey="duplicate">
                        Duplicate order
                    </Dropdown.Item>
                    <Dropdown.Item eventKey="archive">Archive order</Dropdown.Item>
                </Dropdown>
            </div>
        ),
    },
]

export default function DataGridOrderHistory() {
    const [searchQuery, setSearchQuery] = useState('')
    const [paymentStatus, setPaymentStatus] = useState<PaymentFilter>('All')
    const [fulfillmentStatus, setFulfillmentStatus] =
        useState<FulfillmentFilter>('All')
    const [dateRange, setDateRange] = useState<DateRangeOption>(
        dateRangeOptions[0],
    )
    const [sortKey, setSortKey] = useState<SortKey | null>(null)
    const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | ''>('')
    const [pageIndex, setPageIndex] = useState(1)
    const [pageSize, setPageSize] = useState(10)

    const tableData = useMemo(() => {
        const filterMatchedOrders = orderRows.filter((order) => {
            const matchesPayment =
                paymentStatus === 'All' ||
                order.paymentStatus === paymentStatus
            const matchesFulfillment =
                fulfillmentStatus === 'All' ||
                order.fulfillmentStatus === fulfillmentStatus
            const orderDate = new Date(`${order.orderDate}T12:00:00`)
            const matchesDateRange =
                (!dateRange.start || orderDate >= dateRange.start) &&
                (!dateRange.end || orderDate <= dateRange.end)

            return matchesPayment && matchesFulfillment && matchesDateRange
        })
        const normalizedSearchQuery = searchQuery.trim().toLowerCase()
        const searchMatchedOrders = normalizedSearchQuery
            ? filterMatchedOrders.filter((order) =>
                  [order.orderId, order.customer, order.channel].some((value) =>
                      value.toLowerCase().includes(normalizedSearchQuery),
                  ),
              )
            : filterMatchedOrders
        const sortedOrders = [...searchMatchedOrders].sort((left, right) => {
            if (!sortKey || !sortOrder) {
                return 0
            }

            const comparison =
                sortKey === 'total'
                    ? left.total - right.total
                    : left[sortKey].localeCompare(right[sortKey])

            return sortOrder === 'asc' ? comparison : -comparison
        })
        const pageStart = (pageIndex - 1) * pageSize

        return {
            rows: sortedOrders.slice(pageStart, pageStart + pageSize),
            total: sortedOrders.length,
        }
    }, [
        dateRange,
        fulfillmentStatus,
        pageIndex,
        pageSize,
        paymentStatus,
        searchQuery,
        sortKey,
        sortOrder,
    ])

    return (
        <section
            aria-labelledby="data-grid-order-history-title"
        >
            <Container className="space-y-4">
                <h4 id="data-grid-order-history-title" className="font-semibold">
                    Order List
                </h4>

                <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
                <Input
                    aria-label="Search orders or customers"
                    className="w-full md:w-80"
                    onChange={(event) => {
                        setSearchQuery(event.target.value)
                        setPageIndex(1)
                    }}
                    placeholder="Search orders or customers"
                    prefix={<PiMagnifyingGlass className="text-muted-foreground" />}
                    value={searchQuery}
                />
                <div className="flex flex-wrap items-center gap-2">
                    <Dropdown
                        placement="bottom-end"
                        renderTitle={
                            <Button
                                icon={<PiCalendarBlank />}
                                iconAlignment="start"
                            >
                                {dateRange.label}
                            </Button>
                        }
                    >
                        {dateRangeOptions.map((option) => (
                            <Dropdown.Item
                                active={option.id === dateRange.id}
                                eventKey={option.id}
                                key={option.id}
                                onSelect={() => {
                                    setDateRange(option)
                                    setPageIndex(1)
                                }}
                            >
                                <span className="flex w-full items-center justify-between">
                                    <span>{option.label}</span>
                                    {option.id === dateRange.id && (
                                        <PiCheck
                                            aria-hidden="true"
                                            className="shrink-0 text-base"
                                        />
                                    )}
                                </span>
                            </Dropdown.Item>
                        ))}
                    </Dropdown>
                    <Dropdown
                        placement="bottom-end"
                        renderTitle={
                            <Button
                                active={paymentStatus !== 'All'}
                                icon={<PiCaretDown />}
                                iconAlignment="end"
                            >
                                {paymentStatus === 'All'
                                    ? 'Payment Status'
                                    : paymentStatus}
                            </Button>
                        }
                    >
                        <Dropdown.Item
                            active={paymentStatus === 'All'}
                            eventKey="all-payments"
                            onSelect={() => {
                                setPaymentStatus('All')
                                setPageIndex(1)
                            }}
                        >
                            <span className="flex w-full items-center justify-between">
                                <span>All payment statuses</span>
                                {paymentStatus === 'All' && (
                                    <PiCheck
                                        aria-hidden="true"
                                        className="shrink-0 text-base"
                                    />
                                )}
                            </span>
                        </Dropdown.Item>
                        <Dropdown.Item
                            active={paymentStatus === 'Paid'}
                            eventKey="paid"
                            onSelect={() => {
                                setPaymentStatus('Paid')
                                setPageIndex(1)
                            }}
                        >
                            <span className="flex w-full items-center justify-between">
                                <span>Paid</span>
                                {paymentStatus === 'Paid' && (
                                    <PiCheck
                                        aria-hidden="true"
                                        className="shrink-0 text-base"
                                    />
                                )}
                            </span>
                        </Dropdown.Item>
                        <Dropdown.Item
                            active={paymentStatus === 'Canceled'}
                            eventKey="canceled"
                            onSelect={() => {
                                setPaymentStatus('Canceled')
                                setPageIndex(1)
                            }}
                        >
                            <span className="flex w-full items-center justify-between">
                                <span>Canceled</span>
                                {paymentStatus === 'Canceled' && (
                                    <PiCheck
                                        aria-hidden="true"
                                        className="shrink-0 text-base"
                                    />
                                )}
                            </span>
                        </Dropdown.Item>
                    </Dropdown>
                    <Dropdown
                        placement="bottom-end"
                        renderTitle={
                            <Button
                                active={fulfillmentStatus !== 'All'}
                                icon={<PiCaretDown />}
                                iconAlignment="end"
                            >
                                {fulfillmentStatus === 'All'
                                    ? 'Fulfillment'
                                    : fulfillmentStatus}
                            </Button>
                        }
                    >
                        <Dropdown.Item
                            active={fulfillmentStatus === 'All'}
                            eventKey="all-fulfillment"
                            onSelect={() => {
                                setFulfillmentStatus('All')
                                setPageIndex(1)
                            }}
                        >
                            <span className="flex w-full items-center justify-between">
                                <span>All fulfillment statuses</span>
                                {fulfillmentStatus === 'All' && (
                                    <PiCheck
                                        aria-hidden="true"
                                        className="shrink-0 text-base"
                                    />
                                )}
                            </span>
                        </Dropdown.Item>
                        <Dropdown.Item
                            active={fulfillmentStatus === 'Fulfilled'}
                            eventKey="fulfilled"
                            onSelect={() => {
                                setFulfillmentStatus('Fulfilled')
                                setPageIndex(1)
                            }}
                        >
                            <span className="flex w-full items-center justify-between">
                                <span>Fulfilled</span>
                                {fulfillmentStatus === 'Fulfilled' && (
                                    <PiCheck
                                        aria-hidden="true"
                                        className="shrink-0 text-base"
                                    />
                                )}
                            </span>
                        </Dropdown.Item>
                        <Dropdown.Item
                            active={fulfillmentStatus === 'Unfulfilled'}
                            eventKey="unfulfilled"
                            onSelect={() => {
                                setFulfillmentStatus('Unfulfilled')
                                setPageIndex(1)
                            }}
                        >
                            <span className="flex w-full items-center justify-between">
                                <span>Unfulfilled</span>
                                {fulfillmentStatus === 'Unfulfilled' && (
                                    <PiCheck
                                        aria-hidden="true"
                                        className="shrink-0 text-base"
                                    />
                                )}
                            </span>
                        </Dropdown.Item>
                    </Dropdown>
                </div>
                </div>

                <Card bodyClass="px-0 pt-0 overflow-hidden">
                <DataTable
                    columns={columns}
                    data={tableData.rows}
                    onPageSizeChange={(nextPageSize) => {
                        setPageSize(nextPageSize)
                        setPageIndex(1)
                    }}
                    onPaginationChange={setPageIndex}
                    onSort={(sort) => {
                        if (
                            typeof sort.sortKey === 'string' &&
                            sortableKeys.includes(sort.sortKey as SortKey)
                        ) {
                            setSortKey(sort.sortKey as SortKey)
                            setSortOrder(sort.sortOrder)
                            setPageIndex(1)
                        }
                    }}
                    pagingData={{
                        total: tableData.total,
                        pageIndex,
                        pageSize,
                    }}
                    compact
                    hoverable={false}
                    className="min-w-[980px]"
                />
                </Card>
            </Container>
        </section>
    )
}

Data Grid 02

Preview
npx nateui@latest add DataGridCustomerList
Dark
import { useEffect, useMemo, useRef, useState } from 'react'
import AdvancedFilterBuilder, {
    type FieldConfig,
    type FilterTree,
    type Rule,
    type RuleGroup,
} from '@/components/composites/AdvancedFilterBuilder'
import DataTable, {
    type ColumnDef,
    type DataTableResetHandle,
    type Row,
} from '@/components/composites/DataTable'
import ActionBar from '@/components/ui/ActionBar'
import Button from '@/components/ui/Button'
import Card from '@/components/ui/Card'
import Container from '@/components/composites/Container'
import Drawer from '@/components/ui/Drawer'
import Dropdown from '@/components/ui/Dropdown'
import Form from '@/components/ui/Form'
import Input from '@/components/ui/Input'
import Popover from '@/components/ui/Popover'
import Select from '@/components/ui/Select'
import {
    PiArchive,
    PiDotsThreeVerticalBold,
    PiFunnel,
    PiMagnifyingGlass,
    PiPlus,
} from 'react-icons/pi'

const assetBase = 'https://statics.nateui.com/img'

type CustomerRow = {
    id: string
    organization: string
    organizationLogo: string
    email: string
    userName: string
    sessions: number
    notes: string
    outstanding: number
    date: string
    dateLabel: string
}

type CustomerSortKey =
    | 'organization'
    | 'userName'
    | 'sessions'
    | 'notes'
    | 'outstanding'

type CustomerFilterValue = string | number

const pageSizes = [10, 20, 24]

const customerRows: CustomerRow[] = [
    {
        id: 'CUS-2401',
        organization: 'Amazon',
        organizationLogo: `${assetBase}/thumbs/brands/amazon.png`,
        email: 'hello@northwind.example.com',
        userName: 'Avery Chen',
        sessions: 18,
        notes: 'Renewal review',
        outstanding: 314.28,
        date: '2026-03-18',
        dateLabel: '18-03-2026',
    },
    {
        id: 'CUS-2402',
        organization: 'Asana',
        organizationLogo: `${assetBase}/thumbs/brands/asana.png`,
        email: 'team@cedarfinch.example.com',
        userName: 'Rowan Blake',
        sessions: 9,
        notes: 'Account review',
        outstanding: 671.45,
        date: '2026-03-16',
        dateLabel: '16-03-2026',
    },
    {
        id: 'CUS-2403',
        organization: 'Atlassian',
        organizationLogo: `${assetBase}/thumbs/brands/atlassian.png`,
        email: 'ops@atlasworkshop.example.com',
        userName: 'Sage Patel',
        sessions: 27,
        notes: 'Usage follow-up',
        outstanding: 128.36,
        date: '2026-03-14',
        dateLabel: '14-03-2026',
    },
    {
        id: 'CUS-2404',
        organization: 'Canva',
        organizationLogo: `${assetBase}/thumbs/brands/canva.png`,
        email: 'admin@junipersignal.example.com',
        userName: 'Mina Cole',
        sessions: 14,
        notes: 'Billing question',
        outstanding: 944.17,
        date: '2026-03-12',
        dateLabel: '12-03-2026',
    },
    {
        id: 'CUS-2405',
        organization: 'Dropbox',
        organizationLogo: `${assetBase}/thumbs/brands/dropbox.png`,
        email: 'studio@morrowhouse.example.com',
        userName: 'Theo Park',
        sessions: 33,
        notes: 'Onboarding',
        outstanding: 83.52,
        date: '2026-03-10',
        dateLabel: '10-03-2026',
    },
    {
        id: 'CUS-2406',
        organization: 'eBay',
        organizationLogo: `${assetBase}/thumbs/brands/ebay.png`,
        email: 'contact@brightwellworks.example.com',
        userName: 'Ivy Monroe',
        sessions: 6,
        notes: 'Invoice sent',
        outstanding: 527.64,
        date: '2026-03-08',
        dateLabel: '08-03-2026',
    },
    {
        id: 'CUS-2407',
        organization: 'Figma',
        organizationLogo: `${assetBase}/thumbs/brands/figma.png`,
        email: 'team@harbormetric.example.com',
        userName: 'Nico Reyes',
        sessions: 21,
        notes: 'Renewal review',
        outstanding: 149.91,
        date: '2026-03-06',
        dateLabel: '06-03-2026',
    },
    {
        id: 'CUS-2408',
        organization: 'GitHub',
        organizationLogo: `${assetBase}/thumbs/brands/github.png`,
        email: 'hello@cindergrove.example.com',
        userName: 'Elle Foster',
        sessions: 42,
        notes: 'Account review',
        outstanding: 792.08,
        date: '2026-03-04',
        dateLabel: '04-03-2026',
    },
    {
        id: 'CUS-2409',
        organization: 'Google',
        organizationLogo: `${assetBase}/thumbs/brands/google.png`,
        email: 'desk@fieldstoneoffice.example.com',
        userName: 'Jasper Kline',
        sessions: 11,
        notes: 'Usage follow-up',
        outstanding: 266.73,
        date: '2026-03-02',
        dateLabel: '02-03-2026',
    },
    {
        id: 'CUS-2410',
        organization: 'HubSpot',
        organizationLogo: `${assetBase}/thumbs/brands/hubspot.png`,
        email: 'admin@solacecircuit.example.com',
        userName: 'Wren Solis',
        sessions: 16,
        notes: 'Onboarding',
        outstanding: 602.14,
        date: '2026-02-28',
        dateLabel: '28-02-2026',
    },
    {
        id: 'CUS-2411',
        organization: 'Jira',
        organizationLogo: `${assetBase}/thumbs/brands/jira.png`,
        email: 'team@meadowline.example.com',
        userName: 'Luca Mercer',
        sessions: 8,
        notes: 'Billing question',
        outstanding: 39.87,
        date: '2026-02-26',
        dateLabel: '26-02-2026',
    },
    {
        id: 'CUS-2412',
        organization: 'Microsoft',
        organizationLogo: `${assetBase}/thumbs/brands/microsoft.png`,
        email: 'ops@amberrelay.example.com',
        userName: 'Zoe Calloway',
        sessions: 24,
        notes: 'Invoice sent',
        outstanding: 871.22,
        date: '2026-02-24',
        dateLabel: '24-02-2026',
    },
    {
        id: 'CUS-2413',
        organization: 'Notion',
        organizationLogo: `${assetBase}/thumbs/brands/notion.png`,
        email: 'hello@pineharbor.example.com',
        userName: 'Milo Finch',
        sessions: 13,
        notes: 'Renewal review',
        outstanding: 458.63,
        date: '2026-02-22',
        dateLabel: '22-02-2026',
    },
    {
        id: 'CUS-2414',
        organization: 'Oracle',
        organizationLogo: `${assetBase}/thumbs/brands/oracle.png`,
        email: 'studio@lumenyard.example.com',
        userName: 'Anika Brooks',
        sessions: 37,
        notes: 'Account review',
        outstanding: 116.49,
        date: '2026-02-20',
        dateLabel: '20-02-2026',
    },
    {
        id: 'CUS-2415',
        organization: 'Salesforce',
        organizationLogo: `${assetBase}/thumbs/brands/salesforce.png`,
        email: 'contact@orchardlane.example.com',
        userName: 'Rhea Dalton',
        sessions: 5,
        notes: 'Usage follow-up',
        outstanding: 735.18,
        date: '2026-02-18',
        dateLabel: '18-02-2026',
    },
    {
        id: 'CUS-2416',
        organization: 'Shopify',
        organizationLogo: `${assetBase}/thumbs/brands/shopify.png`,
        email: 'team@copperfinch.example.com',
        userName: 'Kian Mercer',
        sessions: 29,
        notes: 'Onboarding',
        outstanding: 182.75,
        date: '2026-02-16',
        dateLabel: '16-02-2026',
    },
    {
        id: 'CUS-2417',
        organization: 'Slack',
        organizationLogo: `${assetBase}/thumbs/brands/slack.png`,
        email: 'hello@marblecurrent.example.com',
        userName: 'Tess Rowan',
        sessions: 19,
        notes: 'Billing question',
        outstanding: 989.41,
        date: '2026-02-14',
        dateLabel: '14-02-2026',
    },
    {
        id: 'CUS-2418',
        organization: 'Spotify',
        organizationLogo: `${assetBase}/thumbs/brands/spotify.png`,
        email: 'ops@rainierthread.example.com',
        userName: 'Omar Vale',
        sessions: 31,
        notes: 'Invoice sent',
        outstanding: 344.06,
        date: '2026-02-12',
        dateLabel: '12-02-2026',
    },
    {
        id: 'CUS-2419',
        organization: 'Stripe',
        organizationLogo: `${assetBase}/thumbs/brands/stripe.png`,
        email: 'admin@quietbeacon.example.com',
        userName: 'Priya Knox',
        sessions: 10,
        notes: 'Renewal review',
        outstanding: 628.57,
        date: '2026-02-10',
        dateLabel: '10-02-2026',
    },
    {
        id: 'CUS-2420',
        organization: 'Tesla',
        organizationLogo: `${assetBase}/thumbs/brands/tesla.png`,
        email: 'team@asterfoundry.example.com',
        userName: 'Silas Reed',
        sessions: 26,
        notes: 'Account review',
        outstanding: 72.94,
        date: '2026-02-08',
        dateLabel: '08-02-2026',
    },
    {
        id: 'CUS-2421',
        organization: 'TikTok',
        organizationLogo: `${assetBase}/thumbs/brands/tiktok.png`,
        email: 'hello@bluecedarlab.example.com',
        userName: 'Nora Quinn',
        sessions: 15,
        notes: 'Usage follow-up',
        outstanding: 809.35,
        date: '2026-02-06',
        dateLabel: '06-02-2026',
    },
    {
        id: 'CUS-2422',
        organization: 'Twitch',
        organizationLogo: `${assetBase}/thumbs/brands/twitch.png`,
        email: 'ops@kindredatlas.example.com',
        userName: 'Cleo Hart',
        sessions: 35,
        notes: 'Onboarding',
        outstanding: 231.68,
        date: '2026-02-04',
        dateLabel: '04-02-2026',
    },
    {
        id: 'CUS-2423',
        organization: 'WhatsApp',
        organizationLogo: `${assetBase}/thumbs/brands/whatsapp.png`,
        email: 'contact@willowindex.example.com',
        userName: 'Bryn Ellis',
        sessions: 7,
        notes: 'Billing question',
        outstanding: 514.82,
        date: '2026-02-02',
        dateLabel: '02-02-2026',
    },
    {
        id: 'CUS-2424',
        organization: 'Zapier',
        organizationLogo: `${assetBase}/thumbs/brands/zapier.png`,
        email: 'studio@signalorchard.example.com',
        userName: 'Kai Monroe',
        sessions: 22,
        notes: 'Invoice sent',
        outstanding: 947.53,
        date: '2026-01-31',
        dateLabel: '31-01-2026',
    },
]

const noteOptions = [
    { value: 'Renewal review', label: 'Renewal review' },
    { value: 'Account review', label: 'Account review' },
    { value: 'Usage follow-up', label: 'Usage follow-up' },
    { value: 'Billing question', label: 'Billing question' },
    { value: 'Onboarding', label: 'Onboarding' },
    { value: 'Invoice sent', label: 'Invoice sent' },
]

const customerFilterFields: FieldConfig[] = [
    { key: 'organization', label: 'Organization', type: 'text' },
    { key: 'contact', label: 'Contact person', type: 'text' },
    { key: 'sessions', label: 'Sessions', type: 'number' },
    { key: 'notes', label: 'Notes', type: 'select', options: noteOptions },
    { key: 'outstanding', label: 'Outstanding amount', type: 'number' },
    { key: 'date', label: 'Date', type: 'date' },
]

const createEmptyFilterTree = (): FilterTree => ({
    id: 'customer-filter-root',
    type: 'group',
    condition: 'AND',
    children: [
        {
            id: 'customer-filter-rule',
            type: 'rule',
            field: 'organization',
            operator: 'contains',
            value: '',
        },
    ],
})

const getFieldValue = (
    row: CustomerRow,
    field: string,
): CustomerFilterValue | undefined => {
    switch (field) {
        case 'organization':
            return row.organization
        case 'contact':
            return row.userName
        case 'sessions':
            return row.sessions
        case 'notes':
            return row.notes
        case 'outstanding':
            return row.outstanding
        case 'date':
            return row.date
        default:
            return undefined
    }
}

const compareFilterRule = (row: CustomerRow, rule: Rule) => {
    const rowValue = getFieldValue(row, rule.field)
    const ruleValue = rule.value

    if (rowValue === undefined || ruleValue === '') {
        return true
    }

    if (typeof rowValue === 'number') {
        const numericValue = Number(ruleValue)
        if (Number.isNaN(numericValue)) {
            return true
        }

        switch (rule.operator) {
            case 'is':
                return rowValue === numericValue
            case 'is_not':
                return rowValue !== numericValue
            case 'greater_than':
                return rowValue > numericValue
            case 'less_than':
                return rowValue < numericValue
            case 'greater_equal':
                return rowValue >= numericValue
            case 'less_equal':
                return rowValue <= numericValue
            default:
                return true
        }
    }

    if (rule.field === 'date') {
        const toDateKey = (value: string) => {
            const date = new Date(value)
            if (Number.isNaN(date.getTime())) {
                return undefined
            }

            return [
                date.getFullYear(),
                String(date.getMonth() + 1).padStart(2, '0'),
                String(date.getDate()).padStart(2, '0'),
            ].join('-')
        }

        const rowDate = toDateKey(`${String(rowValue)}T12:00:00`)
        const ruleDate = toDateKey(String(ruleValue))

        if (!rowDate || !ruleDate) {
            return true
        }

        switch (rule.operator) {
            case 'is':
                return rowDate === ruleDate
            case 'is_not':
                return rowDate !== ruleDate
            case 'greater_than':
                return rowDate > ruleDate
            case 'less_than':
                return rowDate < ruleDate
            case 'greater_equal':
                return rowDate >= ruleDate
            case 'less_equal':
                return rowDate <= ruleDate
            default:
                return true
        }
    }

    const normalizedRowValue = String(rowValue).toLowerCase()
    const normalizedRuleValue = String(ruleValue).toLowerCase()

    switch (rule.operator) {
        case 'is':
            return normalizedRowValue === normalizedRuleValue
        case 'is_not':
            return normalizedRowValue !== normalizedRuleValue
        case 'contains':
            return normalizedRowValue.includes(normalizedRuleValue)
        case 'not_contains':
            return !normalizedRowValue.includes(normalizedRuleValue)
        case 'starts_with':
            return normalizedRowValue.startsWith(normalizedRuleValue)
        case 'ends_with':
            return normalizedRowValue.endsWith(normalizedRuleValue)
        case 'greater_than':
            return normalizedRowValue > normalizedRuleValue
        case 'less_than':
            return normalizedRowValue < normalizedRuleValue
        case 'greater_equal':
            return normalizedRowValue >= normalizedRuleValue
        case 'less_equal':
            return normalizedRowValue <= normalizedRuleValue
        default:
            return true
    }
}

const evaluateFilterNode = (
    row: CustomerRow,
    node: Rule | RuleGroup,
): boolean => {
    if (node.type === 'rule') {
        return compareFilterRule(row, node)
    }

    const results = node.children.map((child) => {
        const result = evaluateFilterNode(row, child)
        return child.logicalOperator === 'NOT' ? !result : result
    })

    if (results.length === 0) {
        return true
    }

    return node.condition === 'OR'
        ? results.some(Boolean)
        : results.every(Boolean)
}

const hasActiveFilter = (node: Rule | RuleGroup): boolean => {
    if (node.type === 'rule') {
        return node.value !== ''
    }

    return node.children.some(hasActiveFilter)
}

const formatOutstanding = (amount: number) =>
    amount.toLocaleString('en-US', {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
    })

const customerSortKeys: CustomerSortKey[] = [
    'organization',
    'userName',
    'sessions',
    'notes',
    'outstanding',
]

const columns: ColumnDef<CustomerRow>[] = [
    {
        header: () => <span className="whitespace-nowrap">Customer Name</span>,
        accessorKey: 'organization',
        enableSorting: true,
        size: 260,
        minSize: 240,
        cell: ({ row }) => (
            <div className="flex min-w-0 items-center gap-2">
                <img
                    alt={row.original.organization}
                    className="size-4"
                    src={row.original.organizationLogo}
                />
                <div className="min-w-0">
                    <div className="whitespace-nowrap font-semibold">
                        {row.original.organization}
                    </div>
                </div>
            </div>
        ),
    },
    {
        header: () => <span className="whitespace-nowrap">User Name</span>,
        accessorKey: 'userName',
        enableSorting: true,
        size: 150,
        cell: ({ row }) => (
            <span className="whitespace-nowrap">{row.original.userName}</span>
        ),
    },
    {
        header: () => <span className="whitespace-nowrap">Sessions</span>,
        accessorKey: 'sessions',
        enableSorting: true,
        size: 100,
        cell: ({ row }) => (
            <span className="block tabular-nums">
                {row.original.sessions}
            </span>
        ),
    },
    {
        header: () => <span className="whitespace-nowrap">Notes</span>,
        accessorKey: 'notes',
        enableSorting: true,
        size: 160,
        cell: ({ row }) => (
            <span className="whitespace-nowrap">
                {row.original.notes}
            </span>
        ),
    },
    {
        header: () => (
            <span className="whitespace-nowrap">Total Outstanding</span>
        ),
        accessorKey: 'outstanding',
        enableSorting: true,
        size: 170,
        cell: ({ row }) => (
            <span className="block whitespace-nowrap">
                <span>$</span>
                {formatOutstanding(row.original.outstanding)}
            </span>
        ),
    },
    {
        header: () => <span className="whitespace-nowrap">Date</span>,
        accessorKey: 'date',
        enableSorting: false,
        size: 130,
        cell: ({ row }) => (
            <span className="whitespace-nowrap">
                {row.original.dateLabel}
            </span>
        ),
    },
    {
        id: 'actions',
        header: () => <span className="whitespace-nowrap">Actions</span>,
        enableSorting: false,
        size: 52,
        minSize: 52,
        maxSize: 52,
        cell: ({ row }) => (
            <div className="flex justify-end">
                <Dropdown
                    placement="bottom-end"
                    renderTitle={
                        <Button
                            aria-label={`Actions for ${row.original.organization}`}
                            icon={<PiDotsThreeVerticalBold />}
                            shape="circle"
                            size="sm"
                            variant="ghost"
                        />
                    }
                >
                    <Dropdown.Item eventKey="view">View customer</Dropdown.Item>
                    <Dropdown.Item eventKey="edit">Edit customer</Dropdown.Item>
                    <Dropdown.Item eventKey="archive">
                        Archive customer
                    </Dropdown.Item>
                </Dropdown>
            </div>
        ),
    },
]

const customerSegments = [
    { value: 'growth', label: 'Growth' },
    { value: 'scale', label: 'Scale' },
    { value: 'studio', label: 'Studio' },
]

export default function DataGridCustomerList() {
    const [customerRowsData, setCustomerRowsData] = useState(customerRows)
    const [searchText, setSearchText] = useState('')
    const [appliedFilter, setAppliedFilter] = useState<FilterTree>(() =>
        createEmptyFilterTree(),
    )
    const [filterDraft, setFilterDraft] = useState<FilterTree>(() =>
        createEmptyFilterTree(),
    )
    const [sortKey, setSortKey] = useState<CustomerSortKey | null>(null)
    const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | ''>('')
    const [pageIndex, setPageIndex] = useState(1)
    const [pageSize, setPageSize] = useState(pageSizes[0])
    const [filterOpen, setFilterOpen] = useState(false)
    const [drawerOpen, setDrawerOpen] = useState(false)
    const [selectedRowIds, setSelectedRowIds] = useState<string[]>([])
    const dataTableRef = useRef<DataTableResetHandle>(null)
    const [organizationName, setOrganizationName] = useState('')
    const [contactName, setContactName] = useState('')
    const [contactEmail, setContactEmail] = useState('')
    const [customerSegment, setCustomerSegment] = useState(customerSegments[0])

    useEffect(() => {
        setPageIndex(1)
        setSelectedRowIds([])
    }, [searchText, appliedFilter])

    const visibleData = useMemo(() => {
        const filterMatchedRows = customerRowsData.filter((row) =>
            evaluateFilterNode(row, appliedFilter),
        )
        const normalizedSearch = searchText.trim().toLowerCase()
        const searchMatchedRows = normalizedSearch
            ? filterMatchedRows.filter((row) =>
                  [row.organization, row.email, row.userName].some((value) =>
                      value.toLowerCase().includes(normalizedSearch),
                  ),
              )
            : filterMatchedRows
        const sortedRows = [...searchMatchedRows].sort((left, right) => {
            if (!sortKey || !sortOrder) {
                return 0
            }

            const leftValue = left[sortKey]
            const rightValue = right[sortKey]
            const comparison =
                typeof leftValue === 'number' && typeof rightValue === 'number'
                    ? leftValue - rightValue
                    : String(leftValue).localeCompare(String(rightValue))

            return sortOrder === 'asc' ? comparison : -comparison
        })
        const pageStart = (pageIndex - 1) * pageSize

        return {
            rows: sortedRows.slice(pageStart, pageStart + pageSize),
            total: sortedRows.length,
        }
    }, [
        appliedFilter,
        customerRowsData,
        pageIndex,
        pageSize,
        searchText,
        sortKey,
        sortOrder,
    ])

    const handleSort = (sort: { sortKey: string | number; sortOrder: 'asc' | 'desc' | '' }) => {
        if (
            typeof sort.sortKey !== 'string' ||
            !customerSortKeys.includes(sort.sortKey as CustomerSortKey)
        ) {
            return
        }

        setSortKey(sort.sortOrder ? (sort.sortKey as CustomerSortKey) : null)
        setSortOrder(sort.sortOrder)
        setPageIndex(1)
        setSelectedRowIds([])
    }

    const handleRowSelect = (checked: boolean, row: CustomerRow) => {
        setSelectedRowIds((current) =>
            checked
                ? Array.from(new Set([...current, row.id]))
                : current.filter((id) => id !== row.id),
        )
    }

    const handleAllRowSelect = (
        checked: boolean,
        rows: Row<CustomerRow>[],
    ) => {
        const pageRowIds = rows.map((row) => row.original.id)
        setSelectedRowIds((current) => {
            if (checked) {
                return Array.from(new Set([...current, ...pageRowIds]))
            }
            return current.filter((id) => !pageRowIds.includes(id))
        })
    }

    const handleApplyFilter = (tree: FilterTree) => {
        setAppliedFilter(tree)
        setFilterDraft(tree)
        setFilterOpen(false)
    }

    const handleResetFilter = (tree: FilterTree) => {
        setAppliedFilter(tree)
        setFilterDraft(tree)
        setPageIndex(1)
        setSelectedRowIds([])
    }

    const handlePageChange = (page: number) => {
        setPageIndex(page)
        setSelectedRowIds([])
    }

    const handlePageSizeChange = (nextPageSize: number) => {
        setPageSize(nextPageSize)
        setPageIndex(1)
        setSelectedRowIds([])
    }

    const archiveSelectedCustomers = () => {
        const selectedIds = new Set(selectedRowIds)
        dataTableRef.current?.resetSelected()
        setCustomerRowsData((current) =>
            current.filter((row) => !selectedIds.has(row.id)),
        )
        setSelectedRowIds([])
        setPageIndex(1)
    }

    return (
        <section
            aria-labelledby="data-grid-customer-list-title"
            className="w-full"
        >
            <Container>
                <Card bodyClass="min-w-0 px-0 pt-0">
                <div className="border-b p-4 md:p-6">
                    <header className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
                        <div>
                            <h4 id="data-grid-customer-list-title" className="font-semibold">
                                Customers List
                            </h4>
                            <p className="mt-1 text-sm text-muted-foreground">
                                {visibleData.total} customers
                            </p>
                        </div>
                        <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
                            <Input
                                aria-label="Search customers"
                                className="w-full sm:w-64"
                                onChange={(event) => setSearchText(event.target.value)}
                                placeholder="Search customers"
                                prefix={<PiMagnifyingGlass className="text-muted-foreground" />}
                                value={searchText}
                            />
                            <Popover
                                open={filterOpen}
                                onOpenChange={setFilterOpen}
                                placement="bottom"
                                className="p-0"
                                renderTrigger={
                                    <Button
                                        active={hasActiveFilter(appliedFilter)}
                                        aria-haspopup="dialog"
                                        icon={<PiFunnel />}
                                    >
                                        Filter
                                    </Button>
                                }
                            >
                                <AdvancedFilterBuilder
                                    fields={customerFilterFields}
                                    value={filterDraft}
                                    onChange={(tree) => setFilterDraft(tree)}
                                    onApply={(tree) => handleApplyFilter(tree)}
                                    onReset={(tree) => handleResetFilter(tree)}
                                />
                            </Popover>
                            <Button
                                icon={<PiPlus />}
                                onClick={() => setDrawerOpen(true)}
                                variant="solid"
                            >
                                New Customer
                            </Button>
                        </div>
                    </header>
                </div>

                <DataTable
                    ref={(
                        instance: DataTableResetHandle | HTMLTableElement | null,
                    ) => {
                        if (instance && 'resetSelected' in instance) {
                            dataTableRef.current = instance
                        }
                    }}
                    columns={columns}
                    data={visibleData.rows}
                    indeterminateCheckboxChecked={(rows) => {
                        return (
                            rows.length > 0 &&
                            rows.every((row) =>
                                selectedRowIds.includes(row.original.id),
                            )
                        )
                    }}
                    onAllRowSelect={handleAllRowSelect}
                    onPageSizeChange={handlePageSizeChange}
                    onPaginationChange={handlePageChange}
                    onRowSelect={handleRowSelect}
                    onSort={handleSort}
                    pageSizes={pageSizes}
                    pagingData={{
                        total: visibleData.total,
                        pageIndex,
                        pageSize,
                    }}
                    selectable
                    checkboxChecked={(row) => selectedRowIds.includes(row.id)}
                    compact
                    hoverable
                />
                <ActionBar open={selectedRowIds.length > 0} width={560}>
                    <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
                        <span className="font-medium">
                            <span className="font-semibold text-foreground">
                                {selectedRowIds.length}{' '}
                                {selectedRowIds.length === 1
                                    ? 'customer'
                                    : 'customers'}
                            </span>{' '}
                            selected
                        </span>
                        <Button
                            icon={<PiArchive />}
                            onClick={archiveSelectedCustomers}
                            destructive
                        >
                            Archive
                        </Button>
                    </div>
                </ActionBar>
                </Card>

                {drawerOpen && (
                    <Drawer
                    isOpen={drawerOpen}
                    onClose={() => setDrawerOpen(false)}
                    placement="right"
                    title="New Customer"
                    width={420}
                    contentClassName="max-w-[calc(100vw-1rem)]"
                    footer={
                        <div className="flex w-full justify-end gap-2">
                            <Button
                                onClick={() => setDrawerOpen(false)}
                                variant="subtle"
                            >
                                Cancel
                            </Button>
                            <Button form="new-customer-form" type="submit" variant="solid">
                                Create customer
                            </Button>
                        </div>
                    }
                >
                    <Form
                        id="new-customer-form"
                        onSubmit={(event) => {
                            event.preventDefault()
                            setDrawerOpen(false)
                        }}
                        size="md"
                    >
                        <Form.Field htmlFor="customer-organization" label="Organization name">
                            <Input
                                id="customer-organization"
                                onChange={(event) => setOrganizationName(event.target.value)}
                                value={organizationName}
                            />
                        </Form.Field>
                        <Form.Field htmlFor="customer-contact" label="Contact person">
                            <Input
                                id="customer-contact"
                                onChange={(event) => setContactName(event.target.value)}
                                value={contactName}
                            />
                        </Form.Field>
                        <Form.Field htmlFor="customer-email" label="Email">
                            <Input
                                id="customer-email"
                                onChange={(event) => setContactEmail(event.target.value)}
                                type="email"
                                value={contactEmail}
                            />
                        </Form.Field>
                        <Form.Field htmlFor="customer-segment" label="Customer segment">
                            <Select
                                inputId="customer-segment"
                                onChange={(option) => {
                                    if (option) {
                                        setCustomerSegment(option)
                                    }
                                }}
                                options={customerSegments}
                                value={customerSegment}
                            />
                        </Form.Field>
                    </Form>
                    </Drawer>
                )}
            </Container>
        </section>
    )
}

Data Grid 03

Preview
npx nateui@latest add DataGridContactFilterBar
Dark
import { useEffect, useMemo, useRef, useState } from 'react'
import DataTable, {
    type ColumnDef,
    type DataTableResetHandle,
    type Row,
} from '@/components/composites/DataTable'
import PopoverFilter from '@/components/composites/PopoverFilter'
import ActionBar from '@/components/ui/ActionBar'
import Avatar from '@/components/ui/Avatar'
import Button from '@/components/ui/Button'
import Calendar from '@/components/ui/Calendar'
import Checkbox from '@/components/ui/Checkbox'
import Dropdown from '@/components/ui/Dropdown'
import Input from '@/components/ui/Input'
import Popover from '@/components/ui/Popover'
import Tabs from '@/components/ui/Tabs'
import Tag from '@/components/ui/Tag'
import Tooltip from '@/components/ui/Tooltip'
import {
    PiAddressBook,
    PiArchive,
    PiArrowUUpLeft,
    PiCaretDown,
    PiCopy,
    PiDotsThreeVerticalBold,
    PiEnvelopeSimple,
    PiEnvelopeSimpleOpen,
    PiExport,
    PiMagnifyingGlass,
    PiPlus,
    PiUsersThree,
} from 'react-icons/pi'

const assetBase = 'https://statics.nateui.com/img'

const currentDate = new Date()
currentDate.setHours(12, 0, 0, 0)

const formatDateKey = (date: Date) => {
    const year = date.getFullYear()
    const month = String(date.getMonth() + 1).padStart(2, '0')
    const day = String(date.getDate()).padStart(2, '0')
    return `${year}-${month}-${day}`
}

const relativeDateKey = (daysFromToday: number) => {
    const date = new Date(currentDate)
    date.setDate(date.getDate() + daysFromToday)
    return formatDateKey(date)
}

type PaletteTone =
    | 'emerald'
    | 'rose'
    | 'blue'
    | 'cyan'
    | 'orange'
    | 'red'
    | 'purple'
    | 'yellow'
    | 'lime'
    | 'gray'

type LeadStatus =
    | 'Open Deal'
    | 'Close Deal'
    | 'Negotiation'
    | 'In Progress'
    | 'Unqualified'

type ContactSegment = 'subscribed' | 'opted-out' | 'customer'

const contactViewTabs = [
    {
        value: 'all',
        id: 'crm-contact-tab-directory',
        label: 'Full directory',
        icon: <PiAddressBook />,
    },
    {
        value: 'subscribed',
        id: 'crm-contact-tab-subscribed',
        label: 'Newsletter members',
        icon: <PiEnvelopeSimple />,
    },
    {
        value: 'opted-out',
        id: 'crm-contact-tab-opted-out',
        label: 'Opted out',
        icon: <PiEnvelopeSimpleOpen />,
    },
    {
        value: 'customer',
        id: 'crm-contact-tab-customers',
        label: 'Customer accounts',
        icon: <PiUsersThree />,
    },
] as const

type ContactView = (typeof contactViewTabs)[number]['value']

const filterToolOptions = [
    { value: 'owner', label: 'Contact owner' },
    { value: 'created-date', label: 'Created date' },
    { value: 'last-activity', label: 'Last activity date' },
    { value: 'lead-status', label: 'Lead status' },
] as const

type FilterTool = (typeof filterToolOptions)[number]['value']

type ContactRow = {
    id: string
    name: string
    email?: string
    phone?: string
    company: string
    companyLogo: string
    avatarSrc?: string
    owner: string
    segment: ContactSegment
    createdAt: string
    lastActivityDate: string
    leadStatus?: LeadStatus
    leadStatusTone?: PaletteTone
}

const pageSizes = [10, 20, 24]

const paletteClassMap: Record<
    PaletteTone,
    { tag: string }
> = {
    emerald: {
        tag: 'border-0 bg-palette-emerald-soft text-palette-emerald-soft-foreground',
    },
    rose: {
        tag: 'border-0 bg-palette-rose-soft text-palette-rose-soft-foreground',
    },
    blue: {
        tag: 'border-0 bg-palette-blue-soft text-palette-blue-soft-foreground',
    },
    cyan: {
        tag: 'border-0 bg-palette-cyan-soft text-palette-cyan-soft-foreground',
    },
    orange: {
        tag: 'border-0 bg-palette-orange-soft text-palette-orange-soft-foreground',
    },
    red: {
        tag: 'border-0 bg-palette-red-soft text-palette-red-soft-foreground',
    },
    purple: {
        tag: 'border-0 bg-palette-purple-soft text-palette-purple-soft-foreground',
    },
    yellow: {
        tag: 'border-0 bg-palette-yellow-soft text-palette-yellow-soft-foreground',
    },
    lime: {
        tag: 'border-0 bg-palette-lime-soft text-palette-lime-soft-foreground',
    },
    gray: {
        tag: 'border-0 bg-palette-gray-soft text-palette-gray-soft-foreground',
    },
}

const contactRows: ContactRow[] = [
    {
        id: 'CNT-1001',
        segment: 'subscribed',
        name: 'Priya Nair',
        email: 'priya.nair@example.com',
        phone: '+60 12 487 2031',
        company: 'Amazon',
        companyLogo: `${assetBase}/thumbs/brands/amazon.png`,
        avatarSrc: `${assetBase}/avatars/thumb-1.jpg`,
        owner: 'Amara Singh',
        createdAt: relativeDateKey(0),
        lastActivityDate: relativeDateKey(0),
        leadStatus: 'Open Deal',
        leadStatusTone: 'emerald',
    },
    {
        id: 'CNT-1002',
        segment: 'customer',
        avatarSrc: `${assetBase}/avatars/thumb-2.jpg`,
        name: 'Marcus Lee',
        email: 'marcus.lee@example.com',
        phone: '+65 8123 0904',
        company: 'Asana',
        companyLogo: `${assetBase}/thumbs/brands/asana.png`,
        owner: 'Ben Ortiz',
        createdAt: relativeDateKey(-1),
        lastActivityDate: relativeDateKey(-1),
        leadStatus: 'Negotiation',
        leadStatusTone: 'purple',
    },
    {
        id: 'CNT-1003',
        segment: 'subscribed',
        name: 'Elena Torres',
        email: 'elena.torres@example.com',
        company: 'Atlassian',
        companyLogo: `${assetBase}/thumbs/brands/atlassian.png`,
        avatarSrc: `${assetBase}/avatars/thumb-3.jpg`,
        owner: 'Camille Park',
        createdAt: relativeDateKey(-2),
        lastActivityDate: relativeDateKey(-2),
        leadStatus: 'In Progress',
        leadStatusTone: 'orange',
    },
    {
        id: 'CNT-1004',
        segment: 'opted-out',
        avatarSrc: `${assetBase}/avatars/thumb-4.jpg`,
        name: 'Noah Brooks',
        email: 'noah.brooks@example.com',
        phone: '+44 20 7946 0821',
        company: 'Canva',
        companyLogo: `${assetBase}/thumbs/brands/canva.png`,
        owner: 'Devon Ellis',
        createdAt: relativeDateKey(-3),
        lastActivityDate: relativeDateKey(-3),
        leadStatus: 'Close Deal',
        leadStatusTone: 'blue',
    },
    {
        id: 'CNT-1005',
        segment: 'customer',
        name: 'Hana Ito',
        email: 'hana.ito@example.com',
        phone: '+81 3 4510 2284',
        company: 'Dropbox',
        companyLogo: `${assetBase}/thumbs/brands/dropbox.png`,
        avatarSrc: `${assetBase}/avatars/thumb-5.jpg`,
        owner: 'Amara Singh',
        createdAt: relativeDateKey(-4),
        lastActivityDate: relativeDateKey(-4),
        leadStatus: 'Open Deal',
        leadStatusTone: 'emerald',
    },
    {
        id: 'CNT-1006',
        segment: 'subscribed',
        avatarSrc: `${assetBase}/avatars/thumb-6.jpg`,
        name: 'Julian Stone',
        email: 'julian.stone@example.com',
        phone: '+1 415 555 0138',
        company: 'eBay',
        companyLogo: `${assetBase}/thumbs/brands/ebay.png`,
        owner: 'Ben Ortiz',
        createdAt: relativeDateKey(-5),
        lastActivityDate: relativeDateKey(-5),
        leadStatus: 'Unqualified',
        leadStatusTone: 'gray',
    },
    {
        id: 'CNT-1007',
        segment: 'subscribed',
        name: 'Clara West',
        email: 'clara.west@example.com',
        company: 'Figma',
        companyLogo: `${assetBase}/thumbs/brands/figma.png`,
        avatarSrc: `${assetBase}/avatars/thumb-7.jpg`,
        owner: 'Camille Park',
        createdAt: relativeDateKey(-6),
        lastActivityDate: relativeDateKey(-6),
        leadStatus: 'In Progress',
        leadStatusTone: 'orange',
    },
    {
        id: 'CNT-1008',
        segment: 'customer',
        avatarSrc: `${assetBase}/avatars/thumb-8.jpg`,
        name: 'Omar Reed',
        email: 'omar.reed@example.com',
        phone: '+1 312 555 0176',
        company: 'GitHub',
        companyLogo: `${assetBase}/thumbs/brands/github.png`,
        owner: 'Devon Ellis',
        createdAt: relativeDateKey(-7),
        lastActivityDate: relativeDateKey(-7),
        leadStatus: 'Negotiation',
        leadStatusTone: 'purple',
    },
    {
        id: 'CNT-1009',
        segment: 'opted-out',
        avatarSrc: `${assetBase}/avatars/thumb-9.jpg`,
        name: 'Layla Cole',
        email: 'layla.cole@example.com',
        phone: '+61 2 8015 4498',
        company: 'Google',
        companyLogo: `${assetBase}/thumbs/brands/google.png`,
        owner: 'Amara Singh',
        createdAt: relativeDateKey(-8),
        lastActivityDate: relativeDateKey(-8),
        leadStatus: 'Open Deal',
        leadStatusTone: 'emerald',
    },
    {
        id: 'CNT-1010',
        segment: 'subscribed',
        avatarSrc: `${assetBase}/avatars/thumb-10.jpg`,
        name: 'Dario Costa',
        email: 'dario.costa@example.com',
        phone: '+39 02 8734 1260',
        company: 'HubSpot',
        companyLogo: `${assetBase}/thumbs/brands/hubspot.png`,
        owner: 'Ben Ortiz',
        createdAt: relativeDateKey(-9),
        lastActivityDate: relativeDateKey(-9),
        leadStatus: 'Close Deal',
        leadStatusTone: 'blue',
    },
    {
        id: 'CNT-1011',
        segment: 'customer',
        name: 'Maya Green',
        email: 'maya.green@example.com',
        company: 'Jira',
        companyLogo: `${assetBase}/thumbs/brands/jira.png`,
        avatarSrc: `${assetBase}/avatars/thumb-11.jpg`,
        owner: 'Camille Park',
        createdAt: relativeDateKey(-10),
        lastActivityDate: relativeDateKey(-10),
        leadStatus: 'In Progress',
        leadStatusTone: 'orange',
    },
    {
        id: 'CNT-1012',
        segment: 'subscribed',
        avatarSrc: `${assetBase}/avatars/thumb-12.jpg`,
        name: 'Theo Martin',
        email: 'theo.martin@example.com',
        phone: '+33 1 84 88 0312',
        company: 'Microsoft',
        companyLogo: `${assetBase}/thumbs/brands/microsoft.png`,
        owner: 'Devon Ellis',
        createdAt: relativeDateKey(-11),
        lastActivityDate: relativeDateKey(-11),
        leadStatus: 'Negotiation',
        leadStatusTone: 'purple',
    },
    {
        id: 'CNT-1013',
        segment: 'customer',
        name: 'Aisha Rahman',
        email: 'aisha.rahman@example.com',
        phone: '+971 4 555 0193',
        company: 'Notion',
        companyLogo: `${assetBase}/thumbs/brands/notion.png`,
        avatarSrc: `${assetBase}/avatars/thumb-13.jpg`,
        owner: 'Amara Singh',
        createdAt: relativeDateKey(-12),
        lastActivityDate: relativeDateKey(-12),
        leadStatus: 'Open Deal',
        leadStatusTone: 'emerald',
    },
    {
        id: 'CNT-1014',
        segment: 'opted-out',
        avatarSrc: `${assetBase}/avatars/thumb-14.jpg`,
        name: 'Felix Morgan',
        email: 'felix.morgan@example.com',
        phone: '+49 30 5550 1882',
        company: 'Oracle',
        companyLogo: `${assetBase}/thumbs/brands/oracle.png`,
        owner: 'Ben Ortiz',
        createdAt: relativeDateKey(-13),
        lastActivityDate: relativeDateKey(-13),
        leadStatus: 'Unqualified',
        leadStatusTone: 'gray',
    },
    {
        id: 'CNT-1015',
        segment: 'subscribed',
        avatarSrc: `${assetBase}/avatars/thumb-15.jpg`,
        name: 'Sofia Marin',
        email: 'sofia.marin@example.com',
        company: 'Salesforce',
        companyLogo: `${assetBase}/thumbs/brands/salesforce.png`,
        owner: 'Camille Park',
        createdAt: relativeDateKey(-14),
        lastActivityDate: relativeDateKey(-14),
        leadStatus: 'In Progress',
        leadStatusTone: 'orange',
    },
    {
        id: 'CNT-1016',
        segment: 'customer',
        avatarSrc: `${assetBase}/avatars/thumb-16.jpg`,
        name: 'Evan Price',
        email: 'evan.price@example.com',
        phone: '+1 206 555 0107',
        company: 'Shopify',
        companyLogo: `${assetBase}/thumbs/brands/shopify.png`,
        owner: 'Devon Ellis',
        createdAt: relativeDateKey(-15),
        lastActivityDate: relativeDateKey(-15),
        leadStatus: 'Close Deal',
        leadStatusTone: 'blue',
    },
    {
        id: 'CNT-1017',
        segment: 'subscribed',
        name: 'Nadia Khan',
        email: 'nadia.khan@example.com',
        phone: '+92 21 555 0144',
        company: 'Slack',
        companyLogo: `${assetBase}/thumbs/brands/slack.png`,
        avatarSrc: `${assetBase}/avatars/thumb-17.jpg`,
        owner: 'Amara Singh',
        createdAt: relativeDateKey(-16),
        lastActivityDate: relativeDateKey(-16),
    },
    {
        id: 'CNT-1018',
        segment: 'opted-out',
        avatarSrc: `${assetBase}/avatars/thumb-18.jpg`,
        name: 'Jonah Bell',
        email: 'jonah.bell@example.com',
        phone: '+1 617 555 0159',
        company: 'Spotify',
        companyLogo: `${assetBase}/thumbs/brands/spotify.png`,
        owner: 'Ben Ortiz',
        createdAt: relativeDateKey(-17),
        lastActivityDate: relativeDateKey(-17),
        leadStatus: 'Negotiation',
        leadStatusTone: 'purple',
    },
    {
        id: 'CNT-1019',
        segment: 'customer',
        name: 'Mina Duarte',
        email: 'mina.duarte@example.com',
        phone: '+351 21 555 0177',
        company: 'Stripe',
        companyLogo: `${assetBase}/thumbs/brands/stripe.png`,
        avatarSrc: `${assetBase}/avatars/thumb-19.jpg`,
        owner: 'Camille Park',
        createdAt: relativeDateKey(-18),
        lastActivityDate: relativeDateKey(-18),
        leadStatus: 'Open Deal',
        leadStatusTone: 'emerald',
    },
    {
        id: 'CNT-1020',
        segment: 'subscribed',
        avatarSrc: `${assetBase}/avatars/thumb-20.jpg`,
        name: 'Ravi Mehta',
        email: 'ravi.mehta@example.com',
        phone: '+91 80 5550 2240',
        company: 'Tesla',
        companyLogo: `${assetBase}/thumbs/brands/tesla.png`,
        owner: 'Devon Ellis',
        createdAt: relativeDateKey(-19),
        lastActivityDate: relativeDateKey(-19),
        leadStatus: 'In Progress',
        leadStatusTone: 'orange',
    },
    {
        id: 'CNT-1021',
        segment: 'customer',
        name: 'Grace Liu',
        email: 'grace.liu@example.com',
        company: 'TikTok',
        companyLogo: `${assetBase}/thumbs/brands/tiktok.png`,
        avatarSrc: `${assetBase}/avatars/thumb-21.jpg`,
        owner: 'Amara Singh',
        createdAt: relativeDateKey(-20),
        lastActivityDate: relativeDateKey(-20),
        leadStatus: 'Close Deal',
        leadStatusTone: 'blue',
    },
    {
        id: 'CNT-1022',
        segment: 'subscribed',
        avatarSrc: `${assetBase}/avatars/thumb-22.jpg`,
        name: 'Mateo Ruiz',
        email: 'mateo.ruiz@example.com',
        phone: '+34 91 555 0118',
        company: 'Twitch',
        companyLogo: `${assetBase}/thumbs/brands/twitch.png`,
        owner: 'Ben Ortiz',
        createdAt: relativeDateKey(-21),
        lastActivityDate: relativeDateKey(-21),
        leadStatus: 'Unqualified',
        leadStatusTone: 'gray',
    },
    {
        id: 'CNT-1023',
        segment: 'opted-out',
        name: 'Iris Novak',
        email: 'iris.novak@example.com',
        phone: '+420 2 555 0127',
        company: 'WhatsApp',
        companyLogo: `${assetBase}/thumbs/brands/whatsapp.png`,
        avatarSrc: `${assetBase}/avatars/thumb-23.jpg`,
        owner: 'Camille Park',
        createdAt: relativeDateKey(-22),
        lastActivityDate: relativeDateKey(-22),
        leadStatus: 'Negotiation',
        leadStatusTone: 'purple',
    },
    {
        id: 'CNT-1024',
        segment: 'customer',
        avatarSrc: `${assetBase}/avatars/thumb-24.jpg`,
        name: 'Samir Patel',
        email: 'samir.patel@example.com',
        company: 'Zapier',
        companyLogo: `${assetBase}/thumbs/brands/zapier.png`,
        owner: 'Devon Ellis',
        createdAt: relativeDateKey(-23),
        lastActivityDate: relativeDateKey(-23),
    },
]

const ownerOptions = [
    { value: 'Amara Singh', label: 'Amara Singh' },
    { value: 'Ben Ortiz', label: 'Ben Ortiz' },
    { value: 'Camille Park', label: 'Camille Park' },
    { value: 'Devon Ellis', label: 'Devon Ellis' },
]

const lastActivityOptions = [
    { value: relativeDateKey(0), label: 'Today' },
    { value: relativeDateKey(-1), label: 'Yesterday' },
    { value: relativeDateKey(-2), label: 'Two days ago' },
    { value: relativeDateKey(-3), label: 'Three days ago' },
]

const leadStatusOptions: Array<{
    value: LeadStatus
    label: LeadStatus
    tone: PaletteTone
}> = [
    { value: 'Open Deal', label: 'Open Deal', tone: 'emerald' },
    { value: 'Close Deal', label: 'Close Deal', tone: 'blue' },
    { value: 'Negotiation', label: 'Negotiation', tone: 'purple' },
    { value: 'In Progress', label: 'In Progress', tone: 'orange' },
    { value: 'Unqualified', label: 'Unqualified', tone: 'gray' },
]

const createPresetOptions = [
    {
        value: 'yesterday',
        label: 'Yesterday',
        start: relativeDateKey(-1),
        end: relativeDateKey(-1),
    },
    {
        value: 'last-week',
        label: 'Last Week',
        start: relativeDateKey(-7),
        end: relativeDateKey(-1),
    },
    {
        value: 'last-month',
        label: 'Last Month',
        start: relativeDateKey(-30),
        end: relativeDateKey(-1),
    },
] as const

type CreatePreset = (typeof createPresetOptions)[number]['value']
type DateRange = [Date | null, Date | null]
type SortKey = 'name' | 'email' | 'company' | 'leadStatus'
type OpenPopover = 'create' | 'lead' | null

const formatDateLabel = (dateKey: string) =>
    new Intl.DateTimeFormat('en-GB', {
        day: '2-digit',
        month: 'short',
        year: 'numeric',
    }).format(new Date(`${dateKey}T12:00:00`))

const getSelectionLabel = (
    fallback: string,
    values: string[],
    options: Array<{ value: string; label: string }>,
) => {
    if (values.length === 1) {
        return options.find((option) => option.value === values[0])?.label || fallback
    }
    return values.length > 1 ? `${fallback} (${values.length})` : fallback
}

const columns: ColumnDef<ContactRow>[] = [
    {
        header: () => <span className="whitespace-nowrap">Name</span>,
        accessorKey: 'name',
        enableSorting: true,
        size: 210,
        minSize: 190,
        cell: ({ row }) => (
            <div className="flex min-w-0 items-center gap-2">
                <Avatar
                    alt={row.original.name}
                    size={24}
                    src={row.original.avatarSrc}
                >
                    {row.original.name
                        .split(' ')
                        .map((part) => part[0])
                        .join('')}
                </Avatar>
                <span className="whitespace-nowrap font-medium">
                    {row.original.name}
                </span>
            </div>
        ),
    },
    {
        header: () => <span className="whitespace-nowrap">Email</span>,
        accessorKey: 'email',
        enableSorting: true,
        size: 220,
        cell: ({ row }) => (
            <span className="whitespace-nowrap">
                {row.original.email || '--'}
            </span>
        ),
    },
    {
        header: () => <span className="whitespace-nowrap">Phone Number</span>,
        accessorKey: 'phone',
        enableSorting: false,
        size: 170,
        cell: ({ row }) => (
            <span className="whitespace-nowrap">
                {row.original.phone || '--'}
            </span>
        ),
    },
    {
        header: () => <span className="whitespace-nowrap">Company</span>,
        accessorKey: 'company',
        enableSorting: true,
        size: 190,
        cell: ({ row }) => (
            <div className="flex items-center gap-2 whitespace-nowrap">
                <img
                    alt=""
                    className="size-4"
                    src={row.original.companyLogo}
                />
                <span>{row.original.company}</span>
            </div>
        ),
    },
    {
        header: () => <span className="whitespace-nowrap">Lead Status</span>,
        accessorKey: 'leadStatus',
        enableSorting: true,
        size: 150,
        cell: ({ row }) =>
            row.original.leadStatus && row.original.leadStatusTone ? (
                <Tag className={paletteClassMap[row.original.leadStatusTone].tag}>
                    {row.original.leadStatus}
                </Tag>
            ) : (
                <span className="text-muted-foreground">--</span>
            ),
    },
    {
        id: 'actions',
        header: '',
        enableSorting: false,
        size: 52,
        minSize: 52,
        maxSize: 52,
        cell: ({ row }) => (
            <div className="flex justify-end">
                <Dropdown
                    placement="bottom-end"
                    renderTitle={
                        <Button
                            aria-label={`Actions for ${row.original.name}`}
                            icon={<PiDotsThreeVerticalBold />}
                            shape="circle"
                            size="sm"
                            variant="ghost"
                        />
                    }
                >
                    <Dropdown.Item eventKey="view">View contact</Dropdown.Item>
                    <Dropdown.Item eventKey="edit">Edit contact</Dropdown.Item>
                    <Dropdown.Item eventKey="archive">
                        Archive contact
                    </Dropdown.Item>
                </Dropdown>
            </div>
        ),
    },
]

export default function DataGridContactFilterBar() {
    const [contactRowsData, setContactRowsData] = useState(contactRows)
    const [activeSegment, setActiveSegment] = useState<ContactView>('all')
    const [searchText, setSearchText] = useState('')
    const [ownerFilter, setOwnerFilter] = useState<string[]>([])
    const [createPreset, setCreatePreset] = useState<CreatePreset | null>(null)
    const [createDateRange, setCreateDateRange] = useState<DateRange>([
        null,
        null,
    ])
    const [lastActivityFilter, setLastActivityFilter] = useState<string[]>([])
    const [leadStatusFilter, setLeadStatusFilter] = useState<LeadStatus[]>([])
    const [leadSearch, setLeadSearch] = useState('')
    const [openPopover, setOpenPopover] = useState<OpenPopover>(null)
    const [sortKey, setSortKey] = useState<SortKey | null>(null)
    const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | ''>('')
    const [pageIndex, setPageIndex] = useState(1)
    const [pageSize, setPageSize] = useState(pageSizes[0])
    const [selectedRowIds, setSelectedRowIds] = useState<string[]>([])
    const dataTableRef = useRef<DataTableResetHandle>(null)
    const [visibleFilterTools, setVisibleFilterTools] = useState<FilterTool[]>(
        filterToolOptions.map((option) => option.value),
    )
    const [copied, setCopied] = useState(false)

    useEffect(() => {
        if (!copied) {
            return
        }

        const timeout = window.setTimeout(() => setCopied(false), 2000)

        return () => window.clearTimeout(timeout)
    }, [copied])

    useEffect(() => {
        setPageIndex(1)
        setSelectedRowIds([])
    }, [
        activeSegment,
        searchText,
        ownerFilter,
        createPreset,
        createDateRange,
        lastActivityFilter,
        leadStatusFilter,
    ])

    const visibleData = useMemo(() => {
        const filteredRows = contactRowsData.filter((row) => {
            const matchesSegment =
                activeSegment === 'all' || row.segment === activeSegment
            const matchesOwner =
                ownerFilter.length === 0 || ownerFilter.includes(row.owner)
            const matchesLastActivity =
                lastActivityFilter.length === 0 ||
                lastActivityFilter.includes(row.lastActivityDate)
            const matchesLeadStatus =
                leadStatusFilter.length === 0 ||
                (row.leadStatus && leadStatusFilter.includes(row.leadStatus))
            const matchesCreateDate = createDateRange[0]
                ? row.createdAt >= formatDateKey(createDateRange[0]) &&
                  (!createDateRange[1] ||
                      row.createdAt <= formatDateKey(createDateRange[1]))
                : createPreset
                  ? (() => {
                        const preset = createPresetOptions.find(
                            (option) => option.value === createPreset,
                        )
                        return Boolean(
                            preset &&
                                row.createdAt >= preset.start &&
                                row.createdAt <= preset.end,
                        )
                    })()
                  : true

            return (
                matchesSegment &&
                matchesOwner &&
                matchesLastActivity &&
                matchesLeadStatus &&
                matchesCreateDate
            )
        })

        const normalizedSearch = searchText.trim().toLowerCase()
        const searchedRows = normalizedSearch
            ? filteredRows.filter((row) =>
                  [row.name, row.email, row.phone].some((value) =>
                      value?.toLowerCase().includes(normalizedSearch),
                  ),
              )
            : filteredRows

        const sortedRows = [...searchedRows].sort((left, right) => {
            if (!sortKey || !sortOrder) {
                return 0
            }

            const leftValue = left[sortKey] || ''
            const rightValue = right[sortKey] || ''
            const comparison = String(leftValue).localeCompare(String(rightValue))
            return sortOrder === 'asc' ? comparison : -comparison
        })
        const pageStart = (pageIndex - 1) * pageSize

        return {
            rows: sortedRows.slice(pageStart, pageStart + pageSize),
            total: sortedRows.length,
        }
    }, [
        activeSegment,
        contactRowsData,
        createDateRange,
        createPreset,
        lastActivityFilter,
        leadStatusFilter,
        ownerFilter,
        pageIndex,
        pageSize,
        searchText,
        sortKey,
        sortOrder,
    ])

    const handleSort = (sort: {
        sortKey: string | number
        sortOrder: 'asc' | 'desc' | ''
    }) => {
        if (
            typeof sort.sortKey !== 'string' ||
            !['name', 'email', 'company', 'leadStatus'].includes(
                sort.sortKey,
            )
        ) {
            return
        }

        setSortKey(sort.sortOrder ? (sort.sortKey as SortKey) : null)
        setSortOrder(sort.sortOrder)
        setPageIndex(1)
        setSelectedRowIds([])
    }

    const handleRowSelect = (checked: boolean, row: ContactRow) => {
        setSelectedRowIds((current) =>
            checked
                ? Array.from(new Set([...current, row.id]))
                : current.filter((id) => id !== row.id),
        )
    }

    const handleAllRowSelect = (
        checked: boolean,
        rows: Row<ContactRow>[],
    ) => {
        const pageRowIds = rows.map((row) => row.original.id)
        setSelectedRowIds((current) =>
            checked
                ? Array.from(new Set([...current, ...pageRowIds]))
                : current.filter((id) => !pageRowIds.includes(id)),
        )
    }

    const handleSegmentChange = (value: string) => {
        if (contactViewTabs.some((tab) => tab.value === value)) {
            setActiveSegment(value as ContactView)
        }
    }

    const activeTabId =
        contactViewTabs.find((tab) => tab.value === activeSegment)?.id ||
        contactViewTabs[0].id

    const resetFilters = () => {
        setSearchText('')
        setOwnerFilter([])
        setCreatePreset(null)
        setCreateDateRange([null, null])
        setLastActivityFilter([])
        setLeadStatusFilter([])
        setLeadSearch('')
        setOpenPopover(null)
    }

    const toggleFilterTool = (filterTool: FilterTool) => {
        setVisibleFilterTools((current) =>
            current.includes(filterTool)
                ? current.filter((item) => item !== filterTool)
                : [...current, filterTool],
        )
        setOpenPopover(null)
    }

    const archiveSelectedContacts = () => {
        const selectedIds = new Set(selectedRowIds)
        dataTableRef.current?.resetSelected()
        setContactRowsData((current) =>
            current.filter((row) => !selectedIds.has(row.id)),
        )
        setSelectedRowIds([])
        setPageIndex(1)
    }

    const filteredLeadStatusOptions = leadStatusOptions.filter((option) =>
        option.label.toLowerCase().includes(leadSearch.toLowerCase()),
    )
    const selectedCreateLabel = createDateRange[0]
        ? createDateRange[1]
            ? `${formatDateLabel(formatDateKey(createDateRange[0]))} – ${formatDateLabel(formatDateKey(createDateRange[1]))}`
            : `${formatDateLabel(formatDateKey(createDateRange[0]))} – Select end`
        : createPresetOptions.find((option) => option.value === createPreset)?.label

    return (
        <section
            aria-labelledby="data-grid-contact-filter-bar-title"
            className="w-full"
        >
            <h4
                id="data-grid-contact-filter-bar-title"
                className="px-4 pt-2 font-semibold"
            >
                Contact
            </h4>
            <div className="pb-4">
                <Tabs
                    value={activeSegment}
                    onChange={handleSegmentChange}
                    variant="underline"
                    className="flex flex-wrap items-end gap-4 border-b px-4 pt-2"
                >
                    <Tabs.TabList
                        aria-label="Contact segments"
                        className="min-w-0 flex-1 border-b-0"
                    >
                        {contactViewTabs.map((tab) => (
                            <Tabs.TabNav
                                key={tab.value}
                                id={tab.id}
                                icon={tab.icon}
                                value={tab.value}
                                className="whitespace-nowrap"
                            >
                                {tab.label}
                            </Tabs.TabNav>
                        ))}
                    </Tabs.TabList>
                </Tabs>
                <div
                    aria-labelledby={activeTabId}
                    className="min-w-0"
                    role="tabpanel"
                    tabIndex={0}
                >
                <div className="border-b p-4">
                    <div className="flex flex-wrap items-center justify-between gap-4">
                        <div className="flex min-w-0 flex-wrap items-center gap-2">
                            {visibleFilterTools.includes('owner') && (
                                <PopoverFilter
                                    data={ownerOptions}
                                    placement="bottom-start"
                                    value={ownerFilter}
                                    onChange={(items) =>
                                        setOwnerFilter(
                                            items.map((item) => item.value),
                                        )
                                    }
                                    renderTrigger={
                                        <Button
                                            active={ownerFilter.length > 0}
                                            icon={<PiCaretDown />}
                                            iconAlignment="end"
                                        >
                                            {getSelectionLabel(
                                                'Contact owner',
                                                ownerFilter,
                                                ownerOptions,
                                            )}
                                        </Button>
                                    }
                                />
                            )}
                            {visibleFilterTools.includes('created-date') && (
                                <Popover
                                    open={openPopover === 'create'}
                                    onOpenChange={(open) =>
                                        setOpenPopover(open ? 'create' : null)
                                    }
                                    placement="bottom-start"
                                    className="p-0"
                                    renderTrigger={
                                        <Button
                                            active={Boolean(selectedCreateLabel)}
                                            icon={<PiCaretDown />}
                                            iconAlignment="end"
                                        >
                                            {selectedCreateLabel || 'Created date'}
                                        </Button>
                                    }
                                >
                                    <div className="flex flex-col sm:flex-row">
                                        <div className="flex w-full flex-col gap-2 border-b pb-4 sm:w-40 sm:border-b-0 sm:border-r sm:py-4 sm:px-2">
                                            {createPresetOptions.map((option) => (
                                                <Button
                                                    active={createPreset === option.value}
                                                    className="justify-start"
                                                    key={option.value}
                                                    onClick={() => {
                                                        setCreatePreset(option.value)
                                                        setCreateDateRange([null, null])
                                                    }}
                                                    variant="ghost"
                                                >
                                                    {option.label}
                                                </Button>
                                            ))}
                                        </div>
                                        <div className="flex min-w-0 flex-col p-4">
                                            <Calendar.Range
                                                value={createDateRange}
                                                defaultMonth={currentDate}
                                                onChange={(range) => {
                                                    setCreateDateRange(range)
                                                    setCreatePreset(null)
                                                }}
                                            />
                                        </div>
                                    </div>
                                </Popover>
                            )}
                            {visibleFilterTools.includes('last-activity') && (
                                <PopoverFilter
                                    data={lastActivityOptions}
                                    placement="bottom-start"
                                    title="Last activity date"
                                    value={lastActivityFilter}
                                    onChange={(items) =>
                                        setLastActivityFilter(
                                            items.map((item) => item.value),
                                        )
                                    }
                                    renderTrigger={
                                        <Button
                                            active={lastActivityFilter.length > 0}
                                            icon={<PiCaretDown />}
                                            iconAlignment="end"
                                        >
                                            {getSelectionLabel(
                                                'Last activity date',
                                                lastActivityFilter,
                                                lastActivityOptions,
                                            )}
                                        </Button>
                                    }
                                />
                            )}
                            {visibleFilterTools.includes('lead-status') && (
                                <Popover
                                    open={openPopover === 'lead'}
                                    onOpenChange={(open) =>
                                        setOpenPopover(open ? 'lead' : null)
                                    }
                                    placement="bottom-start"
                                    className="p-0"
                                    renderTrigger={
                                        <Button
                                            active={leadStatusFilter.length > 0}
                                            icon={<PiCaretDown />}
                                            iconAlignment="end"
                                        >
                                            {getSelectionLabel(
                                                'Lead status',
                                                leadStatusFilter,
                                                leadStatusOptions,
                                            )}
                                        </Button>
                                    }
                                >
                                    <div className="w-64 p-2">
                                        <Input
                                            aria-label="Search lead statuses"
                                            className="mb-2 w-full"
                                            onChange={(event) =>
                                                setLeadSearch(event.target.value)
                                            }
                                            placeholder="Search status"
                                            prefix={
                                                <PiMagnifyingGlass className="text-muted-foreground" />
                                            }
                                            value={leadSearch}
                                        />
                                        <Checkbox.Group
                                            className="w-full gap-0"
                                            onChange={(next) =>
                                                setLeadStatusFilter(next as LeadStatus[])
                                            }
                                            value={leadStatusFilter}
                                            vertical
                                        >
                                            {filteredLeadStatusOptions.map((option) => (
                                                <Checkbox
                                                    className="rounded-control-sm p-2 hover:bg-accent"
                                                    key={option.value}
                                                    value={option.value}
                                                >
                                                    <Tag
                                                        className={
                                                            paletteClassMap[option.tone].tag
                                                        }
                                                    >
                                                        {option.label}
                                                    </Tag>
                                                </Checkbox>
                                            ))}
                                        </Checkbox.Group>
                                    </div>
                                </Popover>
                            )}
                            <Dropdown
                                renderTitle={
                                    <Button
                                        aria-label="Configure visible contact filters"
                                        icon={<PiPlus />}
                                        shape="circle"
                                        variant="ghost"
                                    />
                                }
                            >
                                {filterToolOptions.map((option) => (
                                    <Dropdown.Item
                                        className="w-full p-0"
                                        closeOnClick={false}
                                        eventKey={`filter-tool-${option.value}`}
                                        key={option.value}
                                        variant="custom"
                                    >
                                        <Checkbox
                                            checked={visibleFilterTools.includes(
                                                option.value,
                                            )}
                                            className="w-full rounded-control-sm p-2 hover:bg-accent"
                                            onChange={() =>
                                                toggleFilterTool(option.value)
                                            }
                                        >
                                            {option.label}
                                        </Checkbox>
                                    </Dropdown.Item>
                                ))}
                            </Dropdown>
                        </div>
                        <div className="flex items-center gap-2">
                            <Tooltip title="Reset">
                                <Button
                                    aria-label="Reset filters"
                                    icon={<PiArrowUUpLeft />}
                                    onClick={resetFilters}
                                    shape="circle"
                                    variant="ghost"
                                />
                            </Tooltip>
                            <Tooltip title={copied ? 'Copied' : 'Copy'}>
                                <Button
                                    aria-label="Copy view"
                                    icon={<PiCopy />}
                                    onClick={() => setCopied(true)}
                                    shape="circle"
                                    variant="ghost"
                                />
                            </Tooltip>
                        </div>
                    </div>
                </div>

                <div className="flex flex-wrap items-center justify-between gap-4 border-b p-4">
                    <Input
                        aria-label="Search contacts by name, email, or phone"
                        className="w-full sm:w-96"
                        onChange={(event) => setSearchText(event.target.value)}
                        placeholder="Find a contact"
                        prefix={<PiMagnifyingGlass className="text-muted-foreground" />}
                        value={searchText}
                    />
                    <div className="flex flex-wrap items-center gap-2">
                        <Button icon={<PiExport />}>Export</Button>
                    </div>
                </div>

                <DataTable
                    ref={(
                        instance: DataTableResetHandle | HTMLTableElement | null,
                    ) => {
                        if (instance && 'resetSelected' in instance) {
                            dataTableRef.current = instance
                        }
                    }}
                    columns={columns}
                    data={visibleData.rows}
                    indeterminateCheckboxChecked={(rows) =>
                        rows.length > 0 &&
                        rows.every((row) => selectedRowIds.includes(row.original.id))
                    }
                    onAllRowSelect={handleAllRowSelect}
                    onPageSizeChange={(nextPageSize) => {
                        setPageSize(nextPageSize)
                        setPageIndex(1)
                    }}
                    onPaginationChange={(nextPage) => {
                        setPageIndex(nextPage)
                        setSelectedRowIds([])
                    }}
                    onRowSelect={handleRowSelect}
                    onSort={handleSort}
                    pageSizes={pageSizes}
                    pagingData={{
                        total: visibleData.total,
                        pageIndex,
                        pageSize,
                    }}
                    selectable
                    checkboxChecked={(row) => selectedRowIds.includes(row.id)}
                    compact
                    hoverable
                />
                <ActionBar open={selectedRowIds.length > 0} width={560}>
                    <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
                        <span className="font-medium">
                            <span className="font-semibold text-foreground">
                                {selectedRowIds.length}{' '}
                                {selectedRowIds.length === 1
                                    ? 'contact'
                                    : 'contacts'}
                            </span>{' '}
                            selected
                        </span>
                        <Button
                            icon={<PiArchive />}
                            onClick={archiveSelectedContacts}
                            destructive
                        >
                            Archive
                        </Button>
                    </div>
                </ActionBar>
                </div>
            </div>
        </section>
    )
}

Data Grid 04

Preview
npx nateui@latest add DataGridProductPinnedColumns
Dark
import { useMemo, useRef, useState } from 'react'
import {
    PiArchive,
    PiFunnel,
    PiMagnifyingGlass,
    PiPencilSimple,
    PiPlus,
    PiPushPin,
    PiTrash,
} from 'react-icons/pi'
import Avatar from '@/components/ui/Avatar'
import ActionBar from '@/components/ui/ActionBar'
import Button from '@/components/ui/Button'
import Card from '@/components/ui/Card'
import Checkbox from '@/components/ui/Checkbox'
import Container from '@/components/composites/Container'
import Input from '@/components/ui/Input'
import Popover from '@/components/ui/Popover'
import Progress from '@/components/ui/Progress'
import Segment from '@/components/ui/Segment'
import Slider from '@/components/ui/Slider'
import Tag from '@/components/ui/Tag'
import DataTable from '@/components/composites/DataTable'
import Historgram from '@/components/composites/Historgram'
import type {
    ColumnDef,
    DataTableResetHandle,
    OnSortParam,
    Row,
} from '@/components/composites/DataTable'
import type { ChangeEvent } from 'react'

const assetBase = 'https://statics.nateui.com/img'

const CATEGORIES = [
    'Outdoor Gear',
    'Home Office',
    'Kitchen',
    'Fitness',
    'Audio',
    'Lighting',
] as const

type Category = (typeof CATEGORIES)[number]

const STOCK_LEVELS = ['High', 'Medium', 'Low', 'Out of Stock'] as const

type StockLevel = (typeof STOCK_LEVELS)[number]

type Product = {
    product: string
    sku: string
    price: number
    category: Category
    stock: number
    image: number
}

type SortableKey = 'product' | 'sku' | 'price' | 'category' | 'stock'

type PinState = 'start' | 'end' | 'none'
type PinnableColumnId = 'product' | 'sku' | 'price' | 'category' | 'stock' | 'actions'

const PRODUCTS: Product[] = [
    { product: 'Trail Runner Backpack', sku: 'OG-1001', price: 89, category: 'Outdoor Gear', stock: 132, image: 1 },
    { product: 'Alpine Camp Stove', sku: 'OG-1002', price: 64.5, category: 'Outdoor Gear', stock: 18, image: 2 },
    { product: 'Rip-Stop Tent 2P', sku: 'OG-1003', price: 214, category: 'Outdoor Gear', stock: 0, image: 3 },
    { product: 'Insulated Water Flask', sku: 'OG-1004', price: 28, category: 'Outdoor Gear', stock: 76, image: 4 },
    { product: 'Adjustable Laptop Stand', sku: 'HO-2001', price: 52, category: 'Home Office', stock: 54, image: 5 },
    { product: 'Ergonomic Desk Chair', sku: 'HO-2002', price: 339, category: 'Home Office', stock: 12, image: 6 },
    { product: 'Dual Monitor Arm', sku: 'HO-2003', price: 78, category: 'Home Office', stock: 0, image: 7 },
    { product: 'Desk Lamp USB-C', sku: 'HO-2004', price: 36, category: 'Home Office', stock: 98, image: 8 },
    { product: 'Ceramic Knife Set', sku: 'KT-3001', price: 69, category: 'Kitchen', stock: 41, image: 9 },
    { product: 'Stainless Pour-Over Kettle', sku: 'KT-3002', price: 44, category: 'Kitchen', stock: 8, image: 10 },
    { product: 'Compact Air Fryer', sku: 'KT-3003', price: 129, category: 'Kitchen', stock: 63, image: 11 },
    { product: 'Bamboo Cutting Board', sku: 'KT-3004', price: 22, category: 'Kitchen', stock: 150, image: 12 },
    { product: 'Adjustable Dumbbell Pair', sku: 'FT-4001', price: 189, category: 'Fitness', stock: 27, image: 13 },
    { product: 'Foam Roller Pro', sku: 'FT-4002', price: 24, category: 'Fitness', stock: 0, image: 14 },
    { product: 'Resistance Band Kit', sku: 'FT-4003', price: 19, category: 'Fitness', stock: 112, image: 15 },
    { product: 'Yoga Mat Extra Thick', sku: 'FT-4004', price: 34, category: 'Fitness', stock: 47, image: 16 },
    { product: 'Wireless Earbuds Lite', sku: 'AU-5001', price: 58, category: 'Audio', stock: 84, image: 17 },
    { product: 'Studio Monitor Speaker', sku: 'AU-5002', price: 149, category: 'Audio', stock: 6, image: 18 },
    { product: 'Noise-Cancelling Headset', sku: 'AU-5003', price: 175, category: 'Audio', stock: 33, image: 19 },
    { product: 'USB Condenser Mic', sku: 'AU-5004', price: 99, category: 'Audio', stock: 0, image: 20 },
    { product: 'Smart Desk Lamp', sku: 'LT-6001', price: 46, category: 'Lighting', stock: 71, image: 7 },
    { product: 'LED Panel Light', sku: 'LT-6002', price: 58, category: 'Lighting', stock: 15, image: 14 },
    { product: 'Motion Sensor Nightlight', sku: 'LT-6003', price: 14, category: 'Lighting', stock: 140, image: 19 },
    { product: 'Dimmable Floor Lamp', sku: 'LT-6004', price: 88, category: 'Lighting', stock: 3, image: 6 },
]

const STOCK_LEVEL_STROKE: Record<StockLevel, string> = {
    High: 'bg-success',
    Medium: 'bg-info',
    Low: 'bg-warning',
    'Out of Stock': 'bg-destructive',
}

const STOCK_REFERENCE_MAX = 150

function getStockLevel(units: number): StockLevel {
    if (units <= 0) return 'Out of Stock'
    if (units <= 20) return 'Low'
    if (units <= 70) return 'Medium'
    return 'High'
}

function getStockPercent(units: number): number {
    return Math.max(0, Math.min(100, Math.round((units / STOCK_REFERENCE_MAX) * 100)))
}

function formatPrice(value: number): string {
    return value.toLocaleString('en-US', {
        style: 'currency',
        currency: 'USD',
        minimumFractionDigits: 2,
        maximumFractionDigits: 2,
    })
}

const PRICE_MIN = Math.min(...PRODUCTS.map((product) => product.price))
const PRICE_MAX = Math.max(...PRODUCTS.map((product) => product.price))
const PRICE_BIN_COUNT = 16

function buildPriceHistogram(prices: number[], min: number, max: number, binCount: number) {
    const binSize = (max - min) / binCount
    const counts = new Array(binCount).fill(0)

    prices.forEach((price) => {
        const index = Math.min(binCount - 1, Math.max(0, Math.floor((price - min) / binSize)))
        counts[index] += 1
    })

    return counts.map((value, index) => ({
        value,
        label: `$${Math.round(min + index * binSize)}`,
    }))
}

const PRICE_HISTOGRAM_DATA = buildPriceHistogram(
    PRODUCTS.map((product) => product.price),
    PRICE_MIN,
    PRICE_MAX,
    PRICE_BIN_COUNT,
)

const PINNABLE_COLUMNS: { id: PinnableColumnId; label: string }[] = [
    { id: 'product', label: 'Product' },
    { id: 'sku', label: 'SKU' },
    { id: 'price', label: 'Price' },
    { id: 'category', label: 'Category' },
    { id: 'stock', label: 'Stock' },
    { id: 'actions', label: 'Actions' },
]

const DEFAULT_PIN_CONFIG: Record<PinnableColumnId, PinState> = {
    product: 'start',
    sku: 'none',
    price: 'none',
    category: 'none',
    stock: 'none',
    actions: 'end',
}

const columns: ColumnDef<Product>[] = [
    {
        accessorKey: 'product',
        header: 'Product',
        size: 340,
        cell: ({ row }) => (
            <div className="flex min-w-0 items-center gap-3">
                <Avatar
                    shape="round"
                    size="sm"
                    src={`${assetBase}/thumbs/products/product-${row.original.image}.jpg`}
                    alt=""
                />
                <span className="truncate font-semibold text-foreground">
                    {row.original.product}
                </span>
            </div>
        ),
    },
    {
        accessorKey: 'sku',
        header: 'SKU',
        size: 170,
        cell: ({ row }) => (
            <span>{row.original.sku}</span>
        ),
    },
    {
        accessorKey: 'price',
        header: 'Price',
        size: 160,
        cell: ({ row }) => (
            <span className="tabular-nums font-medium text-foreground">
                {formatPrice(row.original.price)}
            </span>
        ),
    },
    {
        accessorKey: 'category',
        header: 'Category',
        size: 200,
        cell: ({ row }) => <Tag>{row.original.category}</Tag>,
    },
    {
        accessorKey: 'stock',
        header: 'Stock',
        size: 280,
        cell: ({ row }) => {
            const level = getStockLevel(row.original.stock)
            return (
                <div className="flex min-w-0 flex-col gap-1.5">
                    <span>
                        <span className="font-medium text-foreground">
                            {row.original.stock} unit
                        </span>
                        <span className="text-muted-foreground"> · {level}</span>
                    </span>
                    <Progress
                        percent={getStockPercent(row.original.stock)}
                        size="sm"
                        showInfo={false}
                        strokeClass={STOCK_LEVEL_STROKE[level]}
                    />
                </div>
            )
        },
    },
    {
        id: 'actions',
        header: '',
        size: 88,
        enableSorting: false,
        cell: ({ row }) => (
            <div className="flex items-center justify-end gap-2">
                <Button
                    size="sm"
                    variant="ghost"
                    icon={<PiPencilSimple />}
                    aria-label={`Edit ${row.original.product}`}
                />
                <Button
                    size="sm"
                    variant="ghost"
                    destructive
                    icon={<PiTrash />}
                    aria-label={`Delete ${row.original.product}`}
                />
            </div>
        ),
    },
]

export default function DataGridProductPinnedColumns() {
    const [productRows, setProductRows] = useState<Product[]>(PRODUCTS)
    const [search, setSearch] = useState('')
    const [priceRange, setPriceRange] = useState<[number, number]>([PRICE_MIN, PRICE_MAX])
    const [selectedCategories, setSelectedCategories] = useState<Set<Category>>(new Set())
    const [selectedStockLevels, setSelectedStockLevels] = useState<Set<StockLevel>>(new Set())
    const [draftPriceRange, setDraftPriceRange] =
        useState<[number, number]>([PRICE_MIN, PRICE_MAX])
    const [draftCategories, setDraftCategories] = useState<Set<Category>>(new Set())
    const [draftStockLevels, setDraftStockLevels] = useState<Set<StockLevel>>(new Set())
    const [filterOpen, setFilterOpen] = useState(false)
    const [pinConfig, setPinConfig] =
        useState<Record<PinnableColumnId, PinState>>(DEFAULT_PIN_CONFIG)
    const [sortKey, setSortKey] = useState('')
    const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | ''>('')
    const [pageIndex, setPageIndex] = useState(1)
    const [pageSize, setPageSize] = useState(10)
    const [selectedRows, setSelectedRows] = useState<Product[]>([])
    const dataTableRef = useRef<DataTableResetHandle>(null)

    const resetPageAndSelection = () => {
        setPageIndex(1)
        setSelectedRows([])
    }

    const filteredSorted = useMemo(() => {
        const query = search.trim().toLowerCase()

        let rows = productRows.filter((product) => {
            if (product.price < priceRange[0] || product.price > priceRange[1]) {
                return false
            }
            if (
                selectedCategories.size > 0 &&
                !selectedCategories.has(product.category)
            ) {
                return false
            }
            if (
                selectedStockLevels.size > 0 &&
                !selectedStockLevels.has(getStockLevel(product.stock))
            ) {
                return false
            }
            return true
        })

        if (query) {
            rows = rows.filter(
                (product) =>
                    product.product.toLowerCase().includes(query) ||
                    product.sku.toLowerCase().includes(query),
            )
        }

        if (sortKey === 'price' || sortKey === 'stock') {
            const key = sortKey as Extract<SortableKey, 'price' | 'stock'>
            rows = [...rows].sort((a, b) => {
                const diff = a[key] - b[key]
                return sortOrder === 'desc' ? -diff : diff
            })
        } else if (sortKey === 'product' || sortKey === 'sku' || sortKey === 'category') {
            const key = sortKey as Extract<SortableKey, 'product' | 'sku' | 'category'>
            rows = [...rows].sort((a, b) => {
                const diff = a[key].localeCompare(b[key])
                return sortOrder === 'desc' ? -diff : diff
            })
        }

        return rows
    }, [
        productRows,
        search,
        priceRange,
        selectedCategories,
        selectedStockLevels,
        sortKey,
        sortOrder,
    ])

    const total = filteredSorted.length
    const pageRows = useMemo(() => {
        const start = (pageIndex - 1) * pageSize
        return filteredSorted.slice(start, start + pageSize)
    }, [filteredSorted, pageIndex, pageSize])

    const columnPinning = useMemo(() => {
        const start: string[] = []
        const end: string[] = []
        PINNABLE_COLUMNS.forEach(({ id }) => {
            if (pinConfig[id] === 'start') start.push(id)
            if (pinConfig[id] === 'end') end.push(id)
        })
        return { start, end }
    }, [pinConfig])

    const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
        setSearch(e.target.value)
        resetPageAndSelection()
    }

    const handleFilterOpenChange = (open: boolean) => {
        if (open) {
            setDraftPriceRange(priceRange)
            setDraftCategories(new Set(selectedCategories))
            setDraftStockLevels(new Set(selectedStockLevels))
        }
        setFilterOpen(open)
    }

    const handleDraftPriceRangeChange = (value: [number, number]) => {
        setDraftPriceRange(value)
    }

    const handleDraftPriceMinChange = (e: ChangeEvent<HTMLInputElement>) => {
        const next = Number(e.target.value)
        if (Number.isNaN(next)) return
        const clamped = Math.min(Math.max(next, PRICE_MIN), draftPriceRange[1])
        setDraftPriceRange([clamped, draftPriceRange[1]])
    }

    const handleDraftPriceMaxChange = (e: ChangeEvent<HTMLInputElement>) => {
        const next = Number(e.target.value)
        if (Number.isNaN(next)) return
        const clamped = Math.max(Math.min(next, PRICE_MAX), draftPriceRange[0])
        setDraftPriceRange([draftPriceRange[0], clamped])
    }

    const toggleDraftCategory = (category: Category) => {
        setDraftCategories((prev) => {
            const next = new Set(prev)
            if (next.has(category)) {
                next.delete(category)
            } else {
                next.add(category)
            }
            return next
        })
    }

    const toggleDraftStockLevel = (level: StockLevel) => {
        setDraftStockLevels((prev) => {
            const next = new Set(prev)
            if (next.has(level)) {
                next.delete(level)
            } else {
                next.add(level)
            }
            return next
        })
    }

    const handleApplyFilters = () => {
        setPriceRange(draftPriceRange)
        setSelectedCategories(new Set(draftCategories))
        setSelectedStockLevels(new Set(draftStockLevels))
        resetPageAndSelection()
        setFilterOpen(false)
    }

    const handleResetFilters = () => {
        setDraftPriceRange([PRICE_MIN, PRICE_MAX])
        setDraftCategories(new Set())
        setDraftStockLevels(new Set())
        setPriceRange([PRICE_MIN, PRICE_MAX])
        setSelectedCategories(new Set())
        setSelectedStockLevels(new Set())
        resetPageAndSelection()
    }

    const handleSort = (sort: OnSortParam) => {
        setSortKey(String(sort.sortKey))
        setSortOrder(sort.sortOrder)
        setPageIndex(1)
    }

    const handlePaginationChange = (page: number) => {
        setPageIndex(page)
    }

    const handlePageSizeChange = (size: number) => {
        setPageSize(size)
        setPageIndex(1)
    }

    const archiveSelectedProducts = () => {
        const selectedSkus = new Set(selectedRows.map((row) => row.sku))
        dataTableRef.current?.resetSelected()
        setProductRows((current) =>
            current.filter((product) => !selectedSkus.has(product.sku)),
        )
        setSelectedRows([])
        setPageIndex(1)
    }

    const isRowSelected = (row: Product) =>
        selectedRows.some((item) => item.sku === row.sku)

    const handleRowSelect = (checked: boolean, row: Product) => {
        setSelectedRows((prev) =>
            checked ? [...prev, row] : prev.filter((item) => item.sku !== row.sku),
        )
    }

    const handleAllRowSelect = (checked: boolean, rows: Row<Product>[]) => {
        setSelectedRows(checked ? rows.map((row) => row.original) : [])
    }

    const handlePinChange = (id: PinnableColumnId, value: PinState) => {
        setPinConfig((prev) => ({ ...prev, [id]: value }))
    }

    return (
        <section aria-labelledby="product-inventory-heading">
            <Container>
                <Card bodyClass="p-0">
                <div className="flex flex-col gap-4 border-b p-4 sm:flex-row sm:items-center sm:justify-between">
                    <div>
                        <h4 id="product-inventory-heading">Product Inventory</h4>
                        <p className="text-sm text-muted-foreground">
                            Monitor stock levels and manage catalog items in one place
                        </p>
                    </div>
                    <Button variant="solid" icon={<PiPlus />}>
                        Add Product
                    </Button>
                </div>

                <div className="flex flex-col gap-2 border-b p-4 sm:flex-row sm:items-center sm:justify-between">
                    <Input
                        prefix={<PiMagnifyingGlass />}
                        placeholder="Search by name or SKU"
                        aria-label="Search by name or SKU"
                        className="w-full sm:max-w-xs"
                        value={search}
                        onChange={handleSearchChange}
                    />
                    <div className="flex flex-wrap items-center gap-2">
                        <Popover
                            open={filterOpen}
                            onOpenChange={handleFilterOpenChange}
                            width={320}
                            renderTrigger={
                                <Button icon={<PiFunnel />}>
                                    Filter
                                </Button>
                            }
                        >
                            <div className="flex flex-col gap-4">
                                <div>
                                    <h6 className="mb-2">Price Range</h6>
                                    <Historgram
                                        data={PRICE_HISTOGRAM_DATA}
                                        range={draftPriceRange}
                                        min={PRICE_MIN}
                                        max={PRICE_MAX}
                                    />
                                    <div className="mx-1.5 mt-2">
                                        <Slider.Range
                                            min={PRICE_MIN}
                                            max={PRICE_MAX}
                                            value={draftPriceRange}
                                            onChange={handleDraftPriceRangeChange}
                                        />
                                    </div>
                                    <div className="mt-4 flex items-center gap-2">
                                        <Input
                                            size="sm"
                                            type="number"
                                            prefix="$"
                                            className="min-w-0 flex-1"
                                            value={draftPriceRange[0]}
                                            onChange={handleDraftPriceMinChange}
                                            aria-label="Minimum price"
                                        />
                                        <Input
                                            size="sm"
                                            type="number"
                                            prefix="$"
                                            className="min-w-0 flex-1"
                                            value={draftPriceRange[1]}
                                            onChange={handleDraftPriceMaxChange}
                                            aria-label="Maximum price"
                                        />
                                    </div>
                                </div>

                                <div>
                                    <h6 className="mb-2">Categories</h6>
                                    <div className="flex flex-col gap-2">
                                        {CATEGORIES.map((category) => (
                                            <Checkbox
                                                key={category}
                                                checked={draftCategories.has(category)}
                                                onChange={() => toggleDraftCategory(category)}
                                            >
                                                {category}
                                            </Checkbox>
                                        ))}
                                    </div>
                                </div>

                                <div>
                                    <h6 className="mb-2">Stock Level</h6>
                                    <div className="flex flex-col gap-2">
                                        {STOCK_LEVELS.map((level) => (
                                            <Checkbox
                                                key={level}
                                                checked={draftStockLevels.has(level)}
                                                onChange={() => toggleDraftStockLevel(level)}
                                            >
                                                {level}
                                            </Checkbox>
                                        ))}
                                    </div>
                                </div>

                                <div className="flex items-center justify-end gap-2 border-t pt-2">
                                    <Button size="sm" variant="default" onClick={handleResetFilters}>
                                        Reset
                                    </Button>
                                    <Button size="sm" variant="solid" onClick={handleApplyFilters}>
                                        Apply
                                    </Button>
                                </div>
                            </div>
                        </Popover>

                        <Popover
                            width={320}
                            renderTrigger={
                                <Button icon={<PiPushPin />}>
                                    Pin columns
                                </Button>
                            }
                        >
                            <div className="flex flex-col divide-y divide-border">
                                {PINNABLE_COLUMNS.map(({ id, label }) => (
                                    <div
                                        key={id}
                                        className="flex items-center justify-between gap-4 py-2 first:pt-0 last:pb-0"
                                    >
                                        <span className="text-sm font-medium text-foreground">
                                            {label}
                                        </span>
                                        <Segment
                                            size="sm"
                                            role="group"
                                            aria-label={`Pin ${label} column`}
                                            value={pinConfig[id]}
                                            onChange={(value) =>
                                                handlePinChange(id, value as PinState)
                                            }
                                        >
                                            <Segment.Item value="start">Start</Segment.Item>
                                            <Segment.Item value="none">Off</Segment.Item>
                                            <Segment.Item value="end">End</Segment.Item>
                                        </Segment>
                                    </div>
                                ))}
                            </div>
                        </Popover>
                    </div>
                </div>

                <DataTable<Product>
                    ref={(
                        instance: DataTableResetHandle | HTMLTableElement | null,
                    ) => {
                        if (instance && 'resetSelected' in instance) {
                            dataTableRef.current = instance
                        }
                    }}
                    selectable
                    columns={columns}
                    data={pageRows}
                    columnPinning={columnPinning}
                    pagingData={{ total, pageIndex, pageSize }}
                    onPaginationChange={handlePaginationChange}
                    onPageSizeChange={handlePageSizeChange}
                    onSort={handleSort}
                    onRowSelect={handleRowSelect}
                    onAllRowSelect={handleAllRowSelect}
                    checkboxChecked={isRowSelected}
                    indeterminateCheckboxChecked={(rows: Row<Product>[]) =>
                        rows.length > 0 && rows.every((row) => isRowSelected(row.original))
                    }
                />
                <ActionBar open={selectedRows.length > 0} width={560}>
                    <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
                        <span className="font-medium">
                            <span className="font-semibold text-foreground">
                                {selectedRows.length}{' '}
                                {selectedRows.length === 1
                                    ? 'product'
                                    : 'products'}
                            </span>{' '}
                            selected
                        </span>
                        <Button
                            icon={<PiArchive />}
                            onClick={archiveSelectedProducts}
                            destructive
                        >
                            Archive
                        </Button>
                    </div>
                </ActionBar>
                </Card>
            </Container>
        </section>
    )
}

Data Grid 05

Preview
npx nateui@latest add DataGridTicketList
Dark
import { useMemo, useRef, useState } from 'react'
import type { ColumnDef, DataTableResetHandle, OnSortParam, Row } from '@/components/composites/DataTable'
import DataTable from '@/components/composites/DataTable'
import ActionBar from '@/components/ui/ActionBar'
import Avatar from '@/components/ui/Avatar'
import Button from '@/components/ui/Button'
import Card from '@/components/ui/Card'
import Dropdown from '@/components/ui/Dropdown'
import Tag from '@/components/ui/Tag'
import {
    PiArchive,
    PiBug,
    PiCalendarBlank,
    PiCaretDown,
    PiCheck,
    PiCircleFill,
    PiDotsThreeVerticalBold,
    PiFlagFill,
    PiFlag,
    PiListChecks,
    PiQuestion,
    PiTag,
    PiWarningFill,
    PiWrench,
} from 'react-icons/pi'

type Priority = 'Urgent' | 'Medium' | 'Low'
type TicketType = 'Incident' | 'Question' | 'Request' | 'Task'
type Cadence = 'Daily' | 'Weekly' | 'Monthly'
type RaisedDateFilter = 'all' | 'today' | 'week' | 'month'
type SortKey = 'ticketId' | 'type'

const assetBase = 'https://statics.nateui.com/img'

const getAvatarSrc = (ticketId: string) => {
    const ticketNumber = Number(ticketId.replace('#TC-', ''))
    return `${assetBase}/avatars/thumb-${(ticketNumber % 25) + 1}.jpg`
}

type TicketRow = {
    id: string
    ticketId: string
    subject: string
    intent: string
    type: TicketType
    assignee: string
    priority: Priority
    raisedDaysAgo: number
}

const ticketRows: TicketRow[] = [
    {
        id: 'ticket-240',
        ticketId: '#TC-240',
        subject: 'Export pauses after selecting a long date range',
        intent: 'Report export',
        type: 'Incident',
        assignee: 'Alex Morgan',
        priority: 'Urgent',
        raisedDaysAgo: 0,
    },
    {
        id: 'ticket-239',
        ticketId: '#TC-239',
        subject: 'Which seats count toward the renewal total?',
        intent: 'Renewal policy',
        type: 'Question',
        assignee: 'Priya Shah',
        priority: 'Medium',
        raisedDaysAgo: 1,
    },
    {
        id: 'ticket-238',
        ticketId: '#TC-238',
        subject: 'Add an approval step for regional managers',
        intent: 'Approval workflow',
        type: 'Request',
        assignee: 'Jamie Chen',
        priority: 'Low',
        raisedDaysAgo: 2,
    },
    {
        id: 'ticket-237',
        ticketId: '#TC-237',
        subject: 'Activity totals do not match the detail view',
        intent: 'Activity dashboard',
        type: 'Incident',
        assignee: 'Rowan Ellis',
        priority: 'Urgent',
        raisedDaysAgo: 3,
    },
    {
        id: 'ticket-236',
        ticketId: '#TC-236',
        subject: 'Where is the workspace audit history stored?',
        intent: 'Audit history',
        type: 'Question',
        assignee: 'Samira Cole',
        priority: 'Medium',
        raisedDaysAgo: 4,
    },
    {
        id: 'ticket-235',
        ticketId: '#TC-235',
        subject: 'Set a default owner for new members',
        intent: 'Workspace setup',
        type: 'Task',
        assignee: 'Theo Bennett',
        priority: 'Low',
        raisedDaysAgo: 5,
    },
    {
        id: 'ticket-234',
        ticketId: '#TC-234',
        subject: 'Webhook deliveries are timing out',
        intent: 'Webhook delivery',
        type: 'Incident',
        assignee: 'Noor Rahman',
        priority: 'Urgent',
        raisedDaysAgo: 0,
    },
    {
        id: 'ticket-233',
        ticketId: '#TC-233',
        subject: 'Can billing contacts receive usage alerts?',
        intent: 'Billing profile',
        type: 'Question',
        assignee: 'Elena Park',
        priority: 'Medium',
        raisedDaysAgo: 1,
    },
    {
        id: 'ticket-232',
        ticketId: '#TC-232',
        subject: 'Create a read-only role for analytics',
        intent: 'Access roles',
        type: 'Request',
        assignee: 'Miles Carter',
        priority: 'Low',
        raisedDaysAgo: 2,
    },
    {
        id: 'ticket-231',
        ticketId: '#TC-231',
        subject: 'Import validation stops at the address field',
        intent: 'Data import',
        type: 'Incident',
        assignee: 'Casey Nguyen',
        priority: 'Urgent',
        raisedDaysAgo: 3,
    },
    {
        id: 'ticket-230',
        ticketId: '#TC-230',
        subject: 'Update the notification digest schedule',
        intent: 'Notifications',
        type: 'Task',
        assignee: 'Jordan Brooks',
        priority: 'Medium',
        raisedDaysAgo: 4,
    },
    {
        id: 'ticket-229',
        ticketId: '#TC-229',
        subject: 'Add a custom field to the profile form',
        intent: 'Customer profile',
        type: 'Request',
        assignee: 'Taylor Reed',
        priority: 'Low',
        raisedDaysAgo: 5,
    },
    {
        id: 'ticket-228',
        ticketId: '#TC-228',
        subject: 'Yesterday’s activity entries are missing',
        intent: 'Activity feed',
        type: 'Incident',
        assignee: 'Morgan Diaz',
        priority: 'Urgent',
        raisedDaysAgo: 0,
    },
    {
        id: 'ticket-227',
        ticketId: '#TC-227',
        subject: 'Share a saved view with another team',
        intent: 'Saved views',
        type: 'Question',
        assignee: 'Avery King',
        priority: 'Medium',
        raisedDaysAgo: 1,
    },
    {
        id: 'ticket-226',
        ticketId: '#TC-226',
        subject: 'Schedule a data retention reminder',
        intent: 'Data retention',
        type: 'Task',
        assignee: 'Quinn Foster',
        priority: 'Low',
        raisedDaysAgo: 2,
    },
    {
        id: 'ticket-225',
        ticketId: '#TC-225',
        subject: 'The export preview shows an empty page',
        intent: 'Report export',
        type: 'Incident',
        assignee: 'Remy Laurent',
        priority: 'Urgent',
        raisedDaysAgo: 3,
    },
    {
        id: 'ticket-224',
        ticketId: '#TC-224',
        subject: 'Confirm the grace period for paused seats',
        intent: 'Renewal policy',
        type: 'Question',
        assignee: 'Blair Hayes',
        priority: 'Medium',
        raisedDaysAgo: 4,
    },
    {
        id: 'ticket-223',
        ticketId: '#TC-223',
        subject: 'Add a reviewer notification to approvals',
        intent: 'Approval workflow',
        type: 'Request',
        assignee: 'Devon Woods',
        priority: 'Low',
        raisedDaysAgo: 5,
    },
    {
        id: 'ticket-222',
        ticketId: '#TC-222',
        subject: 'Dashboard refreshes with an outdated count',
        intent: 'Activity dashboard',
        type: 'Incident',
        assignee: 'Kai Patel',
        priority: 'Urgent',
        raisedDaysAgo: 0,
    },
    {
        id: 'ticket-221',
        ticketId: '#TC-221',
        subject: 'Which roles can view audit events?',
        intent: 'Audit history',
        type: 'Question',
        assignee: 'Sloane Murphy',
        priority: 'Medium',
        raisedDaysAgo: 2,
    },
    {
        id: 'ticket-220',
        ticketId: '#TC-220',
        subject: 'Webhook retries need a longer backoff',
        intent: 'Webhook delivery',
        type: 'Task',
        assignee: 'Harper Lin',
        priority: 'Low',
        raisedDaysAgo: 8,
    },
    {
        id: 'ticket-219',
        ticketId: '#TC-219',
        subject: 'Change the owner on an existing workspace',
        intent: 'Workspace setup',
        type: 'Request',
        assignee: 'Luca Bennett',
        priority: 'Medium',
        raisedDaysAgo: 10,
    },
    {
        id: 'ticket-218',
        ticketId: '#TC-218',
        subject: 'Billing digest is arriving twice',
        intent: 'Billing profile',
        type: 'Incident',
        assignee: 'Reese Allen',
        priority: 'Urgent',
        raisedDaysAgo: 12,
    },
    {
        id: 'ticket-217',
        ticketId: '#TC-217',
        subject: 'Request a new analytics permission set',
        intent: 'Access roles',
        type: 'Request',
        assignee: 'Dana Ortiz',
        priority: 'Low',
        raisedDaysAgo: 14,
    },
    {
        id: 'ticket-216',
        ticketId: '#TC-216',
        subject: 'Import preview skips the final contact',
        intent: 'Data import',
        type: 'Incident',
        assignee: 'Chris Bell',
        priority: 'Urgent',
        raisedDaysAgo: 17,
    },
    {
        id: 'ticket-215',
        ticketId: '#TC-215',
        subject: 'Can digest emails include a custom footer?',
        intent: 'Notifications',
        type: 'Question',
        assignee: 'River Stone',
        priority: 'Medium',
        raisedDaysAgo: 21,
    },
    {
        id: 'ticket-214',
        ticketId: '#TC-214',
        subject: 'Add a second address to a customer record',
        intent: 'Customer profile',
        type: 'Task',
        assignee: 'Sydney Blake',
        priority: 'Low',
        raisedDaysAgo: 25,
    },
    {
        id: 'ticket-213',
        ticketId: '#TC-213',
        subject: 'Activity feed filters reset after refresh',
        intent: 'Activity feed',
        type: 'Incident',
        assignee: 'Jules Martin',
        priority: 'Urgent',
        raisedDaysAgo: 29,
    },
    {
        id: 'ticket-212',
        ticketId: '#TC-212',
        subject: 'What is included in a saved view?',
        intent: 'Saved views',
        type: 'Question',
        assignee: 'Finley Grant',
        priority: 'Medium',
        raisedDaysAgo: 35,
    },
    {
        id: 'ticket-211',
        ticketId: '#TC-211',
        subject: 'Set a quarterly retention reminder',
        intent: 'Data retention',
        type: 'Task',
        assignee: 'Cameron Avery',
        priority: 'Low',
        raisedDaysAgo: 42,
    },
]

const dateRangeOptions = [3, 6, 9, 12]
const cadenceOptions: Cadence[] = ['Daily', 'Weekly', 'Monthly']
const priorityOptions: Array<Priority | 'All'> = [
    'All',
    'Urgent',
    'Medium',
    'Low',
]
const typeOptions: Array<TicketType | 'All'> = [
    'All',
    'Incident',
    'Question',
    'Request',
    'Task',
]
const raisedDateOptions: Array<{ value: RaisedDateFilter; label: string }> = [
    { value: 'all', label: 'Any date' },
    { value: 'today', label: 'Today' },
    { value: 'week', label: 'This week' },
    { value: 'month', label: 'This month' },
]

const priorityConfig = {
    Urgent: {
        icon: PiWarningFill,
        iconClass: 'text-destructive',
        tagClass: 'bg-destructive-soft text-destructive border-0',
    },
    Medium: {
        icon: PiFlagFill,
        iconClass: 'text-warning',
        tagClass: 'bg-warning-soft text-warning border-0',
    },
    Low: {
        icon: PiCircleFill,
        iconClass: 'text-success',
        tagClass: 'bg-success-soft text-success border-0',
    },
}

const ticketTypeConfig = {
    Incident: { icon: PiBug },
    Question: { icon: PiQuestion },
    Request: { icon: PiWrench },
    Task: { icon: PiListChecks },
}

const cadenceDays: Record<Cadence, number> = {
    Daily: 1,
    Weekly: 7,
    Monthly: 30,
}

const cadenceUnit: Record<Cadence, string> = {
    Daily: 'Day',
    Weekly: 'Week',
    Monthly: 'Month',
}

const getTypeIcon = (type: TicketType) => {
    return ticketTypeConfig[type].icon
}

const renderFilterItem = (
    label: string,
    active: boolean,
    Icon?: typeof PiFlagFill,
    iconClass = 'text-muted-foreground',
) => (
    <span className="flex w-full items-center justify-between gap-4">
        <span className="flex min-w-0 items-center gap-2">
            {Icon && (
                <Icon aria-hidden="true" className={`shrink-0 text-base ${iconClass}`} />
            )}
            <span className="truncate">{label}</span>
        </span>
        {active && (
            <PiCheck aria-hidden="true" className="shrink-0 text-base" />
        )}
    </span>
)

const createColumns = (
    onArchive: (ticketId: string) => void,
): ColumnDef<TicketRow>[] => [
    {
        header: 'Ticket ID',
        accessorKey: 'ticketId',
        size: 190,
        minSize: 160,
        enableSorting: true,
        cell: ({ row }) => {
            const priority = priorityConfig[row.original.priority]
            const PriorityIcon = priority.icon

            return (
                <div className="flex min-w-0 items-center gap-2">
                    <span className="truncate font-semibold">{row.original.ticketId}</span>
                    <Tag
                        className={`gap-1 ${priority.tagClass}`}
                    >
                        <span aria-hidden="true" className="inline-flex">
                            <PriorityIcon className={priority.iconClass} />
                        </span>
                        {row.original.priority}
                    </Tag>
                </div>
            )
        },
    },
    {
        header: 'Subject',
        accessorKey: 'subject',
        size: 260,
        minSize: 220,
        enableSorting: false,
        cell: ({ row }) => (
            <span className="block min-w-0 truncate">{row.original.subject}</span>
        ),
    },
    {
        header: 'Intent',
        accessorKey: 'intent',
        size: 160,
        minSize: 140,
        enableSorting: false,
        cell: ({ row }) => <span className="truncate">{row.original.intent}</span>,
    },
    {
        header: 'Type',
        accessorKey: 'type',
        size: 140,
        minSize: 120,
        enableSorting: true,
        cell: ({ row }) => {
            const TypeIcon = getTypeIcon(row.original.type)
            return (
                <span className="flex items-center gap-2 whitespace-nowrap">
                    <TypeIcon aria-hidden="true" className="text-lg text-muted-foreground" />
                    <span>{row.original.type}</span>
                </span>
            )
        },
    },
    {
        header: 'Assignees',
        accessorKey: 'assignee',
        size: 190,
        minSize: 160,
        enableSorting: false,
        cell: ({ row }) => (
            <span className="flex min-w-0 items-center gap-2">
                <Avatar
                    alt={row.original.assignee}
                    size={24}
                    src={getAvatarSrc(row.original.ticketId)}
                />
                <span className="min-w-0 truncate">{row.original.assignee}</span>
            </span>
        ),
    },
    {
        header: () => (
            <div className="flex h-full items-center justify-end border-l">
                <span className="sr-only">Actions</span>
            </div>
        ),
        id: 'actions',
        size: 72,
        minSize: 64,
        maxSize: 72,
        enableSorting: false,
        cell: ({ row }) => (
            <div className="flex justify-end border-l">
                <Dropdown
                    placement="bottom-end"
                    onSelect={(eventKey) => {
                        if (eventKey === 'archive') {
                            onArchive(row.original.id)
                        }
                    }}
                    renderTitle={
                        <Button
                            aria-label={`Actions for ${row.original.ticketId}`}
                            icon={<PiDotsThreeVerticalBold />}
                            shape="circle"
                            size="sm"
                            variant="ghost"
                        />
                    }
                >
                    <Dropdown.Item eventKey="archive">Archive ticket</Dropdown.Item>
                </Dropdown>
            </div>
        ),
    },
]

export default function DataGridTicketList() {
    const [rows, setRows] = useState(ticketRows)
    const [rangeCount, setRangeCount] = useState(6)
    const [cadence, setCadence] = useState<Cadence>('Daily')
    const [priorityFilter, setPriorityFilter] = useState<Priority | 'All'>('All')
    const [typeFilter, setTypeFilter] = useState<TicketType | 'All'>('All')
    const [raisedDateFilter, setRaisedDateFilter] =
        useState<RaisedDateFilter>('all')
    const [sortKey, setSortKey] = useState<SortKey | null>(null)
    const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | ''>('')
    const [pageIndex, setPageIndex] = useState(1)
    const [pageSize, setPageSize] = useState(10)
    const [selectedRowIds, setSelectedRowIds] = useState<string[]>([])
    const dataTableRef = useRef<DataTableResetHandle>(null)

    const resetSelection = () => {
        dataTableRef.current?.resetSelected()
        setSelectedRowIds([])
    }

    const resetPageAndSelection = () => {
        setPageIndex(1)
        resetSelection()
    }

    const visibleRows = useMemo(() => {
        const maxDays = rangeCount * cadenceDays[cadence]

        const filtered = rows.filter((row) => {
            if (row.raisedDaysAgo >= maxDays) return false
            if (priorityFilter !== 'All' && row.priority !== priorityFilter) {
                return false
            }
            if (typeFilter !== 'All' && row.type !== typeFilter) return false

            if (raisedDateFilter === 'today' && row.raisedDaysAgo !== 0) {
                return false
            }
            if (raisedDateFilter === 'week' && row.raisedDaysAgo >= 7) {
                return false
            }
            if (raisedDateFilter === 'month' && row.raisedDaysAgo >= 30) {
                return false
            }

            return true
        })

        if (!sortKey || !sortOrder) return filtered

        return [...filtered].sort((a, b) => {
            const comparison = a[sortKey].localeCompare(b[sortKey])
            return sortOrder === 'desc' ? -comparison : comparison
        })
    }, [cadence, priorityFilter, raisedDateFilter, rangeCount, rows, sortKey, sortOrder, typeFilter])

    const pageRows = useMemo(() => {
        const start = (pageIndex - 1) * pageSize
        return visibleRows.slice(start, start + pageSize)
    }, [pageIndex, pageSize, visibleRows])

    const handleSort = ({ sortKey: nextKey, sortOrder: nextOrder }: OnSortParam) => {
        if (nextKey !== 'ticketId' && nextKey !== 'type') return
        setSortKey(nextOrder ? (nextKey as SortKey) : null)
        setSortOrder(nextOrder)
        resetPageAndSelection()
    }

    const handleRowSelect = (checked: boolean, row: TicketRow) => {
        setSelectedRowIds((current) =>
            checked
                ? Array.from(new Set([...current, row.id]))
                : current.filter((id) => id !== row.id),
        )
    }

    const handleAllRowSelect = (checked: boolean, selectedRows: Row<TicketRow>[]) => {
        const pageRowIds = selectedRows.map((row) => row.original.id)
        setSelectedRowIds((current) =>
            checked
                ? Array.from(new Set([...current, ...pageRowIds]))
                : current.filter((id) => !pageRowIds.includes(id)),
        )
    }

    const archiveRows = (ids: string[]) => {
        const archivedIds = new Set(ids)
        dataTableRef.current?.resetSelected()
        setRows((current) => current.filter((row) => !archivedIds.has(row.id)))
        setSelectedRowIds([])
        setPageIndex(1)
    }

    const handleArchiveSelected = () => archiveRows(selectedRowIds)
    const columns = createColumns((ticketId) => archiveRows([ticketId]))

    const rangeLabel = `Last ${rangeCount} ${cadenceUnit[cadence]}${rangeCount === 1 ? '' : 's'}`
    const PriorityToolbarIcon =
        priorityFilter === 'All' ? PiFlag : priorityConfig[priorityFilter].icon
    const TypeToolbarIcon =
        typeFilter === 'All' ? PiTag : ticketTypeConfig[typeFilter].icon

    return (
        <section
            aria-labelledby="data-grid-ticket-list-title"
            className="w-full space-y-4"
        >
            <h4 id="data-grid-ticket-list-title" className="font-semibold">
                Tickets
            </h4>
            <div className="flex flex-wrap items-center justify-between gap-4">
                <div className="flex flex-wrap items-center gap-2">
                    <Dropdown
                        activeKey={String(rangeCount)}
                        onSelect={(eventKey) => {
                            setRangeCount(Number(eventKey))
                            resetPageAndSelection()
                        }}
                        renderTitle={
                            <Button
                                type="button"
                                icon={<PiCaretDown aria-hidden="true" />}
                                iconAlignment="end"
                                size="sm"
                                variant="default"
                            >
                                <span className="flex items-center gap-2">{rangeLabel}</span>
                            </Button>
                        }
                    >
                        {dateRangeOptions.map((value) => (
                            <Dropdown.Item
                                key={value}
                                active={value === rangeCount}
                                eventKey={String(value)}
                            >
                                {renderFilterItem(
                                    `Last ${value} ${cadenceUnit[cadence]}${value === 1 ? '' : 's'}`,
                                    value === rangeCount,
                                )}
                            </Dropdown.Item>
                        ))}
                    </Dropdown>
                    <Dropdown
                        activeKey={cadence}
                        onSelect={(eventKey) => {
                            setCadence(eventKey as Cadence)
                            resetPageAndSelection()
                        }}
                        renderTitle={
                            <Button
                                type="button"
                                icon={<PiCaretDown aria-hidden="true" />}
                                iconAlignment="end"
                                size="sm"
                                variant="default"
                            >
                                <span className="flex items-center gap-2">{cadence}</span>
                            </Button>
                        }
                    >
                        {cadenceOptions.map((option) => (
                            <Dropdown.Item
                                key={option}
                                active={option === cadence}
                                eventKey={option}
                            >
                                {renderFilterItem(option, option === cadence)}
                            </Dropdown.Item>
                        ))}
                    </Dropdown>
                </div>

                <div className="flex flex-wrap items-center gap-2 md:ml-auto">
                    <Dropdown
                        activeKey={priorityFilter}
                        onSelect={(eventKey) => {
                            setPriorityFilter(eventKey as Priority | 'All')
                            resetPageAndSelection()
                        }}
                        placement="bottom-end"
                        renderTitle={
                            <Button
                                type="button"
                                icon={<PiCaretDown aria-hidden="true" />}
                                iconAlignment="end"
                                size="sm"
                                variant="default"
                            >
                                <span className="flex items-center gap-2">
                                    <PriorityToolbarIcon
                                        aria-hidden="true"
                                        className="text-base text-muted-foreground"
                                    />
                                    Priority
                                </span>
                            </Button>
                        }
                    >
                        {priorityOptions.map((option) => (
                            <Dropdown.Item
                                key={option}
                                active={option === priorityFilter}
                                eventKey={option}
                            >
                                {renderFilterItem(
                                    option === 'All' ? 'All priorities' : option,
                                    option === priorityFilter,
                                    option === 'All'
                                        ? PiFlag
                                        : priorityConfig[option].icon,
                                    option === 'All'
                                        ? ''
                                        : priorityConfig[option].iconClass,
                                )}
                            </Dropdown.Item>
                        ))}
                    </Dropdown>

                    <Dropdown
                        activeKey={typeFilter}
                        onSelect={(eventKey) => {
                            setTypeFilter(eventKey as TicketType | 'All')
                            resetPageAndSelection()
                        }}
                        placement="bottom-end"
                        renderTitle={
                            <Button
                                type="button"
                                icon={<PiCaretDown aria-hidden="true" />}
                                iconAlignment="end"
                                size="sm"
                                variant="default"
                            >
                                <span className="flex items-center gap-2">
                                    <TypeToolbarIcon
                                        aria-hidden="true"
                                        className="text-base text-muted-foreground"
                                    />
                                    Type
                                </span>
                            </Button>
                        }
                    >
                        {typeOptions.map((option) => (
                            <Dropdown.Item
                                key={option}
                                active={option === typeFilter}
                                eventKey={option}
                            >
                                {renderFilterItem(
                                    option === 'All' ? 'All types' : option,
                                    option === typeFilter,
                                    option === 'All'
                                        ? PiTag
                                        : ticketTypeConfig[option].icon,
                                )}
                            </Dropdown.Item>
                        ))}
                    </Dropdown>

                    <Dropdown
                        activeKey={raisedDateFilter}
                        onSelect={(eventKey) => {
                            setRaisedDateFilter(eventKey as RaisedDateFilter)
                            resetPageAndSelection()
                        }}
                        placement="bottom-end"
                        renderTitle={
                            <Button
                                type="button"
                                icon={<PiCaretDown aria-hidden="true" />}
                                iconAlignment="end"
                                size="sm"
                                variant="default"
                            >
                                <span className="flex items-center gap-2">
                                    <PiCalendarBlank
                                        aria-hidden="true"
                                        className="text-base text-muted-foreground"
                                    />
                                    Raised Date
                                </span>
                            </Button>
                        }
                    >
                        {raisedDateOptions.map((option) => (
                            <Dropdown.Item
                                key={option.value}
                                active={option.value === raisedDateFilter}
                                eventKey={option.value}
                            >
                                {renderFilterItem(option.label, option.value === raisedDateFilter)}
                            </Dropdown.Item>
                        ))}
                    </Dropdown>
                </div>
            </div>

            <Card bodyClass="min-w-0 px-0 pt-0">
                <DataTable<TicketRow>
                    ref={(
                        instance: DataTableResetHandle | HTMLTableElement | null,
                    ) => {
                        if (instance && 'resetSelected' in instance) {
                            dataTableRef.current = instance
                        }
                    }}
                    columns={columns}
                    columnResize={{ mode: 'onChange' }}
                    data={pageRows}
                    indeterminateCheckboxChecked={(tableRows) =>
                        tableRows.length > 0 &&
                        tableRows.every((row) => selectedRowIds.includes(row.original.id))
                    }
                    noData={pageRows.length === 0}
                    onAllRowSelect={handleAllRowSelect}
                    onPageSizeChange={(nextPageSize) => {
                        setPageSize(nextPageSize)
                        resetPageAndSelection()
                    }}
                    onPaginationChange={(nextPage) => {
                        setPageIndex(nextPage)
                        resetSelection()
                    }}
                    onRowSelect={handleRowSelect}
                    onSort={handleSort}
                    pageSizes={[10, 20, 50]}
                    pagingData={{
                        total: visibleRows.length,
                        pageIndex,
                        pageSize,
                    }}
                    selectable
                    checkboxChecked={(row) => selectedRowIds.includes(row.id)}
                    compact
                    hoverable
                />
                <ActionBar open={selectedRowIds.length > 0} width={560}>
                    <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
                        <span className="font-medium">
                            <span className="font-semibold text-foreground">
                                {selectedRowIds.length}{' '}
                                {selectedRowIds.length === 1 ? 'ticket' : 'tickets'}
                            </span>{' '}
                            selected
                        </span>
                        <Button
                            destructive
                            icon={<PiArchive />}
                            onClick={handleArchiveSelected}
                        >
                            Archive
                        </Button>
                    </div>
                </ActionBar>
            </Card>
        </section>
    )
}

Data Grid 06

Preview
npx nateui@latest add DataGridSubscribers
Dark
import { useMemo, useState } from 'react'
import {
    DndContext,
    KeyboardSensor,
    PointerSensor,
    useSensor,
    useSensors,
    type DragEndEvent,
} from '@dnd-kit/core'
import {
    SortableContext,
    arrayMove,
    sortableKeyboardCoordinates,
    useSortable,
    verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import type { CSSProperties, ReactNode } from 'react'
import AdvancedFilterBuilder, {
    type FieldConfig,
    type FilterTree,
    type Rule,
    type RuleGroup,
} from '@/components/composites/AdvancedFilterBuilder'
import DataTable, {
    type ColumnDef,
    type OnSortParam,
} from '@/components/composites/DataTable'
import DebounceInput from '@/components/composites/DebounceInput'
import Button from '@/components/ui/Button'
import CloseButton from '@/components/ui/CloseButton'
import Drawer from '@/components/ui/Drawer'
import Input from '@/components/ui/Input'
import Popover from '@/components/ui/Popover'
import Scroll from '@/components/ui/Scroll'
import Switcher from '@/components/ui/Switcher'
import Tag from '@/components/ui/Tag'
import classNames from '@/utils/classNames'
import formatCurrency from '@/utils/formatCurrency'
import {
    PiAt,
    PiCalendarBlank,
    PiCircle,
    PiCreditCard,
    PiCurrencyDollar,
    PiDeviceMobile,
    PiDeviceTablet,
    PiDownloadSimple,
    PiDotsSixVertical,
    PiGridNine,
    PiHash,
    PiListNumbers,
    PiMagnifyingGlass,
    PiMapPin,
    PiMapTrifold,
    PiMonitor,
    PiMoneyWavy,
    PiSlidersHorizontal,
    PiUser,
    PiX,
} from 'react-icons/pi'

const assetBase = 'https://statics.nateui.com/img'

type Plan = 'Basic' | 'Standard' | 'Pro'
type PaymentMethod = 'Credit Card' | 'PayPal' | 'Stripe' | 'Visa'
type Status = 'Paid' | 'Pending' | 'Failed' | 'Refunded'
type Device = 'Desktop' | 'Mobile' | 'Tablet'
type CountryCode =
    | 'US'
    | 'UK'
    | 'CA'
    | 'DE'
    | 'FR'
    | 'JP'
    | 'AU'
    | 'BR'
    | 'IN'
    | 'NL'
    | 'SE'
    | 'ES'

type SubscriberRow = {
    id: string
    plan: Plan
    customer: string
    email: string
    amount: number
    paymentMethod: PaymentMethod
    status: Status
    signupDate: string
    lastActive: string
    mrrRange: string
    autoRenewal: boolean
    featureUsed: string[]
    device: Device
    country: string
    countryCode: CountryCode
}

const subscriberRows: SubscriberRow[] = [
    {
        id: 'SUB-1001',
        plan: 'Basic',
        customer: 'Mina Patel',
        email: 'mina.patel@example.test',
        amount: 24.5,
        paymentMethod: 'Credit Card',
        status: 'Paid',
        signupDate: 'Mar 14, 2026',
        lastActive: 'Today',
        mrrRange: '$0-$50',
        autoRenewal: true,
        featureUsed: ['Dashboard View', 'Profile Update'],
        device: 'Desktop',
        country: 'United States',
        countryCode: 'US',
    },
    {
        id: 'SUB-1002',
        plan: 'Standard',
        customer: 'Jon Bell',
        email: 'jon.bell@example.test',
        amount: 649,
        paymentMethod: 'PayPal',
        status: 'Pending',
        signupDate: 'Mar 10, 2026',
        lastActive: '1 day ago',
        mrrRange: '$500-$1000',
        autoRenewal: true,
        featureUsed: ['Report Generation', 'Data Export'],
        device: 'Mobile',
        country: 'United Kingdom',
        countryCode: 'UK',
    },
    {
        id: 'SUB-1003',
        plan: 'Pro',
        customer: 'Rina Sato',
        email: 'rina.sato@example.test',
        amount: 7200,
        paymentMethod: 'Visa',
        status: 'Failed',
        signupDate: 'Mar 08, 2026',
        lastActive: '2 days ago',
        mrrRange: '$5000-$10000',
        autoRenewal: false,
        featureUsed: ['Analytics View', 'Custom Charts'],
        device: 'Tablet',
        country: 'Japan',
        countryCode: 'JP',
    },
    {
        id: 'SUB-1004',
        plan: 'Basic',
        customer: 'Owen Brooks',
        email: 'owen.brooks@example.test',
        amount: 88,
        paymentMethod: 'Stripe',
        status: 'Refunded',
        signupDate: 'Mar 03, 2026',
        lastActive: '4 days ago',
        mrrRange: '$50-$100',
        autoRenewal: true,
        featureUsed: ['File Upload'],
        device: 'Desktop',
        country: 'Germany',
        countryCode: 'DE',
    },
    {
        id: 'SUB-1005',
        plan: 'Standard',
        customer: 'Clara Nguyen',
        email: 'clara.nguyen@example.test',
        amount: 1250,
        paymentMethod: 'Credit Card',
        status: 'Paid',
        signupDate: 'Feb 27, 2026',
        lastActive: '5 days ago',
        mrrRange: '$1000-$2000',
        autoRenewal: true,
        featureUsed: ['User Management', 'Team Collaboration'],
        device: 'Mobile',
        country: 'France',
        countryCode: 'FR',
    },
    {
        id: 'SUB-1006',
        plan: 'Pro',
        customer: 'Elias Romero',
        email: 'elias.romero@example.test',
        amount: 3400,
        paymentMethod: 'PayPal',
        status: 'Pending',
        signupDate: 'Feb 22, 2026',
        lastActive: '1 week ago',
        mrrRange: '$2000-$5000',
        autoRenewal: true,
        featureUsed: ['API Access', 'Data Import'],
        device: 'Desktop',
        country: 'Canada',
        countryCode: 'CA',
    },
    {
        id: 'SUB-1007',
        plan: 'Basic',
        customer: 'Nadia Foster',
        email: 'nadia.foster@example.test',
        amount: 42,
        paymentMethod: 'Visa',
        status: 'Failed',
        signupDate: 'Feb 18, 2026',
        lastActive: '1 week ago',
        mrrRange: '$0-$50',
        autoRenewal: false,
        featureUsed: ['Notification Settings', 'Profile Update'],
        device: 'Tablet',
        country: 'Australia',
        countryCode: 'AU',
    },
    {
        id: 'SUB-1008',
        plan: 'Standard',
        customer: 'Marco Silva',
        email: 'marco.silva@example.test',
        amount: 780,
        paymentMethod: 'Stripe',
        status: 'Refunded',
        signupDate: 'Feb 12, 2026',
        lastActive: '2 weeks ago',
        mrrRange: '$500-$1000',
        autoRenewal: true,
        featureUsed: ['Dashboard View', 'File Upload'],
        device: 'Mobile',
        country: 'Brazil',
        countryCode: 'BR',
    },
    {
        id: 'SUB-1009',
        plan: 'Pro',
        customer: 'Isha Mehta',
        email: 'isha.mehta@example.test',
        amount: 9800,
        paymentMethod: 'Credit Card',
        status: 'Paid',
        signupDate: 'Feb 05, 2026',
        lastActive: '2 weeks ago',
        mrrRange: '$5000-$10000',
        autoRenewal: true,
        featureUsed: ['Custom Charts', 'Analytics View', 'API Access'],
        device: 'Desktop',
        country: 'India',
        countryCode: 'IN',
    },
    {
        id: 'SUB-1010',
        plan: 'Basic',
        customer: 'Nora Jensen',
        email: 'nora.jensen@example.test',
        amount: 110,
        paymentMethod: 'PayPal',
        status: 'Pending',
        signupDate: 'Jan 29, 2026',
        lastActive: '3 weeks ago',
        mrrRange: '$100-$200',
        autoRenewal: true,
        featureUsed: ['Report Generation'],
        device: 'Mobile',
        country: 'Netherlands',
        countryCode: 'NL',
    },
    {
        id: 'SUB-1011',
        plan: 'Standard',
        customer: 'Felix Anders',
        email: 'felix.anders@example.test',
        amount: 360,
        paymentMethod: 'Credit Card',
        status: 'Paid',
        signupDate: 'Jan 22, 2026',
        lastActive: '3 weeks ago',
        mrrRange: '$200-$500',
        autoRenewal: true,
        featureUsed: ['Data Export', 'User Management'],
        device: 'Desktop',
        country: 'Sweden',
        countryCode: 'SE',
    },
    {
        id: 'SUB-1012',
        plan: 'Pro',
        customer: 'Leah Carter',
        email: 'leah.carter@example.test',
        amount: 15500,
        paymentMethod: 'Stripe',
        status: 'Refunded',
        signupDate: 'Jan 15, 2026',
        lastActive: '1 month ago',
        mrrRange: '$10000+',
        autoRenewal: true,
        featureUsed: ['Team Collaboration', 'Data Import'],
        device: 'Tablet',
        country: 'Spain',
        countryCode: 'ES',
    },
    {
        id: 'SUB-1013',
        plan: 'Basic',
        customer: 'Arun Das',
        email: 'arun.das@example.test',
        amount: 61,
        paymentMethod: 'Visa',
        status: 'Paid',
        signupDate: 'Jan 09, 2026',
        lastActive: '1 month ago',
        mrrRange: '$50-$100',
        autoRenewal: true,
        featureUsed: ['Profile Update', 'Notification Settings'],
        device: 'Desktop',
        country: 'United States',
        countryCode: 'US',
    },
    {
        id: 'SUB-1014',
        plan: 'Standard',
        customer: 'Mara Collins',
        email: 'mara.collins@example.test',
        amount: 520,
        paymentMethod: 'PayPal',
        status: 'Pending',
        signupDate: 'Dec 28, 2025',
        lastActive: '1 month ago',
        mrrRange: '$500-$1000',
        autoRenewal: true,
        featureUsed: ['Dashboard View', 'Report Generation'],
        device: 'Mobile',
        country: 'United Kingdom',
        countryCode: 'UK',
    },
    {
        id: 'SUB-1015',
        plan: 'Pro',
        customer: 'Theo Wallace',
        email: 'theo.wallace@example.test',
        amount: 2300,
        paymentMethod: 'Credit Card',
        status: 'Failed',
        signupDate: 'Dec 18, 2025',
        lastActive: '2 months ago',
        mrrRange: '$2000-$5000',
        autoRenewal: false,
        featureUsed: ['Custom Charts', 'File Upload'],
        device: 'Tablet',
        country: 'Canada',
        countryCode: 'CA',
    },
    {
        id: 'SUB-1016',
        plan: 'Basic',
        customer: 'Sofia Marin',
        email: 'sofia.marin@example.test',
        amount: 18,
        paymentMethod: 'Stripe',
        status: 'Refunded',
        signupDate: 'Dec 11, 2025',
        lastActive: '2 months ago',
        mrrRange: '$0-$50',
        autoRenewal: true,
        featureUsed: ['File Upload', 'Profile Update'],
        device: 'Mobile',
        country: 'France',
        countryCode: 'FR',
    },
    {
        id: 'SUB-1017',
        plan: 'Standard',
        customer: 'Darius King',
        email: 'darius.king@example.test',
        amount: 890,
        paymentMethod: 'Visa',
        status: 'Paid',
        signupDate: 'Dec 04, 2025',
        lastActive: '2 months ago',
        mrrRange: '$500-$1000',
        autoRenewal: true,
        featureUsed: ['Analytics View', 'Data Export'],
        device: 'Desktop',
        country: 'Germany',
        countryCode: 'DE',
    },
    {
        id: 'SUB-1018',
        plan: 'Pro',
        customer: 'Hana Ito',
        email: 'hana.ito@example.test',
        amount: 5100,
        paymentMethod: 'PayPal',
        status: 'Pending',
        signupDate: 'Nov 25, 2025',
        lastActive: '3 months ago',
        mrrRange: '$5000-$10000',
        autoRenewal: true,
        featureUsed: ['API Access', 'Team Collaboration'],
        device: 'Desktop',
        country: 'Japan',
        countryCode: 'JP',
    },
    {
        id: 'SUB-1019',
        plan: 'Basic',
        customer: 'Brennan Lee',
        email: 'brennan.lee@example.test',
        amount: 145,
        paymentMethod: 'Credit Card',
        status: 'Paid',
        signupDate: 'Nov 17, 2025',
        lastActive: '3 months ago',
        mrrRange: '$100-$200',
        autoRenewal: true,
        featureUsed: ['User Management'],
        device: 'Tablet',
        country: 'Australia',
        countryCode: 'AU',
    },
    {
        id: 'SUB-1020',
        plan: 'Standard',
        customer: 'Lena Duarte',
        email: 'lena.duarte@example.test',
        amount: 1020,
        paymentMethod: 'Stripe',
        status: 'Refunded',
        signupDate: 'Nov 09, 2025',
        lastActive: '4 months ago',
        mrrRange: '$1000-$2000',
        autoRenewal: true,
        featureUsed: ['Notification Settings', 'Data Import'],
        device: 'Mobile',
        country: 'Brazil',
        countryCode: 'BR',
    },
    {
        id: 'SUB-1021',
        plan: 'Pro',
        customer: 'Caleb Stone',
        email: 'caleb.stone@example.test',
        amount: 11200,
        paymentMethod: 'Visa',
        status: 'Failed',
        signupDate: 'Oct 30, 2025',
        lastActive: '4 months ago',
        mrrRange: '$10000+',
        autoRenewal: false,
        featureUsed: ['Custom Charts', 'Report Generation'],
        device: 'Desktop',
        country: 'India',
        countryCode: 'IN',
    },
    {
        id: 'SUB-1022',
        plan: 'Basic',
        customer: 'Uma Shah',
        email: 'uma.shah@example.test',
        amount: 74,
        paymentMethod: 'PayPal',
        status: 'Paid',
        signupDate: 'Oct 21, 2025',
        lastActive: '5 months ago',
        mrrRange: '$50-$100',
        autoRenewal: true,
        featureUsed: ['Dashboard View', 'File Upload'],
        device: 'Mobile',
        country: 'Netherlands',
        countryCode: 'NL',
    },
    {
        id: 'SUB-1023',
        plan: 'Standard',
        customer: 'Pavel Novak',
        email: 'pavel.novak@example.test',
        amount: 410,
        paymentMethod: 'Credit Card',
        status: 'Pending',
        signupDate: 'Oct 13, 2025',
        lastActive: '5 months ago',
        mrrRange: '$200-$500',
        autoRenewal: true,
        featureUsed: ['Profile Update', 'Team Collaboration'],
        device: 'Tablet',
        country: 'Sweden',
        countryCode: 'SE',
    },
    {
        id: 'SUB-1024',
        plan: 'Pro',
        customer: 'Maya Ortiz',
        email: 'maya.ortiz@example.test',
        amount: 4300,
        paymentMethod: 'Stripe',
        status: 'Refunded',
        signupDate: 'Oct 04, 2025',
        lastActive: '6 months ago',
        mrrRange: '$2000-$5000',
        autoRenewal: true,
        featureUsed: ['API Access', 'Data Export'],
        device: 'Desktop',
        country: 'Spain',
        countryCode: 'ES',
    },
    {
        id: 'SUB-1025',
        plan: 'Basic',
        customer: 'Niko Fraser',
        email: 'niko.fraser@example.test',
        amount: 35,
        paymentMethod: 'Visa',
        status: 'Failed',
        signupDate: 'Sep 25, 2025',
        lastActive: '6 months ago',
        mrrRange: '$0-$50',
        autoRenewal: false,
        featureUsed: ['Notification Settings'],
        device: 'Mobile',
        country: 'United States',
        countryCode: 'US',
    },
    {
        id: 'SUB-1026',
        plan: 'Standard',
        customer: 'Ada Laurent',
        email: 'ada.laurent@example.test',
        amount: 1500,
        paymentMethod: 'PayPal',
        status: 'Paid',
        signupDate: 'Sep 16, 2025',
        lastActive: '7 months ago',
        mrrRange: '$1000-$2000',
        autoRenewal: true,
        featureUsed: ['Analytics View', 'Custom Charts'],
        device: 'Desktop',
        country: 'United Kingdom',
        countryCode: 'UK',
    },
    {
        id: 'SUB-1027',
        plan: 'Pro',
        customer: 'Ravi Menon',
        email: 'ravi.menon@example.test',
        amount: 6700,
        paymentMethod: 'Credit Card',
        status: 'Pending',
        signupDate: 'Sep 08, 2025',
        lastActive: '7 months ago',
        mrrRange: '$5000-$10000',
        autoRenewal: true,
        featureUsed: ['User Management', 'Data Import'],
        device: 'Tablet',
        country: 'Canada',
        countryCode: 'CA',
    },
    {
        id: 'SUB-1028',
        plan: 'Basic',
        customer: 'Tessa Green',
        email: 'tessa.green@example.test',
        amount: 96,
        paymentMethod: 'Stripe',
        status: 'Refunded',
        signupDate: 'Aug 30, 2025',
        lastActive: '8 months ago',
        mrrRange: '$50-$100',
        autoRenewal: true,
        featureUsed: ['File Upload', 'Profile Update'],
        device: 'Desktop',
        country: 'France',
        countryCode: 'FR',
    },
    {
        id: 'SUB-1029',
        plan: 'Standard',
        customer: 'Yuki Mori',
        email: 'yuki.mori@example.test',
        amount: 245,
        paymentMethod: 'Visa',
        status: 'Paid',
        signupDate: 'Aug 21, 2025',
        lastActive: '8 months ago',
        mrrRange: '$200-$500',
        autoRenewal: true,
        featureUsed: ['Report Generation', 'Notification Settings'],
        device: 'Mobile',
        country: 'Japan',
        countryCode: 'JP',
    },
    {
        id: 'SUB-1030',
        plan: 'Pro',
        customer: 'Drew Morgan',
        email: 'drew.morgan@example.test',
        amount: 2140,
        paymentMethod: 'PayPal',
        status: 'Failed',
        signupDate: 'Aug 12, 2025',
        lastActive: '9 months ago',
        mrrRange: '$2000-$5000',
        autoRenewal: false,
        featureUsed: ['Dashboard View', 'API Access'],
        device: 'Desktop',
        country: 'Australia',
        countryCode: 'AU',
    },
    {
        id: 'SUB-1031',
        plan: 'Basic',
        customer: 'Evan Kim',
        email: 'evan.kim@example.test',
        amount: 125,
        paymentMethod: 'Credit Card',
        status: 'Paid',
        signupDate: 'Aug 03, 2025',
        lastActive: '9 months ago',
        mrrRange: '$100-$200',
        autoRenewal: true,
        featureUsed: ['Team Collaboration'],
        device: 'Tablet',
        country: 'Brazil',
        countryCode: 'BR',
    },
    {
        id: 'SUB-1032',
        plan: 'Standard',
        customer: 'Kira Owens',
        email: 'kira.owens@example.test',
        amount: 960,
        paymentMethod: 'Stripe',
        status: 'Pending',
        signupDate: 'Jul 25, 2025',
        lastActive: '10 months ago',
        mrrRange: '$500-$1000',
        autoRenewal: true,
        featureUsed: ['Data Export', 'Data Import'],
        device: 'Mobile',
        country: 'India',
        countryCode: 'IN',
    },
    {
        id: 'SUB-1033',
        plan: 'Pro',
        customer: 'Luca Moretti',
        email: 'luca.moretti@example.test',
        amount: 10400,
        paymentMethod: 'Visa',
        status: 'Refunded',
        signupDate: 'Jul 16, 2025',
        lastActive: '10 months ago',
        mrrRange: '$10000+',
        autoRenewal: false,
        featureUsed: ['Custom Charts', 'Analytics View'],
        device: 'Desktop',
        country: 'Netherlands',
        countryCode: 'NL',
    },
    {
        id: 'SUB-1034',
        plan: 'Basic',
        customer: 'Sara Lind',
        email: 'sara.lind@example.test',
        amount: 48,
        paymentMethod: 'PayPal',
        status: 'Failed',
        signupDate: 'Jul 07, 2025',
        lastActive: '11 months ago',
        mrrRange: '$0-$50',
        autoRenewal: false,
        featureUsed: ['Profile Update', 'File Upload'],
        device: 'Mobile',
        country: 'Sweden',
        countryCode: 'SE',
    },
    {
        id: 'SUB-1035',
        plan: 'Standard',
        customer: 'Milo Cruz',
        email: 'milo.cruz@example.test',
        amount: 680,
        paymentMethod: 'Credit Card',
        status: 'Paid',
        signupDate: 'Jun 29, 2025',
        lastActive: '11 months ago',
        mrrRange: '$500-$1000',
        autoRenewal: true,
        featureUsed: ['User Management', 'Dashboard View'],
        device: 'Tablet',
        country: 'Spain',
        countryCode: 'ES',
    },
    {
        id: 'SUB-1036',
        plan: 'Pro',
        customer: 'Amina Yusuf',
        email: 'amina.yusuf@example.test',
        amount: 3800,
        paymentMethod: 'Stripe',
        status: 'Pending',
        signupDate: 'Jun 20, 2025',
        lastActive: '12 months ago',
        mrrRange: '$2000-$5000',
        autoRenewal: true,
        featureUsed: ['API Access', 'Team Collaboration'],
        device: 'Desktop',
        country: 'United States',
        countryCode: 'US',
    },
    {
        id: 'SUB-1037',
        plan: 'Basic',
        customer: 'Ivy Zhang',
        email: 'ivy.zhang@example.test',
        amount: 82,
        paymentMethod: 'Visa',
        status: 'Refunded',
        signupDate: 'Jun 11, 2025',
        lastActive: '12 months ago',
        mrrRange: '$50-$100',
        autoRenewal: true,
        featureUsed: ['Notification Settings'],
        device: 'Mobile',
        country: 'United Kingdom',
        countryCode: 'UK',
    },
    {
        id: 'SUB-1038',
        plan: 'Standard',
        customer: 'Noel Harper',
        email: 'noel.harper@example.test',
        amount: 1320,
        paymentMethod: 'PayPal',
        status: 'Failed',
        signupDate: 'Jun 02, 2025',
        lastActive: '13 months ago',
        mrrRange: '$1000-$2000',
        autoRenewal: true,
        featureUsed: ['Report Generation', 'Custom Charts'],
        device: 'Tablet',
        country: 'Canada',
        countryCode: 'CA',
    },
    {
        id: 'SUB-1039',
        plan: 'Pro',
        customer: 'Faye Laurent',
        email: 'faye.laurent@example.test',
        amount: 8400,
        paymentMethod: 'Credit Card',
        status: 'Paid',
        signupDate: 'May 24, 2025',
        lastActive: '13 months ago',
        mrrRange: '$5000-$10000',
        autoRenewal: true,
        featureUsed: ['Analytics View', 'Data Export', 'API Access'],
        device: 'Desktop',
        country: 'France',
        countryCode: 'FR',
    },
    {
        id: 'SUB-1040',
        plan: 'Basic',
        customer: 'Omar Reed',
        email: 'omar.reed@example.test',
        amount: 199,
        paymentMethod: 'Stripe',
        status: 'Pending',
        signupDate: 'May 15, 2025',
        lastActive: '14 months ago',
        mrrRange: '$100-$200',
        autoRenewal: true,
        featureUsed: ['Data Import', 'Profile Update'],
        device: 'Mobile',
        country: 'Japan',
        countryCode: 'JP',
    },
]

const statusClasses: Record<Status, string> = {
    Paid: 'bg-palette-emerald-soft text-palette-emerald-soft-foreground border-0',
    Pending: 'bg-palette-yellow-soft text-palette-yellow-soft-foreground border-0',
    Failed: 'bg-palette-red-soft text-palette-red-soft-foreground border-0',
    Refunded: 'bg-palette-blue-soft text-palette-blue-soft-foreground border-0',
}

const paymentMethodAssets: Record<PaymentMethod, string> = {
    'Credit Card': `${assetBase}/thumbs/payment/creditCard.png`,
    PayPal: `${assetBase}/thumbs/payment/paypal.png`,
    Stripe: `${assetBase}/thumbs/brands/stripe.png`,
    Visa: `${assetBase}/thumbs/payment/visa.png`,
}

const deviceIcons = {
    Desktop: PiMonitor,
    Mobile: PiDeviceMobile,
    Tablet: PiDeviceTablet,
}

const columnTypeIcons = {
    identifier: <PiHash aria-hidden="true" />,
    person: <PiUser aria-hidden="true" />,
    contact: <PiAt aria-hidden="true" />,
    date: <PiCalendarBlank aria-hidden="true" />,
    currency: <PiCurrencyDollar aria-hidden="true" />,
    payment: <PiCreditCard aria-hidden="true" />,
    state: <PiCircle aria-hidden="true" />,
    text: <PiListNumbers aria-hidden="true" />,
    device: <PiMonitor aria-hidden="true" />,
    place: <PiMapPin aria-hidden="true" />,
    map: <PiMapTrifold aria-hidden="true" />,
    money: <PiMoneyWavy aria-hidden="true" />,
} satisfies Record<string, ReactNode>

type ColumnType = keyof typeof columnTypeIcons
type ColumnId =
    | 'plan'
    | 'customer'
    | 'email'
    | 'amount'
    | 'paymentMethod'
    | 'status'
    | 'signupDate'
    | 'lastActive'
    | 'mrrRange'
    | 'autoRenewal'
    | 'featureUsed'
    | 'device'
    | 'country'

type ColumnDefinition = {
    id: ColumnId
    label: string
    type: ColumnType
    locked?: boolean
}

const renderColumnHeader = (label: string) => () => (
    <span className="whitespace-nowrap">{label}</span>
)

const columnDefinitions: ColumnDefinition[] = [
    { id: 'plan', label: 'Plan', type: 'identifier' },
    { id: 'customer', label: 'Customer', type: 'person', locked: true },
    { id: 'email', label: 'Email', type: 'contact' },
    { id: 'amount', label: 'Amount', type: 'currency' },
    { id: 'paymentMethod', label: 'Payment Method', type: 'payment' },
    { id: 'status', label: 'Status', type: 'state' },
    { id: 'signupDate', label: 'Signup Date', type: 'date' },
    { id: 'lastActive', label: 'Last Active', type: 'date' },
    { id: 'mrrRange', label: 'MRR Range', type: 'money' },
    { id: 'autoRenewal', label: 'Auto Renewal', type: 'state' },
    { id: 'featureUsed', label: 'Features Used', type: 'text' },
    { id: 'device', label: 'Device', type: 'device' },
    { id: 'country', label: 'Country', type: 'map' },
]

const columnDefById: Record<ColumnId, ColumnDef<SubscriberRow>> = {
    plan: {
        id: 'plan',
        header: renderColumnHeader('Plan'),
        accessorKey: 'plan',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="flex min-w-0 items-center gap-2 whitespace-nowrap">
                <img
                    alt={`${row.original.plan} plan`}
                    className="h-5 w-5 shrink-0"
                    src={`${assetBase}/thumbs/plans/${row.original.plan.toLowerCase()}.svg`}
                />
                <span className="font-medium">{row.original.plan}</span>
            </span>
        ),
    },
    customer: {
        id: 'customer',
        header: renderColumnHeader('Customer'),
        accessorKey: 'customer',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="whitespace-nowrap font-medium">
                {row.original.customer}
            </span>
        ),
    },
    email: {
        id: 'email',
        header: renderColumnHeader('Email'),
        accessorKey: 'email',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="block min-w-0 truncate text-muted-foreground">
                {row.original.email}
            </span>
        ),
    },
    amount: {
        id: 'amount',
        header: renderColumnHeader('Amount'),
        accessorKey: 'amount',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="whitespace-nowrap font-medium">
                {formatCurrency(row.original.amount, 'USD', 'en-US', 2)}
            </span>
        ),
    },
    paymentMethod: {
        id: 'paymentMethod',
        header: renderColumnHeader('Payment Method'),
        accessorKey: 'paymentMethod',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="flex items-center gap-2 whitespace-nowrap">
                <img
                    alt={`${row.original.paymentMethod} logo`}
                    className="h-4 w-auto shrink-0"
                    src={paymentMethodAssets[row.original.paymentMethod]}
                />
                <span>{row.original.paymentMethod}</span>
            </span>
        ),
    },
    status: {
        id: 'status',
        header: renderColumnHeader('Status'),
        accessorKey: 'status',
        enableSorting: true,
        cell: ({ row }) => (
            <Tag className={statusClasses[row.original.status]}>
                {row.original.status}
            </Tag>
        ),
    },
    signupDate: {
        id: 'signupDate',
        header: renderColumnHeader('Signup Date'),
        accessorKey: 'signupDate',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="whitespace-nowrap">{row.original.signupDate}</span>
        ),
    },
    lastActive: {
        id: 'lastActive',
        header: renderColumnHeader('Last Active'),
        accessorKey: 'lastActive',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="whitespace-nowrap">{row.original.lastActive}</span>
        ),
    },
    mrrRange: {
        id: 'mrrRange',
        header: renderColumnHeader('MRR Range'),
        accessorKey: 'mrrRange',
        enableSorting: true,
        cell: ({ row }) => (
            <Tag className="border-0">{row.original.mrrRange}</Tag>
        ),
    },
    autoRenewal: {
        id: 'autoRenewal',
        header: renderColumnHeader('Auto Renewal'),
        accessorKey: 'autoRenewal',
        enableSorting: false,
        cell: ({ row }) => (
            <Tag
                className={classNames(
                    'bg-card',
                    row.original.autoRenewal
                        ? 'text-success'
                        : 'text-destructive',
                )}
            >
                {row.original.autoRenewal ? 'Enabled' : 'Disabled'}
            </Tag>
        ),
    },
    featureUsed: {
        id: 'featureUsed',
        header: renderColumnHeader('Features Used'),
        accessorKey: 'featureUsed',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="flex items-center gap-1 whitespace-nowrap">
                {row.original.featureUsed.map((feature) => (
                    <Tag key={feature} className="border-0">
                        {feature}
                    </Tag>
                ))}
            </span>
        ),
    },
    device: {
        id: 'device',
        header: renderColumnHeader('Device'),
        accessorKey: 'device',
        enableSorting: true,
        cell: ({ row }) => {
            const DeviceIcon = deviceIcons[row.original.device]
            return (
                <span className="flex items-center gap-2 whitespace-nowrap">
                    <DeviceIcon
                        aria-hidden="true"
                        className="text-lg text-muted-foreground"
                    />
                    <span>{row.original.device}</span>
                </span>
            )
        },
    },
    country: {
        id: 'country',
        header: renderColumnHeader('Country'),
        accessorKey: 'country',
        enableSorting: true,
        cell: ({ row }) => (
            <span className="flex items-center gap-2 whitespace-nowrap">
                <img
                    alt={`${row.original.country} flag`}
                    className="h-4 w-4 shrink-0"
                    src={`${assetBase}/countries/${row.original.countryCode.toUpperCase()}.png`}
                />
                <span>{row.original.country}</span>
            </span>
        ),
    },
}

const initialShownColumnIds: ColumnId[] = [
    'plan',
    'customer',
    'email',
    'amount',
    'status',
    'featureUsed',
]
const initialHiddenColumnIds: ColumnId[] = columnDefinitions
    .map((column) => column.id)
    .filter((id) => !initialShownColumnIds.includes(id))

const filterFields: FieldConfig[] = [
    { key: 'signupDate', label: 'Signup Date', type: 'date' },
    { key: 'lastActive', label: 'Last Active', type: 'date' },
    {
        key: 'plan',
        label: 'Plan',
        type: 'select',
        options: [
            { label: 'Basic', value: 'Basic' },
            { label: 'Standard', value: 'Standard' },
            { label: 'Pro', value: 'Pro' },
        ],
    },
    {
        key: 'status',
        label: 'Status',
        type: 'select',
        options: [
            { label: 'Paid', value: 'Paid' },
            { label: 'Pending', value: 'Pending' },
            { label: 'Failed', value: 'Failed' },
            { label: 'Refunded', value: 'Refunded' },
        ],
    },
    {
        key: 'paymentMethod',
        label: 'Payment Method',
        type: 'select',
        options: [
            { label: 'Credit Card', value: 'Credit Card' },
            { label: 'PayPal', value: 'PayPal' },
            { label: 'Stripe', value: 'Stripe' },
            { label: 'Visa', value: 'Visa' },
        ],
    },
    { key: 'amount', label: 'Amount', type: 'number' },
    {
        key: 'feature',
        label: 'Feature',
        type: 'select',
        options: [
            { label: 'Dashboard View', value: 'Dashboard View' },
            { label: 'Report Generation', value: 'Report Generation' },
            { label: 'Data Export', value: 'Data Export' },
            { label: 'User Management', value: 'User Management' },
            { label: 'Analytics View', value: 'Analytics View' },
            { label: 'File Upload', value: 'File Upload' },
            { label: 'API Access', value: 'API Access' },
            { label: 'Custom Charts', value: 'Custom Charts' },
            { label: 'Team Collaboration', value: 'Team Collaboration' },
            { label: 'Data Import', value: 'Data Import' },
            { label: 'Notification Settings', value: 'Notification Settings' },
            { label: 'Profile Update', value: 'Profile Update' },
        ],
    },
    {
        key: 'device',
        label: 'Device',
        type: 'select',
        options: [
            { label: 'Desktop', value: 'Desktop' },
            { label: 'Mobile', value: 'Mobile' },
            { label: 'Tablet', value: 'Tablet' },
        ],
    },
    {
        key: 'country',
        label: 'Country',
        type: 'select',
        options: [
            { label: 'United States', value: 'US' },
            { label: 'United Kingdom', value: 'UK' },
            { label: 'Canada', value: 'CA' },
            { label: 'Germany', value: 'DE' },
            { label: 'France', value: 'FR' },
            { label: 'Japan', value: 'JP' },
            { label: 'Australia', value: 'AU' },
            { label: 'Brazil', value: 'BR' },
            { label: 'India', value: 'IN' },
            { label: 'Netherlands', value: 'NL' },
            { label: 'Sweden', value: 'SE' },
            { label: 'Spain', value: 'ES' },
        ],
    },
]

const createEmptyFilterTree = (): FilterTree => ({
    id: 'subscriber-filter-root',
    type: 'group',
    condition: 'AND',
    children: [
        {
            id: 'subscriber-filter-rule',
            type: 'rule',
            field: 'plan',
            operator: 'is',
            value: '',
        },
    ],
})

const getFilterValue = (
    row: SubscriberRow,
    field: string,
): string | number | boolean | string[] | undefined => {
    switch (field) {
        case 'signupDate':
            return row.signupDate
        case 'lastActive':
            return row.lastActive
        case 'plan':
            return row.plan
        case 'status':
            return row.status
        case 'paymentMethod':
            return row.paymentMethod
        case 'amount':
            return row.amount
        case 'feature':
            return row.featureUsed
        case 'device':
            return row.device
        case 'country':
            return row.countryCode
        default:
            return undefined
    }
}

const dateKey = (value: string) => {
    const directMatch = value.match(/^(\d{4})-(\d{2})-(\d{2})$/)
    if (directMatch) return value

    const displayMatch = value.match(/^([A-Za-z]{3}) (\d{1,2}), (\d{4})$/)
    if (!displayMatch) return value.toLowerCase()

    const months: Record<string, string> = {
        jan: '01',
        feb: '02',
        mar: '03',
        apr: '04',
        may: '05',
        jun: '06',
        jul: '07',
        aug: '08',
        sep: '09',
        oct: '10',
        nov: '11',
        dec: '12',
    }
    const month = months[displayMatch[1].toLowerCase()]
    if (!month) return value.toLowerCase()

    return `${displayMatch[3]}-${month}-${displayMatch[2].padStart(2, '0')}`
}

const compareRule = (row: SubscriberRow, rule: Rule) => {
    const rowValue = getFilterValue(row, rule.field)
    const ruleValue = String(rule.value).trim()

    if (rowValue === undefined || ruleValue === '') return true

    if (Array.isArray(rowValue)) {
        const includes = rowValue.some(
            (value) => value.toLowerCase() === ruleValue.toLowerCase(),
        )
        return rule.operator === 'is_not' ? !includes : includes
    }

    if (typeof rowValue === 'number') {
        const numericValue = Number(ruleValue)
        if (Number.isNaN(numericValue)) return true

        switch (rule.operator) {
            case 'is':
                return rowValue === numericValue
            case 'is_not':
                return rowValue !== numericValue
            case 'greater_than':
                return rowValue > numericValue
            case 'less_than':
                return rowValue < numericValue
            case 'greater_equal':
                return rowValue >= numericValue
            case 'less_equal':
                return rowValue <= numericValue
            default:
                return true
        }
    }

    const left = typeof rowValue === 'boolean' ? String(rowValue) : rowValue
    const right = rule.field === 'signupDate' || rule.field === 'lastActive'
        ? dateKey(ruleValue)
        : ruleValue.toLowerCase()
    const normalizedLeft =
        rule.field === 'signupDate' || rule.field === 'lastActive'
            ? dateKey(String(left))
            : String(left).toLowerCase()

    switch (rule.operator) {
        case 'is':
            return normalizedLeft === right
        case 'is_not':
            return normalizedLeft !== right
        case 'contains':
            return normalizedLeft.includes(right)
        case 'not_contains':
            return !normalizedLeft.includes(right)
        case 'starts_with':
            return normalizedLeft.startsWith(right)
        case 'ends_with':
            return normalizedLeft.endsWith(right)
        case 'greater_than':
            return normalizedLeft > right
        case 'less_than':
            return normalizedLeft < right
        case 'greater_equal':
            return normalizedLeft >= right
        case 'less_equal':
            return normalizedLeft <= right
        default:
            return true
    }
}

const evaluateFilter = (row: SubscriberRow, node: Rule | RuleGroup): boolean => {
    if (node.type === 'rule') return compareRule(row, node)

    if (node.children.length === 0) return true

    const results = node.children.map((child) => {
        const result = evaluateFilter(row, child)
        return child.logicalOperator === 'NOT' ? !result : result
    })

    return node.condition === 'OR'
        ? results.some(Boolean)
        : results.every(Boolean)
}

const hasActiveFilter = (node: Rule | RuleGroup): boolean => {
    if (node.type === 'rule') return String(node.value).trim() !== ''
    return node.children.some(hasActiveFilter)
}

const escapeCsv = (value: string) =>
    /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value

const getExportValue = (row: SubscriberRow, id: ColumnId) => {
    switch (id) {
        case 'amount':
            return formatCurrency(row.amount, 'USD', 'en-US', 2)
        case 'featureUsed':
            return row.featureUsed.join(', ')
        case 'autoRenewal':
            return row.autoRenewal ? 'Enabled' : 'Disabled'
        case 'plan':
            return row.plan
        case 'customer':
            return row.customer
        case 'email':
            return row.email
        case 'paymentMethod':
            return row.paymentMethod
        case 'status':
            return row.status
        case 'signupDate':
            return row.signupDate
        case 'lastActive':
            return row.lastActive
        case 'mrrRange':
            return row.mrrRange
        case 'device':
            return row.device
        case 'country':
            return row.country
    }
}

const ColumnRow = ({
    column,
    checked,
    dragHandle,
    onToggle,
    rowRef,
    style,
}: {
    column: ColumnDefinition
    checked: boolean
    dragHandle: ReactNode
    onToggle: (checked: boolean) => void
    rowRef?: (node: HTMLLIElement | null) => void
    style?: CSSProperties
}) => (
    <li
        ref={rowRef}
        className="flex min-w-0 items-center gap-2 rounded-control px-1 py-1.5"
        style={style}
        data-column-id={column.id}
    >
        <span className="flex h-5 w-5 shrink-0 items-center justify-center">
            {dragHandle}
        </span>
        <span
            className={classNames(
                'flex shrink-0 items-center justify-center text-xl',
                column.locked
                    ? 'text-control-disabled-foreground'
                    : 'text-foreground',
            )}
        >
            {columnTypeIcons[column.type]}
        </span>
        <span
            className={classNames(
                'min-w-0 flex-1 truncate',
                column.locked && 'text-control-disabled-foreground',
            )}
        >
            {column.label}
        </span>
        <span className="shrink-0">
            <Switcher
                aria-label={`${checked ? 'Hide' : 'Show'} ${column.label}`}
                checked={checked}
                disabled={column.locked}
                onChange={onToggle}
            />
        </span>
    </li>
)

const InertGrip = () => (
    <span
        aria-hidden="true"
        className="flex h-5 w-5 items-center justify-center text-control-disabled-foreground"
    >
        <PiDotsSixVertical aria-hidden="true" />
    </span>
)

const SortableColumnRow = ({
    column,
    disabled,
    onToggle,
}: {
    column: ColumnDefinition
    disabled: boolean
    onToggle: (checked: boolean) => void
}) => {
    const canDrag = !disabled && !column.locked
    const {
        attributes,
        listeners,
        setActivatorNodeRef,
        setNodeRef,
        transform,
        transition,
    } = useSortable({ id: column.id, disabled: !canDrag })

    return (
        <ColumnRow
            checked
            column={column}
            onToggle={onToggle}
            rowRef={setNodeRef}
            style={{
                transform: CSS.Translate.toString(transform),
                transition,
            }}
            dragHandle={
                canDrag ? (
                    <button
                        ref={setActivatorNodeRef}
                        aria-label={`Reorder ${column.label}`}
                        className="flex h-5 w-5 cursor-grab items-center justify-center rounded-control text-foreground/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary active:cursor-grabbing"
                        type="button"
                        {...attributes}
                        {...listeners}
                    >
                        <PiDotsSixVertical aria-hidden="true" />
                    </button>
                ) : (
                    <InertGrip />
                )
            }
        />
    )
}

export default function DataGridSubscribers() {
    const [searchText, setSearchText] = useState('')
    const [appliedFilter, setAppliedFilter] = useState<FilterTree>(() =>
        createEmptyFilterTree(),
    )
    const [filterDraft, setFilterDraft] = useState<FilterTree>(() =>
        createEmptyFilterTree(),
    )
    const [filterOpen, setFilterOpen] = useState(false)
    const [columnManagerOpen, setColumnManagerOpen] = useState(false)
    const [columnQuery, setColumnQuery] = useState('')
    const [shownColumnIds, setShownColumnIds] = useState(initialShownColumnIds)
    const [hiddenColumnIds, setHiddenColumnIds] = useState(initialHiddenColumnIds)
    const [sortKey, setSortKey] = useState<ColumnId | null>(null)
    const [sortOrder, setSortOrder] = useState<'asc' | 'desc' | ''>('')
    const [pageIndex, setPageIndex] = useState(1)
    const [pageSize, setPageSize] = useState(10)

    const sensors = useSensors(
        useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
        useSensor(KeyboardSensor, {
            coordinateGetter: sortableKeyboardCoordinates,
        }),
    )

    const normalizedQuery = columnQuery.trim().toLowerCase()
    const matchesColumn = (id: ColumnId) =>
        !normalizedQuery ||
        columnDefinitions
            .find((column) => column.id === id)
            ?.label.toLowerCase()
            .includes(normalizedQuery)

    const filteredShownColumnIds = useMemo(
        () => shownColumnIds.filter(matchesColumn),
        [normalizedQuery, shownColumnIds],
    )
    const filteredHiddenColumnIds = useMemo(
        () => hiddenColumnIds.filter(matchesColumn),
        [hiddenColumnIds, normalizedQuery],
    )

    const columns = useMemo(
        () => shownColumnIds.map((id) => columnDefById[id]),
        [shownColumnIds],
    )

    const filteredRows = useMemo(() => {
        const search = searchText.trim().toLowerCase()
        const searchRows = search
            ? subscriberRows.filter((row) =>
                  [row.customer, row.email, row.plan, row.status].some((value) =>
                      value.toLowerCase().includes(search),
                  ),
              )
            : subscriberRows

        const filterRows = searchRows.filter((row) =>
            evaluateFilter(row, appliedFilter),
        )

        if (!sortKey || !sortOrder) return filterRows

        return [...filterRows].sort((left, right) => {
            const leftValue = left[sortKey]
            const rightValue = right[sortKey]
            const leftText = Array.isArray(leftValue)
                ? leftValue.join(', ')
                : String(leftValue)
            const rightText = Array.isArray(rightValue)
                ? rightValue.join(', ')
                : String(rightValue)
            const comparison =
                typeof leftValue === 'number' && typeof rightValue === 'number'
                    ? leftValue - rightValue
                    : leftText.localeCompare(rightText)
            return sortOrder === 'asc' ? comparison : -comparison
        })
    }, [appliedFilter, searchText, sortKey, sortOrder])

    const pageRows = useMemo(() => {
        const start = (pageIndex - 1) * pageSize
        return filteredRows.slice(start, start + pageSize)
    }, [filteredRows, pageIndex, pageSize])

    const handleSort = (sort: OnSortParam) => {
        const nextKey = typeof sort.sortKey === 'string'
            ? (sort.sortKey as ColumnId)
            : null
        setSortKey(sort.sortOrder && nextKey ? nextKey : null)
        setSortOrder(sort.sortOrder)
        setPageIndex(1)
    }

    const handleApplyFilter = (tree: FilterTree) => {
        setAppliedFilter(tree)
        setFilterDraft(tree)
        setFilterOpen(false)
        setPageIndex(1)
    }

    const handleResetFilter = (tree: FilterTree) => {
        setAppliedFilter(tree)
        setFilterDraft(tree)
        setFilterOpen(false)
        setPageIndex(1)
    }

    const handleColumnToggle = (id: ColumnId, checked: boolean) => {
        if (id === 'customer') return

        if (checked) {
            setHiddenColumnIds((current) => current.filter((item) => item !== id))
            setShownColumnIds((current) =>
                current.includes(id) ? current : [...current, id],
            )
            return
        }

        setShownColumnIds((current) => current.filter((item) => item !== id))
        setHiddenColumnIds((current) =>
            current.includes(id) ? current : [...current, id],
        )
    }

    const handleDragEnd = ({ active, over }: DragEndEvent) => {
        if (!over || active.id === over.id || normalizedQuery) return

        const oldIndex = shownColumnIds.indexOf(String(active.id) as ColumnId)
        const newIndex = shownColumnIds.indexOf(String(over.id) as ColumnId)
        if (oldIndex === -1 || newIndex === -1) return

        setShownColumnIds((current) => arrayMove(current, oldIndex, newIndex))
    }

    const handleHideAll = () => {
        const remaining = shownColumnIds.filter((id) => id === 'customer')
        setHiddenColumnIds((current) => [
            ...current,
            ...shownColumnIds.filter(
                (id) => id !== 'customer' && !current.includes(id),
            ),
        ])
        setShownColumnIds(remaining)
    }

    const handleShowAll = () => {
        setShownColumnIds((current) => [
            ...current,
            ...hiddenColumnIds.filter((id) => !current.includes(id)),
        ])
        setHiddenColumnIds([])
    }

    const handleExport = () => {
        const header = shownColumnIds.map((id) => columnDefinitions.find((column) => column.id === id)?.label || id)
        const rows = filteredRows.map((row) =>
            shownColumnIds.map((id) => escapeCsv(getExportValue(row, id))),
        )
        const csv = [header.map(escapeCsv), ...rows]
            .map((row) => row.join(','))
            .join('\n')
        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
        const url = URL.createObjectURL(blob)
        const anchor = document.createElement('a')
        anchor.href = url
        anchor.download = 'subscribers.csv'
        anchor.click()
        URL.revokeObjectURL(url)
    }

    const shownMatch = filteredShownColumnIds.length > 0
    const hiddenMatch = filteredHiddenColumnIds.length > 0

    return (
        <section aria-labelledby="data-grid-subscribers-title" className="w-full pb-4">
            <header className="flex flex-col gap-4 border-b p-4 md:flex-row md:items-center md:justify-between">
                <h4 id="data-grid-subscribers-title">Subscribers</h4>
                <Button
                    disabled={pageRows.length === 0}
                    icon={<PiDownloadSimple aria-hidden="true" />}
                    onClick={handleExport}
                >
                    Export CSV
                </Button>
            </header>

            <div className="flex flex-col gap-4 border-b p-4 lg:flex-row lg:items-center lg:justify-between">
                <DebounceInput
                    aria-label="Search subscribers"
                    className="w-full lg:max-w-xs"
                    placeholder="Search..."
                    prefix={<PiMagnifyingGlass aria-hidden="true" />}
                    wait={300}
                    onChange={(event) => {
                        setSearchText(event.target.value)
                        setPageIndex(1)
                    }}
                />
                <div className="flex flex-wrap items-center gap-2">
                    <Button
                        icon={<PiGridNine aria-hidden="true" />}
                        onClick={() => setColumnManagerOpen(true)}
                    >
                        Column View
                    </Button>
                    <Popover
                        className="p-0"
                        open={filterOpen}
                        placement="bottom-end"
                        width="auto"
                        onOpenChange={setFilterOpen}
                        renderTrigger={
                            <Button
                                active={hasActiveFilter(appliedFilter)}
                                aria-haspopup="dialog"
                                icon={<PiSlidersHorizontal aria-hidden="true" />}
                            >
                                Filter
                            </Button>
                        }
                    >
                        <AdvancedFilterBuilder
                            fields={filterFields}
                            value={filterDraft}
                            onApply={(tree) => handleApplyFilter(tree)}
                            onChange={(tree) => setFilterDraft(tree)}
                            onReset={(tree) => handleResetFilter(tree)}
                        />
                    </Popover>
                </div>
            </div>

            <DataTable
                compact
                columns={columns}
                data={pageRows}
                noData={pageRows.length === 0}
                pageSizes={[10, 20, 50]}
                pagingData={{
                    total: filteredRows.length,
                    pageIndex,
                    pageSize,
                }}
                verticalDivider={{ head: true, body: true, footer: true }}
                onPageSizeChange={(nextPageSize) => {
                    setPageSize(nextPageSize)
                    setPageIndex(1)
                }}
                onPaginationChange={(nextPage) => setPageIndex(nextPage)}
                onSort={handleSort}
            />

            <Drawer
                aria-label="Column manager"
                bodyClass="flex min-h-0 flex-1 flex-col overflow-hidden p-0"
                closable={false}
                contentClassName="max-w-[calc(100vw-1rem)]"
                isOpen={columnManagerOpen}
                placement="right"
                shouldCloseOnEsc
                shouldCloseOnOverlayClick
                width={400}
                onClose={() => setColumnManagerOpen(false)}
            >
                <header className="shrink-0 px-4 pt-4">
                    <div className="flex items-center justify-between gap-4">
                        <h5>Column manager</h5>
                        <CloseButton
                            aria-label="Close column manager"
                            onClick={() => setColumnManagerOpen(false)}
                        />
                    </div>
                    <Input
                        aria-label="Search columns"
                        className="mt-4 w-full"
                        placeholder="Search columns"
                        prefix={<PiMagnifyingGlass aria-hidden="true" />}
                        suffix={
                            columnQuery ? (
                                <Button
                                    aria-label="Clear column search"
                                    icon={<PiX aria-hidden="true" />}
                                    shape="circle"
                                    size="sm"
                                    type="button"
                                    variant="ghost"
                                    onClick={() => setColumnQuery('')}
                                />
                            ) : undefined
                        }
                        value={columnQuery}
                        onChange={(event) => setColumnQuery(event.target.value)}
                    />
                </header>

                <Scroll
                    className="min-h-0 flex-1"
                    contentClassName="p-4"
                    scrollbars="vertical"
                    type="auto"
                >
                    <DndContext sensors={sensors} onDragEnd={handleDragEnd}>
                        <section aria-labelledby="subscribers-columns-shown">
                            <div className="flex items-center justify-between gap-4">
                                <h6 id="subscribers-columns-shown">Shown</h6>
                                <Button
                                    className="px-0 font-normal text-muted-foreground/80 hover:text-foreground"
                                    disabled={shownColumnIds.length === 1}
                                    size="sm"
                                    type="button"
                                    variant="link"
                                    onClick={handleHideAll}
                                >
                                    Hide all
                                </Button>
                            </div>
                            <ul className="mt-2">
                                <SortableContext
                                    items={shownColumnIds}
                                    strategy={verticalListSortingStrategy}
                                >
                                    {shownMatch
                                        ? filteredShownColumnIds.map((id) => (
                                              <SortableColumnRow
                                                  key={id}
                                                  column={columnDefinitions.find(
                                                      (column) => column.id === id,
                                                  ) as ColumnDefinition}
                                                  disabled={Boolean(normalizedQuery)}
                                                  onToggle={(checked) =>
                                                      handleColumnToggle(id, checked)
                                                  }
                                              />
                                          ))
                                        : null}
                                </SortableContext>
                            </ul>
                        </section>

                        <section
                            aria-labelledby="subscribers-columns-hidden"
                            className="mt-2 border-t pt-2"
                        >
                            <div className="flex items-center justify-between gap-4">
                                <h6 id="subscribers-columns-hidden">Hidden</h6>
                                <Button
                                    className="px-0 font-normal text-muted-foreground/80 hover:text-foreground"
                                    disabled={hiddenColumnIds.length === 0}
                                    size="sm"
                                    type="button"
                                    variant="link"
                                    onClick={handleShowAll}
                                >
                                    Show all
                                </Button>
                            </div>
                            <ul className="mt-2">
                                {hiddenMatch
                                    ? filteredHiddenColumnIds.map((id) => (
                                          <ColumnRow
                                              key={id}
                                              checked={false}
                                              column={columnDefinitions.find(
                                                  (column) => column.id === id,
                                              ) as ColumnDefinition}
                                              dragHandle={<InertGrip />}
                                              onToggle={(checked) =>
                                                  handleColumnToggle(id, checked)
                                              }
                                          />
                                      ))
                                    : null}
                            </ul>
                        </section>

                        {normalizedQuery && !shownMatch && !hiddenMatch && (
                            <p className="text-muted-foreground">
                                No columns match this search.
                            </p>
                        )}
                    </DndContext>
                </Scroll>
            </Drawer>
        </section>
    )
}

Data Grid 07

Preview
npx nateui@latest add DataGridTaskReorder
Dark
import { createPortal } from 'react-dom'
import { useMemo, useState } from 'react'
import {
    DndContext,
    DragOverlay,
    KeyboardSensor,
    PointerSensor,
    useSensor,
    useSensors,
    type Active,
    type DragEndEvent,
    type DragOverEvent,
    type DragStartEvent,
    type Over,
} from '@dnd-kit/core'
import {
    SortableContext,
    arrayMove,
    sortableKeyboardCoordinates,
    useSortable,
    verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import Avatar from '@/components/ui/Avatar'
import Button from '@/components/ui/Button'
import Checkbox from '@/components/ui/Checkbox'
import Dropdown from '@/components/ui/Dropdown'
import Table from '@/components/ui/Table'
import Tag from '@/components/ui/Tag'
import Container from '@/components/composites/Container'
import classNames from '@/utils/classNames'
import {
    PiCaretDown,
    PiCaretRight,
    PiCheck,
    PiChatCircle,
    PiDotsSixVertical,
    PiListChecks,
    PiPlus,
} from 'react-icons/pi'

type SectionId = 'discovery' | 'build' | 'rollout'
type ProjectName = 'Platform' | 'Internal'
type LabelName = 'Frontend' | 'Backend' | 'Review' | 'Docs' | 'Urgent'
type SortKey = 'deadline' | 'project' | 'labels'
type SortOrder = 'asc' | 'desc'
type TaskFilter = 'all' | 'open' | 'completed'

type SortState = {
    key: SortKey | null
    order: SortOrder
}

type Section = {
    id: SectionId
    name: string
    dotClassName: string
    collapsed: boolean
    sort: SortState
}

type Task = {
    id: string
    sectionId: SectionId
    title: string
    deadline: string
    deadlineIso: string
    project: ProjectName
    labels: LabelName[]
    progress: string
    commentCount: number
    overdue: boolean
    completed: boolean
}

type SectionDragData = {
    type: 'Section'
    section: Section
}

type TaskDragData = {
    type: 'Task'
    task: Task
}

type DraggableData = SectionDragData | TaskDragData

const emptySort: SortState = { key: null, order: 'asc' }

const taskFilterLabels: Record<TaskFilter, string> = {
    all: 'All',
    open: 'Open',
    completed: 'Completed',
}

const initialSections: Section[] = [
    {
        id: 'discovery',
        name: 'Discovery',
        dotClassName: 'bg-palette-blue',
        collapsed: false,
        sort: emptySort,
    },
    {
        id: 'build',
        name: 'Build',
        dotClassName: 'bg-palette-purple',
        collapsed: false,
        sort: emptySort,
    },
    {
        id: 'rollout',
        name: 'Rollout',
        dotClassName: 'bg-palette-emerald',
        collapsed: false,
        sort: emptySort,
    },
]

const assetBase = 'https://statics.nateui.com/img'

const projectImages: Record<ProjectName, string> = {
    Platform: 'img-1.jpg',
    Internal: 'img-3.jpg',
}

const labelTagClasses: Record<LabelName, string> = {
    Frontend: 'bg-palette-orange-soft text-palette-orange-soft-foreground',
    Backend: 'bg-palette-cyan-soft text-palette-cyan-soft-foreground',
    Review: 'bg-palette-purple-soft text-palette-purple-soft-foreground',
    Docs: 'bg-palette-blue-soft text-palette-blue-soft-foreground',
    Urgent: 'bg-palette-rose-soft text-palette-rose-soft-foreground',
}

const initialTasks: Task[] = [
    {
        id: 'discovery-outline',
        sectionId: 'discovery',
        title: 'Draft requirement outline',
        deadline: '12 Mar 2026',
        deadlineIso: '2026-03-12',
        project: 'Platform',
        labels: ['Frontend', 'Review', 'Docs', 'Urgent'],
        progress: '0/4',
        commentCount: 8,
        overdue: false,
        completed: false,
    },
    {
        id: 'discovery-stakeholders',
        sectionId: 'discovery',
        title: 'Collect stakeholder input',
        deadline: '24 Mar 2026',
        deadlineIso: '2026-03-24',
        project: 'Platform',
        labels: ['Frontend'],
        progress: '1/4',
        commentCount: 5,
        overdue: false,
        completed: false,
    },
    {
        id: 'discovery-data-flow',
        sectionId: 'discovery',
        title: 'Map current data flow',
        deadline: '22 Mar 2026',
        deadlineIso: '2026-03-22',
        project: 'Platform',
        labels: [],
        progress: '2/6',
        commentCount: 3,
        overdue: false,
        completed: false,
    },
    {
        id: 'discovery-metrics',
        sectionId: 'discovery',
        title: 'Agree on success metrics',
        deadline: '16 Mar 2026',
        deadlineIso: '2026-03-16',
        project: 'Internal',
        labels: ['Review', 'Docs'],
        progress: '0/3',
        commentCount: 12,
        overdue: false,
        completed: false,
    },
    {
        id: 'build-settings',
        sectionId: 'build',
        title: 'Wire the settings service',
        deadline: '16 Apr 2026',
        deadlineIso: '2026-04-16',
        project: 'Platform',
        labels: ['Backend', 'Docs'],
        progress: '1/5',
        commentCount: 4,
        overdue: false,
        completed: false,
    },
    {
        id: 'build-audit',
        sectionId: 'build',
        title: 'Add audit trail events',
        deadline: '11 Apr 2026',
        deadlineIso: '2026-04-11',
        project: 'Internal',
        labels: ['Backend'],
        progress: '3/5',
        commentCount: 9,
        overdue: false,
        completed: false,
    },
    {
        id: 'build-legacy',
        sectionId: 'build',
        title: 'Migrate legacy records',
        deadline: '8 Apr 2026',
        deadlineIso: '2026-04-08',
        project: 'Platform',
        labels: ['Backend', 'Review', 'Urgent'],
        progress: '0/8',
        commentCount: 2,
        overdue: false,
        completed: false,
    },
    {
        id: 'rollout-notes',
        sectionId: 'rollout',
        title: 'Prepare release notes',
        deadline: '2 Mar 2026',
        deadlineIso: '2026-03-02',
        project: 'Internal',
        labels: ['Docs'],
        progress: '0/2',
        commentCount: 6,
        overdue: true,
        completed: false,
    },
    {
        id: 'rollout-enablement',
        sectionId: 'rollout',
        title: 'Schedule enablement session',
        deadline: '28 Apr 2026',
        deadlineIso: '2026-04-28',
        project: 'Platform',
        labels: ['Review', 'Docs'],
        progress: '1/3',
        commentCount: 7,
        overdue: false,
        completed: false,
    },
]

function hasDraggableData(
    entry: Active | Over | null | undefined,
): entry is (Active | Over) & { data: { current: DraggableData } } {
    const type = (entry?.data.current as { type?: unknown } | undefined)?.type
    return type === 'Section' || type === 'Task'
}

function sortTasks(tasks: Task[], sort: SortState) {
    if (!sort.key) {
        return tasks
    }

    return [...tasks].sort((left, right) => {
        let comparison = 0

        if (sort.key === 'deadline') {
            comparison = left.deadlineIso.localeCompare(right.deadlineIso)
        }

        if (sort.key === 'project') {
            comparison = left.project.localeCompare(right.project)
        }

        if (sort.key === 'labels') {
            const leftLabel = left.labels[0] ?? ''
            const rightLabel = right.labels[0] ?? ''

            if (!leftLabel && rightLabel) return 1
            if (leftLabel && !rightLabel) return -1
            comparison = leftLabel.localeCompare(rightLabel)
        }

        return sort.order === 'asc' ? comparison : -comparison
    })
}

function TaskTableColumns() {
    return (
        <colgroup>
            <col className="w-2/5" />
            <col className="w-1/5" />
            <col className="w-1/5" />
            <col className="w-1/5" />
        </colgroup>
    )
}

type SortHeaderButtonConfig = {
    label: string
    sectionId: SectionId
    sortKey: SortKey
    sort: SortState
    onSort: (sectionId: SectionId, sortKey: SortKey) => void
}

function SortHeaderButton({
    label,
    sectionId,
    sortKey,
    sort,
    onSort,
}: SortHeaderButtonConfig) {
    const currentSort = sort.key === sortKey ? sort.order : true

    return (
        <button
            type="button"
            className="inline-flex items-center gap-2 rounded-control-sm outline-none focus-visible:ring-2 focus-visible:ring-primary"
            onClick={() => onSort(sectionId, sortKey)}
        >
            <span>{label}</span>
            <Table.Sorter sort={currentSort} />
        </button>
    )
}

type TaskRowConfig = {
    task: Task
    isOverlay?: boolean
    onToggleTask: (taskId: string, completed: boolean) => void
}

function TaskRow({ task, isOverlay, onToggleTask }: TaskRowConfig) {
    const {
        setNodeRef,
        attributes,
        listeners,
        transform,
        transition,
        isDragging,
    } = useSortable({
        id: task.id,
        data: { type: 'Task', task } satisfies TaskDragData,
        attributes: { roleDescription: 'Task' },
    })

    const style = {
        transform: CSS.Translate.toString(transform),
        transition,
    }

    const visibleLabels = task.labels.slice(0, 2)
    const remainingLabels = task.labels.length - visibleLabels.length

    return (
        <Table.Tr
            ref={setNodeRef}
            style={style}
            className={classNames(
                isOverlay
                    ? 'ring-2 ring-primary'
                    : isDragging
                      ? 'ring-2 opacity-30'
                      : '',
                task.completed && 'opacity-50',
            )}
            {...attributes}
        >
            <Table.Td>
                <div className="flex min-w-0 items-center gap-2">
                    <button
                        type="button"
                        aria-label="Drag task"
                        className="shrink-0 cursor-grab rounded-control-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-primary"
                        {...listeners}
                    >
                        <PiDotsSixVertical />
                    </button>
                    <Checkbox
                        aria-label={`${task.completed ? 'Reopen' : 'Complete'} ${task.title}`}
                        checked={task.completed}
                        onChange={(checked) => onToggleTask(task.id, checked)}
                    />
                    <div className="flex min-w-0 flex-1 items-center justify-between gap-2">
                        <span
                            className={classNames(
                                'min-w-0 truncate font-medium',
                                task.completed &&
                                    'line-through text-muted-foreground',
                            )}
                        >
                            {task.title}
                        </span>
                        <span className="flex shrink-0 items-center gap-2">
                            <span className="flex items-center gap-1">
                                <PiListChecks aria-hidden="true" />
                                {task.progress}
                            </span>
                            <span className="flex items-center gap-1">
                                <PiChatCircle aria-hidden="true" />
                                {task.commentCount}
                            </span>
                        </span>
                    </div>
                </div>
            </Table.Td>
            <Table.Td>
                <span
                    className={classNames(
                        'whitespace-nowrap',
                        task.overdue &&
                            !task.completed &&
                            'font-medium text-destructive',
                        task.completed && 'text-muted-foreground',
                    )}
                >
                    {task.deadline}
                </span>
            </Table.Td>
            <Table.Td>
                <span className="flex items-center gap-2 whitespace-nowrap">
                    <Avatar
                        alt={`${task.project} project`}
                        className="border-0"
                        src={`${assetBase}/thumbs/projects/${projectImages[task.project]}`}
                        shape="round"
                        size={20}
                    />
                    <span>{task.project}</span>
                </span>
            </Table.Td>
            <Table.Td>
                <span className="flex flex-nowrap items-center gap-2 whitespace-nowrap">
                    {visibleLabels.map((label) => (
                        <Tag
                            key={label}
                            className={classNames(
                                'border-0',
                                labelTagClasses[label],
                            )}
                        >
                            {label}
                        </Tag>
                    ))}
                    {remainingLabels > 0 && (
                        <Tag className="px-1">
                            +{remainingLabels}
                        </Tag>
                    )}
                </span>
            </Table.Td>
        </Table.Tr>
    )
}

type SectionTableConfig = {
    section: Section
    tasks: Task[]
    isOverlay?: boolean
    onSort: (sectionId: SectionId, sortKey: SortKey) => void
    onToggleCollapsed: (sectionId: SectionId) => void
    onToggleTask: (taskId: string, completed: boolean) => void
}

function SectionTable({
    section,
    tasks,
    isOverlay,
    onSort,
    onToggleCollapsed,
    onToggleTask,
}: SectionTableConfig) {
    const {
        setNodeRef,
        attributes,
        listeners,
        transform,
        transition,
        isDragging,
    } = useSortable({
        id: section.id,
        data: { type: 'Section', section } satisfies SectionDragData,
        attributes: { roleDescription: `Section: ${section.name}` },
    })

    const taskIds = useMemo(() => tasks.map((task) => task.id), [tasks])
    const style = {
        transform: CSS.Translate.toString(transform),
        transition,
    }

    return (
        <div
            ref={setNodeRef}
            style={style}
            className={classNames(
                'flex min-w-0 items-start gap-2',
                isDragging && 'opacity-30',
            )}
            {...attributes}
        >
            <div className="flex shrink-0 items-center gap-2 pt-2">
                <button
                    type="button"
                    aria-label="Drag section"
                    className="cursor-grab rounded-control-sm text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-primary"
                    {...listeners}
                >
                    <PiDotsSixVertical />
                </button>
                <Button
                    aria-label={
                        section.collapsed
                            ? 'Expand section'
                            : 'Collapse section'
                    }
                    icon={
                        section.collapsed ? (
                            <PiCaretRight aria-hidden="true" />
                        ) : (
                            <PiCaretDown aria-hidden="true" />
                        )
                    }
                    size="sm"
                    type="button"
                    variant="subtle"
                    className="size-6! text-xs"
                    onClick={(event) => {
                        event.stopPropagation()
                        onToggleCollapsed(section.id)
                    }}
                    onMouseDown={(event) => event.stopPropagation()}
                />
            </div>

            <div
                className={classNames(
                    'min-w-0 flex-1 overflow-x-auto rounded-card',
                    isOverlay && 'ring-2 ring-primary',
                )}
            >
                <Table
                    bordered
                    overflow={false}
                    className="w-full"
                    style={{ tableLayout: 'fixed' }}
                >
                    <TaskTableColumns />
                    <Table.THead>
                        <Table.Tr className="border-b bg-transparent">
                            <Table.Th scope="col">
                                <span className="flex min-w-0 items-center gap-2  text-foreground">
                                    <span
                                        className={classNames(
                                            'h-2 w-2 shrink-0 rounded-full',
                                            section.dotClassName,
                                        )}
                                    />
                                    <span className="min-w-0 truncate font-medium">
                                        {section.name}
                                    </span>
                                    <span className="shrink-0 rounded-control-sm border px-1 text-xs font-medium">
                                        {tasks.length}
                                    </span>
                                </span>
                            </Table.Th>
                            <Table.Th scope="col">
                                <SortHeaderButton
                                    label="Deadline"
                                    sectionId={section.id}
                                    sortKey="deadline"
                                    sort={section.sort}
                                    onSort={onSort}
                                />
                            </Table.Th>
                            <Table.Th scope="col">
                                <SortHeaderButton
                                    label="Projects"
                                    sectionId={section.id}
                                    sortKey="project"
                                    sort={section.sort}
                                    onSort={onSort}
                                />
                            </Table.Th>
                            <Table.Th scope="col">
                                <SortHeaderButton
                                    label="Labels"
                                    sectionId={section.id}
                                    sortKey="labels"
                                    sort={section.sort}
                                    onSort={onSort}
                                />
                            </Table.Th>
                        </Table.Tr>
                    </Table.THead>
                    {!section.collapsed && (
                        <Table.TBody>
                            <SortableContext
                                items={taskIds}
                                strategy={verticalListSortingStrategy}
                            >
                                {tasks.map((task) => (
                                    <TaskRow
                                        key={task.id}
                                        task={task}
                                        onToggleTask={onToggleTask}
                                    />
                                ))}
                            </SortableContext>
                        </Table.TBody>
                    )}
                </Table>
            </div>
        </div>
    )
}

export default function DataGridTaskReorder() {
    const [sections, setSections] = useState<Section[]>(initialSections)
    const [tasks, setTasks] = useState<Task[]>(initialTasks)
    const [activeSection, setActiveSection] = useState<Section | null>(null)
    const [activeTask, setActiveTask] = useState<Task | null>(null)
    const [taskFilter, setTaskFilter] = useState<TaskFilter>('all')

    const sectionIds = useMemo(
        () => sections.map((section) => section.id),
        [sections],
    )

    const sensors = useSensors(
        useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
        useSensor(KeyboardSensor, {
            coordinateGetter: sortableKeyboardCoordinates,
        }),
    )

    const getSectionTasks = (section: Section) =>
        sortTasks(
            tasks.filter(
                (task) =>
                    task.sectionId === section.id &&
                    (taskFilter === 'all' ||
                        (taskFilter === 'open' && !task.completed) ||
                        (taskFilter === 'completed' && task.completed)),
            ),
            section.sort,
        )

    const resetSectionSort = (sectionIdsToReset: SectionId[]) => {
        setSections((current) =>
            current.map((section) =>
                sectionIdsToReset.includes(section.id)
                    ? { ...section, sort: { ...emptySort } }
                    : section,
            ),
        )
    }

    const handleSort = (sectionId: SectionId, sortKey: SortKey) => {
        setSections((current) =>
            current.map((section) => {
                if (section.id !== sectionId) return section

                const nextSort: SortState =
                    section.sort.key !== sortKey
                        ? { key: sortKey, order: 'asc' }
                        : section.sort.order === 'asc'
                          ? { key: sortKey, order: 'desc' }
                          : { ...emptySort }

                return { ...section, sort: nextSort }
            }),
        )
    }

    const handleToggleCollapsed = (sectionId: SectionId) => {
        setSections((current) =>
            current.map((section) =>
                section.id === sectionId
                    ? { ...section, collapsed: !section.collapsed }
                    : section,
            ),
        )
    }

    const handleToggleTask = (taskId: string, completed: boolean) => {
        setTasks((current) =>
            current.map((task) =>
                task.id === taskId ? { ...task, completed } : task,
            ),
        )
    }

    const handleDragStart = (event: DragStartEvent) => {
        if (!hasDraggableData(event.active)) return

        const data = event.active.data.current

        if (data.type === 'Section') {
            setActiveSection({ ...data.section, sort: { ...emptySort } })
            return
        }

        setActiveTask(data.task)
    }

    const handleDragOver = (event: DragOverEvent) => {
        const { active, over } = event
        if (!over) return
        if (!hasDraggableData(active) || !hasDraggableData(over)) return
        if (active.id === over.id) return

        const activeData = active.data.current
        const overData = over.data.current

        if (activeData.type !== 'Task') return

        if (overData.type === 'Task') {
            const activeSectionId = activeData.task.sectionId
            const overSectionId = overData.task.sectionId

            if (activeSectionId !== overSectionId) {
                resetSectionSort([activeSectionId, overSectionId])
                setTasks((current) => {
                    const activeIndex = current.findIndex(
                        (task) => task.id === active.id,
                    )
                    const overIndex = current.findIndex(
                        (task) => task.id === over.id,
                    )
                    const overTask = current[overIndex]

                    if (activeIndex === -1 || overIndex === -1 || !overTask) {
                        return current
                    }

                    const next = current.map((task, index) =>
                        index === activeIndex
                            ? { ...task, sectionId: overTask.sectionId }
                            : task,
                    )
                    return arrayMove(next, activeIndex, overIndex - 1)
                })
                return
            }

            resetSectionSort([activeSectionId])
            setTasks((current) => {
                const activeIndex = current.findIndex(
                    (task) => task.id === active.id,
                )
                const overIndex = current.findIndex(
                    (task) => task.id === over.id,
                )

                if (activeIndex === -1 || overIndex === -1) return current
                return arrayMove(current, activeIndex, overIndex)
            })
            return
        }

        resetSectionSort([activeData.task.sectionId])
        setTasks((current) => {
            const activeTaskIndex = current.findIndex(
                (task) => task.id === active.id,
            )
            if (activeTaskIndex === -1) return current

            const next = current.map((task, index) =>
                index === activeTaskIndex
                    ? { ...task, sectionId: overData.section.id }
                    : task,
            )
            return arrayMove(next, activeTaskIndex, activeTaskIndex)
        })
    }

    const handleDragEnd = (event: DragEndEvent) => {
        setActiveSection(null)
        setActiveTask(null)

        const { active, over } = event
        if (!over) return
        if (!hasDraggableData(active) || !hasDraggableData(over)) return

        const activeData = active.data.current
        const overData = over.data.current

        if (activeData.type !== 'Section' || overData.type !== 'Section') return
        if (active.id === over.id) return

        resetSectionSort([activeData.section.id])
        setSections((current) => {
            const activeIndex = current.findIndex(
                (section) => section.id === active.id,
            )
            const overIndex = current.findIndex(
                (section) => section.id === over.id,
            )
            if (activeIndex === -1 || overIndex === -1) return current
            return arrayMove(current, activeIndex, overIndex)
        })
    }

    return (
        <section
            aria-labelledby="data-grid-task-reorder-title"
            className="w-full space-y-4"
        >
            <Container size="lg">
                <header className="flex flex-wrap items-center justify-between gap-4 mb-4 pb-4 border-b">
                    <h4
                        id="data-grid-task-reorder-title"
                        className="text-xl font-semibold text-foreground"
                    >
                        My Task
                    </h4>
                    <div className="flex basis-full items-center gap-2 sm:basis-auto">
                        <Dropdown
                            activeKey={taskFilter}
                            onSelect={(eventKey) => {
                                if (
                                    eventKey === 'all' ||
                                    eventKey === 'open' ||
                                    eventKey === 'completed'
                                ) {
                                    setTaskFilter(eventKey)
                                }
                            }}
                            placement="bottom-end"
                            renderTitle={
                                <Button
                                    variant="ghost"
                                    size="sm"
                                    icon={<PiCaretDown aria-hidden="true" />}
                                    iconAlignment="end"
                                >
                                    Task: {taskFilterLabels[taskFilter]}
                                </Button>
                            }
                        >
                            <Dropdown.Item
                                active={taskFilter === 'all'}
                                eventKey="all"
                            >
                                <span className="flex w-full items-center justify-between">
                                    <span>All</span>
                                    {taskFilter === 'all' && (
                                        <PiCheck
                                            aria-hidden="true"
                                            className="shrink-0 text-base"
                                        />
                                    )}
                                </span>
                            </Dropdown.Item>
                            <Dropdown.Item
                                active={taskFilter === 'open'}
                                eventKey="open"
                            >
                                <span className="flex w-full items-center justify-between">
                                    <span>Open</span>
                                    {taskFilter === 'open' && (
                                        <PiCheck
                                            aria-hidden="true"
                                            className="shrink-0 text-base"
                                        />
                                    )}
                                </span>
                            </Dropdown.Item>
                            <Dropdown.Item
                                active={taskFilter === 'completed'}
                                eventKey="completed"
                            >
                                <span className="flex w-full items-center justify-between">
                                    <span>Completed</span>
                                    {taskFilter === 'completed' && (
                                        <PiCheck
                                            aria-hidden="true"
                                            className="shrink-0 text-base"
                                        />
                                    )}
                                </span>
                            </Dropdown.Item>
                        </Dropdown>
                        <Button
                            type="button"
                            variant="solid"
                            size="sm"
                            icon={<PiPlus aria-hidden="true" />}
                            className="focus-visible:ring-2 focus-visible:ring-primary"
                        >
                            Add Task
                        </Button>
                    </div>
                </header>
                <DndContext
                    id="data-grid-task-reorder"
                    sensors={sensors}
                    onDragEnd={handleDragEnd}
                    onDragOver={handleDragOver}
                    onDragStart={handleDragStart}
                >
                    <SortableContext
                        items={sectionIds}
                        strategy={verticalListSortingStrategy}
                    >
                        <div className="space-y-4">
                            {sections.map((section) => (
                                <SectionTable
                                    key={section.id}
                                    section={section}
                                    tasks={getSectionTasks(section)}
                                    onSort={handleSort}
                                    onToggleCollapsed={handleToggleCollapsed}
                                    onToggleTask={handleToggleTask}
                                />
                            ))}
                        </div>
                    </SortableContext>

                    {typeof document !== 'undefined' &&
                        createPortal(
                            <DragOverlay>
                                {activeSection && (
                                    <SectionTable
                                        isOverlay
                                        section={activeSection}
                                        tasks={getSectionTasks(activeSection)}
                                        onSort={handleSort}
                                        onToggleCollapsed={handleToggleCollapsed}
                                        onToggleTask={handleToggleTask}
                                    />
                                )}
                                {activeTask && (
                                    <Table
                                        hoverable={false}
                                        className="w-full"
                                        style={{ tableLayout: 'fixed' }}
                                    >
                                        <TaskTableColumns />
                                        <Table.TBody>
                                            <TaskRow
                                                isOverlay
                                                task={activeTask}
                                                onToggleTask={handleToggleTask}
                                            />
                                        </Table.TBody>
                                    </Table>
                                )}
                            </DragOverlay>,
                            document.body,
                        )}
                </DndContext>
            </Container>
        </section>
    )
}

Data Grid 08

Preview
npx nateui@latest add DataGridFileTree
Dark
import { useCallback, useMemo, useRef, useState } from 'react'
import classNames from '@/utils/classNames'
import ActionBar from '@/components/ui/ActionBar'
import Avatar from '@/components/ui/Avatar'
import Button from '@/components/ui/Button'
import Dropdown from '@/components/ui/Dropdown'
import Tag from '@/components/ui/Tag'
import DataTable from '@/components/composites/DataTable'
import FileIcon from '@/components/composites/FileIcon'
import type {
    ColumnDef,
    DataTableResetHandle,
    Row,
} from '@/components/composites/DataTable'
import { colors } from '@/configs/colors.config'
import {
    PiArrowCounterClockwise,
    PiArrowSquareOut,
    PiArrowsInSimple,
    PiArrowsOutSimple,
    PiCaretRight,
    PiDotsThreeVerticalBold,
    PiPencilSimple,
    PiTrash,
} from 'react-icons/pi'

const assetBase = 'https://statics.nateui.com/img'

type FileType = 'directory' | 'png' | 'csv' | 'pdf' | 'docx' | 'xlsx'

type FileNode = {
    id: string
    name: string
    type: FileType
    size: string
    childCount?: number
    ownerName: string
    ownerAvatar: string
    modified: string
    relativeModified: string
    parentId: string | null
    depth: number
}

const nodes: FileNode[] = [
    {
        id: 'design-system',
        name: 'Design System',
        type: 'directory',
        size: '412.6 MB',
        childCount: 3,
        ownerName: 'A. Rivera',
        ownerAvatar: `${assetBase}/avatars/thumb-4.jpg`,
        modified: '3 Aug 2026',
        relativeModified: 'Yesterday',
        parentId: null,
        depth: 0,
    },
    {
        id: 'tokens',
        name: 'Tokens',
        type: 'directory',
        size: '88.2 KB',
        childCount: 2,
        ownerName: 'A. Rivera',
        ownerAvatar: `${assetBase}/avatars/thumb-4.jpg`,
        modified: '9 Jul 2026',
        relativeModified: 'Last month',
        parentId: 'design-system',
        depth: 1,
    },
    {
        id: 'color-palette',
        name: 'Color Palette.png',
        type: 'png',
        size: '1.8 MB',
        ownerName: 'A. Rivera',
        ownerAvatar: `${assetBase}/avatars/thumb-4.jpg`,
        modified: '9 Jul 2026',
        relativeModified: 'Last month',
        parentId: 'tokens',
        depth: 2,
    },
    {
        id: 'token-export',
        name: 'Token Export.csv',
        type: 'csv',
        size: '42 KB',
        ownerName: 'M. Lindqvist',
        ownerAvatar: `${assetBase}/avatars/thumb-11.jpg`,
        modified: '2 Jul 2026',
        relativeModified: 'Last month',
        parentId: 'tokens',
        depth: 2,
    },
    {
        id: 'components',
        name: 'Components',
        type: 'directory',
        size: '402.1 MB',
        childCount: 4,
        ownerName: 'J. Okafor',
        ownerAvatar: `${assetBase}/avatars/thumb-7.jpg`,
        modified: '1 Aug 2026',
        relativeModified: '3 days ago',
        parentId: 'design-system',
        depth: 1,
    },
    {
        id: 'handoff-notes',
        name: 'Handoff Notes.pdf',
        type: 'pdf',
        size: '6.4 MB',
        ownerName: 'M. Lindqvist',
        ownerAvatar: `${assetBase}/avatars/thumb-11.jpg`,
        modified: '28 Jul 2026',
        relativeModified: 'Last week',
        parentId: 'design-system',
        depth: 1,
    },
    {
        id: 'research',
        name: 'Research',
        type: 'directory',
        size: '1.2 GB',
        childCount: 2,
        ownerName: 'S. Haddad',
        ownerAvatar: `${assetBase}/avatars/thumb-16.jpg`,
        modified: '4 Aug 2026',
        relativeModified: 'Yesterday',
        parentId: null,
        depth: 0,
    },
    {
        id: 'interview-recordings',
        name: 'Interview Recordings',
        type: 'directory',
        size: '1.1 GB',
        childCount: 12,
        ownerName: 'S. Haddad',
        ownerAvatar: `${assetBase}/avatars/thumb-16.jpg`,
        modified: '4 Aug 2026',
        relativeModified: 'Yesterday',
        parentId: 'research',
        depth: 1,
    },
    {
        id: 'findings-summary',
        name: 'Findings Summary.docx',
        type: 'docx',
        size: '3.1 MB',
        ownerName: 'J. Okafor',
        ownerAvatar: `${assetBase}/avatars/thumb-7.jpg`,
        modified: '30 Jul 2026',
        relativeModified: 'Last week',
        parentId: 'research',
        depth: 1,
    },
    {
        id: 'release-checklist',
        name: 'Release Checklist.xlsx',
        type: 'xlsx',
        size: '820 KB',
        ownerName: 'T. Nakamura',
        ownerAvatar: `${assetBase}/avatars/thumb-21.jpg`,
        modified: '26 Jul 2026',
        relativeModified: '2 weeks ago',
        parentId: null,
        depth: 0,
    },
]

const initialExpandedIds = ['design-system', 'tokens']

const typeStyles: Record<
    FileType,
    { label: string; color: keyof typeof colors }
> = {
    directory: { label: 'Folder', color: 'yellow' },
    png: { label: 'Image', color: 'purple' },
    csv: { label: 'Spreadsheet', color: 'emerald' },
    xlsx: { label: 'Spreadsheet', color: 'emerald' },
    pdf: { label: 'Document', color: 'blue' },
    docx: { label: 'Document', color: 'blue' },
}

function isVisible(
    node: FileNode,
    expanded: Set<string>,
    nodesById: Map<string, FileNode>,
) {
    let current = node

    while (current.parentId) {
        const parent = nodesById.get(current.parentId)

        if (!parent || !expanded.has(parent.id)) {
            return false
        }

        current = parent
    }

    return true
}

export default function DataGridFileTree() {
    const [fileNodes, setFileNodes] = useState<FileNode[]>(nodes)
    const [expanded, setExpanded] = useState<Set<string>>(
        new Set(initialExpandedIds),
    )
    const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
    const dataTableRef = useRef<DataTableResetHandle>(null)

    const nodesById = useMemo(
        () => new Map(fileNodes.map((node) => [node.id, node])),
        [fileNodes],
    )

    const visibleNodes = useMemo(
        () =>
            fileNodes.filter((node) =>
                isVisible(node, expanded, nodesById),
            ),
        [expanded, fileNodes, nodesById],
    )

    const clearSelection = useCallback(() => {
        setSelectedIds(new Set())
        dataTableRef.current?.resetSelected()
    }, [])

    const toggle = useCallback(
        (id: string) => {
            setExpanded((previous) => {
                const next = new Set(previous)

                if (next.has(id)) {
                    next.delete(id)
                } else {
                    next.add(id)
                }

                return next
            })
            clearSelection()
        },
        [clearSelection],
    )

    const expandAll = useCallback(() => {
        setExpanded(
            new Set(
                fileNodes
                    .filter((node) => node.type === 'directory')
                    .map((node) => node.id),
            ),
        )
        clearSelection()
    }, [clearSelection, fileNodes])

    const collapseAll = useCallback(() => {
        setExpanded(new Set())
        clearSelection()
    }, [clearSelection])

    const reset = useCallback(() => {
        setExpanded(new Set(initialExpandedIds))
        clearSelection()
    }, [clearSelection])

    const removeSelected = useCallback(() => {
        if (selectedIds.size === 0) {
            return
        }

        setFileNodes((current) => {
            const idsToRemove = new Set(selectedIds)
            let changed = true

            while (changed) {
                changed = false

                current.forEach((node) => {
                    if (
                        node.parentId &&
                        idsToRemove.has(node.parentId) &&
                        !idsToRemove.has(node.id)
                    ) {
                        idsToRemove.add(node.id)
                        changed = true
                    }
                })
            }

            return current.filter((node) => !idsToRemove.has(node.id))
        })
        setExpanded((current) =>
            new Set(
                [...current].filter((id) => !selectedIds.has(id)),
            ),
        )
        clearSelection()
    }, [clearSelection, selectedIds])

    const handleRowSelect = useCallback((checked: boolean, row: FileNode) => {
        setSelectedIds((previous) => {
            const next = new Set(previous)

            if (checked) {
                next.add(row.id)
            } else {
                next.delete(row.id)
            }

            return next
        })
    }, [])

    const handleAllRowSelect = useCallback(
        (checked: boolean, rows: Row<FileNode>[]) => {
            setSelectedIds(
                checked
                    ? new Set(rows.map((row) => row.original.id))
                    : new Set(),
            )
        },
        [],
    )

    const columns: ColumnDef<FileNode>[] = useMemo(
        () => [
            {
                accessorKey: 'name',
                header: 'Name',
                enableSorting: false,
                size: 300,
                cell: ({ row }) => {
                    const node = row.original
                    const isFolder = node.type === 'directory'
                    const isOpen = expanded.has(node.id)

                    return (
                        <div
                            className="flex min-w-0 items-center gap-2"
                            style={{ paddingLeft: node.depth * 20 }}
                        >
                            {isFolder ? (
                                <button
                                    type="button"
                                    aria-label={
                                        isOpen
                                            ? `Collapse ${node.name}`
                                            : `Expand ${node.name}`
                                    }
                                    className="inline-flex shrink-0 cursor-pointer text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
                                    onClick={() => toggle(node.id)}
                                >
                                    <PiCaretRight
                                        className={
                                            isOpen
                                                ? 'rotate-90 transition'
                                                : 'transition'
                                        }
                                    />
                                </button>
                            ) : (
                                <span
                                    className="inline-block w-4 shrink-0"
                                    aria-hidden="true"
                                />
                            )}
                            <span className="shrink-0">
                                <FileIcon type={node.type} />
                            </span>
                            <span
                                className={classNames(
                                    isFolder && 'font-medium',
                                    'min-w-0 truncate text-foreground',
                                )}
                            >
                                {node.name}
                            </span>
                        </div>
                    )
                },
            },
            {
                accessorKey: 'type',
                header: 'Type',
                enableSorting: false,
                size: 100,
                cell: ({ row }) => {
                    const style = typeStyles[row.original.type]
                    const color = colors[style.color]

                    return (
                        <Tag
                            className={classNames(
                                color.tuneBg,
                                color.tuneFg,
                                'border-0',
                            )}
                        >
                            {style.label}
                        </Tag>
                    )
                },
            },
            {
                accessorKey: 'size',
                header: 'Size',
                enableSorting: false,
                size: 130,
                cell: ({ row }) => (
                    <div className="flex flex-col">
                        <span className="text-foreground">{row.original.size}</span>
                        {row.original.type === 'directory' && (
                            <span className="text-xs text-muted-foreground">
                                {row.original.childCount} items
                            </span>
                        )}
                    </div>
                ),
            },
            {
                accessorKey: 'ownerName',
                header: 'Owner',
                enableSorting: false,
                size: 180,
                cell: ({ row }) => (
                    <div className="flex min-w-0 items-center gap-2">
                        <Avatar
                            size={24}
                            src={row.original.ownerAvatar}
                            alt={row.original.ownerName}
                        />
                        <span className="truncate text-foreground font-medium">
                            {row.original.ownerName}
                        </span>
                    </div>
                ),
            },
            {
                accessorKey: 'modified',
                header: 'Modified',
                enableSorting: false,
                size: 160,
                cell: ({ row }) => (
                    <div className="flex flex-col">
                        <span className="text-foreground">{row.original.modified}</span>
                        <span className="text-xs text-muted-foreground">
                            {row.original.relativeModified}
                        </span>
                    </div>
                ),
            },
            {
                id: 'actions',
                header: '',
                enableSorting: false,
                size: 60,
                cell: ({ row }) => (
                    <div className="flex justify-end">
                        <Dropdown
                            placement="bottom-end"
                            renderTitle={
                                <Button
                                    variant="ghost"
                                    shape="circle"
                                    size="sm"
                                    icon={<PiDotsThreeVerticalBold />}
                                    aria-label={`More actions for ${row.original.name}`}
                                />
                            }
                        >
                            <Dropdown.Item
                                className="flex items-center gap-2"
                                eventKey="open"
                            >
                                <PiArrowSquareOut
                                    aria-hidden="true"
                                    className="text-base text-muted-foreground"
                                />
                                <span>Open</span>
                            </Dropdown.Item>
                            <Dropdown.Item
                                className="flex items-center gap-2"
                                eventKey="rename"
                            >
                                <PiPencilSimple
                                    aria-hidden="true"
                                    className="text-base text-muted-foreground"
                                />
                                <span>Rename</span>
                            </Dropdown.Item>
                            <Dropdown.Item
                                className="flex items-center gap-2"
                                eventKey="delete"
                            >
                                <PiTrash
                                    aria-hidden="true"
                                    className="text-base text-muted-foreground"
                                />
                                <span>Delete</span>
                            </Dropdown.Item>
                        </Dropdown>
                    </div>
                ),
            },
        ],
        [expanded, toggle],
    )

    return (
        <section
            aria-labelledby="data-grid-file-tree-heading"
            className="w-full space-y-4"
        >
            <header className="flex flex-wrap items-center justify-between gap-4">
                <div>
                    <h4 id="data-grid-file-tree-heading">
                        Design Library
                    </h4>
                    <p className="text-muted-foreground">
                        {fileNodes.length} items · 1.6 GB of 5 GB used
                    </p>
                </div>
                <div className="flex flex-wrap items-center gap-2">
                    <Button
                        variant="default"
                        size="sm"
                        icon={<PiArrowsOutSimple />}
                        onClick={expandAll}
                    >
                        Expand all
                    </Button>
                    <Button
                        variant="default"
                        size="sm"
                        icon={<PiArrowsInSimple />}
                        onClick={collapseAll}
                    >
                        Collapse all
                    </Button>
                    <Button
                        variant="default"
                        size="sm"
                        icon={<PiArrowCounterClockwise />}
                        onClick={reset}
                    >
                        Reset
                    </Button>
                </div>
            </header>

            <DataTable<FileNode>
                    ref={(
                        instance: DataTableResetHandle | HTMLTableElement | null,
                    ) => {
                        if (instance && 'resetSelected' in instance) {
                            dataTableRef.current = instance
                        }
                    }}
                    overflowClass="border rounded-card"
                    columns={columns}
                    data={visibleNodes}
                    selectable
                    onRowSelect={handleRowSelect}
                    onAllRowSelect={handleAllRowSelect}
                    checkboxChecked={(row) => selectedIds.has(row.id)}
                    pagingData={{
                        total: visibleNodes.length,
                        pageIndex: 1,
                        pageSize: 25,
                    }}
            />
            <ActionBar open={selectedIds.size > 0} width={560}>
                <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
                    <span className="font-medium">
                        <span className="font-semibold text-foreground">
                            {selectedIds.size}{' '}
                            {selectedIds.size === 1 ? 'item' : 'items'}
                        </span>{' '}
                        selected
                    </span>
                    <Button
                        destructive
                        icon={<PiTrash />}
                        onClick={removeSelected}
                    >
                        Delete
                    </Button>
                </div>
            </ActionBar>
        </section>
    )
}

Data Grid 09

Preview
npx nateui@latest add DataGridExpenseApprovals
Dark
import { useMemo, useState } from 'react'
import Avatar from '@/components/ui/Avatar'
import Button from '@/components/ui/Button'
import Dropdown from '@/components/ui/Dropdown'
import Input from '@/components/ui/Input'
import Table from '@/components/ui/Table'
import {
    PiCaretDown,
    PiCheck,
    PiCheckCircleFill,
    PiClockCountdownFill,
    PiExport,
    PiMagnifyingGlass,
    PiDotsThreeVerticalBold,
    PiReceiptFill,
} from 'react-icons/pi'

const assetBase = 'https://statics.nateui.com/img'

type ApprovalStatus = 'Needs receipt' | 'Pending approval' | 'Approved'
type ExpenseCategory = 'Software' | 'Travel' | 'Office' | 'Support' | 'Fees'
type StatusFilter = ApprovalStatus | 'All'
type CategoryFilter = ExpenseCategory | 'All'
type DateRangeFilter = 'Last 30 days' | 'Last 90 days' | 'This year'
type SortKey = 'merchant' | 'amount' | 'submitted'
type SortOrder = 'asc' | 'desc'

type ExpenseRow = {
    id: string
    merchant: string
    logo: string
    category: ExpenseCategory
    status: ApprovalStatus
    amount: number
    submitted: string
    submittedAt: string
    submitter: string
    submitterEmail: string
    submitterAvatar: string
    report: string
}

const expenseRows: ExpenseRow[] = [
    {
        id: 'expense-2481',
        merchant: 'Adobe',
        logo: `${assetBase}/thumbs/brands/adobe.png`,
        category: 'Software',
        status: 'Needs receipt',
        amount: 89,
        submitted: 'Aug 18, 2026',
        submittedAt: '2026-08-18',
        submitter: 'Dana Whitfield',
        submitterEmail: 'dana.whitfield@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-1.jpg`,
        report: 'EXP-2481',
    },
    {
        id: 'expense-2475',
        merchant: 'Dropbox',
        logo: `${assetBase}/thumbs/brands/dropbox.png`,
        category: 'Software',
        status: 'Needs receipt',
        amount: 60,
        submitted: 'Aug 17, 2026',
        submittedAt: '2026-08-17',
        submitter: 'Priya Raghavan',
        submitterEmail: 'priya.r@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-3.jpg`,
        report: 'EXP-2475',
    },
    {
        id: 'expense-2461',
        merchant: 'Airbnb',
        logo: `${assetBase}/thumbs/brands/airbnb.png`,
        category: 'Travel',
        status: 'Pending approval',
        amount: 2480,
        submitted: 'Aug 14, 2026',
        submittedAt: '2026-08-14',
        submitter: 'Dana Whitfield',
        submitterEmail: 'dana.whitfield@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-1.jpg`,
        report: 'EXP-2461',
    },
    {
        id: 'expense-2458',
        merchant: 'Shopify',
        logo: `${assetBase}/thumbs/brands/shopify.png`,
        category: 'Software',
        status: 'Pending approval',
        amount: 79,
        submitted: 'Aug 14, 2026',
        submittedAt: '2026-08-14',
        submitter: 'Marcus Ferreira',
        submitterEmail: 'marcus.f@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-2.jpg`,
        report: 'EXP-2458',
    },
    {
        id: 'expense-2452',
        merchant: 'Notion',
        logo: `${assetBase}/thumbs/brands/notion.png`,
        category: 'Software',
        status: 'Pending approval',
        amount: 192,
        submitted: 'Aug 12, 2026',
        submittedAt: '2026-08-12',
        submitter: 'Priya Raghavan',
        submitterEmail: 'priya.r@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-3.jpg`,
        report: 'EXP-2452',
    },
    {
        id: 'expense-2447',
        merchant: 'Figma',
        logo: `${assetBase}/thumbs/brands/figma.png`,
        category: 'Software',
        status: 'Approved',
        amount: 540,
        submitted: 'Aug 11, 2026',
        submittedAt: '2026-08-11',
        submitter: 'Tomas Lindqvist',
        submitterEmail: 'tomas.l@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-4.jpg`,
        report: 'EXP-2447',
    },
    {
        id: 'expense-2441',
        merchant: 'GitHub',
        logo: `${assetBase}/thumbs/brands/github.png`,
        category: 'Software',
        status: 'Approved',
        amount: 210,
        submitted: 'Aug 10, 2026',
        submittedAt: '2026-08-10',
        submitter: 'Renee Okafor',
        submitterEmail: 'renee.okafor@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-5.jpg`,
        report: 'EXP-2441',
    },
    {
        id: 'expense-2436',
        merchant: 'Amazon',
        logo: `${assetBase}/thumbs/brands/amazon.png`,
        category: 'Office',
        status: 'Approved',
        amount: 436.5,
        submitted: 'Aug 08, 2026',
        submittedAt: '2026-08-08',
        submitter: 'Dana Whitfield',
        submitterEmail: 'dana.whitfield@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-1.jpg`,
        report: 'EXP-2436',
    },
    {
        id: 'expense-2430',
        merchant: 'Stripe',
        logo: `${assetBase}/thumbs/brands/stripe.png`,
        category: 'Fees',
        status: 'Approved',
        amount: 1018.2,
        submitted: 'Aug 07, 2026',
        submittedAt: '2026-08-07',
        submitter: 'Marcus Ferreira',
        submitterEmail: 'marcus.f@example.com',
        submitterAvatar: `${assetBase}/avatars/thumb-2.jpg`,
        report: 'EXP-2430',
    },
]

const statusOptions: StatusFilter[] = [
    'All',
    'Needs receipt',
    'Pending approval',
    'Approved',
]
const categoryOptions: CategoryFilter[] = [
    'All',
    'Software',
    'Travel',
    'Office',
    'Support',
    'Fees',
]
const dateRangeOptions: DateRangeFilter[] = [
    'Last 30 days',
    'Last 90 days',
    'This year',
]
const dateRangeCutoffs: Record<DateRangeFilter, string> = {
    'Last 30 days': '2026-07-23',
    'Last 90 days': '2026-05-24',
    'This year': '2026-01-01',
}

const currencyFormatter = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
})

const groupDefinitions = [
    { key: 'Needs receipt', icon: PiReceiptFill, iconClassName: 'bg-palette-yellow' },
    {
        key: 'Pending approval',
        icon: PiClockCountdownFill,
        iconClassName: 'bg-palette-purple',
    },
    { key: 'Approved', icon: PiCheckCircleFill, iconClassName: 'bg-palette-emerald' },
] as const

const compareRows = (
    rows: ExpenseRow[],
    sortKey: SortKey | null,
    sortOrder: SortOrder | null,
) => {
    if (!sortKey || !sortOrder) {
        return rows
    }

    return [...rows].sort((left, right) => {
        const comparison =
            sortKey === 'amount'
                ? left.amount - right.amount
                : sortKey === 'submitted'
                  ? left.submittedAt.localeCompare(right.submittedAt)
                  : left.merchant.localeCompare(right.merchant)

        return sortOrder === 'asc' ? comparison : -comparison
    })
}

export default function DataGridExpenseApprovals() {
    const [searchQuery, setSearchQuery] = useState('')
    const [statusFilter, setStatusFilter] = useState<StatusFilter>('All')
    const [categoryFilter, setCategoryFilter] =
        useState<CategoryFilter>('All')
    const [dateRange, setDateRange] =
        useState<DateRangeFilter>('Last 30 days')
    const [sortKey, setSortKey] = useState<SortKey | null>(null)
    const [sortOrder, setSortOrder] = useState<SortOrder | null>(null)

    const filteredRows = useMemo(() => {
        const normalizedQuery = searchQuery.trim().toLowerCase()
        const dateCutoff = dateRangeCutoffs[dateRange]

        return expenseRows.filter((row) => {
            const matchesSearch =
                normalizedQuery.length === 0 ||
                row.merchant.toLowerCase().includes(normalizedQuery) ||
                row.report.toLowerCase().includes(normalizedQuery)
            const matchesStatus =
                statusFilter === 'All' || row.status === statusFilter
            const matchesCategory =
                categoryFilter === 'All' || row.category === categoryFilter
            const matchesDate = row.submittedAt >= dateCutoff

            return (
                matchesSearch &&
                matchesStatus &&
                matchesCategory &&
                matchesDate
            )
        })
    }, [categoryFilter, dateRange, searchQuery, statusFilter])

    const groupedRows = useMemo(
        () =>
            groupDefinitions.map((group) => ({
                ...group,
                rows: compareRows(
                    filteredRows.filter((row) => row.status === group.key),
                    sortKey,
                    sortOrder,
                ),
            })),
        [filteredRows, sortKey, sortOrder],
    )

    const handleSort = (nextSortKey: SortKey) => {
        if (sortKey !== nextSortKey) {
            setSortKey(nextSortKey)
            setSortOrder('asc')
            return
        }

        if (sortOrder === 'asc') {
            setSortOrder('desc')
            return
        }

        setSortKey(null)
        setSortOrder(null)
    }

    const getAriaSort = (key: SortKey): 'ascending' | 'descending' | 'none' =>
        sortKey === key && sortOrder
            ? sortOrder === 'asc'
                ? 'ascending'
                : 'descending'
            : 'none'

    const renderSortableHeader = (label: string, key: SortKey) => (
        <button
            type="button"
            className="flex items-center gap-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
            onClick={() => handleSort(key)}
        >
            <span>{label}</span>
            <Table.Sorter
                sort={sortKey === key && sortOrder ? sortOrder : false}
            />
        </button>
    )

    return (
        <section
            aria-labelledby="data-grid-expense-approvals-title"
            className="w-full"
        >
            <div>
                <div className="p-4">
                    <h4
                        id="data-grid-expense-approvals-title"
                        className="font-semibold"
                    >
                        Expense Approvals
                    </h4>

                    <div className="mt-4 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
                        <Input
                            aria-label="Search expenses by merchant or report"
                            className="w-full md:w-80"
                            onChange={(event) => setSearchQuery(event.target.value)}
                            placeholder="Search merchant or report"
                            prefix={
                                <PiMagnifyingGlass className="text-muted-foreground" />
                            }
                            value={searchQuery}
                        />

                        <div className="flex flex-wrap items-center gap-2">
                            <Dropdown
                                placement="bottom-end"
                                renderTitle={
                                    <Button
                                        icon={<PiCaretDown aria-hidden="true" />}
                                        iconAlignment="end"
                                    >
                                        {statusFilter === 'All'
                                            ? 'Status'
                                            : statusFilter}
                                    </Button>
                                }
                            >
                                {statusOptions.map((option) => (
                                    <Dropdown.Item
                                        active={statusFilter === option}
                                        eventKey={option}
                                        key={option}
                                        onSelect={() => setStatusFilter(option)}
                                    >
                                        <span className="flex w-full items-center justify-between">
                                            <span>{option}</span>
                                            {statusFilter === option && (
                                                <PiCheck
                                                    aria-hidden="true"
                                                    className="shrink-0 text-base"
                                                />
                                            )}
                                        </span>
                                    </Dropdown.Item>
                                ))}
                            </Dropdown>

                            <Dropdown
                                placement="bottom-end"
                                renderTitle={
                                    <Button
                                        icon={<PiCaretDown aria-hidden="true" />}
                                        iconAlignment="end"
                                    >
                                        {categoryFilter === 'All'
                                            ? 'Category'
                                            : categoryFilter}
                                    </Button>
                                }
                            >
                                {categoryOptions.map((option) => (
                                    <Dropdown.Item
                                        active={categoryFilter === option}
                                        eventKey={option}
                                        key={option}
                                        onSelect={() => setCategoryFilter(option)}
                                    >
                                        <span className="flex w-full items-center justify-between">
                                            <span>{option}</span>
                                            {categoryFilter === option && (
                                                <PiCheck
                                                    aria-hidden="true"
                                                    className="shrink-0 text-base"
                                                />
                                            )}
                                        </span>
                                    </Dropdown.Item>
                                ))}
                            </Dropdown>

                            <Dropdown
                                placement="bottom-end"
                                renderTitle={
                                    <Button
                                        icon={<PiCaretDown aria-hidden="true" />}
                                        iconAlignment="end"
                                    >
                                        {dateRange}
                                    </Button>
                                }
                            >
                                {dateRangeOptions.map((option) => (
                                    <Dropdown.Item
                                        active={dateRange === option}
                                        eventKey={option}
                                        key={option}
                                        onSelect={() => setDateRange(option)}
                                    >
                                        <span className="flex w-full items-center justify-between">
                                            <span>{option}</span>
                                            {dateRange === option && (
                                                <PiCheck
                                                    aria-hidden="true"
                                                    className="shrink-0 text-base"
                                                />
                                            )}
                                        </span>
                                    </Dropdown.Item>
                                ))}
                            </Dropdown>

                            <Button
                                icon={<PiExport aria-hidden="true" />}
                                iconAlignment="start"
                            >
                                Export
                            </Button>
                        </div>
                    </div>
                </div>

                <div className="overflow-hidden">
                    <Table 
                        className="border-t border-b"
                        verticalDivider={{
                            body: true,
                            head: true
                        }}
                    >
                        <Table.THead>
                            <Table.Tr className="bg-transparent">
                                <Table.Th
                                    scope="col"
                                    className="whitespace-nowrap text-foreground"
                                    aria-sort={getAriaSort('merchant')}
                                >
                                    {renderSortableHeader('MERCHANT', 'merchant')}
                                </Table.Th>
                                <Table.Th
                                    scope="col"
                                    className="whitespace-nowrap text-foreground"
                                    aria-sort={getAriaSort('amount')}
                                >
                                    {renderSortableHeader('AMOUNT', 'amount')}
                                </Table.Th>
                                <Table.Th
                                    scope="col"
                                    className="whitespace-nowrap text-foreground"
                                    aria-sort={getAriaSort('submitted')}
                                >
                                    {renderSortableHeader('SUBMITTED', 'submitted')}
                                </Table.Th>
                                <Table.Th scope="col" className="whitespace-nowrap text-foreground">
                                    Submitted by
                                </Table.Th>
                                <Table.Th scope="col" className="whitespace-nowrap text-foreground">
                                    Report
                                </Table.Th>
                                <Table.Th scope="col" className="whitespace-nowrap text-foreground">
                                    <span className="sr-only">Actions</span>
                                </Table.Th>
                            </Table.Tr>
                        </Table.THead>

                        {groupedRows.map((group) => {
                            const GroupIcon = group.icon

                            return (
                                <Table.TBody key={group.key}>
                                    <Table.Tr className="border-b bg-muted">
                                        <Table.Td colSpan={6} className="py-2">
                                            <span className="flex items-center gap-2">
                                                <span className={`min-w-0 size-4 flex items-center justify-center rounded ${group.iconClassName}`}>
                                                    <GroupIcon
                                                        aria-hidden="true"
                                                        className="text-xs text-white"
                                                    />
                                                </span>
                                                <span className="font-medium">
                                                    {group.key}
                                                </span>
                                                <span className="text-muted-foreground">
                                                    {group.rows.length}
                                                </span>
                                            </span>
                                        </Table.Td>
                                    </Table.Tr>

                                    {group.rows.map((row) => (
                                        <Table.Tr key={row.id}>
                                            <Table.Td className="whitespace-nowrap">
                                                <div className="flex min-w-0 items-center gap-4">
                                                    <div>
                                                        <img
                                                            alt=""
                                                            className="size-6 object-contain"
                                                            src={row.logo}
                                                        />
                                                    </div>
                                                    <span className="min-w-0">
                                                        <span className="block truncate font-medium">
                                                            {row.merchant}
                                                        </span>
                                                        <span className="block truncate text-muted-foreground">
                                                            {row.category}
                                                        </span>
                                                    </span>
                                                </div>
                                            </Table.Td>
                                            <Table.Td className="whitespace-nowrap">
                                                <span className="font-medium">
                                                    {currencyFormatter.format(row.amount)}
                                                </span>
                                            </Table.Td>
                                            <Table.Td className="whitespace-nowrap">
                                                {row.submitted}
                                            </Table.Td>
                                            <Table.Td className="whitespace-nowrap">
                                                <div className="flex items-center gap-2">
                                                    <Avatar
                                                        alt={row.submitter}
                                                        size={32}
                                                        src={row.submitterAvatar}
                                                    />
                                                    <span className="min-w-0">
                                                        <span className="block truncate font-medium">
                                                            {row.submitter}
                                                        </span>
                                                        <span className="block truncate text-muted-foreground">
                                                            {row.submitterEmail}
                                                        </span>
                                                    </span>
                                                </div>
                                            </Table.Td>
                                            <Table.Td className="whitespace-nowrap">
                                                {row.report}
                                            </Table.Td>
                                            <Table.Td className="whitespace-nowrap">
                                                <div className="flex justify-end">
                                                    <Dropdown
                                                        placement="bottom-end"
                                                        renderTitle={
                                                            <Button
                                                                aria-label={`More actions for ${row.report}`}
                                                                icon={
                                                                    <PiDotsThreeVerticalBold aria-hidden="true" />
                                                                }
                                                                shape="circle"
                                                                size="sm"
                                                                variant="ghost"
                                                            />
                                                        }
                                                    >
                                                        {row.status === 'Needs receipt' ? (
                                                            <>
                                                                <Dropdown.Item eventKey="add-receipt">
                                                                    Add receipt
                                                                </Dropdown.Item>
                                                                <Dropdown.Item
                                                                    className="text-destructive"
                                                                    eventKey="discard"
                                                                >
                                                                    Discard
                                                                </Dropdown.Item>
                                                            </>
                                                        ) : row.status === 'Pending approval' ? (
                                                            <>
                                                                <Dropdown.Item eventKey="approve">
                                                                    Approve
                                                                </Dropdown.Item>
                                                                <Dropdown.Item
                                                                    className="text-destructive"
                                                                    eventKey="reject"
                                                                >
                                                                    Reject
                                                                </Dropdown.Item>
                                                            </>
                                                        ) : (
                                                            <>
                                                                <Dropdown.Item eventKey="view-receipt">
                                                                    View receipt
                                                                </Dropdown.Item>
                                                                <Dropdown.Item eventKey="download-pdf">
                                                                    Download PDF
                                                                </Dropdown.Item>
                                                                <Dropdown.Item
                                                                    eventKey="approved-divider"
                                                                    variant="divider"
                                                                />
                                                                <Dropdown.Item eventKey="reopen">
                                                                    Reopen
                                                                </Dropdown.Item>
                                                            </>
                                                        )}
                                                    </Dropdown>
                                                </div>
                                            </Table.Td>
                                        </Table.Tr>
                                    ))}
                                </Table.TBody>
                            )
                        })}
                    </Table>
                </div>
            </div>
        </section>
    )
}

Data Grid 10

Preview
npx nateui@latest add DataGridShipmentInlineEdit
Dark
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import Button from '@/components/ui/Button'
import Calendar from '@/components/ui/Calendar'
import Checkbox from '@/components/ui/Checkbox'
import Dropdown from '@/components/ui/Dropdown'
import Input from '@/components/ui/Input'
import Pagination from '@/components/ui/Pagination'
import Popover from '@/components/ui/Popover'
import Table from '@/components/ui/Table'
import Tag from '@/components/ui/Tag'
import {
    PiArchive,
    PiCalendarBlank,
    PiCheck,
    PiCircleDashed,
    PiCopy,
    PiCube,
    PiDotsThreeVertical,
    PiEye,
    PiExport,
    PiMagnifyingGlass,
    PiMapPin,
    PiPackage,
    PiPencilSimple,
    PiPlus,
    PiScales,
    PiTrash,
    PiTruck,
} from 'react-icons/pi'
import type { ComponentType, KeyboardEvent } from 'react'

type ShipmentStatus = 'In transit' | 'Delivered' | 'Customs hold' | 'Delayed'
type DateFilter = 'All dates' | 'Next 7 days' | 'Next 14 days'
type SortKey =
    | 'shipmentId'
    | 'origin'
    | 'destination'
    | 'carrier'
    | 'status'
    | 'eta'
    | 'weight'
type SortOrder = 'asc' | 'desc'
type EditableField = 'shipmentId' | 'origin' | 'destination' | 'weight'
type ActiveColumn = EditableField | 'carrier' | 'status' | 'eta'
type ActiveCell = { rowId: string; columnKey: ActiveColumn } | null

type ShipmentRow = {
    id: string
    shipmentId: string
    origin: string
    destination: string
    carrier: Carrier
    status: ShipmentStatus
    eta: Date
    weight: string
}

type HeaderIcon = ComponentType<{
    className?: string
    'aria-hidden'?: boolean
}>

const assetBase = 'https://statics.nateui.com/img'

const carrierOptions = ['DHL', 'Evri', 'FedEx', 'UPS'] as const
type Carrier = (typeof carrierOptions)[number]

const carrierAssets: Record<Carrier, string> = {
    DHL: 'dhl.png',
    Evri: 'evri.png',
    FedEx: 'fedex.png',
    UPS: 'ups.png',
}

const citySuggestions = [
    'Auckland',
    'Brno',
    'Cork',
    'Dakar',
    'Genoa',
    'Hobart',
    'Izmir',
    'Kyoto',
    'Malmo',
    'Osaka',
    'Pune',
    'Turin',
    'Reykjavik',
    'Lisbon',
    'Wellington',
    'Warsaw',
    'Nairobi',
    'Prague',
    'Quito',
    'Tallinn',
    'Valencia',
    'Helsinki',
    'Bratislava',
    'Marrakesh',
    'Bergen',
    'Zagreb',
    'Sapporo',
    'Dublin',
    'Lima',
    'Riga',
    'Adelaide',
    'Vienna',
    'Tbilisi',
    'Aarhus',
    'Kigali',
    'Ljubljana',
]

const getCitySuggestions = (query: string) => {
    const normalizedQuery = query.trim().toLowerCase()

    if (normalizedQuery.length === 0) return []

    return citySuggestions.filter((city) =>
        city.toLowerCase().includes(normalizedQuery),
    )
}

const shipmentRows: ShipmentRow[] = [
    {
        id: 'shipment-1001',
        shipmentId: 'SHP-73014',
        origin: 'Reykjavik',
        destination: 'Lisbon',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 7, 19),
        weight: '18.4 kg',
    },
    {
        id: 'shipment-1002',
        shipmentId: 'SHP-73029',
        origin: 'Wellington',
        destination: 'Warsaw',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 7, 20),
        weight: '42.8 kg',
    },
    {
        id: 'shipment-1003',
        shipmentId: 'SHP-73041',
        origin: 'Nairobi',
        destination: 'Prague',
        carrier: 'FedEx',
        status: 'Customs hold',
        eta: new Date(2026, 7, 21),
        weight: '7.6 kg',
    },
    {
        id: 'shipment-1004',
        shipmentId: 'SHP-73056',
        origin: 'Quito',
        destination: 'Tallinn',
        carrier: 'UPS',
        status: 'Delivered',
        eta: new Date(2026, 7, 22),
        weight: '63.2 kg',
    },
    {
        id: 'shipment-1005',
        shipmentId: 'SHP-73068',
        origin: 'Valencia',
        destination: 'Helsinki',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 7, 23),
        weight: '11.9 kg',
    },
    {
        id: 'shipment-1006',
        shipmentId: 'SHP-73077',
        origin: 'Bratislava',
        destination: 'Marrakesh',
        carrier: 'Evri',
        status: 'Customs hold',
        eta: new Date(2026, 7, 24),
        weight: '28.7 kg',
    },
    {
        id: 'shipment-1007',
        shipmentId: 'SHP-73089',
        origin: 'Bergen',
        destination: 'Zagreb',
        carrier: 'FedEx',
        status: 'Delayed',
        eta: new Date(2026, 7, 25),
        weight: '9.3 kg',
    },
    {
        id: 'shipment-1008',
        shipmentId: 'SHP-73102',
        origin: 'Sapporo',
        destination: 'Dublin',
        carrier: 'UPS',
        status: 'Delivered',
        eta: new Date(2026, 7, 26),
        weight: '54.1 kg',
    },
    {
        id: 'shipment-1009',
        shipmentId: 'SHP-73118',
        origin: 'Lima',
        destination: 'Riga',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 7, 27),
        weight: '36.5 kg',
    },
    {
        id: 'shipment-1010',
        shipmentId: 'SHP-73126',
        origin: 'Adelaide',
        destination: 'Vienna',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 7, 29),
        weight: '81.4 kg',
    },
    {
        id: 'shipment-1011',
        shipmentId: 'SHP-73139',
        origin: 'Tbilisi',
        destination: 'Aarhus',
        carrier: 'FedEx',
        status: 'Delayed',
        eta: new Date(2026, 8, 1),
        weight: '15.8 kg',
    },
    {
        id: 'shipment-1012',
        shipmentId: 'SHP-73144',
        origin: 'Kigali',
        destination: 'Ljubljana',
        carrier: 'UPS',
        status: 'In transit',
        eta: new Date(2026, 8, 4),
        weight: '24.6 kg',
    },
    {
        id: 'shipment-1013',
        shipmentId: 'SHP-73155',
        origin: 'Auckland',
        destination: 'Brno',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 7, 28),
        weight: '22.1 kg',
    },
    {
        id: 'shipment-1014',
        shipmentId: 'SHP-73163',
        origin: 'Cork',
        destination: 'Dakar',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 7, 30),
        weight: '31.6 kg',
    },
    {
        id: 'shipment-1015',
        shipmentId: 'SHP-73171',
        origin: 'Genoa',
        destination: 'Hobart',
        carrier: 'FedEx',
        status: 'Customs hold',
        eta: new Date(2026, 8, 2),
        weight: '13.8 kg',
    },
    {
        id: 'shipment-1016',
        shipmentId: 'SHP-73186',
        origin: 'Izmir',
        destination: 'Kyoto',
        carrier: 'UPS',
        status: 'Delayed',
        eta: new Date(2026, 8, 3),
        weight: '46.2 kg',
    },
    {
        id: 'shipment-1017',
        shipmentId: 'SHP-73194',
        origin: 'Malmo',
        destination: 'Osaka',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 8, 5),
        weight: '19.7 kg',
    },
    {
        id: 'shipment-1018',
        shipmentId: 'SHP-73203',
        origin: 'Pune',
        destination: 'Turin',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 8, 6),
        weight: '58.3 kg',
    },
    {
        id: 'shipment-1019',
        shipmentId: 'SHP-73211',
        origin: 'Reykjavik',
        destination: 'Wellington',
        carrier: 'FedEx',
        status: 'Customs hold',
        eta: new Date(2026, 8, 7),
        weight: '8.4 kg',
    },
    {
        id: 'shipment-1020',
        shipmentId: 'SHP-73224',
        origin: 'Lisbon',
        destination: 'Nairobi',
        carrier: 'UPS',
        status: 'Delayed',
        eta: new Date(2026, 8, 8),
        weight: '67.5 kg',
    },
    {
        id: 'shipment-1021',
        shipmentId: 'SHP-73231',
        origin: 'Warsaw',
        destination: 'Quito',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 8, 9),
        weight: '27.3 kg',
    },
    {
        id: 'shipment-1022',
        shipmentId: 'SHP-73245',
        origin: 'Prague',
        destination: 'Valencia',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 8, 10),
        weight: '44.9 kg',
    },
    {
        id: 'shipment-1023',
        shipmentId: 'SHP-73252',
        origin: 'Tallinn',
        destination: 'Helsinki',
        carrier: 'FedEx',
        status: 'Customs hold',
        eta: new Date(2026, 8, 11),
        weight: '12.6 kg',
    },
    {
        id: 'shipment-1024',
        shipmentId: 'SHP-73268',
        origin: 'Marrakesh',
        destination: 'Bergen',
        carrier: 'UPS',
        status: 'Delayed',
        eta: new Date(2026, 8, 12),
        weight: '39.2 kg',
    },
    {
        id: 'shipment-1025',
        shipmentId: 'SHP-73274',
        origin: 'Zagreb',
        destination: 'Sapporo',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 8, 13),
        weight: '16.5 kg',
    },
    {
        id: 'shipment-1026',
        shipmentId: 'SHP-73289',
        origin: 'Dublin',
        destination: 'Lima',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 8, 14),
        weight: '72.8 kg',
    },
    {
        id: 'shipment-1027',
        shipmentId: 'SHP-73296',
        origin: 'Riga',
        destination: 'Adelaide',
        carrier: 'FedEx',
        status: 'Customs hold',
        eta: new Date(2026, 8, 15),
        weight: '6.9 kg',
    },
    {
        id: 'shipment-1028',
        shipmentId: 'SHP-73305',
        origin: 'Vienna',
        destination: 'Tbilisi',
        carrier: 'UPS',
        status: 'Delayed',
        eta: new Date(2026, 8, 16),
        weight: '51.4 kg',
    },
    {
        id: 'shipment-1029',
        shipmentId: 'SHP-73317',
        origin: 'Aarhus',
        destination: 'Kigali',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 8, 17),
        weight: '25.7 kg',
    },
    {
        id: 'shipment-1030',
        shipmentId: 'SHP-73328',
        origin: 'Ljubljana',
        destination: 'Auckland',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 8, 18),
        weight: '33.1 kg',
    },
    {
        id: 'shipment-1031',
        shipmentId: 'SHP-73334',
        origin: 'Brno',
        destination: 'Cork',
        carrier: 'FedEx',
        status: 'Customs hold',
        eta: new Date(2026, 8, 19),
        weight: '10.2 kg',
    },
    {
        id: 'shipment-1032',
        shipmentId: 'SHP-73349',
        origin: 'Dakar',
        destination: 'Genoa',
        carrier: 'UPS',
        status: 'Delayed',
        eta: new Date(2026, 8, 20),
        weight: '60.6 kg',
    },
    {
        id: 'shipment-1033',
        shipmentId: 'SHP-73356',
        origin: 'Hobart',
        destination: 'Izmir',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 8, 21),
        weight: '14.8 kg',
    },
    {
        id: 'shipment-1034',
        shipmentId: 'SHP-73361',
        origin: 'Kyoto',
        destination: 'Malmo',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 8, 22),
        weight: '48.5 kg',
    },
    {
        id: 'shipment-1035',
        shipmentId: 'SHP-73377',
        origin: 'Osaka',
        destination: 'Pune',
        carrier: 'FedEx',
        status: 'Customs hold',
        eta: new Date(2026, 8, 23),
        weight: '21.9 kg',
    },
    {
        id: 'shipment-1036',
        shipmentId: 'SHP-73388',
        origin: 'Turin',
        destination: 'Reykjavik',
        carrier: 'UPS',
        status: 'Delayed',
        eta: new Date(2026, 8, 24),
        weight: '74.2 kg',
    },
    {
        id: 'shipment-1037',
        shipmentId: 'SHP-73395',
        origin: 'Wellington',
        destination: 'Lisbon',
        carrier: 'DHL',
        status: 'In transit',
        eta: new Date(2026, 8, 25),
        weight: '29.4 kg',
    },
    {
        id: 'shipment-1038',
        shipmentId: 'SHP-73402',
        origin: 'Nairobi',
        destination: 'Warsaw',
        carrier: 'Evri',
        status: 'Delivered',
        eta: new Date(2026, 8, 26),
        weight: '56.7 kg',
    },
    {
        id: 'shipment-1039',
        shipmentId: 'SHP-73418',
        origin: 'Quito',
        destination: 'Prague',
        carrier: 'FedEx',
        status: 'Customs hold',
        eta: new Date(2026, 8, 27),
        weight: '17.2 kg',
    },
    {
        id: 'shipment-1040',
        shipmentId: 'SHP-73427',
        origin: 'Valencia',
        destination: 'Tallinn',
        carrier: 'UPS',
        status: 'Delayed',
        eta: new Date(2026, 8, 28),
        weight: '41.8 kg',
    },
]

const statusOptions: ShipmentStatus[] = [
    'In transit',
    'Delivered',
    'Customs hold',
    'Delayed',
]
const dateFilterOptions: DateFilter[] = [
    'All dates',
    'Next 7 days',
    'Next 14 days',
]
const filterAnchor = new Date(2026, 7, 18)
const initialSelectedIds = new Set<string>()


const statusDotClasses: Record<ShipmentStatus, string> = {
    'In transit': 'bg-info',
    Delivered: 'bg-success',
    'Customs hold': 'bg-warning',
    Delayed: 'bg-destructive',
}

const formatDate = (value: Date) =>
    new Intl.DateTimeFormat('en-US', {
        day: '2-digit',
        month: 'short',
        year: 'numeric',
    }).format(value)

const compareRows = (
    rows: ShipmentRow[],
    sortKey: SortKey | null,
    sortOrder: SortOrder | null,
) => {
    if (!sortKey || !sortOrder) return rows

    return [...rows].sort((left, right) => {
        let comparison = 0

        if (sortKey === 'eta') {
            comparison = left.eta.getTime() - right.eta.getTime()
        } else if (sortKey === 'weight') {
            comparison =
                Number.parseFloat(left.weight) - Number.parseFloat(right.weight)
        } else {
            comparison = left[sortKey].localeCompare(right[sortKey])
        }

        return sortOrder === 'asc' ? comparison : -comparison
    })
}

const matchesDateFilter = (date: Date, filter: DateFilter) => {
    if (filter === 'All dates') return true

    const days = filter === 'Next 7 days' ? 7 : 14
    const cutoff = new Date(filterAnchor)
    cutoff.setDate(cutoff.getDate() + days)

    return date >= filterAnchor && date <= cutoff
}

const getEditableValue = (row: ShipmentRow, field: EditableField) =>
    row[field]

const updateEditableValue = (
    row: ShipmentRow,
    field: EditableField,
    value: string,
) => ({ ...row, [field]: value })

export default function DataGridShipmentInlineEdit() {
    const pageSize = 12
    const [rows, setRows] = useState<ShipmentRow[]>(shipmentRows)
    const [searchQuery, setSearchQuery] = useState('')
    const [selectedIds, setSelectedIds] = useState<Set<string>>(
        initialSelectedIds,
    )
    const [dateFilter, setDateFilter] = useState<DateFilter>('All dates')
    const [statusFilter, setStatusFilter] = useState<ShipmentStatus | 'All'>(
        'All',
    )
    const [sortKey, setSortKey] = useState<SortKey | null>(null)
    const [sortOrder, setSortOrder] = useState<SortOrder | null>(null)
    const [editingCell, setEditingCell] = useState<{
        rowId: string
        field: EditableField
    } | null>(null)
    const [draftValue, setDraftValue] = useState('')
    const [openDateCell, setOpenDateCell] = useState<string | null>(null)
    const [activeCell, setActiveCell] = useState<ActiveCell>(null)
    const [openDropdownCell, setOpenDropdownCell] =
        useState<ActiveCell>(null)
    const [autocompleteOpen, setAutocompleteOpen] = useState(false)
    const [suggestionHighlight, setSuggestionHighlight] = useState(0)
    const [autocompleteQuery, setAutocompleteQuery] = useState('')
    const autocompleteInputRef = useRef<HTMLInputElement>(null)
    const suggestionRefs = useRef<Array<HTMLDivElement | null>>([])
    const [pageIndex, setPageIndex] = useState(1)

    const filteredRows = useMemo(() => {
        const normalizedQuery = searchQuery.trim().toLowerCase()

        return rows.filter((row) => {
            const matchesSearch =
                normalizedQuery.length === 0 ||
                [
                    row.shipmentId,
                    row.origin,
                    row.destination,
                    row.carrier,
                ].some((value) => value.toLowerCase().includes(normalizedQuery))
            const matchesStatus =
                statusFilter === 'All' || row.status === statusFilter

            return (
                matchesSearch &&
                matchesStatus &&
                matchesDateFilter(row.eta, dateFilter)
            )
        })
    }, [dateFilter, rows, searchQuery, statusFilter])

    const visibleRows = useMemo(
        () => compareRows(filteredRows, sortKey, sortOrder),
        [filteredRows, sortKey, sortOrder],
    )

    const pageCount = Math.max(1, Math.ceil(visibleRows.length / pageSize))
    const pagedRows = useMemo(
        () =>
            visibleRows.slice(
                (pageIndex - 1) * pageSize,
                pageIndex * pageSize,
            ),
        [pageIndex, visibleRows],
    )

    useEffect(() => {
        setPageIndex(1)
    }, [dateFilter, searchQuery, statusFilter])

    useEffect(() => {
        setPageIndex((current) => Math.min(current, pageCount))
    }, [pageCount])

    useLayoutEffect(() => {
        if (!autocompleteOpen) return

        const revealTimer = window.setTimeout(() => {
            autocompleteInputRef.current?.focus()

            if (!editingCell) return

            const suggestionListId = `suggestions-${editingCell.rowId}-${editingCell.field}`
            const popover = document
                .getElementById(suggestionListId)
                ?.closest('.popover')

            if (popover instanceof HTMLElement) {
                popover.style.transition = 'none'
                popover.style.opacity = '1'
                popover.style.transform = 'none'
            }
        }, 0)

        return () => window.clearTimeout(revealTimer)

    }, [autocompleteOpen, editingCell])

    const visibleSelectedCount = pagedRows.filter((row) =>
        selectedIds.has(row.id),
    ).length
    const allVisibleSelected =
        pagedRows.length > 0 && visibleSelectedCount === pagedRows.length
    const someVisibleSelected =
        visibleSelectedCount > 0 && !allVisibleSelected

    const handleSort = (nextSortKey: SortKey) => {
        if (sortKey !== nextSortKey) {
            setSortKey(nextSortKey)
            setSortOrder('asc')
            return
        }

        if (sortOrder === 'asc') {
            setSortOrder('desc')
            return
        }

        setSortKey(null)
        setSortOrder(null)
    }

    const startEditing = (row: ShipmentRow, field: EditableField) => {
        setEditingCell({ rowId: row.id, field })
        setDraftValue(getEditableValue(row, field))
        setActiveCell({ rowId: row.id, columnKey: field })
        setAutocompleteQuery('')
        suggestionRefs.current = []
        setSuggestionHighlight(
            field === 'origin' || field === 'destination'
                ? Math.max(0, citySuggestions.indexOf(getEditableValue(row, field)))
                : 0,
        )
        setAutocompleteOpen(false)
    }

    const commitEditing = (nextValue = draftValue) => {
        if (!editingCell) return

        setRows((current) =>
            current.map((row) =>
                row.id === editingCell.rowId
                    ? updateEditableValue(
                          row,
                          editingCell.field,
                          nextValue.trim(),
                      )
                    : row,
            ),
        )
        setEditingCell(null)
        setAutocompleteOpen(false)
        setAutocompleteQuery('')
        setActiveCell(null)
    }

    const cancelEditing = () => {
        setEditingCell(null)
        setAutocompleteOpen(false)
        setAutocompleteQuery('')
        setActiveCell(null)
    }

    const toggleRowSelection = (rowId: string, checked: boolean) => {
        setSelectedIds((current) => {
            const next = new Set(current)
            if (checked) next.add(rowId)
            else next.delete(rowId)
            return next
        })
    }

    const toggleAllVisible = (checked: boolean) => {
        setSelectedIds((current) => {
            const next = new Set(current)
            pagedRows.forEach((row) => {
                if (checked) next.add(row.id)
                else next.delete(row.id)
            })
            return next
        })
    }

    const renderSortableHeader = (
        label: string,
        key: SortKey,
        Icon: HeaderIcon,
    ) => (
        <button
            type="button"
            className="flex h-full w-full min-w-0 items-center gap-2 px-4 text-left uppercase outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary"
            onClick={() => handleSort(key)}
        >
            <span className="flex items-center gap-2">
                <Icon
                    aria-hidden={true}
                    className="shrink-0 text-base text-muted-foreground"
                />
                <span className="min-w-0 truncate">{label}</span>
            </span>
            <Table.Sorter
                sort={sortKey === key && sortOrder ? sortOrder : false}
            />
        </button>
    )

    const renderTextCell = (row: ShipmentRow, field: EditableField) => {
        const isEditing =
            editingCell?.rowId === row.id && editingCell.field === field
        const isAutocompleteField = field === 'origin' || field === 'destination'
        const suggestions = isAutocompleteField
            ? getCitySuggestions(autocompleteQuery)
            : []
        const suggestionListId = `suggestions-${row.id}-${field}`
        const isAutocompleteOpen = autocompleteOpen
        const activeSuggestionId =
            isAutocompleteOpen && suggestions[suggestionHighlight]
                ? `${suggestionListId}-${suggestionHighlight}`
                : undefined
        const activeClass =
            activeCell?.rowId === row.id && activeCell.columnKey === field
                ? 'ring-2 ring-inset ring-primary'
                : ''

        if (isEditing) {
            return (
                <Table.Td className="h-12 p-0">
                    {isAutocompleteField ? (
                        <Popover
                            open={isAutocompleteOpen}
                            onOpenChange={(open) => {
                                if (
                                    !open &&
                                    editingCell?.rowId === row.id &&
                                    editingCell.field === field
                                ) {
                                    return
                                }
                                setAutocompleteOpen(open)
                                if (!open) setActiveCell(null)
                            }}
                            placement="bottom-start"
                            className="max-h-80 overflow-y-auto px-1 py-2"
                            renderTrigger={
                                <div
                                    className={`relative flex h-full w-full min-w-0 items-center px-4 hover:bg-muted ${activeClass}`}
                                    onMouseDown={() =>
                                        setActiveCell({
                                            rowId: row.id,
                                            columnKey: field,
                                        })
                                    }
                                >
                                    <input
                                        ref={autocompleteInputRef}
                                        aria-activedescendant={activeSuggestionId}
                                        aria-autocomplete="list"
                                        aria-controls={
                                            isAutocompleteOpen
                                                ? suggestionListId
                                                : undefined
                                        }
                                        aria-expanded={isAutocompleteOpen}
                                        aria-haspopup="listbox"
                                        aria-label={`Edit ${field} for ${row.shipmentId}`}
                                        autoFocus
                                        className="relative z-10 w-full min-w-0 border-0 bg-transparent px-0 outline-none"
                                        onBlur={() => {
                                            setTimeout(() => {
                                                const focused = document.activeElement
                                                const cell = focused?.closest('td')
                                                const listbox = document.getElementById(
                                                    suggestionListId,
                                                )
                                                const popover = document.querySelector(
                                                    '.popover-wrapper',
                                                )
                                                if (
                                                    cell?.contains(focused) ||
                                                    listbox?.contains(focused) ||
                                                    popover?.contains(focused)
                                                ) {
                                                    return
                                                }
                                                commitEditing()
                                            }, 0)
                                        }}
                                        onChange={(event) => {
                                            const nextValue = event.target.value
                                            const nextSuggestions =
                                                getCitySuggestions(nextValue)

                                            setDraftValue(nextValue)
                                            setAutocompleteQuery(nextValue)
                                            setSuggestionHighlight(0)
                                            setAutocompleteOpen(
                                                nextSuggestions.length > 0,
                                            )
                                        }}
                                        onClick={() => {
                                            setActiveCell({
                                                rowId: row.id,
                                                columnKey: field,
                                            })
                                        }}
                                        onFocus={() =>
                                            setActiveCell({
                                                rowId: row.id,
                                                columnKey: field,
                                            })
                                        }
                                        onKeyDown={(event) => {
                                            if (
                                                event.key === 'Tab' &&
                                                !event.shiftKey &&
                                                isAutocompleteOpen &&
                                                suggestions.length > 0
                                            ) {
                                                const option = suggestionRefs.current[
                                                    suggestionHighlight
                                                ]
                                                if (option) {
                                                    event.preventDefault()
                                                    option.focus()
                                                    return
                                                }
                                            }
                                            if (
                                                event.key === 'ArrowDown' &&
                                                suggestions.length > 0
                                            ) {
                                                event.preventDefault()
                                                setAutocompleteOpen(true)
                                                setSuggestionHighlight((current) =>
                                                    Math.min(
                                                        current + 1,
                                                        suggestions.length - 1,
                                                    ),
                                                )
                                            }
                                            if (
                                                event.key === 'ArrowUp' &&
                                                suggestions.length > 0
                                            ) {
                                                event.preventDefault()
                                                setAutocompleteOpen(true)
                                                setSuggestionHighlight((current) =>
                                                    Math.max(current - 1, 0),
                                                )
                                            }
                                            if (event.key === 'Enter') {
                                                event.preventDefault()
                                                commitEditing()
                                            }
                                            if (event.key === 'Escape') {
                                                event.preventDefault()
                                                cancelEditing()
                                            }
                                        }}
                                        role="combobox"
                                        value={draftValue}
                                    />
                                </div>
                            }
                        >
                            <div
                                className="grid"
                                id={suggestionListId}
                                role="listbox"
                            >
                                {suggestions.map((suggestion, index) => (
                                    <div
                                        aria-selected={index === suggestionHighlight}
                                        className="select-option mx-1 flex items-center rounded-control-sm px-3 py-2 font-medium leading-5 text-left text-popover-foreground whitespace-nowrap outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-muted focus-visible:text-foreground"
                                        id={`${suggestionListId}-${index}`}
                                        key={suggestion}
                                        onBlur={() => {
                                            setTimeout(() => {
                                                const focused = document.activeElement
                                                const listbox = document.getElementById(
                                                    suggestionListId,
                                                )
                                                if (
                                                    focused ===
                                                        autocompleteInputRef.current ||
                                                    listbox?.contains(focused)
                                                ) {
                                                    return
                                                }
                                                commitEditing(suggestion)
                                            }, 0)
                                        }}
                                        onFocus={() => setSuggestionHighlight(index)}
                                        onKeyDown={(event) => {
                                            if (event.key === 'ArrowDown') {
                                                event.preventDefault()
                                                const nextIndex = Math.min(
                                                    index + 1,
                                                    suggestions.length - 1,
                                                )
                                                setSuggestionHighlight(nextIndex)
                                                suggestionRefs.current[
                                                    nextIndex
                                                ]?.focus()
                                            }
                                            if (event.key === 'ArrowUp') {
                                                event.preventDefault()
                                                const nextIndex = Math.max(index - 1, 0)
                                                setSuggestionHighlight(nextIndex)
                                                suggestionRefs.current[
                                                    nextIndex
                                                ]?.focus()
                                            }
                                            if (event.key === 'Enter') {
                                                event.preventDefault()
                                                setDraftValue(suggestion)
                                                commitEditing(suggestion)
                                            }
                                            if (event.key === 'Escape') {
                                                event.preventDefault()
                                                cancelEditing()
                                            }
                                            if (event.key === 'Tab') {
                                                event.preventDefault()
                                                if (event.shiftKey) {
                                                    autocompleteInputRef.current?.focus()
                                                    return
                                                }

                                                const nextControl =
                                                    autocompleteInputRef.current
                                                        ?.closest('td')
                                                        ?.nextElementSibling?.querySelector<HTMLElement>(
                                                            'button, input, [tabindex="0"]',
                                                        )
                                                commitEditing(suggestion)
                                                window.requestAnimationFrame(() =>
                                                    nextControl?.focus(),
                                                )
                                            }
                                        }}
                                        onMouseDown={(event) => {
                                            event.preventDefault()
                                            setDraftValue(suggestion)
                                            commitEditing(suggestion)
                                        }}
                                        role="option"
                                        ref={(node) => {
                                            suggestionRefs.current[index] = node
                                        }}
                                        tabIndex={-1}
                                    >
                                        {suggestion}
                                    </div>
                                ))}
                            </div>
                        </Popover>
                    ) : (
                        <div
                            className={`flex h-full w-full min-w-0 items-center px-4 ${activeClass}`}
                        >
                            <input
                                aria-label={`Edit ${field} for ${row.shipmentId}`}
                                autoFocus
                                className="w-full min-w-0 border-0 bg-transparent px-0 outline-none"
                                onBlur={() => commitEditing()}
                                onChange={(event) => setDraftValue(event.target.value)}
                                onFocus={() =>
                                    setActiveCell({
                                        rowId: row.id,
                                        columnKey: field,
                                    })
                                }
                                onKeyDown={(event) => {
                                    if (event.key === 'Enter') {
                                        event.preventDefault()
                                        commitEditing()
                                    }
                                    if (event.key === 'Escape') {
                                        event.preventDefault()
                                        cancelEditing()
                                    }
                                }}
                                value={draftValue}
                            />
                        </div>
                    )}
                </Table.Td>
            )
        }

        return (
            <Table.Td className="h-12 p-0">
                <button
                    type="button"
                    className={`flex h-full w-full min-w-0 items-center px-4 text-left whitespace-nowrap outline-none hover:bg-muted focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary ${activeClass}`}
                    onClick={() => startEditing(row, field)}
                    onFocus={() =>
                        setActiveCell({ rowId: row.id, columnKey: field })
                    }
                    onKeyDown={(event) => {
                        if (event.key === 'Enter' || event.key === ' ') {
                            event.preventDefault()
                            startEditing(row, field)
                        }
                    }}
                >
                    {getEditableValue(row, field)}
                </button>
            </Table.Td>
        )
    }

    const renderCarrierCell = (row: ShipmentRow) => (
        <Table.Td className="h-12 p-0">
            <Dropdown
                placement="bottom-start"
                aria-label={`Change carrier for ${row.shipmentId}`}
                role="button"
                tabIndex={0}
                toggleClassName={`flex h-full w-full min-w-0 items-center px-4 hover:bg-muted focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary ${openDropdownCell?.rowId === row.id && openDropdownCell.columnKey === 'carrier' ? 'ring-2 ring-inset ring-primary' : ''}`}
                activeKey={row.carrier}
                onOpen={(open: boolean) => {
                    setOpenDropdownCell((current) => {
                        if (open) {
                            return { rowId: row.id, columnKey: 'carrier' }
                        }
                        return current?.rowId === row.id &&
                            current.columnKey === 'carrier'
                            ? null
                            : current
                    })
                    if (open) {
                        setActiveCell({ rowId: row.id, columnKey: 'carrier' })
                    }
                }}
                onSelect={(eventKey) => {
                    if (!carrierOptions.includes(eventKey as Carrier)) return
                    setRows((current) =>
                        current.map((item) =>
                            item.id === row.id
                                ? { ...item, carrier: eventKey as Carrier }
                                : item,
                        ),
                    )
                }}
                onFocus={() =>
                    setActiveCell({ rowId: row.id, columnKey: 'carrier' })
                }
                onClick={() =>
                    setActiveCell({ rowId: row.id, columnKey: 'carrier' })
                }
                onKeyDown={(event: KeyboardEvent<HTMLElement>) => {
                    if (event.key === 'Escape') setActiveCell(null)
                }}
                renderTitle={
                    <span
                        aria-label={`Change carrier for ${row.shipmentId}`}
                        className="flex h-full w-full min-w-0 items-center gap-2 text-left whitespace-nowrap"
                        onFocus={() =>
                            setActiveCell({ rowId: row.id, columnKey: 'carrier' })
                        }
                        onMouseDown={() =>
                            setActiveCell({
                                rowId: row.id,
                                columnKey: 'carrier',
                            })
                        }
                    >
                        <img
                            alt=""
                            className="h-4 w-4 shrink-0 object-contain"
                            src={`${assetBase}/thumbs/brands/${carrierAssets[row.carrier]}`}
                        />
                        {row.carrier}
                    </span>
                }
            >
                {carrierOptions.map((option) => (
                    <Dropdown.Item
                        active={row.carrier === option}
                        eventKey={option}
                        key={option}
                    >
                        <span className="flex w-full items-center justify-between gap-4">
                            <span className="flex items-center gap-2">
                                <img
                                    alt=""
                                    className="h-4 w-4 shrink-0 object-contain"
                                    src={`${assetBase}/thumbs/brands/${carrierAssets[option]}`}
                                />
                                <span>{option}</span>
                            </span>
                            {row.carrier === option && (
                                <PiCheck
                                    aria-hidden="true"
                                    className="shrink-0 text-base"
                                />
                            )}
                        </span>
                    </Dropdown.Item>
                ))}
            </Dropdown>
        </Table.Td>
    )

    const renderStatusCell = (row: ShipmentRow) => (
        <Table.Td className="h-12 p-0">
            <Dropdown
                placement="bottom-start"
                aria-label={`Change status for ${row.shipmentId}`}
                role="button"
                tabIndex={0}
                toggleClassName={`flex h-full w-full min-w-0 items-center px-4 hover:bg-muted focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary ${openDropdownCell?.rowId === row.id && openDropdownCell.columnKey === 'status' ? 'ring-2 ring-inset ring-primary' : ''}`}
                activeKey={row.status}
                onOpen={(open: boolean) => {
                    setOpenDropdownCell((current) => {
                        if (open) {
                            return { rowId: row.id, columnKey: 'status' }
                        }
                        return current?.rowId === row.id &&
                            current.columnKey === 'status'
                            ? null
                            : current
                    })
                    if (open) {
                        setActiveCell({ rowId: row.id, columnKey: 'status' })
                    }
                }}
                onSelect={(eventKey) => {
                    if (!statusOptions.includes(eventKey as ShipmentStatus)) {
                        return
                    }
                    setRows((current) =>
                        current.map((item) =>
                            item.id === row.id
                                ? {
                                      ...item,
                                      status: eventKey as ShipmentStatus,
                                  }
                                : item,
                        ),
                    )
                }}
                onFocus={() =>
                    setActiveCell({ rowId: row.id, columnKey: 'status' })
                }
                onClick={() =>
                    setActiveCell({ rowId: row.id, columnKey: 'status' })
                }
                onKeyDown={(event: KeyboardEvent<HTMLElement>) => {
                    if (event.key === 'Escape') setActiveCell(null)
                }}
                renderTitle={
                    <span
                        aria-label={`Change status for ${row.shipmentId}`}
                        className="flex h-full w-full min-w-0 items-center text-left whitespace-nowrap"
                        onFocus={() =>
                            setActiveCell({ rowId: row.id, columnKey: 'status' })
                        }
                        onMouseDown={() =>
                            setActiveCell({
                                rowId: row.id,
                                columnKey: 'status',
                            })
                        }
                    >
                        <Tag
                            className="bg-card"
                            prefix
                            prefixClass={statusDotClasses[row.status]}
                        >
                            {row.status}
                        </Tag>
                    </span>
                }
            >
                {statusOptions.map((option) => (
                    <Dropdown.Item
                        active={row.status === option}
                        eventKey={option}
                        key={option}
                    >
                        <span className="flex w-full items-center justify-between gap-4">
                            <Tag
                                className="bg-card"
                                prefix
                                prefixClass={statusDotClasses[option]}
                            >
                                {option}
                            </Tag>
                            {row.status === option && (
                                <PiCheck
                                    aria-hidden="true"
                                    className="shrink-0 text-base"
                                />
                            )}
                        </span>
                    </Dropdown.Item>
                ))}
            </Dropdown>
        </Table.Td>
    )

    const renderDateCell = (row: ShipmentRow) => (
        <Table.Td className="h-12 p-0">
            <Popover
                open={openDateCell === row.id}
                onOpenChange={(open) => {
                    setOpenDateCell(open ? row.id : null)
                    if (!open) setActiveCell(null)
                }}
                placement="bottom-start"
                renderTrigger={
                    <button
                        type="button"
                        aria-label={`Change ETA for ${row.shipmentId}`}
                        className={`flex h-full w-full min-w-0 items-center px-4 text-left whitespace-nowrap outline-none hover:bg-muted focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary ${activeCell?.rowId === row.id && activeCell.columnKey === 'eta' ? 'ring-2 ring-inset ring-primary' : ''}`}
                        onClick={() =>
                            setActiveCell({ rowId: row.id, columnKey: 'eta' })
                        }
                        onFocus={() =>
                            setActiveCell({ rowId: row.id, columnKey: 'eta' })
                        }
                        onKeyDown={(event) => {
                            if (event.key === 'Escape') {
                                setOpenDateCell(null)
                                setActiveCell(null)
                            }
                        }}
                    >
                        {formatDate(row.eta)}
                    </button>
                }
            >
                <Calendar
                    defaultMonth={row.eta}
                    value={row.eta}
                    onChange={(date) => {
                        if (!(date instanceof Date)) return
                        setRows((current) =>
                            current.map((item) =>
                                item.id === row.id
                                    ? { ...item, eta: date }
                                    : item,
                            ),
                        )
                        setOpenDateCell(null)
                    }}
                />
            </Popover>
        </Table.Td>
    )

    return (
        <section
            aria-labelledby="data-grid-shipment-inline-edit-title"
            className="w-full min-w-0 overflow-hidden"
            onBlur={(event) => {
                if (openDateCell || autocompleteOpen) return
                if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
                    setActiveCell(null)
                }
            }}
        >
            <header className="flex flex-wrap items-center justify-between gap-4 border-b px-4 py-3">
                <h4
                    id="data-grid-shipment-inline-edit-title"
                    className="truncate font-semibold"
                >
                    Shipment overview
                </h4>

                <div className="flex flex-wrap items-center gap-2">
                    <Button
                        icon={<PiExport aria-hidden="true" />}
                    >
                        Export rows
                    </Button>
                    <Button
                        icon={<PiPlus aria-hidden="true" />}
                        iconAlignment="end"
                        variant="solid"
                    >
                        Add shipment
                    </Button>
                </div>
            </header>

            <div className="flex flex-wrap items-center justify-between gap-4 border-b p-4">
                <Input
                    aria-label="Search shipment records"
                    className="min-w-0 flex-1 basis-full sm:basis-auto max-w-sm"
                    onChange={(event) => setSearchQuery(event.target.value)}
                    placeholder="Search shipment records"
                    prefix={
                        <PiMagnifyingGlass className="text-muted-foreground" />
                    }
                    value={searchQuery}
                />

                <div className="flex flex-wrap items-center gap-4">
                    {selectedIds.size > 0 && (
                        <div className="flex shrink-0 items-center rounded-control border px-2 py-0.5">
                            <Checkbox
                                aria-label="Selected shipments"
                                checked={true}
                                onChange={() => setSelectedIds(new Set())}
                            />
                            <span className="whitespace-nowrap font-medium mx-2">
                                {selectedIds.size} Selected
                            </span>
                            <span aria-hidden="true" className="h-4 border-l" />
                            <Button
                                aria-label="Edit selected shipments"
                                icon={<PiPencilSimple aria-hidden="true" />}
                                size="sm"
                                variant="ghost"
                            />
                            <Button
                                aria-label="Remove selected shipments"
                                destructive
                                icon={<PiTrash aria-hidden="true" />}
                                size="sm"
                                variant="ghost"
                            />
                        </div>
                    )}

                    <div className="ml-auto flex flex-wrap items-center gap-2">
                        <Dropdown
                            placement="bottom-end"
                            activeKey={dateFilter}
                            onSelect={(eventKey) => {
                                if (dateFilterOptions.includes(eventKey as DateFilter)) {
                                    setDateFilter(eventKey as DateFilter)
                                }
                            }}
                            renderTitle={
                                <Button
                                    icon={<PiCalendarBlank aria-hidden="true" />}
                                    iconAlignment="start"
                                >
                                    {dateFilter === 'All dates'
                                        ? 'Date Range'
                                        : dateFilter}
                                </Button>
                            }
                        >
                            {dateFilterOptions.map((option) => (
                                <Dropdown.Item
                                    active={dateFilter === option}
                                    eventKey={option}
                                    key={option}
                                >
                                    <span className="flex w-full items-center justify-between">
                                        <span>{option}</span>
                                        {dateFilter === option && (
                                            <PiCheck
                                                aria-hidden="true"
                                                className="shrink-0 text-base"
                                            />
                                        )}
                                    </span>
                                </Dropdown.Item>
                            ))}
                        </Dropdown>

                        <Dropdown
                            placement="bottom-end"
                            activeKey={statusFilter}
                            onSelect={(eventKey) => {
                                if (eventKey === 'All') {
                                    setStatusFilter('All')
                                } else if (
                                    statusOptions.includes(
                                        eventKey as ShipmentStatus,
                                    )
                                ) {
                                    setStatusFilter(eventKey as ShipmentStatus)
                                }
                            }}
                            renderTitle={
                                <Button
                                    icon={<PiCircleDashed aria-hidden="true" />}
                                    iconAlignment="start"
                                >
                                    {statusFilter === 'All' ? 'Status' : statusFilter}
                                </Button>
                            }
                        >
                            {(['All', ...statusOptions] as const).map((option) => (
                                <Dropdown.Item
                                    active={statusFilter === option}
                                    eventKey={option}
                                    key={option}
                                >
                                    <span className="flex w-full items-center justify-between">
                                        <span>{option}</span>
                                        {statusFilter === option && (
                                            <PiCheck
                                                aria-hidden="true"
                                                className="shrink-0 text-base"
                                            />
                                        )}
                                    </span>
                                </Dropdown.Item>
                            ))}
                        </Dropdown>
                    </div>
                </div>
            </div>

            <div className="min-w-0 overflow-x-auto">
                <Table
                    className="table-fixed border-t border-b"
                    hoverable={false}
                    overflow={false}
                    verticalDivider={{ body: true, head: true }}
                >
                    <Table.THead>
                        <Table.Tr className="h-12">
                            <Table.Th className="w-12 h-12 p-0" scope="col">
                                <div className="flex h-full items-center justify-center px-4">
                                    <Checkbox
                                        aria-label="Select all shipments"
                                        checked={allVisibleSelected}
                                        indeterminate={someVisibleSelected}
                                        onChange={toggleAllVisible}
                                    />
                                </div>
                            </Table.Th>
                            <Table.Th className="h-12 w-40 p-0" scope="col">
                                {renderSortableHeader(
                                    'Shipment ID',
                                    'shipmentId',
                                    PiPackage,
                                )}
                            </Table.Th>
                            <Table.Th className="h-12 w-40 p-0" scope="col">
                                {renderSortableHeader('Origin', 'origin', PiMapPin)}
                            </Table.Th>
                            <Table.Th className="h-12 w-40 p-0" scope="col">
                                {renderSortableHeader(
                                    'Destination',
                                    'destination',
                                    PiMapPin,
                                )}
                            </Table.Th>
                            <Table.Th className="h-12 w-40 p-0" scope="col">
                                {renderSortableHeader('Carrier', 'carrier', PiTruck)}
                            </Table.Th>
                            <Table.Th className="h-12 w-40 p-0" scope="col">
                                {renderSortableHeader('Status', 'status', PiCircleDashed)}
                            </Table.Th>
                            <Table.Th className="h-12 w-40 p-0" scope="col">
                                {renderSortableHeader('ETA', 'eta', PiCalendarBlank)}
                            </Table.Th>
                            <Table.Th className="h-12 w-32 p-0" scope="col">
                                {renderSortableHeader('Weight', 'weight', PiScales)}
                            </Table.Th>
                            <Table.Th className="w-12 h-12 p-0" scope="col">
                                <div className="flex h-full items-center justify-center px-4">
                                    <span className="sr-only">Actions</span>
                                </div>
                            </Table.Th>
                        </Table.Tr>
                    </Table.THead>
                    <Table.TBody>
                        {pagedRows.map((row) => (
                            <Table.Tr className="h-12" key={row.id}>
                                <Table.Td className="w-12 h-12 p-0">
                                    <div className="flex h-full items-center justify-center px-4">
                                        <Checkbox
                                            aria-label={`Select ${row.shipmentId}`}
                                            checked={selectedIds.has(row.id)}
                                            onChange={(checked) =>
                                                toggleRowSelection(row.id, checked)
                                            }
                                        />
                                    </div>
                                </Table.Td>
                                {renderTextCell(row, 'shipmentId')}
                                {renderTextCell(row, 'origin')}
                                {renderTextCell(row, 'destination')}
                                {renderCarrierCell(row)}
                                {renderStatusCell(row)}
                                {renderDateCell(row)}
                                {renderTextCell(row, 'weight')}
                                <Table.Td className="w-12 h-12 p-0">
                                    <div className="flex h-full items-center justify-center px-4">
                                        <Dropdown
                                            placement="bottom-end"
                                            renderTitle={
                                                <Button
                                                    aria-label={`More actions for ${row.shipmentId}`}
                                                    icon={
                                                        <PiDotsThreeVertical aria-hidden="true" />
                                                    }
                                                    size="sm"
                                                    variant="ghost"
                                                />
                                            }
                                        >
                                            <Dropdown.Item
                                                className="flex items-center gap-2"
                                                eventKey={`view-${row.id}`}
                                            >
                                                <PiEye
                                                    aria-hidden="true"
                                                    className="text-base text-muted-foreground"
                                                />
                                                <span>View shipment</span>
                                            </Dropdown.Item>
                                            <Dropdown.Item
                                                className="flex items-center gap-2"
                                                eventKey={`duplicate-${row.id}`}
                                            >
                                                <PiCopy
                                                    aria-hidden="true"
                                                    className="text-base text-muted-foreground"
                                                />
                                                <span>Duplicate shipment</span>
                                            </Dropdown.Item>
                                            <Dropdown.Item
                                                className="flex items-center gap-2"
                                                eventKey={`archive-${row.id}`}
                                            >
                                                <PiArchive
                                                    aria-hidden="true"
                                                    className="text-base text-muted-foreground"
                                                />
                                                <span>Archive shipment</span>
                                            </Dropdown.Item>
                                        </Dropdown>
                                    </div>
                                </Table.Td>
                            </Table.Tr>
                        ))}
                    </Table.TBody>
                </Table>
            </div>

            <footer className="flex flex-wrap items-center justify-between gap-4 border-t px-4 py-3">
                <p className="text-sm text-muted-foreground">
                    Showing{' '}
                    <span className="text-foreground">
                        {pagedRows.length === 0
                            ? '0'
                            : `${(pageIndex - 1) * pageSize + 1}–${Math.min(pageIndex * pageSize, visibleRows.length)}`}
                    </span>{' '}
                    of {visibleRows.length} shipments
                </p>
                <Pagination
                    currentPage={pageIndex}
                    onChange={setPageIndex}
                    pageSize={pageSize}
                    total={visibleRows.length}
                />
            </footer>
        </section>
    )
}

Data Grid 11

Preview
npx nateui@latest add DataGridOrderExpandable
Dark
import { Fragment, useEffect, useMemo, useRef, useState } from 'react'
import Avatar from '@/components/ui/Avatar'
import Button from '@/components/ui/Button'
import Calendar from '@/components/ui/Calendar'
import Checkbox from '@/components/ui/Checkbox'
import Collapsible from '@/components/ui/Collapsible'
import Dropdown from '@/components/ui/Dropdown'
import Input from '@/components/ui/Input'
import Popover from '@/components/ui/Popover'
import Table from '@/components/ui/Table'
import Tag from '@/components/ui/Tag'
import DataTable from '@/components/composites/DataTable'
import { colors } from '@/configs/colors.config'
import type { ComponentType, ReactNode } from 'react'
import {
    PiBuildings,
    PiCalendarBlank,
    PiCaretDown,
    PiCaretRight,
    PiCheck,
    PiCircleDashed,
    PiCreditCard,
    PiCurrencyCircleDollar,
    PiDotsThreeVertical,
    PiDownloadSimple,
    PiEnvelope,
    PiExport,
    PiMagnifyingGlass,
    PiMapPin,
    PiPackage,
    PiPhone,
    PiPlus,
    PiPrinter,
    PiTruck,
    PiUser,
} from 'react-icons/pi'

const assetBase = 'https://statics.nateui.com/img'
const columnCount = 7
const pageSize = 6

type Client =
    | 'Northstar Labs'
    | 'Pine & Co.'
    | 'Terra Goods'
    | 'Harbor Market'

const statusOptions = [
    'Ready to ship',
    'Processing',
    'Delivered',
    'On hold',
] as const
type OrderStatus = (typeof statusOptions)[number]
type StatusFilter = OrderStatus | 'All statuses'
type DateRange = [Date | null, Date | null]

type PaymentBrand =
    | 'visa'
    | 'master'
    | 'amex'
    | 'paypal'
    | 'applePay'
    | 'googlePay'
    | 'unionPay'
    | 'creditCard'

type OrderItem = {
    id: string
    name: string
    code: string
    quantity: number
    price: number
    image: string
}

type OrderRow = {
    id: string
    orderNo: string
    date: Date
    customerName: string
    customerAvatar: string
    client: Client
    address: string
    status: OrderStatus
    email: string
    phone: string
    paymentBrand: PaymentBrand
    paymentLabel: string
    shippingMethod: string
    shippingAddress: string
    billingAddress: string
    deliveryFee: number
    items: OrderItem[]
}

type PanelIcon = ComponentType<{
    className?: string
    'aria-hidden'?: boolean
}>

const statusTagClasses: Record<OrderStatus, string> = {
    'Ready to ship': `${colors.blue.tuneBg} ${colors.blue.tuneFg}`,
    Processing: `${colors.orange.tuneBg} ${colors.orange.tuneFg}`,
    Delivered: `${colors.emerald.tuneBg} ${colors.emerald.tuneFg}`,
    'On hold': `${colors.red.tuneBg} ${colors.red.tuneFg}`,
}

const statusDotClasses: Record<OrderStatus, string> = {
    'Ready to ship': colors.blue.bg,
    Processing: colors.orange.bg,
    Delivered: colors.emerald.bg,
    'On hold': colors.red.bg,
}

const currentDate = new Date()
currentDate.setHours(12, 0, 0, 0)

const createOrderDate = (daysAgo: number) => {
    const date = new Date(currentDate)
    date.setDate(date.getDate() - daysAgo)
    return date
}

const formatOrderDate = (date: Date) =>
    new Intl.DateTimeFormat('en-GB', {
        day: 'numeric',
        month: 'short',
        year: 'numeric',
    }).format(date)

const formatDateRange = (range: DateRange) => {
    if (!range[0]) return 'Date range'
    if (!range[1]) return 'Select end date'

    return `${formatOrderDate(range[0])} – ${formatOrderDate(range[1])}`
}

const dateValue = (date: Date) =>
    new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()

const createItem = (
    id: string,
    name: string,
    code: string,
    quantity: number,
    price: number,
    imageNumber: number,
): OrderItem => ({
    id,
    name,
    code,
    quantity,
    price,
    image: `${assetBase}/thumbs/products/product-${imageNumber}.jpg`,
})

const orderRows: OrderRow[] = [
    {
        id: 'order-47281',
        orderNo: 'ORD-47281',
        date: createOrderDate(5),
        customerName: 'Avery Morgan',
        customerAvatar: `${assetBase}/avatars/thumb-1.jpg`,
        client: 'Northstar Labs',
        address: '24 Harbor View, Helsinki',
        status: 'Ready to ship',
        email: 'avery.morgan@example.com',
        phone: '+358 40 218 6401',
        paymentBrand: 'visa',
        paymentLabel: 'Visa •••• 4821',
        shippingMethod: 'Priority courier',
        shippingAddress: '24 Harbor View\nHelsinki 00150\nFinland',
        billingAddress: 'Northstar Labs\n8 Meridian Road\nCopenhagen 2100\nDenmark',
        deliveryFee: 18,
        items: [
            createItem('item-47281-a', 'Slate notebook', 'SLT-204', 2, 24.5, 1),
            createItem('item-47281-b', 'Field cable', 'FCL-118', 1, 16.75, 2),
        ],
    },
    {
        id: 'order-47276',
        orderNo: 'ORD-47276',
        date: createOrderDate(6),
        customerName: 'Jordan Lee',
        customerAvatar: `${assetBase}/avatars/thumb-2.jpg`,
        client: 'Pine & Co.',
        address: '9 Birch Lane, Riga',
        status: 'Processing',
        email: 'jordan.lee@example.com',
        phone: '+371 26 402 187',
        paymentBrand: 'master',
        paymentLabel: 'Mastercard •••• 7610',
        shippingMethod: 'Standard freight',
        shippingAddress: '9 Birch Lane\nRiga LV-1010\nLatvia',
        billingAddress: 'Pine & Co.\n15 Market Street\nTallinn 10111\nEstonia',
        deliveryFee: 12,
        items: [
            createItem('item-47276-a', 'Cedar stand', 'CDR-390', 1, 38, 3),
            createItem('item-47276-b', 'Signal case', 'SGN-442', 2, 17.25, 4),
        ],
    },
    {
        id: 'order-47269',
        orderNo: 'ORD-47269',
        date: createOrderDate(7),
        customerName: 'Taylor Smith',
        customerAvatar: `${assetBase}/avatars/thumb-3.jpg`,
        client: 'Terra Goods',
        address: '72 Rowan Street, Gothenburg',
        status: 'Ready to ship',
        email: 'taylor.smith@example.com',
        phone: '+46 70 318 5502',
        paymentBrand: 'amex',
        paymentLabel: 'Amex •••• 1056',
        shippingMethod: 'Economy parcel',
        shippingAddress: '72 Rowan Street\nGothenburg 413 04\nSweden',
        billingAddress: 'Terra Goods\n31 Fjord Avenue\nOslo 0250\nNorway',
        deliveryFee: 9,
        items: [
            createItem('item-47269-a', 'Studio lamp', 'STL-812', 1, 64.9, 5),
            createItem('item-47269-b', 'Harbor light', 'HBL-246', 1, 42.4, 6),
        ],
    },
    {
        id: 'order-47261',
        orderNo: 'ORD-47261',
        date: createOrderDate(8),
        customerName: 'Morgan Chen',
        customerAvatar: `${assetBase}/avatars/thumb-4.jpg`,
        client: 'Harbor Market',
        address: '18 Lucerne Way, Lyon',
        status: 'Delivered',
        email: 'morgan.chen@example.com',
        phone: '+33 6 42 19 8031',
        paymentBrand: 'paypal',
        paymentLabel: 'PayPal account',
        shippingMethod: 'Standard courier',
        shippingAddress: '18 Lucerne Way\nLyon 69002\nFrance',
        billingAddress: 'Harbor Market\n5 Quay Road\nZurich 8005\nSwitzerland',
        deliveryFee: 15,
        items: [
            createItem('item-47261-a', 'Canvas organiser', 'CVO-165', 3, 19.8, 7),
            createItem('item-47261-b', 'Maple tray', 'MPT-511', 1, 44.25, 8),
        ],
    },
    {
        id: 'order-47254',
        orderNo: 'ORD-47254',
        date: createOrderDate(9),
        customerName: 'Riley Patel',
        customerAvatar: `${assetBase}/avatars/thumb-5.jpg`,
        client: 'Northstar Labs',
        address: '6 Linden Court, Bratislava',
        status: 'Ready to ship',
        email: 'riley.patel@example.com',
        phone: '+421 905 114 683',
        paymentBrand: 'applePay',
        paymentLabel: 'Apple Pay •••• 2844',
        shippingMethod: 'Priority courier',
        shippingAddress: '6 Linden Court\nBratislava 811 01\nSlovakia',
        billingAddress: 'Northstar Labs\n8 Meridian Road\nVienna 1030\nAustria',
        deliveryFee: 11,
        items: [
            createItem('item-47254-a', 'Quartz timer', 'QTM-702', 2, 28.75, 9),
            createItem('item-47254-b', 'Desk caddy', 'DSC-330', 1, 31.5, 10),
        ],
    },
    {
        id: 'order-47249',
        orderNo: 'ORD-47249',
        date: createOrderDate(10),
        customerName: 'Casey Kim',
        customerAvatar: `${assetBase}/avatars/thumb-6.jpg`,
        client: 'Pine & Co.',
        address: '42 Namu Road, Busan',
        status: 'Processing',
        email: 'casey.kim@example.com',
        phone: '+82 10 4281 6602',
        paymentBrand: 'googlePay',
        paymentLabel: 'Google Pay •••• 6098',
        shippingMethod: 'Standard freight',
        shippingAddress: '42 Namu Road\nBusan 48058\nSouth Korea',
        billingAddress: 'Pine & Co.\n19 Han River Lane\nSeoul 04524\nSouth Korea',
        deliveryFee: 13,
        items: [
            createItem('item-47249-a', 'Orbit pouch', 'OBP-428', 1, 27.4, 11),
            createItem('item-47249-b', 'Studio lamp', 'STL-812', 2, 64.9, 12),
        ],
    },
    {
        id: 'order-47243',
        orderNo: 'ORD-47243',
        date: createOrderDate(11),
        customerName: 'Drew Wilson',
        customerAvatar: `${assetBase}/avatars/thumb-7.jpg`,
        client: 'Terra Goods',
        address: '11 Cedar Walk, Porto',
        status: 'On hold',
        email: 'drew.wilson@example.com',
        phone: '+351 912 644 083',
        paymentBrand: 'creditCard',
        paymentLabel: 'Card •••• 3375',
        shippingMethod: 'Economy parcel',
        shippingAddress: '11 Cedar Walk\nPorto 4050-321\nPortugal',
        billingAddress: 'Terra Goods\n4 Atlas Road\nLisbon 1100-017\nPortugal',
        deliveryFee: 8,
        items: [
            createItem('item-47243-a', 'Field cable', 'FCL-118', 4, 16.75, 13),
            createItem('item-47243-b', 'Slate notebook', 'SLT-204', 2, 24.5, 14),
        ],
    },
    {
        id: 'order-47238',
        orderNo: 'ORD-47238',
        date: createOrderDate(12),
        customerName: 'Alex Rivera',
        customerAvatar: `${assetBase}/avatars/thumb-8.jpg`,
        client: 'Harbor Market',
        address: '55 Orange Street, Valencia',
        status: 'Ready to ship',
        email: 'alex.rivera@example.com',
        phone: '+34 644 207 519',
        paymentBrand: 'visa',
        paymentLabel: 'Visa •••• 9016',
        shippingMethod: 'Priority courier',
        shippingAddress: '55 Orange Street\nValencia 46002\nSpain',
        billingAddress: 'Harbor Market\n17 Prado Lane\nMadrid 28014\nSpain',
        deliveryFee: 14,
        items: [
            createItem('item-47238-a', 'Cedar stand', 'CDR-390', 2, 38, 15),
            createItem('item-47238-b', 'Maple tray', 'MPT-511', 1, 44.25, 16),
        ],
    },
    {
        id: 'order-47231',
        orderNo: 'ORD-47231',
        date: createOrderDate(13),
        customerName: 'Jamie Park',
        customerAvatar: `${assetBase}/avatars/thumb-9.jpg`,
        client: 'Northstar Labs',
        address: '3 Kumo Avenue, Nagoya',
        status: 'Processing',
        email: 'jamie.park@example.com',
        phone: '+81 90 2714 6842',
        paymentBrand: 'master',
        paymentLabel: 'Mastercard •••• 2239',
        shippingMethod: 'Standard freight',
        shippingAddress: '3 Kumo Avenue\nNagoya 460-0008\nJapan',
        billingAddress: 'Northstar Labs\n29 Sakura Lane\nTokyo 100-0001\nJapan',
        deliveryFee: 17,
        items: [
            createItem('item-47231-a', 'Signal case', 'SGN-442', 1, 17.25, 17),
            createItem('item-47231-b', 'Canvas organiser', 'CVO-165', 2, 19.8, 18),
        ],
    },
    {
        id: 'order-47224',
        orderNo: 'ORD-47224',
        date: createOrderDate(14),
        customerName: 'Cameron Brooks',
        customerAvatar: `${assetBase}/avatars/thumb-10.jpg`,
        client: 'Pine & Co.',
        address: '88 Rowan Quay, Cork',
        status: 'Delivered',
        email: 'cameron.brooks@example.com',
        phone: '+353 85 214 9027',
        paymentBrand: 'paypal',
        paymentLabel: 'PayPal account',
        shippingMethod: 'Standard courier',
        shippingAddress: '88 Rowan Quay\nCork T12 K6R2\nIreland',
        billingAddress: 'Pine & Co.\n6 River Road\nDublin D02 YX44\nIreland',
        deliveryFee: 10,
        items: [
            createItem('item-47224-a', 'Harbor light', 'HBL-246', 2, 42.4, 19),
            createItem('item-47224-b', 'Desk caddy', 'DSC-330', 1, 31.5, 20),
        ],
    },
    {
        id: 'order-47218',
        orderNo: 'ORD-47218',
        date: createOrderDate(15),
        customerName: 'Quinn Davis',
        customerAvatar: `${assetBase}/avatars/thumb-11.jpg`,
        client: 'Terra Goods',
        address: '16 Fern Crescent, Glasgow',
        status: 'Ready to ship',
        email: 'quinn.davis@example.com',
        phone: '+44 7700 184 621',
        paymentBrand: 'amex',
        paymentLabel: 'Amex •••• 7781',
        shippingMethod: 'Priority courier',
        shippingAddress: '16 Fern Crescent\nGlasgow G2 4AA\nUnited Kingdom',
        billingAddress: 'Terra Goods\n7 Castle Street\nEdinburgh EH2 3AT\nUnited Kingdom',
        deliveryFee: 16,
        items: [
            createItem('item-47218-a', 'Quartz timer', 'QTM-702', 1, 28.75, 21),
            createItem('item-47218-b', 'Orbit pouch', 'OBP-428', 3, 27.4, 22),
        ],
    },
    {
        id: 'order-47212',
        orderNo: 'ORD-47212',
        date: createOrderDate(16),
        customerName: 'Sam Taylor',
        customerAvatar: `${assetBase}/avatars/thumb-12.jpg`,
        client: 'Harbor Market',
        address: '41 Elm Street, Providence',
        status: 'On hold',
        email: 'sam.taylor@example.com',
        phone: '+1 401 555 0138',
        paymentBrand: 'googlePay',
        paymentLabel: 'Google Pay •••• 4172',
        shippingMethod: 'Economy parcel',
        shippingAddress: '41 Elm Street\nProvidence, RI 02903\nUnited States',
        billingAddress: 'Harbor Market\n12 Beacon Road\nBoston, MA 02108\nUnited States',
        deliveryFee: 7,
        items: [
            createItem('item-47212-a', 'Maple tray', 'MPT-511', 1, 44.25, 23),
            createItem('item-47212-b', 'Field cable', 'FCL-118', 2, 16.75, 24),
        ],
    },
    {
        id: 'order-47206',
        orderNo: 'ORD-47206',
        date: createOrderDate(17),
        customerName: 'Parker Reed',
        customerAvatar: `${assetBase}/avatars/thumb-13.jpg`,
        client: 'Northstar Labs',
        address: '90 Cedar Road, Ottawa',
        status: 'Ready to ship',
        email: 'parker.reed@example.com',
        phone: '+1 613 555 0194',
        paymentBrand: 'visa',
        paymentLabel: 'Visa •••• 6440',
        shippingMethod: 'Priority courier',
        shippingAddress: '90 Cedar Road\nOttawa, ON K1P 1J1\nCanada',
        billingAddress: 'Northstar Labs\n22 Queen Street\nToronto, ON M5H 2N2\nCanada',
        deliveryFee: 19,
        items: [
            createItem('item-47206-a', 'Studio lamp', 'STL-812', 1, 64.9, 25),
            createItem('item-47206-b', 'Slate notebook', 'SLT-204', 3, 24.5, 26),
        ],
    },
    {
        id: 'order-47199',
        orderNo: 'ORD-47199',
        date: createOrderDate(18),
        customerName: 'Elliot Stone',
        customerAvatar: `${assetBase}/avatars/thumb-14.jpg`,
        client: 'Pine & Co.',
        address: '7 Wattle Drive, Geelong',
        status: 'Delivered',
        email: 'elliot.stone@example.com',
        phone: '+61 412 580 774',
        paymentBrand: 'creditCard',
        paymentLabel: 'Card •••• 5301',
        shippingMethod: 'Standard freight',
        shippingAddress: '7 Wattle Drive\nGeelong VIC 3220\nAustralia',
        billingAddress: 'Pine & Co.\n14 Collins Street\nMelbourne VIC 3000\nAustralia',
        deliveryFee: 21,
        items: [
            createItem('item-47199-a', 'Cedar stand', 'CDR-390', 1, 38, 27),
            createItem('item-47199-b', 'Harbor light', 'HBL-246', 1, 42.4, 28),
        ],
    },
    {
        id: 'order-47192',
        orderNo: 'ORD-47192',
        date: createOrderDate(19),
        customerName: 'Rowan Bell',
        customerAvatar: `${assetBase}/avatars/thumb-15.jpg`,
        client: 'Terra Goods',
        address: '33 Kauri Lane, Hamilton',
        status: 'Processing',
        email: 'rowan.bell@example.com',
        phone: '+64 21 804 311',
        paymentBrand: 'master',
        paymentLabel: 'Mastercard •••• 3094',
        shippingMethod: 'Economy parcel',
        shippingAddress: '33 Kauri Lane\nHamilton 3204\nNew Zealand',
        billingAddress: 'Terra Goods\n52 Queen Street\nAuckland 1010\nNew Zealand',
        deliveryFee: 8,
        items: [
            createItem('item-47192-a', 'Signal case', 'SGN-442', 2, 17.25, 29),
            createItem('item-47192-b', 'Canvas organiser', 'CVO-165', 1, 19.8, 30),
        ],
    },
    {
        id: 'order-47185',
        orderNo: 'ORD-47185',
        date: createOrderDate(20),
        customerName: 'Skyler Cole',
        customerAvatar: `${assetBase}/avatars/thumb-16.jpg`,
        client: 'Harbor Market',
        address: '20 Protea Road, Stellenbosch',
        status: 'Ready to ship',
        email: 'skyler.cole@example.com',
        phone: '+27 72 481 6902',
        paymentBrand: 'paypal',
        paymentLabel: 'PayPal account',
        shippingMethod: 'Priority courier',
        shippingAddress: '20 Protea Road\nStellenbosch 7600\nSouth Africa',
        billingAddress: 'Harbor Market\n4 Signal Hill Road\nCape Town 8001\nSouth Africa',
        deliveryFee: 15,
        items: [
            createItem('item-47185-a', 'Desk caddy', 'DSC-330', 2, 31.5, 31),
            createItem('item-47185-b', 'Quartz timer', 'QTM-702', 1, 28.75, 32),
        ],
    },
    {
        id: 'order-47178',
        orderNo: 'ORD-47178',
        date: createOrderDate(21),
        customerName: 'Emerson Hart',
        customerAvatar: `${assetBase}/avatars/thumb-17.jpg`,
        client: 'Northstar Labs',
        address: '63 Lake Avenue, Milwaukee',
        status: 'On hold',
        email: 'emerson.hart@example.com',
        phone: '+1 414 555 0182',
        paymentBrand: 'amex',
        paymentLabel: 'Amex •••• 8621',
        shippingMethod: 'Standard courier',
        shippingAddress: '63 Lake Avenue\nMilwaukee, WI 53202\nUnited States',
        billingAddress: 'Northstar Labs\n120 State Street\nChicago, IL 60601\nUnited States',
        deliveryFee: 10,
        items: [
            createItem('item-47178-a', 'Orbit pouch', 'OBP-428', 1, 27.4, 33),
            createItem('item-47178-b', 'Maple tray', 'MPT-511', 2, 44.25, 34),
        ],
    },
    {
        id: 'order-47170',
        orderNo: 'ORD-47170',
        date: createOrderDate(22),
        customerName: 'Finley Gray',
        customerAvatar: `${assetBase}/avatars/thumb-18.jpg`,
        client: 'Pine & Co.',
        address: '5 Willow Passage, Reims',
        status: 'Delivered',
        email: 'finley.gray@example.com',
        phone: '+33 6 18 42 7609',
        paymentBrand: 'unionPay',
        paymentLabel: 'UnionPay •••• 1902',
        shippingMethod: 'Economy parcel',
        shippingAddress: '5 Willow Passage\nReims 51100\nFrance',
        billingAddress: 'Pine & Co.\n44 Rue du Bac\nParis 75007\nFrance',
        deliveryFee: 9,
        items: [
            createItem('item-47170-a', 'Field cable', 'FCL-118', 1, 16.75, 35),
            createItem('item-47170-b', 'Studio lamp', 'STL-812', 1, 64.9, 36),
        ],
    },
]

const formatCurrency = (value: number) =>
    new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency: 'USD',
    }).format(value)

const calculateOrderSummary = (order: OrderRow) => {
    const subtotal = Math.round(
        order.items.reduce((sum, item) => sum + item.price * item.quantity, 0) * 100,
    ) / 100
    const tax = Math.round(subtotal * 0.08 * 100) / 100
    const total = Math.round((subtotal + tax + order.deliveryFee) * 100) / 100

    return { subtotal, tax, total }
}

const statusTag = (status: OrderStatus) => (
    <Tag
        className={`border-0 ${statusTagClasses[status]}`}
        prefix
        prefixClass={statusDotClasses[status]}
    >
        {status}
    </Tag>
)

const PanelHeading = ({
    Icon,
    children,
}: {
    Icon: PanelIcon
    children: ReactNode
}) => (
    <h5 className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-foreground">
        <Icon aria-hidden={true} className="text-base text-muted-foreground" />
        {children}
    </h5>
)

const DetailField = ({
    Icon,
    label,
    value,
}: {
    Icon: PanelIcon
    label: string
    value: ReactNode
}) => (
    <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4">
        <div className="flex shrink-0 items-center gap-2 text-muted-foreground">
            <Icon aria-hidden={true} className="text-base" />
            <span className="font-medium">{label}</span>
        </div>
        <span className="min-w-0 break-words font-medium sm:ml-0 ml-4">
            {value}
        </span>
    </div>
)

const OrderDetailsPanel = ({ order }: { order: OrderRow }) => {
    const summary = calculateOrderSummary(order)

    return (
        <div className="bg-card p-4">
            <div className="flex items-center justify-end gap-2">
                <Button
                    aria-label={`Download details for ${order.orderNo}`}
                    icon={<PiDownloadSimple aria-hidden="true" />}
                    shape="circle"
                    size="sm"
                    variant="ghost"
                />
                <Button
                    aria-label={`Print details for ${order.orderNo}`}
                    icon={<PiPrinter aria-hidden="true" />}
                    shape="circle"
                    size="sm"
                    variant="ghost"
                />
            </div>

            <div className="mt-4 grid grid-cols-1 gap-8 md:grid-cols-2">
                <section className="space-y-4" aria-labelledby={`${order.id}-contact`}>
                    <PanelHeading Icon={PiUser}>
                        <span id={`${order.id}-contact`}>Contact Information</span>
                    </PanelHeading>
                    <div className="space-y-4">
                        <DetailField
                            Icon={PiBuildings}
                            label="Client"
                            value={order.client}
                        />
                        <DetailField
                            Icon={PiMapPin}
                            label="Address"
                            value={order.address}
                        />
                        <DetailField
                            Icon={PiEnvelope}
                            label="Email"
                            value={order.email}
                        />
                        <DetailField
                            Icon={PiPhone}
                            label="Phone"
                            value={order.phone}
                        />
                    </div>
                </section>

                <section className="space-y-4" aria-labelledby={`${order.id}-payment-shipping`}>
                    <PanelHeading Icon={PiCreditCard}>
                        <span id={`${order.id}-payment-shipping`}>
                            Payment &amp; Shipping
                        </span>
                    </PanelHeading>
                    <div className="space-y-4">
                        <DetailField
                            Icon={PiCurrencyCircleDollar}
                            label="Payment method"
                            value={
                                <span className="flex items-center gap-2">
                                    <img
                                        alt=""
                                        className="h-6 w-auto object-contain"
                                        src={`${assetBase}/thumbs/payment/${order.paymentBrand}.png`}
                                    />
                                    {order.paymentLabel}
                                </span>
                            }
                        />
                        <DetailField
                            Icon={PiTruck}
                            label="Shipping method"
                            value={order.shippingMethod}
                        />
                    </div>
                </section>
            </div>

            <div className="mt-8 grid grid-cols-1 gap-8 md:grid-cols-2">
                <section className="space-y-4" aria-labelledby={`${order.id}-shipping-address`}>
                    <PanelHeading Icon={PiMapPin}>
                        <span id={`${order.id}-shipping-address`}>
                            Shipping Address
                        </span>
                    </PanelHeading>
                    <p className="whitespace-pre-line font-medium">
                        {order.shippingAddress}
                    </p>
                </section>

                <section className="space-y-4" aria-labelledby={`${order.id}-billing-address`}>
                    <PanelHeading Icon={PiMapPin}>
                        <span id={`${order.id}-billing-address`}>
                            Billing Address
                        </span>
                    </PanelHeading>
                    <p className="whitespace-pre-line font-medium">
                        {order.billingAddress}
                    </p>
                </section>
            </div>

            <section className="mt-8 space-y-4" aria-labelledby={`${order.id}-items`}>
                <PanelHeading Icon={PiPackage}>
                    <span id={`${order.id}-items`}>
                        Order Items ({order.items.length})
                    </span>
                </PanelHeading>

                <div className="hidden min-w-0 overflow-x-auto md:block">
                    <Table className="w-full min-w-full" hoverable overflow={false}>
                        <Table.THead>
                            <Table.Tr>
                                <Table.Th>Product</Table.Th>
                                <Table.Th>Code</Table.Th>
                                <Table.Th className="text-center">Quantity</Table.Th>
                                <Table.Th className="text-right">Price</Table.Th>
                                <Table.Th className="text-right">Total</Table.Th>
                            </Table.Tr>
                        </Table.THead>
                        <Table.TBody>
                            {order.items.map((item) => (
                                <Table.Tr key={item.id}>
                                    <Table.Td>
                                        <div className="flex min-w-0 items-center gap-4">
                                            <img
                                                alt={item.name}
                                                className="h-10 w-10 shrink-0 rounded-control-sm object-cover"
                                                src={item.image}
                                            />
                                            <span className="min-w-0 truncate font-medium">
                                                {item.name}
                                            </span>
                                        </div>
                                    </Table.Td>
                                    <Table.Td>{item.code}</Table.Td>
                                    <Table.Td className="text-center">
                                        <span className="font-medium">{item.quantity}</span>
                                    </Table.Td>
                                    <Table.Td className="text-right">
                                        <span className="font-medium">
                                            {formatCurrency(item.price)}
                                        </span>
                                    </Table.Td>
                                    <Table.Td className="text-right">
                                        <span className="font-semibold">
                                            {formatCurrency(item.price * item.quantity)}
                                        </span>
                                    </Table.Td>
                                </Table.Tr>
                            ))}
                        </Table.TBody>
                    </Table>
                </div>

                <div className="divide-y divide-border overflow-hidden rounded-card border md:hidden">
                    {order.items.map((item) => (
                        <div className="flex gap-4 p-4" key={item.id}>
                            <img
                                alt={item.name}
                                className="h-10 w-10 shrink-0 rounded-control-sm object-cover"
                                src={item.image}
                            />
                            <div className="min-w-0 flex-1">
                                <div className="truncate font-medium">{item.name}</div>
                                <div className="text-xs text-muted-foreground">
                                    {item.code}
                                </div>
                                <div className="mt-2 flex items-center justify-between gap-4">
                                    <span className="text-sm">
                                        {formatCurrency(item.price)} × {item.quantity}
                                    </span>
                                    <span className="font-semibold">
                                        {formatCurrency(item.price * item.quantity)}
                                    </span>
                                </div>
                            </div>
                        </div>
                    ))}
                </div>
            </section>

            <section className="mt-8 space-y-4" aria-labelledby={`${order.id}-summary`}>
                <PanelHeading Icon={PiCurrencyCircleDollar}>
                    <span id={`${order.id}-summary`}>Payment Summary</span>
                </PanelHeading>
                <div className="grid gap-2 rounded-card bg-muted p-4">
                    <div className="flex items-center justify-between gap-4">
                        <span className="font-medium">Subtotal</span>
                        <span className="font-medium">{formatCurrency(summary.subtotal)}</span>
                    </div>
                    <div className="flex items-center justify-between gap-4">
                        <span className="font-medium">Tax</span>
                        <span className="font-medium">{formatCurrency(summary.tax)}</span>
                    </div>
                    <div className="flex items-center justify-between gap-4">
                        <span className="font-medium">Delivery fees</span>
                        <span className="font-medium">{formatCurrency(order.deliveryFee)}</span>
                    </div>
                    <div className="border-t pt-4">
                        <div className="flex items-center justify-between gap-4">
                            <span className="text-base font-semibold">Total</span>
                            <span className="text-base font-semibold">
                                {formatCurrency(summary.total)}
                            </span>
                        </div>
                    </div>
                </div>
            </section>
        </div>
    )
}

export default function DataGridOrderExpandable() {
    const [searchQuery, setSearchQuery] = useState('')
    const [dateRange, setDateRange] = useState<DateRange>([null, null])
    const [dateRangeOpen, setDateRangeOpen] = useState(false)
    const [statusFilter, setStatusFilter] =
        useState<StatusFilter>('Ready to ship')
    const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
    const [expandedIds, setExpandedIds] = useState<Set<string>>(
        () => new Set([orderRows[0].id]),
    )
    const [pageIndex, setPageIndex] = useState(1)
    const [scrollerWidth, setScrollerWidth] = useState<number | null>(null)
    const scrollerRef = useRef<HTMLDivElement>(null)

    const filteredRows = useMemo(() => {
        const normalizedQuery = searchQuery.trim().toLowerCase()

        return orderRows.filter((order) => {
            const matchesSearch =
                normalizedQuery.length === 0 ||
                [
                    order.orderNo,
                    order.customerName,
                    order.client,
                    order.address,
                ].some((value) => value.toLowerCase().includes(normalizedQuery))
            const matchesDate =
                !dateRange[0] ||
                !dateRange[1] ||
                (dateValue(order.date) >= dateValue(dateRange[0]) &&
                    dateValue(order.date) <= dateValue(dateRange[1]))
            const matchesStatus =
                statusFilter === 'All statuses' || order.status === statusFilter

            return matchesSearch && matchesDate && matchesStatus
        })
    }, [dateRange, searchQuery, statusFilter])

    const availableOrderDates = useMemo(() => {
        const normalizedQuery = searchQuery.trim().toLowerCase()

        return new Set(
            orderRows
                .filter((order) => {
                    const matchesSearch =
                        normalizedQuery.length === 0 ||
                        [
                            order.orderNo,
                            order.customerName,
                            order.client,
                            order.address,
                        ].some((value) =>
                            value.toLowerCase().includes(normalizedQuery),
                        )
                    const matchesStatus =
                        statusFilter === 'All statuses' ||
                        order.status === statusFilter

                    return matchesSearch && matchesStatus
                })
                .map((order) => dateValue(order.date)),
        )
    }, [searchQuery, statusFilter])

    const pageCount = Math.max(1, Math.ceil(filteredRows.length / pageSize))
    const pagedRows = useMemo(
        () =>
            filteredRows.slice(
                (pageIndex - 1) * pageSize,
                pageIndex * pageSize,
            ),
        [filteredRows, pageIndex],
    )

    useEffect(() => {
        setPageIndex(1)
    }, [dateRange, searchQuery, statusFilter])

    useEffect(() => {
        setPageIndex((current) => Math.min(current, pageCount))
    }, [pageCount])

    useEffect(() => {
        const root = document.documentElement
        const previousScrollbarGutter = root.style.scrollbarGutter

        root.style.scrollbarGutter = 'stable'

        return () => {
            root.style.scrollbarGutter = previousScrollbarGutter
        }
    }, [])

    useEffect(() => {
        const scroller = scrollerRef.current
        if (!scroller) return

        const updateScrollerWidth = () => setScrollerWidth(scroller.clientWidth)
        const observer = new ResizeObserver(updateScrollerWidth)

        updateScrollerWidth()
        observer.observe(scroller)

        return () => observer.disconnect()
    }, [])

    const updateSelectedIds = (rowId: string, checked: boolean) => {
        setSelectedIds((current) => {
            const next = new Set(current)
            if (checked) next.add(rowId)
            else next.delete(rowId)
            return next
        })
    }

    const visibleSelectedCount = pagedRows.filter((row) =>
        selectedIds.has(row.id),
    ).length
    const allVisibleSelected =
        pagedRows.length > 0 && visibleSelectedCount === pagedRows.length
    const someVisibleSelected =
        visibleSelectedCount > 0 && !allVisibleSelected

    const updateAllVisibleSelection = (checked: boolean) => {
        setSelectedIds((current) => {
            const next = new Set(current)
            pagedRows.forEach((row) => {
                if (checked) next.add(row.id)
                else next.delete(row.id)
            })
            return next
        })
    }

    const toggleExpanded = (rowId: string) => {
        setExpandedIds((current) => {
            const next = new Set(current)
            if (next.has(rowId)) next.delete(rowId)
            else next.add(rowId)
            return next
        })
    }

    const applyDateRange = (nextRange: DateRange) => {
        setDateRange(nextRange)
        if (nextRange[0] && nextRange[1]) setDateRangeOpen(false)
    }

    const applyStatusFilter = (value: string) => {
        const nextValue = value as StatusFilter
        setStatusFilter(nextValue)
    }

    const rangeStart = filteredRows.length === 0 ? 0 : (pageIndex - 1) * pageSize + 1
    const rangeEnd = Math.min(pageIndex * pageSize, filteredRows.length)

    return (
        <section
            aria-labelledby="data-grid-order-expandable-title"
            className="w-full min-w-0 overflow-hidden"
        >
            <header className="flex flex-wrap items-center justify-between gap-4 border-b px-4 py-4">
                <div className="min-w-0">
                    <div className="flex min-w-0 items-center gap-2">
                        <h4
                            className="truncate font-semibold"
                            id="data-grid-order-expandable-title"
                        >
                            Orders
                        </h4>
                    </div>
                    <p className="text-muted-foreground">
                        Review order details without leaving the list.
                    </p>
                </div>

                <div className="flex flex-wrap items-center gap-2">
                    <Button icon={<PiExport aria-hidden="true" />}>
                        Export list
                    </Button>
                    <Button
                        icon={<PiPlus aria-hidden="true" />}
                        iconAlignment="end"
                        variant="solid"
                    >
                        New order
                    </Button>
                </div>
            </header>

            <div className="flex flex-wrap items-center justify-between gap-4 px-4 py-4">
                <div className="flex flex-wrap items-center gap-2">
                    <Popover
                        open={dateRangeOpen}
                        onOpenChange={setDateRangeOpen}
                        placement="bottom-start"
                        className="p-0"
                        renderTrigger={
                            <Button
                                icon={<PiCalendarBlank aria-hidden="true" />}
                            >
                                {formatDateRange(dateRange)}
                            </Button>
                        }
                    >
                        <div className="p-4">
                            <Calendar.Range
                                defaultMonth={currentDate}
                                disableDate={(date) =>
                                    !availableOrderDates.has(dateValue(date))
                                }
                                value={dateRange}
                                onChange={applyDateRange}
                            />
                        </div>
                    </Popover>

                    <Dropdown
                        activeKey={statusFilter}
                        onSelect={applyStatusFilter}
                        placement="bottom-start"
                        renderTitle={
                            <Button
                                icon={<PiCaretDown aria-hidden="true" />}
                                iconAlignment="end"
                            >
                                {statusFilter === 'All statuses' ? 'Status' : statusFilter}
                            </Button>
                        }
                    >
                        <Dropdown.Item active={statusFilter === 'All statuses'} eventKey="All statuses">
                            <span className="flex w-full items-center justify-between gap-4">
                                <span>All statuses</span>
                                {statusFilter === 'All statuses' && <PiCheck aria-hidden="true" />}
                            </span>
                        </Dropdown.Item>
                        {statusOptions.map((option) => (
                            <Dropdown.Item
                                active={statusFilter === option}
                                eventKey={option}
                                key={option}
                            >
                                <span className="flex w-full items-center justify-between gap-4">
                                    <span>{option}</span>
                                    {statusFilter === option && <PiCheck aria-hidden="true" />}
                                </span>
                            </Dropdown.Item>
                        ))}
                    </Dropdown>

                </div>

                <Input
                    aria-label="Search orders"
                    className="w-full sm:w-60"
                    onChange={(event) => setSearchQuery(event.target.value)}
                    placeholder="Search orders"
                    prefix={<PiMagnifyingGlass className="text-muted-foreground" />}
                    value={searchQuery}
                />
            </div>

            <DataTable<OrderRow>
                columns={[]}
                data={pagedRows}
                onPaginationChange={setPageIndex}
                pageSizes={[pageSize]}
                pagingData={{
                    total: filteredRows.length,
                    pageIndex,
                    pageSize,
                }}
                overflow={false}
                hoverable={false}
                children={() => (
                    <>
                        <div className="min-w-0 overflow-x-auto" ref={scrollerRef}>
                            <Table
                                className="table-fixed border-t border-b"
                                hoverable={false}
                                overflow={false}
                            >
                                <Table.THead>
                                    <Table.Tr className="h-12">
                                        <Table.Th
                                            className="h-12 w-12 p-0"
                                            scope="col"
                                        >
                                            <div className="flex h-full items-center justify-center px-4">
                                                <Checkbox
                                                    aria-label="Select all visible orders"
                                                    checked={allVisibleSelected}
                                                    indeterminate={someVisibleSelected}
                                                    onChange={updateAllVisibleSelection}
                                                />
                                            </div>
                                        </Table.Th>
                                        <Table.Th
                                            className="h-12 w-12 p-0 normal-case tracking-normal"
                                            scope="col"
                                        >
                                            <div className="relative flex h-full items-center justify-center px-4">
                                                <span className="sr-only">Expand</span>
                                            </div>
                                        </Table.Th>
                                        <Table.Th
                                            className="h-12 w-32 p-0 normal-case tracking-normal"
                                            scope="col"
                                        >
                                            <div className="flex h-full items-center px-4">Order No</div>
                                        </Table.Th>
                                        <Table.Th
                                            className="h-12 w-28 p-0 normal-case tracking-normal"
                                            scope="col"
                                        >
                                            <div className="flex h-full items-center px-4">Date</div>
                                        </Table.Th>
                                        <Table.Th
                                            className="h-12 w-64 p-0 normal-case tracking-normal"
                                            scope="col"
                                        >
                                            <div className="flex h-full items-center px-4">Name</div>
                                        </Table.Th>
                                        <Table.Th
                                            className="h-12 w-36 p-0 normal-case tracking-normal"
                                            scope="col"
                                        >
                                            <div className="flex h-full items-center px-4">Status</div>
                                        </Table.Th>
                                        <Table.Th
                                            className="h-12 w-12 p-0 normal-case tracking-normal"
                                            scope="col"
                                        >
                                            <div className="relative flex h-full items-center justify-center px-4">
                                                <span className="sr-only">Actions</span>
                                            </div>
                                        </Table.Th>
                                    </Table.Tr>
                                </Table.THead>
                                <Table.TBody>
                                    {pagedRows.length === 0 && (
                                        <Table.Tr>
                                            <Table.Td
                                                className="p-4 text-center text-muted-foreground"
                                                colSpan={columnCount}
                                            >
                                                No orders match the current filters.
                                            </Table.Td>
                                        </Table.Tr>
                                    )}
                                    {pagedRows.map((row) => {
                                        const isExpanded = expandedIds.has(row.id)
                                        const panelId = `${row.id}-details`

                                        return (
                                            <Fragment key={row.id}>
                                                <Table.Tr className="h-12">
                                                    <Table.Td className="h-12 w-12 p-0">
                                                        <div className="flex h-full items-center justify-center px-4">
                                                            <Checkbox
                                                                aria-label={`Select ${row.orderNo}`}
                                                                checked={selectedIds.has(row.id)}
                                                                onChange={(checked) =>
                                                                    updateSelectedIds(row.id, checked)
                                                                }
                                                            />
                                                        </div>
                                                    </Table.Td>
                                                    <Table.Td className="h-12 w-12 p-0">
                                                        <button
                                                            aria-controls={panelId}
                                                            aria-expanded={isExpanded}
                                                            aria-label={`${isExpanded ? 'Collapse' : 'Expand'} details for ${row.orderNo}`}
                                                            className="flex h-full w-full items-center justify-center text-muted-foreground hover:text-accent-foreground focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-primary"
                                                            onClick={() => toggleExpanded(row.id)}
                                                        >
                                                            <PiCaretRight
                                                                aria-hidden="true"
                                                                className={`transition-transform duration-150 ${isExpanded ? 'rotate-90' : ''}`}
                                                            />
                                                        </button>
                                                    </Table.Td>
                                                    <Table.Td className="h-12 p-0">
                                                        <div className="flex h-full items-center px-4 font-medium whitespace-nowrap">
                                                            {row.orderNo}
                                                        </div>
                                                    </Table.Td>
                                                    <Table.Td className="h-12 p-0">
                                                        <div className="flex h-full items-center px-4 whitespace-nowrap">
                                                            {formatOrderDate(row.date)}
                                                        </div>
                                                    </Table.Td>
                                                    <Table.Td className="h-12 p-0">
                                                        <div className="flex h-full min-w-0 items-center gap-2 px-4">
                                                            <Avatar
                                                                alt={row.customerName}
                                                                className="shrink-0"
                                                                size={32}
                                                                src={row.customerAvatar}
                                                            />
                                                            <span className="min-w-0 truncate font-medium">
                                                                {row.customerName}
                                                            </span>
                                                        </div>
                                                    </Table.Td>
                                                    <Table.Td className="h-12 p-0">
                                                        <div className="flex h-full items-center px-4">
                                                            {statusTag(row.status)}
                                                        </div>
                                                    </Table.Td>
                                                    <Table.Td className="h-12 w-12 p-0">
                                                        <div className="flex h-full items-center justify-center px-4">
                                                            <Dropdown
                                                                placement="bottom-end"
                                                                renderTitle={
                                                                    <Button
                                                                        aria-label={`More actions for ${row.orderNo}`}
                                                                        icon={<PiDotsThreeVertical aria-hidden="true" />}
                                                                        size="sm"
                                                                        variant="ghost"
                                                                    />
                                                                }
                                                            >
                                                                <Dropdown.Item eventKey={`view-${row.id}`}>
                                                                    View order
                                                                </Dropdown.Item>
                                                                <Dropdown.Item eventKey={`duplicate-${row.id}`}>
                                                                    Duplicate order
                                                                </Dropdown.Item>
                                                                <Dropdown.Item eventKey={`archive-${row.id}`}>
                                                                    Archive order
                                                                </Dropdown.Item>
                                                            </Dropdown>
                                                        </div>
                                                    </Table.Td>
                                                </Table.Tr>
                                                {isExpanded && (
                                                    <Table.Tr>
                                                        <Table.Td className="p-0" colSpan={columnCount}>
                                                            <div
                                                                className="sticky start-0"
                                                                id={panelId}
                                                                style={
                                                                    scrollerWidth === null
                                                                        ? undefined
                                                                        : { width: scrollerWidth }
                                                                }
                                                            >
                                                                <Collapsible open={isExpanded}>
                                                                    <OrderDetailsPanel order={row} />
                                                                </Collapsible>
                                                            </div>
                                                        </Table.Td>
                                                    </Table.Tr>
                                                )}
                                            </Fragment>
                                        )
                                    })}
                                </Table.TBody>
                            </Table>
                        </div>

                        <footer className="flex flex-wrap items-center justify-between gap-4 border-t px-4 py-4">
                            <p aria-live="polite" className="text-sm text-muted-foreground">
                                Showing <span className="text-foreground">{rangeStart}–{rangeEnd}</span> of{' '}
                                {filteredRows.length} orders
                            </p>
                        </footer>
                    </>
                )}
            />
        </section>
    )
}