← full-stack-fastapi-template  /  frontend/src/components/Common/DataTable.tsx

1
import {
2
  type ColumnDef,
3
  flexRender,
4
  getCoreRowModel,
5
  getPaginationRowModel,
6
  useReactTable,
7
} from "@tanstack/react-table"
8
import {
9
  ChevronLeft,
10
  ChevronRight,
11
  ChevronsLeft,
12
  ChevronsRight,
13
} from "lucide-react"
14
15
import { Button } from "@/components/ui/button"
16
import {
17
  Select,
18
  SelectContent,
19
  SelectItem,
20
  SelectTrigger,
21
  SelectValue,
22
} from "@/components/ui/select"
23
import {
24
  Table,
25
  TableBody,
26
  TableCell,
27
  TableHead,
28
  TableHeader,
29
  TableRow,
30
} from "@/components/ui/table"
31
32
interface DataTableProps<TData, TValue> {
33
  columns: ColumnDef<TData, TValue>[]
34
  data: TData[]
35
}
36
37
export function DataTable<TData, TValue>({
38
  columns,
39
  data,
40
}: DataTableProps<TData, TValue>) {
41
  const table = useReactTable({
42
    data,
43
    columns,
44
    getCoreRowModel: getCoreRowModel(),
45
    getPaginationRowModel: getPaginationRowModel(),
46
  })
47
48
  return (
49
    <div className="flex flex-col gap-4">
50
      <Table>
51
        <TableHeader>
52
          {table.getHeaderGroups().map((headerGroup) => (
53
            <TableRow key={headerGroup.id} className="hover:bg-transparent">
54
              {headerGroup.headers.map((header) => {
55
                return (
56
                  <TableHead key={header.id}>
57
                    {header.isPlaceholder
58
                      ? null
59
                      : flexRender(
60
                          header.column.columnDef.header,
61
                          header.getContext(),
62
                        )}
63
                  </TableHead>
64
                )
65
              })}
66
            </TableRow>
67
          ))}
68
        </TableHeader>
69
        <TableBody>
70
          {table.getRowModel().rows.length ? (
71
            table.getRowModel().rows.map((row) => (
72
              <TableRow key={row.id}>
73
                {row.getVisibleCells().map((cell) => (
74
                  <TableCell key={cell.id}>
75
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
76
                  </TableCell>
77
                ))}
78
              </TableRow>
79
            ))
80
          ) : (
81
            <TableRow className="hover:bg-transparent">
82
              <TableCell
83
                colSpan={columns.length}
84
                className="h-32 text-center text-muted-foreground"
85
              >
86
                No results found.
87
              </TableCell>
88
            </TableRow>
89
          )}
90
        </TableBody>
91
      </Table>
92
93
      {table.getPageCount() > 1 && (
94
        <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 p-4 border-t bg-muted/20">
95
          <div className="flex flex-col sm:flex-row sm:items-center gap-4">
96
            <div className="text-sm text-muted-foreground">
97
              Showing{" "}
98
              {table.getState().pagination.pageIndex *
99
                table.getState().pagination.pageSize +
100
                1}{" "}
101
              to{" "}
102
              {Math.min(
103
                (table.getState().pagination.pageIndex + 1) *
104
                  table.getState().pagination.pageSize,
105
                data.length,
106
              )}{" "}
107
              of{" "}
108
              <span className="font-medium text-foreground">{data.length}</span>{" "}
109
              entries
110
            </div>
111
            <div className="flex items-center gap-x-2">
112
              <p className="text-sm text-muted-foreground">Rows per page</p>
113
              <Select
114
                value={`${table.getState().pagination.pageSize}`}
115
                onValueChange={(value) => {
116
                  table.setPageSize(Number(value))
117
                }}
118
              >
119
                <SelectTrigger className="h-8 w-[70px]">
120
                  <SelectValue
121
                    placeholder={table.getState().pagination.pageSize}
122
                  />
123
                </SelectTrigger>
124
                <SelectContent side="top">
125
                  {[5, 10, 25, 50].map((pageSize) => (
126
                    <SelectItem key={pageSize} value={`${pageSize}`}>
127
                      {pageSize}
128
                    </SelectItem>
129
                  ))}
130
                </SelectContent>
131
              </Select>
132
            </div>
133
          </div>
134
135
          <div className="flex items-center gap-x-6">
136
            <div className="flex items-center gap-x-1 text-sm text-muted-foreground">
137
              <span>Page</span>
138
              <span className="font-medium text-foreground">
139
                {table.getState().pagination.pageIndex + 1}
140
              </span>
141
              <span>of</span>
142
              <span className="font-medium text-foreground">
143
                {table.getPageCount()}
144
              </span>
145
            </div>
146
147
            <div className="flex items-center gap-x-1">
148
              <Button
149
                variant="outline"
150
                size="sm"
151
                className="h-8 w-8 p-0"
152
                onClick={() => table.setPageIndex(0)}
153
                disabled={!table.getCanPreviousPage()}
154
              >
155
                <span className="sr-only">Go to first page</span>
156
                <ChevronsLeft className="h-4 w-4" />
157
              </Button>
158
              <Button
159
                variant="outline"
160
                size="sm"
161
                className="h-8 w-8 p-0"
162
                onClick={() => table.previousPage()}
163
                disabled={!table.getCanPreviousPage()}
164
              >
165
                <span className="sr-only">Go to previous page</span>
166
                <ChevronLeft className="h-4 w-4" />
167
              </Button>
168
              <Button
169
                variant="outline"
170
                size="sm"
171
                className="h-8 w-8 p-0"
172
                onClick={() => table.nextPage()}
173
                disabled={!table.getCanNextPage()}
174
              >
175
                <span className="sr-only">Go to next page</span>
176
                <ChevronRight className="h-4 w-4" />
177
              </Button>
178
              <Button
179
                variant="outline"
180
                size="sm"
181
                className="h-8 w-8 p-0"
182
                onClick={() => table.setPageIndex(table.getPageCount() - 1)}
183
                disabled={!table.getCanNextPage()}
184
              >
185
                <span className="sr-only">Go to last page</span>
186
                <ChevronsRight className="h-4 w-4" />
187
              </Button>
188
            </div>
189
          </div>
190
        </div>
191
      )}
192
    </div>
193
  )
194
}
195