Adding sorting to items in settings
All checks were successful
Deploy to production / build (push) Successful in 18s

This commit is contained in:
2026-10-12 17:10:03 -05:00
parent 78e6bd3374
commit 01dd6b1aa2
9 changed files with 200 additions and 63 deletions

View File

@@ -3,6 +3,7 @@ import {
Button, Button,
Group, Group,
Highlight, Highlight,
Menu,
Popover, Popover,
Stack, Stack,
Switch, Switch,
@@ -10,7 +11,8 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { useCommonHooks } from 'hooks'; import { useCommonHooks } from 'hooks';
import { useState } from 'react'; import { useState } from 'react';
import { FiHelpCircle, FiLoader, FiPlus, FiX } from 'react-icons/fi'; import { FaSort } from 'react-icons/fa';
import { FiHelpCircle, FiPlus, FiX } from 'react-icons/fi';
import { import {
actionIconClasses, actionIconClasses,
buttonClasses, buttonClasses,
@@ -31,6 +33,8 @@ export function SettingsHeader({
searchPlaceholder, searchPlaceholder,
isLoading, isLoading,
setIsLoading, setIsLoading,
sortBy,
setSortBy,
}: { }: {
setDragging: undefined | ((dragging: boolean) => void); setDragging: undefined | ((dragging: boolean) => void);
openModal: undefined | (() => void); openModal: undefined | (() => void);
@@ -45,6 +49,8 @@ export function SettingsHeader({
searchPlaceholder: string | undefined; searchPlaceholder: string | undefined;
isLoading: boolean; isLoading: boolean;
setIsLoading: (isLoading: boolean) => void; setIsLoading: (isLoading: boolean) => void;
sortBy: string;
setSortBy: undefined | ((sortBy: string) => void);
}) { }) {
const { isMobile, theme } = useCommonHooks(); const { isMobile, theme } = useCommonHooks();
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
@@ -123,50 +129,141 @@ export function SettingsHeader({
)} )}
</Group> </Group>
{searchValue !== undefined && ( {searchValue !== undefined && (
<DebouncedInput <Group
disabled={disabled} gap={4}
w={isMobile ? undefined : '35%'} w={isMobile ? undefined : '35%'}
pos={isMobile ? undefined : 'absolute'} pos={isMobile ? undefined : 'absolute'}
className={textInputClasses.default} >
classNames={textInputClasses} <DebouncedInput
autoFocus={!isMobile} disabled={disabled}
placeholder={searchPlaceholder ?? 'Search...'} w={'90%'}
value={searchValue} // pos={isMobile ? undefined : 'absolute'}
onInput={() => setIsLoading(true)} className={textInputClasses.default}
onChange={(value: any) => { classNames={textInputClasses}
setSearchValue(value); autoFocus={!isMobile}
}} placeholder={searchPlaceholder ?? 'Search...'}
style={{ value={searchValue}
zIndex: 100, onInput={() => setIsLoading(true)}
}} onChange={(value: any) => {
styles={ setSearchValue(value);
theme.textShadowsEnabled }}
? { style={{
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' }, zIndex: 100,
} }}
: undefined styles={
} theme.textShadowsEnabled
rightSection={ ? {
isLoading ? ( label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
<ActionIcon }
className={actionIconClasses.loadingSpinner} : undefined
classNames={actionIconClasses} }
> rightSection={
<FiLoader /> isLoading ? (
</ActionIcon>
) : (
searchValue && (
<ActionIcon <ActionIcon
className={actionIconClasses.settingsTooltip} className={actionIconClasses.loadingSpinner}
classNames={actionIconClasses} classNames={actionIconClasses}
onClick={() => setSearchValue('')} />
> ) : (
<FiX /> searchValue && (
</ActionIcon> <ActionIcon
className={actionIconClasses.settingsTooltip}
classNames={actionIconClasses}
onClick={() => setSearchValue('')}
>
<FiX />
</ActionIcon>
)
) )
) }
} />
/> {setSortBy !== undefined && (
<Menu>
<Menu.Target>
<ActionIcon>
<FaSort />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Sort By</Menu.Label>
<Menu.Item
onClick={() => {
setSortBy('A>Z');
}}
style={{
color:
sortBy === 'A>Z'
? 'var(--mantine-color-background-0)'
: 'var(--mantine-color-primary-0)',
backgroundColor:
sortBy === 'A>Z' ? 'var(--mantine-color-primary-0)' : undefined,
}}
>
{'A>Z'}
</Menu.Item>
<Menu.Item
onClick={() => {
setSortBy('Z>A');
}}
style={{
color:
sortBy === 'Z>A'
? 'var(--mantine-color-background-0)'
: 'var(--mantine-color-primary-0)',
backgroundColor:
sortBy === 'Z>A' ? 'var(--mantine-color-primary-0)' : undefined,
}}
>
{'Z>A'}
</Menu.Item>
<Menu.Item
onClick={() => {
setSortBy('Newest');
}}
style={{
color:
sortBy === 'Newest'
? 'var(--mantine-color-background-0)'
: 'var(--mantine-color-primary-0)',
backgroundColor:
sortBy === 'Newest' ? 'var(--mantine-color-primary-0)' : undefined,
}}
>
{'Newest'}
</Menu.Item>
<Menu.Item
onClick={() => {
setSortBy('Oldest');
}}
style={{
color:
sortBy === 'Oldest'
? 'var(--mantine-color-background-0)'
: 'var(--mantine-color-primary-0)',
backgroundColor:
sortBy === 'Oldest' ? 'var(--mantine-color-primary-0)' : undefined,
}}
>
{'Oldest'}
</Menu.Item>
<Menu.Item
onClick={() => {
setSortBy('Custom');
}}
style={{
color:
sortBy === 'Custom'
? 'var(--mantine-color-background-0)'
: 'var(--mantine-color-primary-0)',
backgroundColor:
sortBy === 'Custom' ? 'var(--mantine-color-primary-0)' : undefined,
}}
>
{'Custom'}
</Menu.Item>
</Menu.Dropdown>
</Menu>
)}
</Group>
)} )}
{setDragging && ( {setDragging && (
<Switch <Switch

View File

@@ -27,6 +27,7 @@ import type { Application } from 'types';
import DebouncedApplication from './DebouncedApplication'; import DebouncedApplication from './DebouncedApplication';
export function Applications() { export function Applications() {
const [sortBy, setSortBy] = useState('Custom');
const allIcons: any = { ...fa6, ...vsc, ...si }; const allIcons: any = { ...fa6, ...vsc, ...si };
const defaultApplication: Application = { const defaultApplication: Application = {
id: null, id: null,
@@ -38,6 +39,7 @@ export function Applications() {
index: 0, index: 0,
enabled: 1, enabled: 1,
}; };
const [dragging, setDragging] = useState(false);
const [searchValue, setSearchValue] = useState<string | number>(''); const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const form = useForm({ const form = useForm({
@@ -54,19 +56,41 @@ export function Applications() {
const { data: applicationData, isFetching: applicationDataIsFetching } = useQueryAPI({ const { data: applicationData, isFetching: applicationDataIsFetching } = useQueryAPI({
endpoint: appsEndpoint, endpoint: appsEndpoint,
}); });
const applications: Application[] = const applications: Application[] = applicationData
applicationData ?.filter(
?.filter( (a: Application) =>
(a: Application) => a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || a.baseUrl.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
a.baseUrl.toLowerCase().includes((debouncedSearch as string).toLowerCase()), )
) .sort((a: Application, b: Application) => {
.sort((a: Application, b: Application) => (a.index > b.index ? 1 : -1)) || []; if (!dragging) {
switch (sortBy) {
case 'A>Z':
return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1;
case 'Z>A':
return a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase() ? 1 : -1;
case 'Newest':
if (a.id && b.id) {
return a.id < b.id ? 1 : -1;
}
return -1;
case 'Oldest':
if (a.id && b.id) {
return a.id > b.id ? 1 : -1;
}
return -1;
case 'Custom':
return a.index > b.index ? 1 : -1;
default:
return 0;
}
}
return a.index > b.index ? 1 : -1;
});
const [applicationList, setApplicationList] = useState<Application[]>([]); const [applicationList, setApplicationList] = useState<Application[]>([]);
const { isMobile, reorder, theme } = useCommonHooks(); const { isMobile, reorder, theme } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [dragging, setDragging] = useState(false);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -104,7 +128,7 @@ export function Applications() {
useEffect(() => { useEffect(() => {
if (applicationList.length > 0 && applicationList !== applications) { if (applicationList.length > 0 && applicationList !== applications) {
mutateAsync({ void mutateAsync({
method: 'PATCH', method: 'PATCH',
endpoint: appsOrderEndpoint, endpoint: appsOrderEndpoint,
body: applicationList, body: applicationList,
@@ -114,7 +138,7 @@ export function Applications() {
return ( return (
<Stack> <Stack>
<SettingsHeader <SettingsHeader
setDragging={applications.length > 0 ? setDragging : undefined} setDragging={applications?.length > 0 ? setDragging : undefined}
openModal={openModal} openModal={openModal}
title={'Applications'} title={'Applications'}
tooltip={{ tooltip={{
@@ -140,6 +164,8 @@ export function Applications() {
searchPlaceholder={'Search Applications...'} searchPlaceholder={'Search Applications...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={sortBy}
setSortBy={setSortBy}
/> />
{modalOpened && ( {modalOpened && (
<AddItemModal <AddItemModal
@@ -149,12 +175,12 @@ export function Applications() {
body={ body={
<form <form
onSubmit={form.onSubmit((values) => { onSubmit={form.onSubmit((values) => {
mutateAsync({ void mutateAsync({
method: 'POST', method: 'POST',
endpoint: appsEndpoint, endpoint: appsEndpoint,
body: { body: {
...values, ...values,
index: applications.length, index: applications?.length,
}, },
}); });
handleClose(); handleClose();
@@ -318,7 +344,7 @@ export function Applications() {
size: 'xl', size: 'xl',
}} }}
/> />
{applications.length > 0 ? ( {applications?.length > 0 ? (
<Droppable droppableId={'groupList'}> <Droppable droppableId={'groupList'}>
{(provided) => ( {(provided) => (
<Grid <Grid

View File

@@ -111,7 +111,7 @@ export function Bookmarks() {
useEffect(() => { useEffect(() => {
if (groupList.length > 0 && groupList !== groups) { if (groupList.length > 0 && groupList !== groups) {
mutateAsync({ void mutateAsync({
method: 'PATCH', method: 'PATCH',
endpoint: 'api/bookmarkCategoryOrder', endpoint: 'api/bookmarkCategoryOrder',
body: groupList, body: groupList,
@@ -129,7 +129,7 @@ export function Bookmarks() {
body={ body={
<form <form
onSubmit={form.onSubmit((values) => { onSubmit={form.onSubmit((values) => {
mutateAsync({ void mutateAsync({
method: 'POST', method: 'POST',
endpoint: categoriesEndpoint, endpoint: categoriesEndpoint,
body: { body: {
@@ -221,6 +221,8 @@ export function Bookmarks() {
searchPlaceholder={'Search Bookmarks...'} searchPlaceholder={'Search Bookmarks...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''}
setSortBy={undefined}
/> />
{dragging ? ( {dragging ? (
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>

View File

@@ -80,7 +80,7 @@ export function GuacamoleClients() {
body={ body={
<form <form
onSubmit={form.onSubmit((values) => { onSubmit={form.onSubmit((values) => {
mutateAsync({ void mutateAsync({
method: 'POST', method: 'POST',
endpoint: endpoint, endpoint: endpoint,
body: { ...values, enabled: 1 }, body: { ...values, enabled: 1 },
@@ -210,6 +210,8 @@ export function GuacamoleClients() {
searchPlaceholder={'Search Clients...'} searchPlaceholder={'Search Clients...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''}
setSortBy={undefined}
/> />
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}

View File

@@ -49,7 +49,7 @@ export function Messages() {
useEffect(() => { useEffect(() => {
if (Object.keys(debounced).length) { if (Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }); void mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
} }
}, [debounced]); }, [debounced]);
@@ -82,6 +82,8 @@ export function Messages() {
searchPlaceholder={'Search Messages...'} searchPlaceholder={'Search Messages...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''}
setSortBy={undefined}
/> />
{modalOpened && ( {modalOpened && (
<AddItemModal <AddItemModal
@@ -91,7 +93,7 @@ export function Messages() {
body={ body={
<form <form
onSubmit={form.onSubmit((values) => { onSubmit={form.onSubmit((values) => {
mutateAsync({ void mutateAsync({
method: 'POST', method: 'POST',
endpoint: endpoint, endpoint: endpoint,
body: { message: values.message }, body: { message: values.message },

View File

@@ -37,7 +37,7 @@ export function Personalization() {
const { isMobile, theme } = useCommonHooks(); const { isMobile, theme } = useCommonHooks();
useEffect(() => { useEffect(() => {
if (debounced && Object.keys(debounced).length) { if (debounced && Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced }); void mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
setUpdate(undefined); setUpdate(undefined);
} }
}, [debounced]); }, [debounced]);
@@ -87,6 +87,8 @@ export function Personalization() {
searchPlaceholder={undefined} searchPlaceholder={undefined}
isLoading={false} isLoading={false}
setIsLoading={() => {}} setIsLoading={() => {}}
sortBy={''}
setSortBy={undefined}
/> />
<Stack align='center' mt={'6rem'} pos={'relative'}> <Stack align='center' mt={'6rem'} pos={'relative'}>
<LoadingOverlay <LoadingOverlay

View File

@@ -86,6 +86,8 @@ export function SearchHistory() {
searchPlaceholder={'Search your Search History...'} searchPlaceholder={'Search your Search History...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''}
setSortBy={undefined}
/> />
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}

View File

@@ -96,7 +96,7 @@ export function SearchProviders() {
body={ body={
<form <form
onSubmit={form.onSubmit((values) => { onSubmit={form.onSubmit((values) => {
mutateAsync({ void mutateAsync({
method: 'POST', method: 'POST',
endpoint: endpoint, endpoint: endpoint,
body: values, body: values,
@@ -281,6 +281,8 @@ export function SearchProviders() {
searchPlaceholder={'Search Providers...'} searchPlaceholder={'Search Providers...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''}
setSortBy={undefined}
/> />
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}

View File

@@ -73,7 +73,7 @@ export function WolClients() {
body={ body={
<form <form
onSubmit={form.onSubmit((values) => { onSubmit={form.onSubmit((values) => {
mutateAsync({ void mutateAsync({
method: 'POST', method: 'POST',
endpoint: endpoint, endpoint: endpoint,
body: values, body: values,
@@ -151,6 +151,8 @@ export function WolClients() {
searchPlaceholder={'Search Clients...'} searchPlaceholder={'Search Clients...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''}
setSortBy={undefined}
/> />
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}