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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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