mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 16:59:12 +00:00
feat(web): migrate react-table to v8 (#1866)
* feat(web): migrate react-table to v8 * chore(web): cleanup * chore(web): fix types * chore(web): ignore unused * chore(web): fix types ActivityTable.tsx * chore(web): remove console log
This commit is contained in:
parent
172fa651af
commit
ec85d53d8f
9 changed files with 440 additions and 540 deletions
|
@ -6,84 +6,35 @@
|
|||
import React, { useState } from "react";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
useTable,
|
||||
useFilters,
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
usePagination, FilterProps, Column
|
||||
} from "react-table";
|
||||
useReactTable,
|
||||
getCoreRowModel,
|
||||
flexRender,
|
||||
ColumnDef
|
||||
} from "@tanstack/react-table";
|
||||
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
import { EmptyListState } from "@components/emptystates";
|
||||
import * as Icons from "@components/Icons";
|
||||
import * as DataTable from "@components/data-table";
|
||||
import { RandomLinuxIsos } from "@utils";
|
||||
import { ReleasesLatestQueryOptions } from "@api/queries";
|
||||
import { IndexerCell } from "@components/data-table";
|
||||
|
||||
// This is a custom filter UI for selecting
|
||||
// a unique option from a list
|
||||
function SelectColumnFilter({
|
||||
column: { filterValue, setFilter, preFilteredRows, id, render }
|
||||
}: FilterProps<object>) {
|
||||
// Calculate the options for filtering
|
||||
// using the preFilteredRows
|
||||
const options = React.useMemo(() => {
|
||||
const options = new Set<string>();
|
||||
preFilteredRows.forEach((row: { values: { [x: string]: string } }) => {
|
||||
options.add(row.values[id]);
|
||||
});
|
||||
return [...options.values()];
|
||||
}, [id, preFilteredRows]);
|
||||
|
||||
// Render a multi-select box
|
||||
return (
|
||||
<label className="flex items-baseline gap-x-2">
|
||||
<span className="text-gray-700"><>{render("Header")}:</></span>
|
||||
<select
|
||||
className="border-gray-300 rounded-md shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
|
||||
name={id}
|
||||
id={id}
|
||||
value={filterValue}
|
||||
onChange={e => {
|
||||
setFilter(e.target.value || undefined);
|
||||
}}
|
||||
>
|
||||
<option value="">All</option>
|
||||
{options.map((option, i) => (
|
||||
<option key={i} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
interface TableProps {
|
||||
columns: Column[];
|
||||
columns: ColumnDef<Release>[];
|
||||
data: Release[];
|
||||
}
|
||||
|
||||
function Table({ columns, data }: TableProps) {
|
||||
// Use the state and functions returned from useTable to build your UI
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
prepareRow,
|
||||
page // Instead of using 'rows', we'll use page,
|
||||
} = useTable(
|
||||
{ columns, data },
|
||||
useFilters,
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
usePagination
|
||||
);
|
||||
const tableInstance = useReactTable({
|
||||
columns,
|
||||
data,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
})
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="mt-4 mb-2 bg-white dark:bg-gray-800 border border-gray-250 dark:border-gray-775 shadow-table rounded-md overflow-auto">
|
||||
<div
|
||||
className="mt-4 mb-2 bg-white dark:bg-gray-800 border border-gray-250 dark:border-gray-775 shadow-table rounded-md overflow-auto">
|
||||
<div className="flex items-center justify-center py-16">
|
||||
<EmptyListState text="No recent activity"/>
|
||||
</div>
|
||||
|
@ -91,74 +42,49 @@ function Table({ columns, data }: TableProps) {
|
|||
)
|
||||
}
|
||||
|
||||
// Render the UI for your table
|
||||
return (
|
||||
<div className="inline-block min-w-full mt-4 mb-2 align-middle">
|
||||
<div className="bg-white dark:bg-gray-800 border border-gray-250 dark:border-gray-775 shadow-table rounded-md overflow-auto">
|
||||
<table {...getTableProps()} className="min-w-full rounded-md divide-y divide-gray-200 dark:divide-gray-750">
|
||||
<table className="min-w-full rounded-md divide-y divide-gray-200 dark:divide-gray-750">
|
||||
<thead className="bg-gray-100 dark:bg-gray-850">
|
||||
{headerGroups.map((headerGroup) => {
|
||||
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
|
||||
return (
|
||||
<tr key={rowKey} {...rowRest}>
|
||||
{headerGroup.headers.map((column) => {
|
||||
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
|
||||
return (
|
||||
// Add the sorting props to control sorting. For this example
|
||||
// we can add them into the header props
|
||||
<th
|
||||
key={`${rowKey}-${columnKey}`}
|
||||
scope="col"
|
||||
className="first:pl-5 first:rounded-tl-md last:rounded-tr-md pl-3 pr-3 py-3 text-xs font-medium tracking-wider text-left uppercase group text-gray-600 dark:text-gray-400 transition hover:bg-gray-200 dark:hover:bg-gray-775"
|
||||
{...columnRest}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<>{column.render("Header")}</>
|
||||
{/* Add a sort direction indicator */}
|
||||
<span>
|
||||
{column.isSorted ? (
|
||||
column.isSortedDesc ? (
|
||||
<Icons.SortDownIcon className="w-4 h-4 text-gray-400" />
|
||||
) : (
|
||||
<Icons.SortUpIcon className="w-4 h-4 text-gray-400" />
|
||||
)
|
||||
) : (
|
||||
<Icons.SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{tableInstance.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th
|
||||
key={header.id}
|
||||
scope="col"
|
||||
className="first:pl-5 first:rounded-tl-md last:rounded-tr-md pl-3 pr-3 py-3 text-xs font-medium tracking-wider text-left uppercase group text-gray-600 dark:text-gray-400 transition hover:bg-gray-200 dark:hover:bg-gray-775"
|
||||
colSpan={header.colSpan}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody
|
||||
{...getTableBodyProps()}
|
||||
className="divide-y divide-gray-150 dark:divide-gray-750"
|
||||
>
|
||||
{page.map((row) => {
|
||||
prepareRow(row);
|
||||
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
|
||||
return (
|
||||
<tr key={bodyRowKey} {...bodyRowRest}>
|
||||
{row.cells.map((cell) => {
|
||||
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
|
||||
return (
|
||||
<td
|
||||
key={cellRowKey}
|
||||
className="first:pl-5 pl-3 pr-3 whitespace-nowrap"
|
||||
role="cell"
|
||||
{...cellRowRest}
|
||||
>
|
||||
<>{cell.render("Cell")}</>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
||||
<tbody className="divide-y divide-gray-150 dark:divide-gray-750">
|
||||
{tableInstance.getRowModel().rows.map((row) => (
|
||||
<tr key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className="first:pl-5 pl-3 pr-3 whitespace-nowrap"
|
||||
role="cell"
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -167,30 +93,28 @@ function Table({ columns, data }: TableProps) {
|
|||
}
|
||||
|
||||
export const ActivityTable = () => {
|
||||
const columns = React.useMemo(() => [
|
||||
const columns = React.useMemo<ColumnDef<Release, unknown>[]>(() => [
|
||||
{
|
||||
Header: "Age",
|
||||
accessor: "timestamp",
|
||||
Cell: DataTable.AgeCell
|
||||
header: "Age",
|
||||
accessorKey: "timestamp",
|
||||
cell: DataTable.AgeCell
|
||||
},
|
||||
{
|
||||
Header: "Release",
|
||||
accessor: "name",
|
||||
Cell: DataTable.TitleCell
|
||||
header: "Release",
|
||||
accessorKey: "name",
|
||||
cell: DataTable.TitleCell,
|
||||
},
|
||||
{
|
||||
Header: "Actions",
|
||||
accessor: "action_status",
|
||||
Cell: DataTable.ReleaseStatusCell
|
||||
header: "Actions",
|
||||
accessorKey: "action_status",
|
||||
cell: DataTable.ReleaseStatusCell
|
||||
},
|
||||
{
|
||||
Header: "Indexer",
|
||||
accessor: "indexer.identifier",
|
||||
Cell: IndexerCell,
|
||||
Filter: SelectColumnFilter,
|
||||
filter: "includes"
|
||||
header: "Indexer",
|
||||
accessorKey: "indexer.identifier",
|
||||
cell: IndexerCell,
|
||||
}
|
||||
] as Column[], []);
|
||||
], []);
|
||||
|
||||
const { isLoading, data } = useSuspenseQuery(ReleasesLatestQueryOptions());
|
||||
|
||||
|
@ -236,7 +160,7 @@ export const ActivityTable = () => {
|
|||
Recent activity
|
||||
</h3>
|
||||
|
||||
<Table columns={columns} data={displayData} />
|
||||
<Table columns={columns} data={displayData}/>
|
||||
|
||||
<button
|
||||
onClick={toggleReleaseNames}
|
||||
|
@ -245,9 +169,9 @@ export const ActivityTable = () => {
|
|||
title="Go incognito"
|
||||
>
|
||||
{showLinuxIsos ? (
|
||||
<EyeIcon className="h-4 w-4" />
|
||||
<EyeIcon className="h-4 w-4"/>
|
||||
) : (
|
||||
<EyeSlashIcon className="h-4 w-4" />
|
||||
<EyeSlashIcon className="h-4 w-4"/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue