mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
enhancement(settings): make lists sortable (#800)
* initial commit * sorting for clients and networks * sort feeds * cursor pointer on hover
This commit is contained in:
parent
30cf9c55f0
commit
59f41be3ef
4 changed files with 305 additions and 33 deletions
|
@ -8,12 +8,74 @@ import { APIClient } from "../../api/APIClient";
|
||||||
import { DownloadClientTypeNameMap } from "../../domain/constants";
|
import { DownloadClientTypeNameMap } from "../../domain/constants";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
import Toast from "../../components/notifications/Toast";
|
||||||
|
import { useState, useMemo } from "react";
|
||||||
|
|
||||||
interface DLSettingsItemProps {
|
interface DLSettingsItemProps {
|
||||||
client: DownloadClient;
|
client: DownloadClient;
|
||||||
idx: number;
|
idx: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ListItemProps {
|
||||||
|
clients: DownloadClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SortConfig {
|
||||||
|
key: keyof ListItemProps["clients"] | "enabled";
|
||||||
|
direction: "ascending" | "descending";
|
||||||
|
}
|
||||||
|
|
||||||
|
function useSort(items: ListItemProps["clients"][], config?: SortConfig) {
|
||||||
|
const [sortConfig, setSortConfig] = useState(config);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const sortedItems = useMemo(() => {
|
||||||
|
if (!sortConfig) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortableItems = [...items];
|
||||||
|
|
||||||
|
sortableItems.sort((a, b) => {
|
||||||
|
const aValue = sortConfig.key === "enabled" ? (a[sortConfig.key] ?? false) as number | boolean | string : a[sortConfig.key] as number | boolean | string;
|
||||||
|
const bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
||||||
|
|
||||||
|
if (aValue < bValue) {
|
||||||
|
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (aValue > bValue) {
|
||||||
|
return sortConfig.direction === "ascending" ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortableItems;
|
||||||
|
}, [items, sortConfig]);
|
||||||
|
|
||||||
|
const requestSort = (key: keyof ListItemProps["clients"]) => {
|
||||||
|
let direction: "ascending" | "descending" = "ascending";
|
||||||
|
if (
|
||||||
|
sortConfig &&
|
||||||
|
sortConfig.key === key &&
|
||||||
|
sortConfig.direction === "ascending"
|
||||||
|
) {
|
||||||
|
direction = "descending";
|
||||||
|
}
|
||||||
|
setSortConfig({ key, direction });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getSortIndicator = (key: keyof ListItemProps["clients"]) => {
|
||||||
|
if (!sortConfig || sortConfig.key !== key) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
||||||
|
};
|
||||||
|
|
||||||
|
return { items: sortedItems, requestSort, sortConfig, getSortIndicator };
|
||||||
|
}
|
||||||
|
|
||||||
function DownloadClientSettingsListItem({ client }: DLSettingsItemProps) {
|
function DownloadClientSettingsListItem({ client }: DLSettingsItemProps) {
|
||||||
const [updateClientIsOpen, toggleUpdateClient] = useToggle(false);
|
const [updateClientIsOpen, toggleUpdateClient] = useToggle(false);
|
||||||
|
|
||||||
|
@ -84,10 +146,13 @@ function DownloadClientSettings() {
|
||||||
{ refetchOnWindowFocus: false }
|
{ refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sortedClients = useSort(data || []);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <p>Failed to fetch download clients</p>;
|
return <p>Failed to fetch download clients</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:col-span-9">
|
<div className="lg:col-span-9">
|
||||||
|
|
||||||
|
@ -113,24 +178,33 @@ function DownloadClientSettings() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col mt-6 px-4">
|
<div className="flex flex-col mt-6 px-4">
|
||||||
{data && data.length > 0 ?
|
{sortedClients.items.length > 0 ?
|
||||||
<section className="light:bg-white dark:bg-gray-800 light:shadow sm:rounded-sm">
|
<section className="light:bg-white dark:bg-gray-800 light:shadow sm:rounded-sm">
|
||||||
<ol className="min-w-full relative">
|
<ol className="min-w-full relative">
|
||||||
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div className="flex col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
Enabled
|
onClick={() => sortedClients.requestSort("enabled")}>
|
||||||
|
Enabled <span className="sort-indicator">{sortedClients.getSortIndicator("enabled")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-6 sm:col-span-4 lg:col-span-4 pl-12 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div
|
||||||
Name
|
className="col-span-6 sm:col-span-4 lg:col-span-4 pl-12 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedClients.requestSort("name")}
|
||||||
|
>
|
||||||
|
Name <span className="sort-indicator">{sortedClients.getSortIndicator("name")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:flex col-span-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div
|
||||||
Host
|
className="hidden sm:flex col-span-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedClients.requestSort("host")}
|
||||||
|
>
|
||||||
|
Host <span className="sort-indicator">{sortedClients.getSortIndicator("host")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:flex col-span-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div className="hidden sm:flex col-span-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
Type
|
onClick={() => sortedClients.requestSort("type")}
|
||||||
|
>
|
||||||
|
Type <span className="sort-indicator">{sortedClients.getSortIndicator("type")}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{data && data.map((client, idx) => (
|
{sortedClients.items.map((client, idx) => (
|
||||||
<DownloadClientSettingsListItem client={client} idx={idx} key={idx} />
|
<DownloadClientSettingsListItem client={client} idx={idx} key={idx} />
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { APIClient } from "../../api/APIClient";
|
||||||
import { Menu, Switch, Transition } from "@headlessui/react";
|
import { Menu, Switch, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import { baseUrl, classNames, IsEmptyDate, simplifyDate } from "../../utils";
|
import { baseUrl, classNames, IsEmptyDate, simplifyDate } from "../../utils";
|
||||||
import { Fragment, useRef, useState } from "react";
|
import { Fragment, useRef, useState, useMemo } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
import Toast from "../../components/notifications/Toast";
|
||||||
import { queryClient } from "../../App";
|
import { queryClient } from "../../App";
|
||||||
|
@ -20,6 +20,62 @@ import { FeedUpdateForm } from "../../forms/settings/FeedForms";
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
import { EmptySimple } from "../../components/emptystates";
|
||||||
import { ImplementationBadges } from "./Indexer";
|
import { ImplementationBadges } from "./Indexer";
|
||||||
|
|
||||||
|
interface SortConfig {
|
||||||
|
key: keyof ListItemProps["feed"] | "enabled";
|
||||||
|
direction: "ascending" | "descending";
|
||||||
|
}
|
||||||
|
|
||||||
|
function useSort(items: ListItemProps["feed"][], config?: SortConfig) {
|
||||||
|
const [sortConfig, setSortConfig] = useState(config);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const sortedItems = useMemo(() => {
|
||||||
|
if (!sortConfig) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortableItems = [...items];
|
||||||
|
|
||||||
|
sortableItems.sort((a, b) => {
|
||||||
|
const aValue = sortConfig.key === "enabled" ? (a[sortConfig.key] ?? false) as number | boolean | string : a[sortConfig.key] as number | boolean | string;
|
||||||
|
const bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
||||||
|
|
||||||
|
if (aValue < bValue) {
|
||||||
|
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (aValue > bValue) {
|
||||||
|
return sortConfig.direction === "ascending" ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortableItems;
|
||||||
|
}, [items, sortConfig]);
|
||||||
|
|
||||||
|
const requestSort = (key: keyof ListItemProps["feed"] | "enabled") => { let direction: "ascending" | "descending" = "ascending";
|
||||||
|
if (
|
||||||
|
sortConfig &&
|
||||||
|
sortConfig.key === key &&
|
||||||
|
sortConfig.direction === "ascending"
|
||||||
|
) {
|
||||||
|
direction = "descending";
|
||||||
|
}
|
||||||
|
setSortConfig({ key, direction });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getSortIndicator = (key: keyof ListItemProps["feed"]) => {
|
||||||
|
if (!sortConfig || sortConfig.key !== key) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
||||||
|
};
|
||||||
|
|
||||||
|
return { items: sortedItems, requestSort, sortConfig, getSortIndicator };
|
||||||
|
}
|
||||||
|
|
||||||
function FeedSettings() {
|
function FeedSettings() {
|
||||||
const { data } = useQuery(
|
const { data } = useQuery(
|
||||||
"feeds",
|
"feeds",
|
||||||
|
@ -27,6 +83,8 @@ function FeedSettings() {
|
||||||
{ refetchOnWindowFocus: false }
|
{ refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sortedFeeds = useSort(data || []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:col-span-9">
|
<div className="lg:col-span-9">
|
||||||
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
<div className="py-6 px-4 sm:p-6 lg:pb-8">
|
||||||
|
@ -44,20 +102,28 @@ function FeedSettings() {
|
||||||
<ol className="min-w-full relative">
|
<ol className="min-w-full relative">
|
||||||
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div
|
<div
|
||||||
className="col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Enabled
|
className="flex col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedFeeds.requestSort("enabled")}>
|
||||||
|
Enabled <span className="sort-indicator">{sortedFeeds.getSortIndicator("enabled")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="col-span-6 pl-12 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Name
|
className="col-span-6 pl-12 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedFeeds.requestSort("name")}>
|
||||||
|
Name <span className="sort-indicator">{sortedFeeds.getSortIndicator("name")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="hidden md:flex col-span-1 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Type
|
className="hidden md:flex col-span-1 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedFeeds.requestSort("type")}>
|
||||||
|
Type <span className="sort-indicator">{sortedFeeds.getSortIndicator("type")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="hidden md:flex col-span-3 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Last run
|
className="hidden md:flex col-span-3 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedFeeds.requestSort("last_run")}>
|
||||||
|
Last run <span className="sort-indicator">{sortedFeeds.getSortIndicator("last_run")}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{data && data.map((f) => (
|
{sortedFeeds.items.map((feed, idx) => (
|
||||||
<ListItem key={f.id} feed={f}/>
|
<ListItem key={feed.id} feed={feed}/>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -6,6 +6,61 @@ import { classNames } from "../../utils";
|
||||||
import { EmptySimple } from "../../components/emptystates";
|
import { EmptySimple } from "../../components/emptystates";
|
||||||
import { APIClient } from "../../api/APIClient";
|
import { APIClient } from "../../api/APIClient";
|
||||||
import { componentMapType } from "../../forms/settings/DownloadClientForms";
|
import { componentMapType } from "../../forms/settings/DownloadClientForms";
|
||||||
|
import { useState, useMemo } from "react";
|
||||||
|
|
||||||
|
interface SortConfig {
|
||||||
|
key: keyof ListItemProps["indexer"] | "enabled";
|
||||||
|
direction: "ascending" | "descending";
|
||||||
|
}
|
||||||
|
|
||||||
|
function useSort(items: ListItemProps["indexer"][], config?: SortConfig) {
|
||||||
|
const [sortConfig, setSortConfig] = useState(config);
|
||||||
|
|
||||||
|
const sortedItems = useMemo(() => {
|
||||||
|
if (!sortConfig) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortableItems = [...items];
|
||||||
|
|
||||||
|
sortableItems.sort((a, b) => {
|
||||||
|
const aValue = sortConfig.key === "enabled" ? (a[sortConfig.key] ?? false) as number | boolean | string : a[sortConfig.key] as number | boolean | string;
|
||||||
|
const bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
||||||
|
|
||||||
|
if (aValue < bValue) {
|
||||||
|
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (aValue > bValue) {
|
||||||
|
return sortConfig.direction === "ascending" ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortableItems;
|
||||||
|
}, [items, sortConfig]);
|
||||||
|
|
||||||
|
const requestSort = (key: keyof ListItemProps["indexer"]) => {
|
||||||
|
let direction: "ascending" | "descending" = "ascending";
|
||||||
|
if (
|
||||||
|
sortConfig &&
|
||||||
|
sortConfig.key === key &&
|
||||||
|
sortConfig.direction === "ascending"
|
||||||
|
) {
|
||||||
|
direction = "descending";
|
||||||
|
}
|
||||||
|
setSortConfig({ key, direction });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSortIndicator = (key: keyof ListItemProps["indexer"]) => {
|
||||||
|
if (!sortConfig || sortConfig.key !== key) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
||||||
|
};
|
||||||
|
|
||||||
|
return { items: sortedItems, requestSort, sortConfig, getSortIndicator };
|
||||||
|
}
|
||||||
|
|
||||||
const ImplementationBadgeIRC = () => (
|
const ImplementationBadgeIRC = () => (
|
||||||
<span className="mr-2 inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-medium bg-green-200 dark:bg-green-400 text-green-800 dark:text-green-800">
|
<span className="mr-2 inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-medium bg-green-200 dark:bg-green-400 text-green-800 dark:text-green-800">
|
||||||
|
@ -100,6 +155,8 @@ function IndexerSettings() {
|
||||||
{ refetchOnWindowFocus: false }
|
{ refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sortedIndexers = useSort(data || []);
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
return (<p>An error has occurred</p>);
|
return (<p>An error has occurred</p>);
|
||||||
|
|
||||||
|
@ -133,17 +190,26 @@ function IndexerSettings() {
|
||||||
<section className="light:bg-white dark:bg-gray-800 light:shadow sm:rounded-md">
|
<section className="light:bg-white dark:bg-gray-800 light:shadow sm:rounded-md">
|
||||||
<ol className="min-w-full relative">
|
<ol className="min-w-full relative">
|
||||||
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
<li className="grid grid-cols-12 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div
|
||||||
Enabled
|
className="flex col-span-2 sm:col-span-1 px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedIndexers.requestSort("enabled")}
|
||||||
|
>
|
||||||
|
Enabled <span className="sort-indicator">{sortedIndexers.getSortIndicator("enabled")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-7 sm:col-span-8 pl-12 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div
|
||||||
Name
|
className="col-span-7 sm:col-span-8 pl-12 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedIndexers.requestSort("name")}
|
||||||
|
>
|
||||||
|
Name <span className="sort-indicator">{sortedIndexers.getSortIndicator("name")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden md:flex col-span-1 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div
|
||||||
Implementation
|
className="hidden md:flex col-span-1 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
|
onClick={() => sortedIndexers.requestSort("implementation")}
|
||||||
|
>
|
||||||
|
Implementation <span className="sort-indicator">{sortedIndexers.getSortIndicator("implementation")}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{data.map((indexer, idx) => (
|
{sortedIndexers.items.map((indexer, idx) => (
|
||||||
<ListItem indexer={indexer} key={idx} />
|
<ListItem indexer={indexer} key={idx} />
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/solid";
|
||||||
import { Menu, Switch, Transition } from "@headlessui/react";
|
import { Menu, Switch, Transition } from "@headlessui/react";
|
||||||
import { Fragment, useRef } from "react";
|
import { Fragment, useRef } from "react";
|
||||||
import { DeleteModal } from "../../components/modals";
|
import { DeleteModal } from "../../components/modals";
|
||||||
|
import { useState, useMemo } from "react";
|
||||||
|
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import Toast from "../../components/notifications/Toast";
|
import Toast from "../../components/notifications/Toast";
|
||||||
|
@ -21,6 +22,64 @@ import {
|
||||||
TrashIcon
|
TrashIcon
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
|
interface SortConfig {
|
||||||
|
key: keyof ListItemProps["network"] | "enabled";
|
||||||
|
direction: "ascending" | "descending";
|
||||||
|
}
|
||||||
|
|
||||||
|
function useSort(items: ListItemProps["network"][], config?: SortConfig) {
|
||||||
|
const [sortConfig, setSortConfig] = useState(config);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const sortedItems = useMemo(() => {
|
||||||
|
if (!sortConfig) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortableItems = [...items];
|
||||||
|
|
||||||
|
sortableItems.sort((a, b) => {
|
||||||
|
const aValue = sortConfig.key === "enabled" ? (a[sortConfig.key] ?? false) as number | boolean | string : a[sortConfig.key] as number | boolean | string;
|
||||||
|
const bValue = sortConfig.key === "enabled" ? (b[sortConfig.key] ?? false) as number | boolean | string : b[sortConfig.key] as number | boolean | string;
|
||||||
|
|
||||||
|
if (aValue < bValue) {
|
||||||
|
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (aValue > bValue) {
|
||||||
|
return sortConfig.direction === "ascending" ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortableItems;
|
||||||
|
}, [items, sortConfig]);
|
||||||
|
|
||||||
|
const requestSort = (key: keyof ListItemProps["network"]) => {
|
||||||
|
let direction: "ascending" | "descending" = "ascending";
|
||||||
|
if (
|
||||||
|
sortConfig &&
|
||||||
|
sortConfig.key === key &&
|
||||||
|
sortConfig.direction === "ascending"
|
||||||
|
) {
|
||||||
|
direction = "descending";
|
||||||
|
}
|
||||||
|
setSortConfig({ key, direction });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getSortIndicator = (key: keyof ListItemProps["network"]) => {
|
||||||
|
if (!sortConfig || sortConfig.key !== key) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortConfig.direction === "ascending" ? "↑" : "↓";
|
||||||
|
};
|
||||||
|
|
||||||
|
return { items: sortedItems, requestSort, sortConfig, getSortIndicator };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const IrcSettings = () => {
|
const IrcSettings = () => {
|
||||||
const [expandNetworks, toggleExpand] = useToggle(false);
|
const [expandNetworks, toggleExpand] = useToggle(false);
|
||||||
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false);
|
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false);
|
||||||
|
@ -31,6 +90,9 @@ const IrcSettings = () => {
|
||||||
refetchInterval: 3000
|
refetchInterval: 3000
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sortedNetworks = useSort(data || []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="lg:col-span-9">
|
<div className="lg:col-span-9">
|
||||||
<IrcNetworkAddForm isOpen={addNetworkIsOpen} toggle={toggleAddNetwork} />
|
<IrcNetworkAddForm isOpen={addNetworkIsOpen} toggle={toggleAddNetwork} />
|
||||||
|
@ -100,21 +162,25 @@ const IrcSettings = () => {
|
||||||
<section className="mt-6 light:bg-white dark:bg-gray-800 light:shadow md:rounded-md">
|
<section className="mt-6 light:bg-white dark:bg-gray-800 light:shadow md:rounded-md">
|
||||||
<ol className="min-w-full relative">
|
<ol className="min-w-full relative">
|
||||||
<li className="grid grid-cols-12 gap-4 border-b border-gray-200 dark:border-gray-700">
|
<li className="grid grid-cols-12 gap-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div className="col-span-2 md:col-span-1 px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div className="flex col-span-2 md:col-span-1 px-3 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
Enabled
|
onClick={() => sortedNetworks.requestSort("enabled")}>
|
||||||
|
Enabled <span className="sort-indicator">{sortedNetworks.getSortIndicator("enabled")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-10 md:col-span-3 px-8 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div className="col-span-10 md:col-span-3 px-8 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
Network
|
onClick={() => sortedNetworks.requestSort("name")}>
|
||||||
|
Network <span className="sort-indicator">{sortedNetworks.getSortIndicator("name")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden md:flex col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div className="hidden md:flex col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
Server
|
onClick={() => sortedNetworks.requestSort("server")}>
|
||||||
|
Server <span className="sort-indicator">{sortedNetworks.getSortIndicator("server")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden md:flex col-span-3 px-5 lg:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
<div className="hidden md:flex col-span-3 px-5 lg:px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer"
|
||||||
Nick
|
onClick={() => sortedNetworks.requestSort("nick")}>
|
||||||
|
Nick <span className="sort-indicator">{sortedNetworks.getSortIndicator("nick")}</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{data &&
|
{data &&
|
||||||
data.map((network, idx) => (
|
sortedNetworks.items.map((network, idx) => (
|
||||||
<ListItem key={idx} idx={idx} expanded={expandNetworks} network={network} />
|
<ListItem key={idx} idx={idx} expanded={expandNetworks} network={network} />
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue