168 lines
4.3 KiB
TypeScript
168 lines
4.3 KiB
TypeScript
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
|
import {
|
|
ColumnFiltersState,
|
|
createColumnHelper,
|
|
flexRender,
|
|
getCoreRowModel,
|
|
PaginationState,
|
|
useReactTable,
|
|
} from "@tanstack/react-table";
|
|
import { useState } from "react";
|
|
import { DebounceInput } from "react-debounce-input";
|
|
|
|
const PageSize = 30;
|
|
|
|
async function loader(page = 0, category: string | undefined = "") {
|
|
const url = new URL("http://localhost:9000/transactions");
|
|
url.search = new URLSearchParams({
|
|
limit: String(PageSize),
|
|
offset: String(page * PageSize),
|
|
...(category !== "" && { category: category }),
|
|
}).toString();
|
|
|
|
return await fetch(url).then((response) => response.json());
|
|
}
|
|
|
|
type Transaction = {
|
|
id: number;
|
|
date: string;
|
|
description: string;
|
|
value: number;
|
|
category: string;
|
|
};
|
|
|
|
const columnHelper = createColumnHelper<Transaction>();
|
|
|
|
const columns = [
|
|
columnHelper.accessor("date", {
|
|
enableColumnFilter: false,
|
|
}),
|
|
columnHelper.accessor("description", {
|
|
enableColumnFilter: false,
|
|
}),
|
|
columnHelper.accessor("value", {
|
|
enableColumnFilter: false,
|
|
}),
|
|
columnHelper.accessor("category", {}),
|
|
];
|
|
|
|
export default function Transactions() {
|
|
const [pagination, setPagination] = useState<PaginationState>({
|
|
pageIndex: 0,
|
|
pageSize: PageSize,
|
|
});
|
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([
|
|
{
|
|
id: "category",
|
|
value: "",
|
|
},
|
|
]);
|
|
|
|
const { data, isPending, isError } = useQuery({
|
|
queryKey: ["transactions", pagination.pageIndex, columnFilters],
|
|
queryFn: () =>
|
|
loader(
|
|
pagination.pageIndex,
|
|
columnFilters.find((filter) => filter.id == "category")!.value as string
|
|
),
|
|
placeholderData: keepPreviousData,
|
|
});
|
|
|
|
const table = useReactTable({
|
|
columns,
|
|
data,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
manualPagination: true,
|
|
manualFiltering: true,
|
|
// rowCount: , // TODO: get this from the server
|
|
pageCount: -1,
|
|
onPaginationChange: setPagination,
|
|
onColumnFiltersChange: setColumnFilters,
|
|
state: {
|
|
pagination,
|
|
columnFilters,
|
|
},
|
|
});
|
|
|
|
if (isPending) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
|
|
if (isError) {
|
|
return <div>Error loading transactions</div>;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<table>
|
|
<thead>
|
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
<tr key={headerGroup.id}>
|
|
{headerGroup.headers.map((header) => (
|
|
<th key={header.id}>
|
|
{header.isPlaceholder ? null : (
|
|
<>
|
|
{flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext()
|
|
)}
|
|
{header.column.getCanFilter() ? (
|
|
<div>
|
|
<DebounceInput
|
|
debounceTimeout={275}
|
|
onChange={(e) =>
|
|
header.column.setFilterValue(e.target.value)
|
|
}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
</>
|
|
)}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</thead>
|
|
<tbody>
|
|
{table.getRowModel().rows.map((row) => (
|
|
<tr key={row.id}>
|
|
{row.getVisibleCells().map((cell) => (
|
|
<td key={cell.id}>
|
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
<div>
|
|
<button
|
|
onClick={() => table.firstPage()}
|
|
disabled={!table.getCanPreviousPage()}
|
|
>
|
|
First
|
|
</button>
|
|
<button
|
|
onClick={() => table.previousPage()}
|
|
disabled={!table.getCanPreviousPage()}
|
|
>
|
|
Previous
|
|
</button>
|
|
<button
|
|
onClick={() => table.nextPage()}
|
|
disabled={!table.getCanNextPage()}
|
|
>
|
|
Next
|
|
</button>
|
|
<button
|
|
onClick={() => table.lastPage()}
|
|
disabled={!table.getCanNextPage()}
|
|
>
|
|
Last
|
|
</button>
|
|
</div>
|
|
<div>Page {table.getState().pagination.pageIndex + 1}</div>
|
|
</>
|
|
);
|
|
}
|