'use client'
import { useState } from 'react'
import { DownloadIcon, FileTextIcon, FileSpreadsheetIcon } from 'lucide-react'
import Papa from 'papaparse'
import * as XLSX from 'xlsx'
import type { ColumnDef, ColumnFiltersState, SortingState, VisibilityState } from '@tanstack/react-table'
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable
} from '@tanstack/react-table'
import { Badge } from '@/registry/new-york/ui/badge'
import { Button } from '@/registry/new-york/ui/button'
import { Checkbox } from '@/registry/new-york/ui/checkbox'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/registry/new-york/ui/dropdown-menu'
import { Input } from '@/registry/new-york/ui/input'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/registry/new-york/ui/table'
import { cn } from '@/registry/new-york/lib/utils'
const data: Payment[] = [
{
id: '1',
name: 'Shang Chain',
amount: 699,
status: 'success',
email: 'shang07@yahoo.com'
},
{
id: '2',
name: 'Kevin Lincoln',
amount: 242,
status: 'success',
email: 'kevinli09@gmail.com'
},
{
id: '3',
name: 'Milton Rose',
amount: 655,
status: 'processing',
email: 'rose96@gmail.com'
},
{
id: '4',
name: 'Silas Ryan',
amount: 874,
status: 'success',
email: 'silas22@gmail.com'
},
{
id: '5',
name: 'Ben Tenison',
amount: 541,
status: 'failed',
email: 'bent@hotmail.com'
},
{
id: '6',
name: 'Alice Cooper',
amount: 321,
status: 'processing',
email: 'alice@email.com'
},
{
id: '7',
name: 'Bob Johnson',
amount: 789,
status: 'success',
email: 'bob.j@company.com'
},
{
id: '8',
name: 'Carol Williams',
amount: 456,
status: 'processing',
email: 'carol.w@domain.org'
}
]
export type Payment = {
id: string
name: string
amount: number
status: 'processing' | 'success' | 'failed'
email: string
}
export const columns: ColumnDef<Payment>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')}
onCheckedChange={value => table.toggleAllPageRowsSelected(!!value)}
aria-label='Select all'
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={value => row.toggleSelected(!!value)}
aria-label='Select row'
/>
),
enableSorting: false,
enableHiding: false
},
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => <div className='font-medium'>{row.getValue('name')}</div>
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const status = row.getValue('status') as string
const styles = {
success:
'bg-green-600/10 text-green-600 focus-visible:ring-green-600/20 dark:bg-green-400/10 dark:text-green-400 dark:focus-visible:ring-green-400/40 [a&]:hover:bg-green-600/5 dark:[a&]:hover:bg-green-400/5',
failed:
'bg-destructive/10 [a&]:hover:bg-destructive/5 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive',
processing:
'bg-amber-600/10 text-amber-600 focus-visible:ring-amber-600/20 dark:bg-amber-400/10 dark:text-amber-400 dark:focus-visible:ring-amber-400/40 [a&]:hover:bg-amber-600/5 dark:[a&]:hover:bg-amber-400/5'
}[status]
return <Badge className={(cn('border-none focus-visible:outline-none'), styles)}>{row.getValue('status')}</Badge>
}
},
{
accessorKey: 'email',
header: 'Email',
cell: ({ row }) => <div className='lowercase'>{row.getValue('email')}</div>
},
{
accessorKey: 'amount',
header: () => <div className='text-right'>Amount</div>,
cell: ({ row }) => {
const amount = parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount)
return <div className='text-right font-medium'>{formatted}</div>
}
}
]
const DataTableWithExportDemo = () => {
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
const [rowSelection, setRowSelection] = useState({})
const [globalFilter, setGlobalFilter] = useState('')
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
onGlobalFilterChange: setGlobalFilter,
globalFilterFn: 'includesString',
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
globalFilter
}
})
const exportToCSV = () => {
const selectedRows = table.getSelectedRowModel().rows
const dataToExport =
selectedRows.length > 0
? selectedRows.map(row => row.original)
: table.getFilteredRowModel().rows.map(row => row.original)
const csv = Papa.unparse(dataToExport, {
header: true
})
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
const url = URL.createObjectURL(blob)
link.setAttribute('href', url)
link.setAttribute('download', `payments-export-${new Date().toISOString().split('T')[0]}.csv`)
link.style.visibility = 'hidden'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
const exportToExcel = () => {
const selectedRows = table.getSelectedRowModel().rows
const dataToExport =
selectedRows.length > 0
? selectedRows.map(row => row.original)
: table.getFilteredRowModel().rows.map(row => row.original)
const worksheet = XLSX.utils.json_to_sheet(dataToExport)
const workbook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workbook, worksheet, 'Payments')
const cols = [{ wch: 10 }, { wch: 20 }, { wch: 15 }, { wch: 25 }, { wch: 15 }]
worksheet['!cols'] = cols
XLSX.writeFile(workbook, `payments-export-${new Date().toISOString().split('T')[0]}.xlsx`)
}
const exportToJSON = () => {
const selectedRows = table.getSelectedRowModel().rows
const dataToExport =
selectedRows.length > 0
? selectedRows.map(row => row.original)
: table.getFilteredRowModel().rows.map(row => row.original)
const json = JSON.stringify(dataToExport, null, 2)
const blob = new Blob([json], { type: 'application/json' })
const link = document.createElement('a')
const url = URL.createObjectURL(blob)
link.setAttribute('href', url)
link.setAttribute('download', `payments-export-${new Date().toISOString().split('T')[0]}.json`)
link.style.visibility = 'hidden'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
return (
<div className='w-full'>
<div className='flex justify-between gap-2 pb-4 max-sm:flex-col sm:items-center'>
<div className='flex items-center space-x-2'>
<Input
placeholder='Search all columns...'
value={globalFilter ?? ''}
onChange={event => setGlobalFilter(String(event.target.value))}
className='max-w-sm'
/>
</div>
<div className='flex items-center space-x-2'>
<div className='text-muted-foreground text-sm'>
{table.getSelectedRowModel().rows.length > 0 && (
<span className='mr-2'>
{table.getSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s) selected
</span>
)}
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' size='sm'>
<DownloadIcon className='mr-2' />
Export
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem onClick={exportToCSV}>
<FileTextIcon className='mr-2 size-4' />
Export as CSV
</DropdownMenuItem>
<DropdownMenuItem onClick={exportToExcel}>
<FileSpreadsheetIcon className='mr-2 size-4' />
Export as Excel
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={exportToJSON}>
<FileTextIcon className='mr-2 size-4' />
Export as JSON
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className='rounded-md border'>
<Table>
<TableHeader>
{table.getHeaderGroups().map(headerGroup => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => (
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
{row.getVisibleCells().map(cell => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className='h-24 text-center'>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<p className='text-muted-foreground mt-4 text-center text-sm'>
Data table with export functionality (CSV, Excel, JSON)
</p>
</div>
)
}
export default DataTableWithExportDemo