Search bar fixes and performance updates

This commit is contained in:
Sean Edmunds
2026-01-08 12:53:50 -06:00
parent 38285943cd
commit 93ea28f968
20 changed files with 376 additions and 543 deletions

24
package-lock.json generated
View File

@@ -24,7 +24,7 @@
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-icons": "^5.5.0",
"react-router-dom": "^7.11.0",
"react-router-dom": "^7.12.0",
"recharts": "^3.6.0"
},
"devDependencies": {
@@ -35,7 +35,7 @@
"@vitest/coverage-v8": "^4.0.16",
"@vitest/ui": "^4.0.16",
"typescript": "^5.9.3",
"vite": "^7.3.0",
"vite": "^7.3.1",
"vite-plugin-dts": "~4.5.4",
"vite-tsconfig-paths": "^6.0.3",
"vitest": "^4.0.16"
@@ -3486,9 +3486,9 @@
}
},
"node_modules/react-router": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
"integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
"integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
@@ -3507,11 +3507,11 @@
}
},
"node_modules/react-router-dom": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz",
"integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==",
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz",
"integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==",
"dependencies": {
"react-router": "7.11.0"
"react-router": "7.12.0"
},
"engines": {
"node": ">=20.0.0"
@@ -4110,9 +4110,9 @@
}
},
"node_modules/vite": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"dependencies": {
"esbuild": "^0.27.0",

View File

@@ -1,7 +1,7 @@
{
"name": "triggerfrontend",
"private": true,
"version": "1.0.0",
"version": "1.1.0",
"type": "module",
"scripts": {
"dev": "vite",
@@ -26,7 +26,7 @@
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-icons": "^5.5.0",
"react-router-dom": "^7.11.0",
"react-router-dom": "^7.12.0",
"recharts": "^3.6.0"
},
"devDependencies": {
@@ -37,7 +37,7 @@
"@vitest/coverage-v8": "^4.0.16",
"@vitest/ui": "^4.0.16",
"typescript": "^5.9.3",
"vite": "^7.3.0",
"vite": "^7.3.1",
"vite-plugin-dts": "~4.5.4",
"vite-tsconfig-paths": "^6.0.3",
"vitest": "^4.0.16"

View File

@@ -41,11 +41,7 @@ export function Homepage() {
const { data: personalData, isFetched: personalIsFetched } = useQueryAPI({
endpoint: 'api/personalData',
});
const {
data: messageData,
isFetching: isMessagesFetching,
isFetched: isMessagesFetched,
} = useQueryAPI({
const { data: messageData, isFetching: isMessagesFetching } = useQueryAPI({
endpoint: 'api/displayedGreeting',
});
const { data: weatherMetadata } = useOSMAPI({
@@ -80,8 +76,8 @@ export function Homepage() {
y: points?.properties.gridY ?? currentLocation.y,
});
const [personal, setPersonal] = useState<PersonalData>();
const [message, setMessage] = useState<Message>();
const personal: PersonalData = personalData?.[0];
const message: Message = messageData?.[0];
const [displayedGreeting, setDisplayedGreeting] = useState<string | undefined>('');
const [current, setCurrent] = useState<CW>({
icon: '',
@@ -110,15 +106,9 @@ export function Homepage() {
const [weatherModalOpened, { open: openWeatherModal, close: closeWeatherModal }] =
useDisclosure(false);
useEffect(() => {
if (messageData && isMessagesFetched) {
setMessage(messageData[0]);
}
}, [messageData, isMessagesFetched]);
useEffect(() => {
if (personalData && personalIsFetched) {
setCurrentUser(personalData[0]);
setPersonal(personalData[0]);
}
}, [personalData, personalIsFetched]);
useEffect(() => {
@@ -220,7 +210,7 @@ export function Homepage() {
closeWeatherModal={closeWeatherModal}
openWeatherModal={openWeatherModal}
position='top'
personal={personal as PersonalData}
personal={personal}
/>
<Header
greet={displayedGreeting}
@@ -246,7 +236,7 @@ export function Homepage() {
closeWeatherModal={closeWeatherModal}
openWeatherModal={openWeatherModal}
position='center'
personal={personal as PersonalData}
personal={personal}
/>
</Box>
)}

View File

@@ -1,22 +1,16 @@
import { Stack, Text } from '@mantine/core';
import { AppItem } from 'components';
import { useCommonHooks, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { textClasses } from 'styles';
import type { Application } from 'types';
function Applications({ contentType }: { contentType: string[] }) {
const { data: applicationData, isFetching: isApplicationDataFetching } = useQueryAPI({
const { data: applicationData } = useQueryAPI({
endpoint: 'api/enabledApplications',
});
const { appSort, appDensity, appScale, theme, textShadowStyle } = useCommonHooks();
const [applications, setApplications] = useState<Application[]>([]);
useEffect(() => {
if (applicationData && !isApplicationDataFetching) {
setApplications(
applicationData
.sort((a: Application, b: Application) => {
const applications = applicationData
?.sort((a: Application, b: Application) => {
switch (appSort) {
case 'A>Z':
return a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase() ? 1 : -1;
@@ -41,14 +35,11 @@ function Applications({ contentType }: { contentType: string[] }) {
?.filter(
(application: Application) =>
contentType.includes(application.filterName) || contentType.includes('All'),
),
);
}
}, [applicationData, isApplicationDataFetching, appSort, contentType]);
return (
<>
{applications.length > 0 && contentType.length > 0 && (
{applications?.length > 0 && contentType.length > 0 && (
<Stack gap={0}>
<Text
className={textClasses.sectionTitle}
@@ -66,7 +57,7 @@ function Applications({ contentType }: { contentType: string[] }) {
gridTemplateColumns: `repeat(${appDensity}, 1fr)`,
}}
>
{applications.map((application: Application) => {
{applications?.map((application: Application) => {
return (
<AppItem
key={application.name}

View File

@@ -1,6 +1,5 @@
import { Stack, Text } from '@mantine/core';
import { useCommonHooks, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { textClasses } from 'styles';
import type { Bookmark, BookmarkGroup } from 'types';
import BookmarkGroups from './BookmarkGroups';
@@ -14,32 +13,19 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
});
const { bookmarkSort, bookmarkDensity, bookmarkScale, theme, textShadowStyle } =
useCommonHooks();
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
useEffect(() => {
if (bookmarkData && bookmarksIsFetched) {
setBookmarks(bookmarkData);
}
}, [bookmarksIsFetched, bookmarkData]);
useEffect(() => {
if (groupData && groupsIsFetched) {
const groupList = groupData?.map((category: BookmarkGroup) => {
return category;
});
setGroups(
groupList
.sort((a: BookmarkGroup, b: BookmarkGroup) => {
const bookmarks = bookmarkData || [];
const groups = groupList
?.sort((a: BookmarkGroup, b: BookmarkGroup) => {
switch (bookmarkSort) {
case 'A>Z':
return a.categoryName.toLocaleLowerCase() >
b.categoryName.toLocaleLowerCase()
return a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase()
? 1
: -1;
case 'Z>A':
return a.categoryName.toLocaleLowerCase() <
b.categoryName.toLocaleLowerCase()
return a.categoryName.toLocaleLowerCase() < b.categoryName.toLocaleLowerCase()
? 1
: -1;
case 'Newest':
@@ -61,10 +47,7 @@ function Bookmarks({ contentType }: { contentType: string[] }) {
?.filter(
(category: BookmarkGroup) =>
contentType.includes(category.filterName) || contentType.includes('All'),
),
);
}
}, [groupData, groupsIsFetched, bookmarkSort, contentType]);
return (
<>

View File

@@ -49,46 +49,7 @@ export default function Search({
}) {
const { ref, height } = useElementSize();
const allIcons: any = { ...fa6, ...vsc, ...si };
const { data: providerData, isFetched: providerDataIsFetched } = useQueryAPI({
endpoint: 'api/providers',
});
const { data: applicationData, isFetching: isApplicationDataFetching } = useQueryAPI({
endpoint: 'api/enabledApplications',
});
const { data: guacamoleHostData, isFetched: guacamoleHostDataIsFetched } = useQueryAPI({
endpoint: 'api/guacamoleHost',
});
const { data: guacamoleClientData, isFetched: guacamoleClientDataIsFetched } =
useQueryAPI({
endpoint: 'api/guacamoleClients',
});
const { data: bookmarkData, isFetched: bookmarksIsFetched } = useQueryAPI({
endpoint: 'api/bookmarks',
});
const { data: defaultProviderData, isFetched: defaultProviderDataIsFetched } =
useQueryAPI({
endpoint: 'api/providers/default',
});
const [providers, setProviders] = useState<Provider[]>([]);
const [guacamoleHost, setGuacamoleHost] = useState<string | undefined>('');
const [guacamoleClients, setGuacamoleClients] = useState<GuacamoleClient[]>([]);
const [filteredGuacamoleClients, setFilteredGuacamoleClients] = useState<
GuacamoleClient[]
>([]);
const [applications, setApplications] = useState<Application[]>([]);
const [filteredApplications, setFilteredApplications] = useState<Application[]>([]);
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
const searchEndpoint = 'api/searchHistory';
const { data: historyData, isFetched: historyDataIsFetched } = useQueryAPI({
endpoint: searchEndpoint,
});
const [searchHistory, setSearchHistory] = useState<SearchHistoryItem[]>([]);
const [filteredHistory, setFilteredHistory] = useState<SearchHistoryItem[]>([]);
const { mutateAsync } = useMutationAPI();
const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] =
useDisclosure(false);
const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure();
const [listIndex, setListIndex] = useState(-1);
const {
isMobile,
urlPattern,
@@ -111,6 +72,64 @@ export default function Search({
defaultProviderIcon,
setDefaultProviderIcon,
} = useCommonHooks();
const { data: applicationData } = useQueryAPI({
endpoint: 'api/enabledApplications',
});
const { data: bookmarkData } = useQueryAPI({
endpoint: 'api/bookmarks',
});
const { data: guacamoleHostData } = useQueryAPI({
endpoint: 'api/guacamoleHost',
});
const { data: guacamoleClientData } = useQueryAPI({
endpoint: 'api/guacamoleClients',
});
const { data: providerData } = useQueryAPI({
endpoint: 'api/providers',
});
const { data: defaultProviderData, isFetched: defaultProviderDataIsFetched } =
useQueryAPI({
endpoint: 'api/providers/default',
});
const { data: historyData, isFetched: historyDataIsFetched } = useQueryAPI({
endpoint: searchEndpoint,
});
const applications: Application[] = applicationData || [];
const filteredApplications = applications?.filter((app: Application) =>
app.name.toLowerCase().includes((searchValue as string).substring(1).toLowerCase()),
);
const bookmarks: Bookmark[] =
(searchValue as string).startsWith('>') ||
(searchValue as string).startsWith('!') ||
(searchValue as string).startsWith('/')
? bookmarkData?.filter(
(bookmark: Bookmark) =>
bookmark.enabled === 1 &&
bookmark.name
.toLowerCase()
.includes((searchValue as string).substring(1).toLowerCase()),
)
: bookmarkData?.filter(
(bookmark: Bookmark) =>
bookmark.enabled === 1 &&
bookmark.name.toLowerCase().includes((searchValue as string).toLowerCase()),
) || [];
const providers = providerData || [];
const guacamoleHost = guacamoleHostData?.[0]?.guacamoleHost;
const guacamoleClients = guacamoleClientData || [];
const filteredGuacamoleClients =
guacamoleClients.filter((client: GuacamoleClient) =>
client.clientName
.toLowerCase()
.includes((searchValue as string).substring(1).toLowerCase()),
) || [];
const searchHistory = historyData || [];
const [filteredHistory, setFilteredHistory] = useState<SearchHistoryItem[]>([]);
const { mutateAsync } = useMutationAPI();
const [providerModalOpened, { open: openProviderModal, close: closeProviderModal }] =
useDisclosure(false);
const [dropdownOpened, { open: openDropdown, close: closeDropdown }] = useDisclosure();
const [listIndex, setListIndex] = useState(-1);
const [placeholderValue, setPlaceholderValue] = useState<string>('');
const [value, setValue] = useState(0);
const [historyMultiplier, setHistoryMultiplier] = useState(1);
@@ -155,7 +174,7 @@ export default function Search({
} else {
subtext = item.target.value;
}
if (item.key === 'Enter') {
if (item.code === 'Enter') {
if (providerLabel === 'Show help') {
setSearchValue('');
closeDropdown();
@@ -171,9 +190,11 @@ export default function Search({
} else if (providerLabel === 'Show weather') {
openWeatherModal();
} else if (providerLabel.startsWith('Launch')) {
const url = applications.find(
(app: Application) =>
app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1),
const url = applications?.find((app: Application) =>
app.baseUrl
.substring(1)
.toLowerCase()
.startsWith(item.target.value.substr(1).toLowerCase()),
)?.url;
const validUrl =
url?.startsWith('http://') || url?.startsWith('https://')
@@ -181,9 +202,10 @@ export default function Search({
: `http://${url}`;
open(validUrl, '_self');
} else if (providerLabel.startsWith('Connect to')) {
const client = guacamoleClients?.find(
(client: GuacamoleClient) =>
client.clientName.toLowerCase() === item.target.value.toLowerCase().substr(1),
const client = guacamoleClients?.find((client: GuacamoleClient) =>
client.clientName
.toLowerCase()
.startsWith(item.target.value.toLowerCase().substr(1)),
);
open(`${guacamoleHost}/#/client/${client?.clientIdentifier}`, '_self');
} else if (urlPattern.test(item.target.value)) {
@@ -203,7 +225,6 @@ export default function Search({
}
}
};
const handleKeyUp = (item: any) => {
if (item.code === 'Escape') {
setListIndex(-1);
@@ -218,7 +239,7 @@ export default function Search({
openDropdown();
setPlaceholderValue(
searchHistory.length > 0
? `${searchHistory[listIndex].prefix} ${searchHistory[listIndex].searchTerm}`
? `${searchHistory[listIndex ?? 0]?.prefix} ${searchHistory[listIndex ?? 0]?.searchTerm}`
: '',
);
}
@@ -248,16 +269,21 @@ export default function Search({
setValue(searchHistory[listIndex].id ?? 0);
}
} else if (searchValue !== '') {
const filteredList: any = (searchValue as string).startsWith('!')
? filteredApplications
: (searchValue as string).startsWith('>')
? filteredGuacamoleClients
: filteredHistory;
if (item.code === 'ArrowUp') {
if (listIndex === 0) {
setListIndex(filteredHistory.length - 1);
} else if (listIndex >= filteredHistory.length) {
setListIndex(filteredHistory.length - 1);
setListIndex(filteredList.length - 1);
} else if (listIndex >= filteredList.length) {
setListIndex(filteredList.length - 1);
} else {
setListIndex(listIndex - 1);
}
} else if (item.code === 'ArrowDown') {
if (listIndex >= filteredHistory.length - 1) {
if (listIndex >= filteredList.length - 1) {
setListIndex(0);
} else if (value === 0) {
setListIndex(0);
@@ -266,10 +292,17 @@ export default function Search({
setListIndex(listIndex + 1);
}
} else if (item.code === 'ArrowRight' || item.code === 'Tab') {
if (Object.keys(filteredList[listIndex]).includes('provider')) {
setSearchValue(
`${filteredHistory[listIndex].prefix} ${filteredHistory[listIndex].searchTerm}`,
`${filteredList[listIndex].prefix} ${filteredList[listIndex].searchTerm}`,
);
setValue(filteredHistory[listIndex].id ?? 0);
} else if (Object.keys(filteredList[listIndex]).includes('clientName')) {
setSearchValue(`>${filteredList[listIndex].clientName}`);
} else if (Object.keys(filteredList[listIndex]).includes('name')) {
setSearchValue(`!${filteredList[listIndex].name}`);
}
setValue(filteredList[listIndex].id ?? 0);
}
}
}
@@ -301,26 +334,48 @@ export default function Search({
} else if (item.target.value.startsWith('!')) {
if (item.target.value === '!w' || item.target.value === '!weather') {
setProviderLabel('Show weather');
setProviderIcon('FaCloudSun');
} else if (
applications?.find(
(app: Application) =>
app.baseUrl.substring(1).toLowerCase() === item.target.value.substr(1),
applications?.find((app: Application) =>
app.baseUrl.substring(1).toLowerCase().startsWith(item.target.value.substr(1)),
)
) {
setProviderLabel(`Launch ${item.target.value.substr(1)}`);
setProviderLabel(
`Launch ${
applications?.find((app: Application) =>
app.baseUrl
.substring(1)
.toLowerCase()
.startsWith(item.target.value.substr(1)),
)?.name
}`,
);
setProviderIcon('');
} else {
setProviderLabel('Invalid function');
setProviderIcon('');
}
} else if (item.target.value.startsWith('>') && personal.enableGuacamole === 1) {
if (
guacamoleClients?.find(
(client: GuacamoleClient) =>
client.clientName.toLowerCase() === item.target.value.toLowerCase().substr(1),
guacamoleClients?.find((client: GuacamoleClient) =>
client.clientName
.toLowerCase()
.startsWith(item.target.value.toLowerCase().substr(1)),
)
) {
setProviderLabel(`Connect to ${item.target.value.substr(1)}`);
setProviderLabel(
`Connect to ${
guacamoleClients?.find((client: GuacamoleClient) =>
client.clientName
.toLowerCase()
.startsWith(item.target.value.toLowerCase().substr(1)),
)?.clientName
}`,
);
setProviderIcon('');
} else {
setProviderLabel('Invalid connection');
setProviderIcon('');
}
} else {
setProviderLabel(defaultProviderLabel);
@@ -333,11 +388,7 @@ export default function Search({
const optionsFilter: OptionsFilter = ({ options, search }) => {
const splittedSearch = search.toLowerCase().trim().split(' ');
if (splittedSearch[0].startsWith('!')) {
return applicationData
.filter((app: Application) =>
app.name.toLowerCase().includes(splittedSearch[0].substring(1).toLowerCase()),
)
.map((app: Application) => {
return filteredApplications.map((app: Application) => {
return {
label: app.name,
value: app.baseUrl,
@@ -346,13 +397,7 @@ export default function Search({
});
}
if (splittedSearch[0].startsWith('>') && personal.enableGuacamole === 1) {
return guacamoleClientData
.filter((guacamoleClient: GuacamoleClient) =>
guacamoleClient.clientName
.toLowerCase()
.includes(splittedSearch[0].substring(1).toLowerCase()),
)
.map((guacamoleClient: GuacamoleClient) => {
return filteredGuacamoleClients.map((guacamoleClient: GuacamoleClient) => {
return {
label: guacamoleClient.clientName,
value: guacamoleClient.clientIdentifier,
@@ -495,65 +540,8 @@ export default function Search({
);
};
useEffect(() => {
if (providerData && providerDataIsFetched) {
setProviders(providerData);
}
}, [providerData, providerDataIsFetched]);
useEffect(() => {
if (guacamoleHostData && guacamoleHostDataIsFetched) {
setGuacamoleHost(guacamoleHostData[0].guacamoleHost);
}
}, [guacamoleClientData, guacamoleClientDataIsFetched]);
useEffect(() => {
if (guacamoleClientData && guacamoleClientDataIsFetched) {
setGuacamoleClients(guacamoleClientData);
}
}, [guacamoleClientData, guacamoleClientDataIsFetched]);
useEffect(() => {
if (guacamoleClients) {
setFilteredGuacamoleClients(
guacamoleClients.filter((client: GuacamoleClient) =>
client.clientName.toLowerCase().includes((searchValue as string).toLowerCase()),
),
);
}
}, [guacamoleClients, searchValue]);
useEffect(() => {
if (applicationData && !isApplicationDataFetching) {
setApplications(applicationData);
}
}, [applicationData, isApplicationDataFetching]);
useEffect(() => {
if (applications) {
setFilteredApplications(
applications.filter((app: Application) =>
app.name.toLowerCase().includes((searchValue as string).toLowerCase()),
),
);
}
}, [applications, searchValue]);
useEffect(() => {
if (bookmarkData && bookmarksIsFetched) {
setBookmarks(
bookmarkData.filter(
(bookmark: Bookmark) =>
bookmark.enabled === 1 &&
bookmark.name.toLowerCase().includes((searchValue as string).toLowerCase()),
),
);
}
}, [bookmarksIsFetched, bookmarkData, searchValue]);
useEffect(() => {
if (historyData && historyDataIsFetched) {
setSearchHistory(historyData);
if (filteredHistory.length === 0) {
setFilteredHistory(historyData.slice(0, 10 * historyMultiplier));
}
@@ -592,7 +580,6 @@ export default function Search({
setDefaultProviderIcon(defaultProviderData[0]?.icon ?? 'FaGoogle');
}
}, [defaultProviderData, defaultProviderDataIsFetched]);
return (
<>
{dropdownOpened && <Overlay blur={5} onClick={closeDropdown} fixed zIndex={200} />}
@@ -654,7 +641,6 @@ export default function Search({
}
rightSection={<div />}
clearable
onClear={handleResetSearch}
clearButtonProps={{
'aria-label': 'Clear search',
c: 'var(--mantine-color-accent-0)',
@@ -662,25 +648,26 @@ export default function Search({
}}
scrollAreaProps={{ viewportRef: ref }}
renderOption={(item: any) => renderItem(item)}
onBlur={() => {
setPlaceholderValue('');
setListIndex(-1);
}}
onFocus={() => {
if (searchValue !== '') {
openDropdown();
}
}}
onKeyDown={(e) => {
if (e.key === 'Tab') {
e.preventDefault();
}
onBlur={() => {
setPlaceholderValue('');
setListIndex(-1);
}}
onClear={handleResetSearch}
onDoubleClick={() => {
dropdownOpened ? closeDropdown() : openDropdown();
}}
onInput={onInput}
onKeyUp={handleKeyUp}
onKeyDown={(e) => {
if (e.key === 'Tab') {
e.preventDefault();
}
}}
/>
{dropdownOpened && (
<Card

View File

@@ -19,7 +19,7 @@ export default function RecommendedBookmarks({
backgroundColor: 'var(--mantine-color-search-0)',
backdropFilter: 'blur(15px) brightness(1.75)',
borderRadius: '4px',
opacity: searchValue && dropdownOpened && bookmarks.length > 0 ? 1 : 0,
opacity: searchValue && dropdownOpened ? 1 : 0,
}}
>
<Stack w={'100%'} mah={'636px'}>
@@ -63,6 +63,22 @@ export default function RecommendedBookmarks({
))
.toSorted((a: any, b: any) => (a.name > b.name ? 1 : -1))
.slice(0, 5)}
{!bookmarks.length && (
<Text
c={'var(--mantine-color-primary-0)'}
style={{
backgroundColor: 'var(--mantine-color-search-0)',
padding: '.5rem',
borderRadius: '10px',
overflow: 'ellipsis',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{' '}
No bookmarks found{' '}
</Text>
)}
</Stack>
<Stack
w={bookmarks.length <= 5 ? '0%' : '48%'}

View File

@@ -13,13 +13,13 @@ function SettingsButton() {
const { data: appCategoryData, isFetched: appCategoryDataIsFetched } = useQueryAPI({
endpoint: 'api/appCategories',
});
const { data: wolClientData, isFetched: wolClientDataIsFetched } = useQueryAPI({
const { data: wolClientData } = useQueryAPI({
endpoint: 'api/wolClients',
});
const { data: wolEnabledData, isFetched: wolEnabledDataIsFetched } = useQueryAPI({
endpoint: 'api/wolEnabled',
});
const [wolClients, setWolClients] = useState<WolClient[]>([]);
const wolClients = wolClientData || [];
const [enabled, setEnabled] = useState<number>(0);
const { mutateAsync } = useMutationAPI();
const [categories, setCategories] = useState([{ value: 'All', label: 'All' }]);
@@ -64,12 +64,6 @@ function SettingsButton() {
}
}, [appCategoryData, appCategoryDataIsFetched]);
useEffect(() => {
if (wolClientData && wolClientDataIsFetched) {
setWolClients(wolClientData);
}
}, [wolClientData, wolClientDataIsFetched]);
useEffect(() => {
if (wolEnabledData && wolEnabledDataIsFetched) {
setEnabled(wolEnabledData[0].enableWol);

View File

@@ -26,15 +26,11 @@ function ThemingCard({ wolEnabled }: { wolEnabled: number }) {
const { data: themePlaylistData } = useQueryAPI({
endpoint: 'api/themePlaylists',
});
const { data: allThemeData, isFetched: allThemeDataIsFetched } = useQueryAPI({
const { data: allThemeData } = useQueryAPI({
endpoint: 'api/themes',
});
const [allThemes, setAllThemes] = useState<Theme[]>([]);
useEffect(() => {
if (allThemeData && allThemeDataIsFetched) {
setAllThemes(allThemeData);
}
}, [allThemeData, allThemeDataIsFetched]);
const allThemes: Theme[] = allThemeData || [];
const {
isMobile,
currentTheme,

View File

@@ -1,6 +1,5 @@
import { ActionIcon, Divider, Group, ScrollArea, Stack, Tabs, Text } from '@mantine/core';
import { useCommonHooks, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { FaPaintBrush, FaRegListAlt } from 'react-icons/fa';
import {
FiArrowLeftCircle,
@@ -27,19 +26,14 @@ import WolClients from './components/WoL/WolClients';
export function Settings() {
const endpoint = 'api/personalData';
const { data: personalData, isFetched: personalDataIsFetched } = useQueryAPI({
const { data: personalData } = useQueryAPI({
endpoint: endpoint,
});
const [personal, setPersonal] = useState<PersonalData>();
const personal: PersonalData = personalData?.[0];
const { isMobile, theme } = useCommonHooks();
const navigate = useNavigate();
const { tabValue } = useParams();
useEffect(() => {
if (personalData && personalDataIsFetched) {
setPersonal(personalData[0]);
}
}, [personalData, personalDataIsFetched]);
return (
<Stack h={'100%'} gap={0}>
<Group gap={0} style={{ position: 'relative', paddingBottom: '1rem' }}>

View File

@@ -38,6 +38,8 @@ const ApplicationSettings = () => {
index: 0,
enabled: 1,
};
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const form = useForm({
initialValues: defaultApplication,
validate: {
@@ -49,19 +51,21 @@ const ApplicationSettings = () => {
});
const appsEndpoint = 'api/applications';
const appsOrderEndpoint = 'api/applicationOrder';
const {
data: applicationData,
isFetched: applicationDataIsFetched,
isFetching: applicationDataIsFetching,
} = useQueryAPI({
const { data: applicationData, isFetching: applicationDataIsFetching } = useQueryAPI({
endpoint: appsEndpoint,
});
const [applications, setApplications] = useState<Application[]>([]);
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 [applicationList, setApplicationList] = useState<Application[]>([]);
const { isMobile, reorder, theme } = useCommonHooks();
const { mutateAsync } = useMutationAPI();
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [dragging, setDragging] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@@ -93,22 +97,10 @@ const ApplicationSettings = () => {
form.reset();
};
useEffect(() => {
if (applicationData && applicationDataIsFetched) {
setApplications(
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)),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [applicationData, applicationDataIsFetched, debouncedSearch]);
}, [debouncedSearch]);
useEffect(() => {
if (applicationList.length > 0 && applicationList !== applications) {

View File

@@ -33,14 +33,10 @@ const BookmarkSettings = () => {
},
});
const bookmarksEndpoint = 'api/bookmarks';
const {
data: bookmarkData,
isFetched: bookmarkDataIsFetched,
isFetching: bookmarkDataIsFetching,
} = useQueryAPI({
const { data: bookmarkData, isFetching: bookmarkDataIsFetching } = useQueryAPI({
endpoint: bookmarksEndpoint,
});
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
const bookmarks = bookmarkData || [];
const categoriesEndpoint = 'api/bookmarkCategories';
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
endpoint: categoriesEndpoint,
@@ -54,7 +50,27 @@ const BookmarkSettings = () => {
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [isLoading, setIsLoading] = useState(false);
const foundBookmarkGroups = [
...new Set(
bookmarks
.filter(
(a: Bookmark) =>
a.name
.toLowerCase()
.includes((debouncedGroupSearch as string).toLowerCase()) ||
a.url.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()),
)
.map((a: Bookmark) =>
groupData.find(
(b: BookmarkGroup) =>
b.categoryName.toLowerCase() === a.category.toLowerCase(),
),
),
),
];
const foundGroups = groupData.filter((a: BookmarkGroup) =>
a.categoryName.toLowerCase().includes((debouncedGroupSearch as string).toLowerCase()),
);
const handleClose = () => {
closeModal();
form.reset();
@@ -80,40 +96,8 @@ const BookmarkSettings = () => {
);
}
useEffect(() => {
if (bookmarkData && bookmarkDataIsFetched) {
setBookmarks(bookmarkData);
}
}, [bookmarkDataIsFetched, bookmarkData]);
useEffect(() => {
if (groupData && groupsIsFetched) {
const foundBookmarkGroups = [
...new Set(
bookmarks
.filter(
(a: Bookmark) =>
a.name
.toLowerCase()
.includes((debouncedGroupSearch as string).toLowerCase()) ||
a.url
.toLowerCase()
.includes((debouncedGroupSearch as string).toLowerCase()),
)
.map((a: Bookmark) =>
groupData.find(
(b: BookmarkGroup) =>
b.categoryName.toLowerCase() === a.category.toLowerCase(),
),
),
),
];
const foundGroups = groupData.filter((a: BookmarkGroup) =>
a.categoryName
.toLowerCase()
.includes((debouncedGroupSearch as string).toLowerCase()),
);
const groupList = [...new Set([...foundBookmarkGroups, ...foundGroups])].sort(
(a: BookmarkGroup, b: BookmarkGroup) =>
a.categoryIndex > b.categoryIndex ? 1 : -1,

View File

@@ -16,11 +16,15 @@ function DebouncedBookmarkItem({
const { handleDelete, isMobile, theme } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const { mutateAsync } = useMutationAPI();
const { data: groupData, isFetched: groupsIsFetched } = useQueryAPI({
const { data: groupData } = useQueryAPI({
endpoint: 'api/bookmarkCategories',
});
const [bookmark, setBookmark] = useState<Bookmark>(bookmarkItem);
const [groups, setGroups] = useState<BookmarkGroup[]>([]);
const groups: BookmarkGroup[] =
groupData.sort(
(a: BookmarkGroup, b: BookmarkGroup) =>
a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase(),
) || [];
const [update, setUpdate] = useState<any>({});
const [debounced] = useDebouncedValue(update, 1000);
const handleClose = () => {
@@ -39,17 +43,6 @@ function DebouncedBookmarkItem({
}
}, [update]);
useEffect(() => {
if (groupData) {
setGroups(
groupData.sort(
(a: BookmarkGroup, b: BookmarkGroup) =>
a.categoryName.toLocaleLowerCase() > b.categoryName.toLocaleLowerCase(),
),
);
}
}, [groupData, groupsIsFetched]);
return (
<Card
p={isMobile ? 'xl' : undefined}

View File

@@ -15,7 +15,7 @@ import { isNotEmpty, useForm } from '@mantine/form';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { AddItemModal, NoItemsFound, SettingsHeader } from 'components';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { FaTerminal } from 'react-icons/fa';
import { FaComputer, FaDesktop } from 'react-icons/fa6';
import { popoverClasses, selectClasses, textInputClasses } from 'styles';
@@ -39,20 +39,24 @@ const GuacamoleClients = () => {
},
});
const types: string[] = ['SSH', 'RDP', 'VNC'];
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const endpoint = 'api/guacamoleClients';
const {
data: guacamoleClientsData,
isFetched: guacamoleClientsIsFetched,
isFetching: guacamoleClientsIsFetching,
} = useQueryAPI({
const { data: guacamoleClientsData, isFetching: guacamoleClientsIsFetching } =
useQueryAPI({
endpoint: endpoint,
});
const [guacamoleClients, setGuacamoleClients] = useState<GuacamoleClient[]>([]);
const guacamoleClients =
guacamoleClientsData.filter(
(a: GuacamoleClient) =>
a.clientName.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.clientIdentifier
.toLowerCase()
.includes((debouncedSearch as string).toLowerCase()),
) || [];
const { mutateAsync } = useMutationAPI();
const { isMobile, theme } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@@ -61,25 +65,6 @@ const GuacamoleClients = () => {
form.reset();
};
useEffect(() => {
if (guacamoleClientsData && guacamoleClientsIsFetched) {
setGuacamoleClients(
guacamoleClientsData.filter(
(a: GuacamoleClient) =>
a.clientName
.toLowerCase()
.includes((debouncedSearch as string).toLowerCase()) ||
a.clientIdentifier
.toLowerCase()
.includes((debouncedSearch as string).toLowerCase()),
),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [guacamoleClientsData, guacamoleClientsIsFetched, debouncedSearch]);
return (
<Stack>
<AddItemModal

View File

@@ -18,11 +18,7 @@ import DebouncedMessage from './DebouncedMessage';
const MessageSettings = () => {
const endpoint = 'api/messages';
const {
data: messageData,
isFetched: messageDataIsFetched,
isFetching: messageDataIsFetching,
} = useQueryAPI({
const { data: messageData, isFetching: messageDataIsFetching } = useQueryAPI({
endpoint: endpoint,
});
const form = useForm({
@@ -33,13 +29,16 @@ const MessageSettings = () => {
message: isNotEmpty('Message is required'),
},
});
const { isMobile, theme } = useCommonHooks();
const { mutateAsync } = useMutationAPI();
const [messages, setMessages] = useState<Message[]>([]);
const [message, setMessage] = useState({});
const [debounced] = useDebouncedValue(message, 1000);
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const { isMobile, theme } = useCommonHooks();
const { mutateAsync } = useMutationAPI();
const messages =
messageData.filter((m: Message) =>
m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
) || [];
const [message, setMessage] = useState({});
const [debounced] = useDebouncedValue(message, 1000);
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [isLoading, setIsLoading] = useState(false);
@@ -48,19 +47,6 @@ const MessageSettings = () => {
form.reset();
};
useEffect(() => {
if (messageData && messageDataIsFetched) {
setMessages(
messageData.filter((m: Message) =>
m.message.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [messageData, messageDataIsFetched, debouncedSearch]);
useEffect(() => {
if (Object.keys(debounced).length) {
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });

View File

@@ -9,14 +9,10 @@ import type { SearchHistoryItem } from 'types';
const SearchHistory = () => {
const searchEndpoint = 'api/searchHistory';
const {
data: historyData,
isFetched: historyDataIsFetched,
isFetching: historyDataIsFetching,
} = useQueryAPI({
const { data: historyData, isFetching: historyDataIsFetching } = useQueryAPI({
endpoint: searchEndpoint,
});
const [searchHistory, setSearchHistory] = useState<SearchHistoryItem[]>([]);
const searchHistory: SearchHistoryItem[] = historyData || [];
const [searchValue, setSearchValue] = useState<string | number>('');
const [searchHistoryItem, setSearchHistoryItem] = useState<SearchHistoryItem>();
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
@@ -36,12 +32,6 @@ const SearchHistory = () => {
},
]);
useEffect(() => {
if (historyData && historyDataIsFetched) {
setSearchHistory(historyData);
}
}, [historyData, historyDataIsFetched]);
useEffect(() => {
setGlobalFilter(debouncedSearch);
if (debouncedSearch === globalFilter) {

View File

@@ -52,19 +52,23 @@ const SearchProviderSettings = () => {
},
});
const endpoint = 'api/providers';
const {
data: providerData,
isFetched: providerDataIsFetched,
isFetching: providerDataIsFetching,
} = useQueryAPI({
const { data: providerData, isFetching: providerDataIsFetching } = useQueryAPI({
endpoint: endpoint,
});
const [providers, setProviders] = useState<Provider[]>([]);
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const providers =
providerData
.filter(
(a: Provider) =>
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.url.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)) ?? [];
const { isMobile, theme, iconFilter } = useCommonHooks();
const { mutateAsync } = useMutationAPI();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@@ -74,22 +78,10 @@ const SearchProviderSettings = () => {
};
useEffect(() => {
if (providerData && providerDataIsFetched) {
setProviders(
providerData
.filter(
(a: Provider) =>
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.url.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)),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [providerData, providerDataIsFetched, debouncedSearch]);
}, [debouncedSearch]);
return (
<Stack>

View File

@@ -15,14 +15,18 @@ export function ThemePlaylists() {
endpoint: themesEndpoint,
});
const themePlaylistsEndpoint = 'api/themePlaylists';
const {
data: themePlaylistData,
isFetched: themePlaylistDataIsFetched,
isFetching: themePlaylistDataIsFetching,
} = useQueryAPI({
const { data: themePlaylistData, isFetching: themePlaylistDataIsFetching } =
useQueryAPI({
endpoint: themePlaylistsEndpoint,
});
const [themePlaylists, setThemePlaylists] = useState<ThemePlaylist[]>([]);
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const themePlaylists: ThemePlaylist[] =
themePlaylistData?.filter(
(a: ThemePlaylist) =>
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.name.includes('Time of Day'),
) || [];
const defaultPlaylist = {
id: undefined,
default: 0,
@@ -34,8 +38,6 @@ export function ThemePlaylists() {
const [selectedPlaylist, setSelectedPlaylist] =
useState<ThemePlaylist>(defaultPlaylist);
const { currentThemePlaylist, theme, isMobile } = useCommonHooks();
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [isLoading, setIsLoading] = useState(false);
const handleOpen = (playlist: ThemePlaylist) => {
@@ -58,19 +60,10 @@ export function ThemePlaylists() {
};
useEffect(() => {
if (themePlaylistData && themePlaylistDataIsFetched) {
setThemePlaylists(
themePlaylistData.filter(
(a: ThemePlaylist) =>
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.name.includes('Time of Day'),
),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [themePlaylistData, themePlaylistDataIsFetched, debouncedSearch]);
}, [debouncedSearch]);
return (
<Stack>

View File

@@ -25,14 +25,14 @@ import { ThemeModal } from './ThemeModal';
export function Themes() {
const backgroundsEndpoint = 'api/backgrounds';
const {
data: allThemeData,
isFetched: allThemeDataIsFetched,
isFetching: allThemeDataIsFetching,
} = useQueryAPI({
const { data: allThemeData, isFetching: allThemeDataIsFetching } = useQueryAPI({
endpoint: 'api/themes',
});
const [allThemes, setAllThemes] = useState<Theme[]>([]);
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const allThemes: Theme[] = allThemeData?.filter((a: Theme) =>
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
);
const {
data: backgroundData,
isFetched: backgroundDataIsFetched,
@@ -43,7 +43,7 @@ export function Themes() {
const { data: themePlaylistData } = useQueryAPI({
endpoint: 'api/themePlaylists',
});
const [themePlaylists, setThemePlaylists] = useState<ThemePlaylist[]>([]);
const themePlaylists: ThemePlaylist[] = themePlaylistData || [];
const [themeModalOpened, { open: openThemeModal, close: closeThemeModal }] =
useDisclosure(false);
const [
@@ -51,7 +51,18 @@ export function Themes() {
{ open: openBackgroundModal, close: closeBackgroundModal },
] = useDisclosure(false);
const [backgroundList, setBackgroundList] = useState<string[]>(['None']);
const [inUseBackgrounds, setInUseBackgrounds] = useState<string[]>([]);
const inUseBackgrounds: string[] = [
...new Set(
allThemes
?.filter((t) => {
if (t.backgroundImage !== 'none') {
return t.backgroundImage;
}
return undefined;
})
.map((t) => t.backgroundImage),
),
] as string[];
const [selectedTheme, setSelectedTheme] = useState<Theme>({
id: undefined,
name: 'default',
@@ -65,8 +76,6 @@ export function Themes() {
textShadowsEnabled: 0,
appShadowsEnabled: 0,
});
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [isLoading, setIsLoading] = useState(false);
const [isNew, setIsNew] = useState(false);
const navigate = useNavigate();
@@ -94,40 +103,10 @@ export function Themes() {
}, [backgroundData, backgroundDataIsFetched]);
useEffect(() => {
if (themePlaylistData) {
setThemePlaylists(themePlaylistData);
}
}, [themePlaylistData]);
useEffect(() => {
if (allThemeData && allThemeDataIsFetched) {
setAllThemes(
allThemeData.filter((a: Theme) =>
a.name.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [allThemeData, allThemeDataIsFetched, debouncedSearch]);
useEffect(() => {
if (allThemes) {
setInUseBackgrounds([
...new Set(
allThemes
.filter((t) => {
if (t.backgroundImage !== 'none') {
return t.backgroundImage;
}
return undefined;
})
.map((t) => t.backgroundImage),
),
]);
}
}, [allThemes]);
}, [debouncedSearch]);
return (
<Stack>
@@ -195,7 +174,7 @@ export function Themes() {
}}
pos={'absolute'}
/>
{allThemes.length > 0 ? (
{allThemes?.length > 0 ? (
allThemes.map((cardTheme: Theme) => (
<Group key={cardTheme.name} pos={'relative'}>
{cardTheme.name === currentTheme && (

View File

@@ -35,19 +35,20 @@ const WolClientSettings = () => {
},
});
const endpoint = 'api/wolClients';
const {
data: wolClientsData,
isFetched: wolClientsIsFetched,
isFetching: wolClientsIsFetching,
} = useQueryAPI({
const { data: wolClientsData, isFetching: wolClientsIsFetching } = useQueryAPI({
endpoint: endpoint,
});
const [wolClients, setWolClients] = useState<WolClient[]>([]);
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const wolClients: WolClient[] =
wolClientsData.filter(
(a: WolClient) =>
a.hostname.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
a.macAddress.toLowerCase().includes((debouncedSearch as string).toLowerCase()),
) || [];
const { mutateAsync } = useMutationAPI();
const { isMobile, theme } = useCommonHooks();
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [searchValue, setSearchValue] = useState<string | number>('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@@ -57,23 +58,10 @@ const WolClientSettings = () => {
};
useEffect(() => {
if (wolClientsData && wolClientsIsFetched) {
setWolClients(
wolClientsData.filter(
(a: WolClient) =>
a.hostname
.toLowerCase()
.includes((debouncedSearch as string).toLowerCase()) ||
a.macAddress
.toLowerCase()
.includes((debouncedSearch as string).toLowerCase()),
),
);
}
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [wolClientsData, wolClientsIsFetched, debouncedSearch]);
}, [debouncedSearch]);
return (
<Stack>