mirror of
https://github.com/idanoo/autobrr
synced 2025-07-26 18:29:14 +00:00

* Various WebUI changes and fixes. * feat(tooltip): make tooltip display upwards * fix(tooltip): place tooltip to the right * fix(web): add missing ml-px to SwitchGroup header current: https://i.imgur.com/2WXstPV.png new: https://i.imgur.com/QGQ49mP.png * fix(web): collapse sections * fix(web): improve freeleech section * fix(web): rename action to action_components Renamed the 'action' folder to 'action_components' to resolve import issues due to case sensitivity. * fix(web): align CollapsibleSection Old Advanced tab: https://i.imgur.com/MXaJ5eJ.png New Advanced tab: https://i.imgur.com/4nPJJRw.png Music tab for comparison: https://i.imgur.com/I59X7ot.png * fix(web): remove invalid CSS class * revert: vertical padding on switchgroup added py-0 on the freeleech part instead * feat(settings): add back log files * fix(settings): irc channels and font sizes * fix(components): radio select roundness * fix(styling): various minor changes * fix(filters): remove jitter fields --------- Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com> Co-authored-by: soup <soup@r4tio.dev> Co-authored-by: ze0s <ze0s@riseup.net>
214 lines
6.8 KiB
TypeScript
214 lines
6.8 KiB
TypeScript
/*
|
|
* Copyright (c) 2021 - 2023, Ludvig Lundgren and the autobrr contributors.
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
import * as React from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import {
|
|
useTable,
|
|
useFilters,
|
|
useGlobalFilter,
|
|
useSortBy,
|
|
usePagination, FilterProps, Column
|
|
} from "react-table";
|
|
|
|
import { APIClient } from "@api/APIClient";
|
|
import { EmptyListState } from "@components/emptystates";
|
|
import * as Icons from "@components/Icons";
|
|
import * as DataTable 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[];
|
|
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
|
|
);
|
|
|
|
if (!page.length) {
|
|
return <EmptyListState text="No recent activity" />;
|
|
}
|
|
|
|
// 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">
|
|
<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>
|
|
);
|
|
})}
|
|
</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>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export const ActivityTable = () => {
|
|
const columns = React.useMemo(() => [
|
|
{
|
|
Header: "Age",
|
|
accessor: "timestamp",
|
|
Cell: DataTable.AgeCell
|
|
},
|
|
{
|
|
Header: "Release",
|
|
accessor: "torrent_name",
|
|
Cell: DataTable.TitleCell
|
|
},
|
|
{
|
|
Header: "Actions",
|
|
accessor: "action_status",
|
|
Cell: DataTable.ReleaseStatusCell
|
|
},
|
|
{
|
|
Header: "Indexer",
|
|
accessor: "indexer",
|
|
Cell: DataTable.TitleCell,
|
|
Filter: SelectColumnFilter,
|
|
filter: "includes"
|
|
}
|
|
], []);
|
|
|
|
const { isLoading, data } = useQuery({
|
|
queryKey: ["dash_recent_releases"],
|
|
queryFn: APIClient.release.findRecent,
|
|
refetchOnWindowFocus: false
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex flex-col mt-12">
|
|
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200">
|
|
|
|
</h3>
|
|
<div className="animate-pulse text-black dark:text-white">
|
|
<EmptyListState text="Loading..."/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col mt-12">
|
|
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200">
|
|
Recent activity
|
|
</h3>
|
|
|
|
<Table columns={columns} data={data?.data ?? []} />
|
|
</div>
|
|
);
|
|
};
|