mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
enhancement(web): stats and releases pretty loading (#731)
* enhancement(web): improved loading message * dark mode fix * added skeleton for loading * placeholder * handle activitytable loading * name StatsItems in isLoading * handling ReleaseTable loading * made releasetable 10 rows while loading * derp * style: simplify loading state --------- Co-authored-by: soup <soup@r4tio.cat> Co-authored-by: ze0s <ze0s@riseup.net>
This commit is contained in:
parent
65f51da68e
commit
d172e70046
3 changed files with 174 additions and 12 deletions
|
@ -185,7 +185,16 @@ export const ActivityTable = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return null;
|
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 (
|
return (
|
||||||
<div className="flex flex-col mt-12">
|
<div className="flex flex-col mt-12">
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
|
import { classNames } from "../../utils";
|
||||||
|
|
||||||
interface StatsItemProps {
|
interface StatsItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
value?: number;
|
value?: number;
|
||||||
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatsItem = ({ name, value }: StatsItemProps) => (
|
const StatsItem = ({ name, placeholder, value }: StatsItemProps) => (
|
||||||
<div
|
<div
|
||||||
className="relative px-4 py-5 overflow-hidden bg-white rounded-lg shadow-lg dark:bg-gray-800"
|
className="relative px-4 py-5 overflow-hidden bg-white rounded-lg shadow-lg dark:bg-gray-800"
|
||||||
title="All time"
|
title="All time"
|
||||||
|
@ -15,6 +17,10 @@ const StatsItem = ({ name, value }: StatsItemProps) => (
|
||||||
<p className="pb-1 text-sm font-medium text-gray-500 truncate">{name}</p>
|
<p className="pb-1 text-sm font-medium text-gray-500 truncate">{name}</p>
|
||||||
</dt>
|
</dt>
|
||||||
|
|
||||||
|
<dd className="flex items-baseline">
|
||||||
|
<p className="text-3xl font-extrabold text-gray-900 dark:text-gray-200">{placeholder}</p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
<dd className="flex items-baseline">
|
<dd className="flex items-baseline">
|
||||||
<p className="text-3xl font-extrabold text-gray-900 dark:text-gray-200">{value}</p>
|
<p className="text-3xl font-extrabold text-gray-900 dark:text-gray-200">{value}</p>
|
||||||
</dd>
|
</dd>
|
||||||
|
@ -28,20 +34,16 @@ export const Stats = () => {
|
||||||
{ refetchOnWindowFocus: false }
|
{ refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-black dark:text-white">
|
<h1 className="text-3xl font-bold text-black dark:text-white">
|
||||||
Stats
|
Stats
|
||||||
</h1>
|
</h1>
|
||||||
|
<dl className={classNames("grid grid-cols-1 gap-5 mt-5 sm:grid-cols-2 lg:grid-cols-3", isLoading ? "animate-pulse" : "")}>
|
||||||
<dl className="grid grid-cols-1 gap-5 mt-5 sm:grid-cols-2 lg:grid-cols-3">
|
<StatsItem name="Filtered Releases" value={data?.filtered_count ?? 0} />
|
||||||
<StatsItem name="Filtered Releases" value={data?.filtered_count} />
|
|
||||||
{/* <StatsItem name="Filter Rejected Releases" stat={data?.filter_rejected_count} /> */}
|
{/* <StatsItem name="Filter Rejected Releases" stat={data?.filter_rejected_count} /> */}
|
||||||
<StatsItem name="Rejected Pushes" value={data?.push_rejected_count} />
|
<StatsItem name="Rejected Pushes" value={data?.push_rejected_count ?? 0 } />
|
||||||
<StatsItem name="Approved Pushes" value={data?.push_approved_count} />
|
<StatsItem name="Approved Pushes" value={data?.push_approved_count ?? 0} />
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -196,7 +196,159 @@ export const ReleaseTable = () => {
|
||||||
return <p>Error</p>;
|
return <p>Error</p>;
|
||||||
|
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return <p>Loading...</p>;
|
return (
|
||||||
|
<div className="flex flex-col animate-pulse">
|
||||||
|
<div className="flex mb-6 flex-col sm:flex-row">
|
||||||
|
{headerGroups.map((headerGroup) =>
|
||||||
|
headerGroup.headers.map((column) => (
|
||||||
|
column.Filter ? (
|
||||||
|
<React.Fragment key={column.id}>{column.render("Filter")}</React.Fragment>
|
||||||
|
) : null
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="bg-white shadow-lg dark:bg-gray-800 rounded-md overflow-auto">
|
||||||
|
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<th
|
||||||
|
|
||||||
|
scope="col"
|
||||||
|
className="first:pl-5 pl-3 pr-3 py-3 first:rounded-tl-md last:rounded-tr-md text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
||||||
|
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
{/* Add a sort direction indicator */}
|
||||||
|
<span className="h-4">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
</thead>
|
||||||
|
<tbody className=" divide-gray-200 dark:divide-gray-700">
|
||||||
|
<tr className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
</tr>
|
||||||
|
<tr className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
</tr>
|
||||||
|
<tr className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr className="flex justify-between py-4 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
</tr>
|
||||||
|
<tr className="justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap text-center">
|
||||||
|
<p className="text-black dark:text-white">Loading release table...</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap"> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
</tr>
|
||||||
|
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
</tr>
|
||||||
|
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
</tr>
|
||||||
|
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
</tr>
|
||||||
|
<tr className="flex justify-between py-3 text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[96px] sm:max-w-[216px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px]">
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
<td className="first:pl-5 pl-3 pr-3 whitespace-nowrap "> </td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="flex justify-between flex-1 sm:hidden">
|
||||||
|
<DataTable.Button onClick={() => previousPage()} disabled={!canPreviousPage}>Previous</DataTable.Button>
|
||||||
|
<DataTable.Button onClick={() => nextPage()} disabled={!canNextPage}>Next</DataTable.Button>
|
||||||
|
</div>
|
||||||
|
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||||
|
<div className="flex items-baseline gap-x-2">
|
||||||
|
<span className="text-sm text-gray-700 dark:text-gray-500">
|
||||||
|
Page <span className="font-medium">{pageIndex + 1}</span> of <span className="font-medium">{pageOptions.length}</span>
|
||||||
|
</span>
|
||||||
|
<label>
|
||||||
|
<span className="sr-only bg-gray-700">Items Per Page</span>
|
||||||
|
<select
|
||||||
|
className="py-1 pl-2 pr-8 text-sm block w-full border-gray-300 rounded-md shadow-sm cursor-pointer dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:text-gray-500 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
|
||||||
|
value={pageSize}
|
||||||
|
onChange={e => {
|
||||||
|
setPageSize(Number(e.target.value));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[5, 10, 20, 50].map(pageSize => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<nav className="inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
|
||||||
|
<DataTable.PageButton
|
||||||
|
className="rounded-l-md"
|
||||||
|
onClick={() => gotoPage(0)}
|
||||||
|
disabled={!canPreviousPage}
|
||||||
|
>
|
||||||
|
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">First</span>
|
||||||
|
<ChevronDoubleLeftIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true" />
|
||||||
|
</DataTable.PageButton>
|
||||||
|
<DataTable.PageButton
|
||||||
|
onClick={() => previousPage()}
|
||||||
|
disabled={!canPreviousPage}
|
||||||
|
>
|
||||||
|
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Previous</span>
|
||||||
|
<ChevronLeftIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true" />
|
||||||
|
</DataTable.PageButton>
|
||||||
|
<DataTable.PageButton
|
||||||
|
onClick={() => nextPage()}
|
||||||
|
disabled={!canNextPage}>
|
||||||
|
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Next</span>
|
||||||
|
<ChevronRightIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true" />
|
||||||
|
</DataTable.PageButton>
|
||||||
|
<DataTable.PageButton
|
||||||
|
className="rounded-r-md"
|
||||||
|
onClick={() => gotoPage(pageCount - 1)}
|
||||||
|
disabled={!canNextPage}
|
||||||
|
>
|
||||||
|
<span className="sr-only text-gray-400 dark:text-gray-500 dark:bg-gray-700">Last</span>
|
||||||
|
<ChevronDoubleRightIcon className="w-4 h-4 text-gray-400 dark:text-gray-500" aria-hidden="true" />
|
||||||
|
</DataTable.PageButton>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
if (!data)
|
if (!data)
|
||||||
return <EmptyListState text="No recent activity" />;
|
return <EmptyListState text="No recent activity" />;
|
||||||
|
@ -281,7 +433,6 @@ export const ReleaseTable = () => {
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
<div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700">
|
<div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex justify-between flex-1 sm:hidden">
|
<div className="flex justify-between flex-1 sm:hidden">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue