feat: improve data-table responsiveness and add bulk action support

This commit is contained in:
Yoga Pangestu 2026-04-14 23:05:37 +07:00
parent 54ccda7d80
commit 68e007e305

View File

@ -160,14 +160,39 @@ export function DataTable<TData, TValue>({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-between gap-2"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex flex-1 items-center gap-2"> <div className="flex flex-1 flex-col sm:flex-row items-stretch sm:items-center gap-2">
<Input <Input
placeholder="Cari..." placeholder="Cari..."
value={globalFilter ?? ""} value={globalFilter ?? ""}
onChange={(event) => setGlobalFilter(event.target.value)} onChange={(event) => setGlobalFilter(event.target.value)}
className="max-w-sm" className="max-w-sm"
/> />
</div>
<div className="flex items-center gap-2 ml-auto">
{selectedRows.length > 0 && bulkActions && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" className="gap-2 animate-in fade-in slide-in-from-left-2 transition-all">
Aksi Massal ({selectedRows.length})
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{bulkActions.map((action, i) => (
<DropdownMenuItem
key={i}
onClick={() => action.onClick(selectedRows.map((r) => r.original))}
className={action.variant === "destructive" ? "text-red-600 focus:text-red-600" : ""}
>
{action.icon && <action.icon className="mr-2 h-4 w-4" />}
{action.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
{filters && filters.length > 0 && ( {filters && filters.length > 0 && (
<DropdownMenu> <DropdownMenu>
@ -177,7 +202,7 @@ export function DataTable<TData, TValue>({
Filter Filter
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48"> <DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel>Filter Berdasarkan</DropdownMenuLabel> <DropdownMenuLabel>Filter Berdasarkan</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
{filters.map((filter) => { {filters.map((filter) => {
@ -216,64 +241,41 @@ export function DataTable<TData, TValue>({
</DropdownMenu> </DropdownMenu>
)} )}
{selectedRows.length > 0 && bulkActions && ( <DropdownMenu>
<DropdownMenu> <DropdownMenuTrigger asChild>
<DropdownMenuTrigger asChild> <Button variant="outline" className="gap-2">
<Button variant="secondary" className="gap-2 animate-in fade-in slide-in-from-left-2 transition-all"> <Settings2 className="h-4 w-4" />
Aksi Massal ({selectedRows.length}) Kolom
<ChevronDown className="h-4 w-4" /> </Button>
</Button> </DropdownMenuTrigger>
</DropdownMenuTrigger> <DropdownMenuContent align="end" className="w-48">
<DropdownMenuContent align="start"> <DropdownMenuLabel>Tampilan Kolom</DropdownMenuLabel>
{bulkActions.map((action, i) => ( <DropdownMenuSeparator />
<DropdownMenuItem {table
key={i} .getAllColumns()
onClick={() => action.onClick(selectedRows.map((r) => r.original))} .filter((column) => column.getCanHide())
className={action.variant === "destructive" ? "text-red-600 focus:text-red-600" : ""} .map((column) => {
> return (
{action.icon && <action.icon className="mr-2 h-4 w-4" />} <DropdownMenuCheckboxItem
{action.label} key={column.id}
</DropdownMenuItem> className="capitalize"
))} checked={column.getIsVisible()}
</DropdownMenuContent> onCheckedChange={(value) => column.toggleVisibility(!!value)}
</DropdownMenu> onSelect={(e) => e.preventDefault()}
)} >
{(column.columnDef.meta as any)?.title ||
(typeof column.columnDef.header === "string"
? column.columnDef.header
: String(column.id).replace(/_/g, " "))}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto gap-2">
<Settings2 className="h-4 w-4" />
Kolom
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel>Tampilan Kolom</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
onSelect={(e) => e.preventDefault()}
>
{(column.columnDef.meta as any)?.title ||
(typeof column.columnDef.header === "string"
? column.columnDef.header
: String(column.id).replace(/_/g, " "))}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
<div className="overflow-hidden rounded-md border bg-background"> <div className="overflow-x-auto rounded-md border bg-background">
<Table> <Table>
<TableHeader> <TableHeader>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
@ -333,13 +335,13 @@ export function DataTable<TData, TValue>({
</Table> </Table>
</div> </div>
<div className="flex items-center justify-between py-4"> <div className="flex flex-col sm:flex-row items-center justify-between gap-4 py-4">
<div className="flex-1 text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} dari{" "} {table.getFilteredSelectedRowModel().rows.length} dari{" "}
{table.getFilteredRowModel().rows.length} baris dipilih. {table.getFilteredRowModel().rows.length} baris dipilih.
</div> </div>
<div className="flex items-center space-x-6 lg:space-x-8"> <div className="flex flex-col sm:flex-row items-center gap-4 sm:gap-6 lg:gap-8">
<div className="flex items-center space-x-2"> <div className="flex items-center gap-2">
<p className="text-sm font-medium">Baris per halaman</p> <p className="text-sm font-medium">Baris per halaman</p>
<Select <Select
value={`${table.getState().pagination.pageSize}`} value={`${table.getState().pagination.pageSize}`}