Updates to settings to allow sorting
All checks were successful
Deploy to production / build (push) Successful in 20s

This commit is contained in:
2026-10-13 11:37:27 -05:00
parent 01dd6b1aa2
commit e4b07a3907
10 changed files with 220 additions and 50 deletions

View File

@@ -35,6 +35,7 @@ export function SettingsHeader({
setIsLoading, setIsLoading,
sortBy, sortBy,
setSortBy, setSortBy,
customSearchEnabled,
}: { }: {
setDragging: undefined | ((dragging: boolean) => void); setDragging: undefined | ((dragging: boolean) => void);
openModal: undefined | (() => void); openModal: undefined | (() => void);
@@ -51,6 +52,7 @@ export function SettingsHeader({
setIsLoading: (isLoading: boolean) => void; setIsLoading: (isLoading: boolean) => void;
sortBy: string; sortBy: string;
setSortBy: undefined | ((sortBy: string) => void); setSortBy: undefined | ((sortBy: string) => void);
customSearchEnabled: boolean;
}) { }) {
const { isMobile, theme } = useCommonHooks(); const { isMobile, theme } = useCommonHooks();
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
@@ -245,21 +247,25 @@ export function SettingsHeader({
> >
{'Oldest'} {'Oldest'}
</Menu.Item> </Menu.Item>
<Menu.Item {customSearchEnabled && (
onClick={() => { <Menu.Item
setSortBy('Custom'); onClick={() => {
}} setSortBy('Custom');
style={{ }}
color: style={{
sortBy === 'Custom' color:
? 'var(--mantine-color-background-0)' sortBy === 'Custom'
: 'var(--mantine-color-primary-0)', ? 'var(--mantine-color-background-0)'
backgroundColor: : 'var(--mantine-color-primary-0)',
sortBy === 'Custom' ? 'var(--mantine-color-primary-0)' : undefined, backgroundColor:
}} sortBy === 'Custom'
> ? 'var(--mantine-color-primary-0)'
{'Custom'} : undefined,
</Menu.Item> }}
>
{'Custom'}
</Menu.Item>
)}
</Menu.Dropdown> </Menu.Dropdown>
</Menu> </Menu>
)} )}

View File

@@ -166,6 +166,7 @@ export function Applications() {
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={sortBy} sortBy={sortBy}
setSortBy={setSortBy} setSortBy={setSortBy}
customSearchEnabled
/> />
{modalOpened && ( {modalOpened && (
<AddItemModal <AddItemModal

View File

@@ -19,6 +19,7 @@ import type { Bookmark, BookmarkGroup } from 'types';
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem'; import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
export function Bookmarks() { export function Bookmarks() {
const [sortBy, setSortBy] = useState('Custom');
const defaultGroup = { const defaultGroup = {
id: null, id: null,
categoryName: '', categoryName: '',
@@ -32,6 +33,7 @@ export function Bookmarks() {
filterName: isNotEmpty('Filter name is required'), filterName: isNotEmpty('Filter name is required'),
}, },
}); });
const [dragging, setDragging] = useState(false);
const bookmarksEndpoint = 'api/bookmarks'; const bookmarksEndpoint = 'api/bookmarks';
const { data: bookmarkData, isFetching: bookmarkDataIsFetching } = useQueryAPI({ const { data: bookmarkData, isFetching: bookmarkDataIsFetching } = useQueryAPI({
endpoint: bookmarksEndpoint, endpoint: bookmarksEndpoint,
@@ -41,7 +43,6 @@ export function Bookmarks() {
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({ const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
endpoint: categoriesEndpoint, endpoint: categoriesEndpoint,
}); });
const [dragging, setDragging] = useState(false);
const [groups, setGroups] = useState<BookmarkGroup[]>([]); const [groups, setGroups] = useState<BookmarkGroup[]>([]);
const [groupList, setGroupList] = useState<BookmarkGroup[]>([]); const [groupList, setGroupList] = useState<BookmarkGroup[]>([]);
const { isMobile, reorder, theme } = useCommonHooks(); const { isMobile, reorder, theme } = useCommonHooks();
@@ -71,6 +72,7 @@ export function Bookmarks() {
const foundGroups: BookmarkGroup[] = groupData?.filter((a: BookmarkGroup) => const foundGroups: BookmarkGroup[] = groupData?.filter((a: BookmarkGroup) =>
a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()), a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()),
); );
const handleClose = () => { const handleClose = () => {
closeModal(); closeModal();
form.reset(); form.reset();
@@ -99,15 +101,44 @@ export function Bookmarks() {
useEffect(() => { useEffect(() => {
if (groupData && groupsIsFetched) { if (groupData && groupsIsFetched) {
const groupList = [...new Set([...foundBookmarkGroups, ...foundGroups])].sort( const groupList = [...new Set([...foundBookmarkGroups, ...foundGroups])].sort(
(a: BookmarkGroup, b: BookmarkGroup) => (a: BookmarkGroup, b: BookmarkGroup) => {
a.categoryIndex > b.categoryIndex ? 1 : -1, if (!dragging) {
switch (sortBy) {
case 'A>Z':
return a.categoryName.toLocaleLowerCase() >
b.categoryName.toLocaleLowerCase()
? 1
: -1;
case 'Z>A':
return a.categoryName.toLocaleLowerCase() <
b.categoryName.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.categoryIndex > b.categoryIndex ? 1 : -1;
default:
return 0;
}
}
return a.categoryIndex > b.categoryIndex ? 1 : -1;
},
); );
setGroups(groupList); setGroups(groupList);
} }
if (debouncedGroupSearch === searchValue) { if (debouncedGroupSearch === searchValue) {
setIsLoading(false); setIsLoading(false);
} }
}, [groupData, groupsIsFetched, debouncedGroupSearch]); }, [groupData, groupsIsFetched, debouncedGroupSearch, sortBy, dragging]);
useEffect(() => { useEffect(() => {
if (groupList.length > 0 && groupList !== groups) { if (groupList.length > 0 && groupList !== groups) {
@@ -221,8 +252,9 @@ export function Bookmarks() {
searchPlaceholder={'Search Bookmarks...'} searchPlaceholder={'Search Bookmarks...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''} sortBy={sortBy}
setSortBy={undefined} setSortBy={setSortBy}
customSearchEnabled
/> />
{dragging ? ( {dragging ? (
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>

View File

@@ -23,6 +23,7 @@ import DebouncedGuacamoleClient from './DebouncedGuacamoleClient';
import GuacamoleClientSettingsItem from './GuacamoleClientSettingsItem'; import GuacamoleClientSettingsItem from './GuacamoleClientSettingsItem';
export function GuacamoleClients() { export function GuacamoleClients() {
const [sortBy, setSortBy] = useState('Oldest');
const defaultGuacamoleClient: GuacamoleClient = { const defaultGuacamoleClient: GuacamoleClient = {
id: undefined, id: undefined,
clientName: '', clientName: '',
@@ -46,13 +47,40 @@ export function GuacamoleClients() {
endpoint: endpoint, endpoint: endpoint,
}); });
const guacamoleClients: GuacamoleClient[] = const guacamoleClients: GuacamoleClient[] =
guacamoleClientsData.filter( guacamoleClientsData
(a: GuacamoleClient) => .filter(
a.clientName.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || (a: GuacamoleClient) =>
a.clientIdentifier a.clientName
.toLowerCase() .toLowerCase()
.includes((debouncedSearch as string).toLowerCase()), .includes((debouncedSearch as string).toLowerCase()) ||
) || []; a.clientIdentifier
.toLowerCase()
.includes((debouncedSearch as string).toLowerCase()),
)
.sort((a: GuacamoleClient, b: GuacamoleClient) => {
switch (sortBy) {
case 'A>Z':
return a.clientName.toLocaleLowerCase() > b.clientName.toLocaleLowerCase()
? 1
: -1;
case 'Z>A':
return a.clientName.toLocaleLowerCase() < b.clientName.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;
default:
return 0;
}
}) || [];
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const { isMobile, theme } = useCommonHooks(); const { isMobile, theme } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
@@ -210,8 +238,9 @@ export function GuacamoleClients() {
searchPlaceholder={'Search Clients...'} searchPlaceholder={'Search Clients...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''} sortBy={sortBy}
setSortBy={undefined} setSortBy={setSortBy}
customSearchEnabled={false}
/> />
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}

View File

@@ -29,14 +29,36 @@ export function Messages() {
message: isNotEmpty('Message is required'), message: isNotEmpty('Message is required'),
}, },
}); });
const [sortBy, setSortBy] = useState('Oldest');
const [searchValue, setSearchValue] = useState<string | number>(''); const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const { isMobile, theme } = useCommonHooks(); const { isMobile, theme } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const messages: Message[] = const messages: Message[] =
messageData?.filter((m: Message) => messageData
m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()), ?.filter((m: Message) =>
) || []; m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
)
.sort((a: Message, b: Message) => {
switch (sortBy) {
case 'A>Z':
return a.message.toLocaleLowerCase() > b.message.toLocaleLowerCase() ? 1 : -1;
case 'Z>A':
return a.message.toLocaleLowerCase() < b.message.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;
default:
return 0;
}
}) || [];
const [message, setMessage] = useState({}); const [message, setMessage] = useState({});
const [debounced] = useDebouncedValue(message, 1000); const [debounced] = useDebouncedValue(message, 1000);
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
@@ -82,8 +104,9 @@ export function Messages() {
searchPlaceholder={'Search Messages...'} searchPlaceholder={'Search Messages...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''} sortBy={sortBy}
setSortBy={undefined} setSortBy={setSortBy}
customSearchEnabled={false}
/> />
{modalOpened && ( {modalOpened && (
<AddItemModal <AddItemModal

View File

@@ -88,6 +88,7 @@ export function SearchHistory() {
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''} sortBy={''}
setSortBy={undefined} setSortBy={undefined}
customSearchEnabled={false}
/> />
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}

View File

@@ -36,6 +36,7 @@ import DebouncedSearchProvider from './DebouncedSearchProvider';
import SearchProviderSettingsItem from './SearchProviderSettingsItem'; import SearchProviderSettingsItem from './SearchProviderSettingsItem';
export function SearchProviders() { export function SearchProviders() {
const [sortBy, setSortBy] = useState('Oldest');
const allIcons: any = { ...fa6, ...vsc, ...si }; const allIcons: any = { ...fa6, ...vsc, ...si };
const defaultProvider: Provider = { const defaultProvider: Provider = {
id: undefined, id: undefined,
@@ -68,7 +69,26 @@ export function SearchProviders() {
a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || a.url.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.prefix.toLowerCase().includes((debouncedSearch as string).toLowerCase()), a.prefix.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
) )
.sort((a: Provider, b: Provider) => (a.prefix > b.prefix ? 1 : -1)) ?? []; .sort((a: Provider, b: Provider) => {
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;
default:
return 0;
}
}) ?? [];
const { isMobile, theme, iconFilter } = useCommonHooks(); const { isMobile, theme, iconFilter } = useCommonHooks();
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
@@ -281,8 +301,9 @@ export function SearchProviders() {
searchPlaceholder={'Search Providers...'} searchPlaceholder={'Search Providers...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''} sortBy={sortBy}
setSortBy={undefined} setSortBy={setSortBy}
customSearchEnabled={false}
/> />
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}

View File

@@ -58,7 +58,7 @@ export function ThemePlaylists() {
}, [close]); }, [close]);
const handleSavePlaylist = useCallback(() => { const handleSavePlaylist = useCallback(() => {
mutateAsync({ void mutateAsync({
method: selectedPlaylist?.id === undefined ? 'POST' : 'PATCH', method: selectedPlaylist?.id === undefined ? 'POST' : 'PATCH',
endpoint: themePlaylistsEndpoint, endpoint: themePlaylistsEndpoint,
body: selectedPlaylist ?? {}, body: selectedPlaylist ?? {},
@@ -162,6 +162,9 @@ export function ThemePlaylists() {
searchPlaceholder={'Search Theme Playlists...'} searchPlaceholder={'Search Theme Playlists...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''}
setSortBy={undefined}
customSearchEnabled={false}
/> />
<Stack mt={isMobile ? '10rem' : '6rem'}> <Stack mt={isMobile ? '10rem' : '6rem'}>
<LoadingOverlay <LoadingOverlay

View File

@@ -28,11 +28,33 @@ export function Themes() {
const { data: allThemeData, isFetching: allThemeDataIsFetching } = useQueryAPI({ const { data: allThemeData, isFetching: allThemeDataIsFetching } = useQueryAPI({
endpoint: 'api/themes', endpoint: 'api/themes',
}); });
const [sortBy, setSortBy] = useState('Oldest');
const [searchValue, setSearchValue] = useState<string | number>(''); const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const allThemes: Theme[] = allThemeData?.filter((a: Theme) => const allThemes: Theme[] = allThemeData
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()), ?.filter((a: Theme) =>
); a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
)
.sort((a: Theme, b: Theme) => {
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;
default:
return 0;
}
});
const { data: backgroundData, isFetching: backgroundDataIsFetching } = useQueryAPI({ const { data: backgroundData, isFetching: backgroundDataIsFetching } = useQueryAPI({
endpoint: backgroundsEndpoint, endpoint: backgroundsEndpoint,
}); });
@@ -139,6 +161,9 @@ export function Themes() {
searchPlaceholder={'Search Themes...'} searchPlaceholder={'Search Themes...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={sortBy}
setSortBy={setSortBy}
customSearchEnabled={false}
/> />
<Stack mt={isMobile ? '11rem' : '7rem'} align='center'> <Stack mt={isMobile ? '11rem' : '7rem'} align='center'>
<LoadingOverlay <LoadingOverlay
@@ -296,7 +321,9 @@ export function Themes() {
<Button <Button
onClick={() => { onClick={() => {
if (currentThemePlaylist === 'All') { if (currentThemePlaylist === 'All') {
navigate(`/${import.meta.env.VITE_BASE_URL}/settings/themePlaylists`); void navigate(
`/${import.meta.env.VITE_BASE_URL}/settings/themePlaylists`,
);
} else { } else {
setCurrentThemePlaylist('All'); setCurrentThemePlaylist('All');
setCurrentTheme(theme.name); setCurrentTheme(theme.name);

View File

@@ -38,14 +38,40 @@ export function WolClients() {
const { data: wolClientsData, isFetching: wolClientsIsFetching } = useQueryAPI({ const { data: wolClientsData, isFetching: wolClientsIsFetching } = useQueryAPI({
endpoint: endpoint, endpoint: endpoint,
}); });
const [sortBy, setSortBy] = useState('Oldest');
const [searchValue, setSearchValue] = useState<string | number>(''); const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500); const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const wolClients: WolClient[] = const wolClients: WolClient[] =
wolClientsData.filter( wolClientsData
(a: WolClient) => .filter(
a.hostname.toLowerCase().includes((debouncedSearch as string).toLowerCase()) || (a: WolClient) =>
a.macAddress.toLowerCase().includes((debouncedSearch as string).toLowerCase()), a.hostname.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
) || []; a.macAddress.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
)
.sort((a: WolClient, b: WolClient) => {
switch (sortBy) {
case 'A>Z':
return a.hostname.toLocaleLowerCase() > b.hostname.toLocaleLowerCase()
? 1
: -1;
case 'Z>A':
return a.hostname.toLocaleLowerCase() < b.hostname.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;
default:
return 0;
}
}) || [];
const { mutateAsync } = useMutationAPI(); const { mutateAsync } = useMutationAPI();
const { isMobile, theme } = useCommonHooks(); const { isMobile, theme } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false); const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
@@ -151,8 +177,9 @@ export function WolClients() {
searchPlaceholder={'Search Clients...'} searchPlaceholder={'Search Clients...'}
isLoading={isLoading} isLoading={isLoading}
setIsLoading={setIsLoading} setIsLoading={setIsLoading}
sortBy={''} sortBy={sortBy}
setSortBy={undefined} setSortBy={setSortBy}
customSearchEnabled={false}
/> />
<Stack <Stack
mt={isMobile ? '10rem' : '6rem'} mt={isMobile ? '10rem' : '6rem'}