State management
This commit is contained in:
@@ -19,7 +19,6 @@ import { Loading, MainLayout } from './components';
|
||||
import '@mantine/core/styles.css';
|
||||
import '@mantine/notifications/styles.css';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import './index.css';
|
||||
import { Homepage, Settings } from 'pages';
|
||||
import type { Theme, ThemePlaylist } from 'types';
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ export default function WeatherGraphs({
|
||||
{payload.map((item: any) => (
|
||||
<Text
|
||||
key={item.name}
|
||||
size='sm'
|
||||
size='md'
|
||||
c={item.stroke}
|
||||
>{`${item.name} - ${item.value}${item.unit}`}</Text>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
@@ -41,7 +41,6 @@ export function useCommonHooks() {
|
||||
key: 'theme',
|
||||
defaultValue: 'blackboard',
|
||||
});
|
||||
|
||||
const [currentThemePlaylist, setCurrentThemePlaylist] = useLocalStorage<string>({
|
||||
key: 'themePlaylist',
|
||||
defaultValue: 'All',
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Jost:ital,wght@0,100;0,200;0,400;1,100;1,200&display=swap");
|
||||
@@ -24,6 +24,7 @@ import * as vsc from 'react-icons/vsc';
|
||||
import { popoverClasses, selectClasses, textInputClasses } from 'styles';
|
||||
import type { Application } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import NoItemsFound from '../Shared/NoItemsFound';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import AppSettingsItem from './AppSettingsItem';
|
||||
import DebouncedApplication from './DebouncedApplication';
|
||||
@@ -54,6 +55,7 @@ const ApplicationSettings = () => {
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
function onDragEnd(result: any) {
|
||||
if (!result.destination) {
|
||||
@@ -81,17 +83,22 @@ const ApplicationSettings = () => {
|
||||
closeModal();
|
||||
setNewApplication(defaultApplication);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (applicationData && !isAppsFetching) {
|
||||
setApplications(
|
||||
applicationData
|
||||
.filter((a: Application) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
.filter(
|
||||
(a: Application) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
|
||||
a.url.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
|
||||
a.baseUrl.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
)
|
||||
.sort((a: Application, b: Application) => (a.index > b.index ? 1 : -1)),
|
||||
);
|
||||
}
|
||||
if (debouncedSearch === searchValue) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [applicationData, isAppsFetching, debouncedSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -103,7 +110,6 @@ const ApplicationSettings = () => {
|
||||
});
|
||||
}
|
||||
}, [applicationList]);
|
||||
|
||||
return (
|
||||
<Stack w={'100%'}>
|
||||
<AddItemModal
|
||||
@@ -253,11 +259,13 @@ const ApplicationSettings = () => {
|
||||
title={'Applications'}
|
||||
tooltip={{
|
||||
title:
|
||||
'Applications are the applicationData that will be displayed on the homepage.',
|
||||
'Applications are the app buttons that will be displayed on the homepage.',
|
||||
highlightList: [
|
||||
'Applications',
|
||||
'applicationData',
|
||||
'application',
|
||||
'app',
|
||||
'apps',
|
||||
'name',
|
||||
'base url',
|
||||
'url',
|
||||
@@ -270,6 +278,8 @@ const ApplicationSettings = () => {
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
searchPlaceholder={'Search Applications...'}
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
{dragging ? (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
@@ -361,39 +371,49 @@ const ApplicationSettings = () => {
|
||||
align='center'
|
||||
>
|
||||
<Grid columns={18} w={'75%'}>
|
||||
{applications?.map((application: Application) => (
|
||||
<GridCol span={6} key={application.id}>
|
||||
<Popover
|
||||
withArrow
|
||||
withOverlay
|
||||
closeOnClickOutside={!isOpen}
|
||||
classNames={popoverClasses}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Box
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<AppSettingsItem
|
||||
url={application.url}
|
||||
icon={application.icon}
|
||||
name={application.name}
|
||||
enabled={application.enabled}
|
||||
{applications.length > 0 ? (
|
||||
applications?.map((application: Application) => (
|
||||
<GridCol span={6} key={application.id}>
|
||||
<Popover
|
||||
withArrow
|
||||
withOverlay
|
||||
closeOnClickOutside={!isOpen}
|
||||
classNames={popoverClasses}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Box
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<AppSettingsItem
|
||||
url={application.url}
|
||||
icon={application.icon}
|
||||
name={application.name}
|
||||
enabled={application.enabled}
|
||||
allIcons={allIcons}
|
||||
/>
|
||||
</Box>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown w={isMobile ? '100%' : undefined}>
|
||||
<DebouncedApplication
|
||||
application={application}
|
||||
setIsOpen={setIsOpen}
|
||||
allIcons={allIcons}
|
||||
/>
|
||||
</Box>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown w={isMobile ? '100%' : undefined}>
|
||||
<DebouncedApplication
|
||||
application={application}
|
||||
setIsOpen={setIsOpen}
|
||||
allIcons={allIcons}
|
||||
/>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</GridCol>
|
||||
))
|
||||
) : (
|
||||
<GridCol
|
||||
w={'100%'}
|
||||
span={18}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<NoItemsFound />
|
||||
</GridCol>
|
||||
))}
|
||||
)}
|
||||
</Grid>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useEffect, useState } from 'react';
|
||||
import { textInputClasses } from 'styles';
|
||||
import type { Bookmark, BookmarkGroup } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import NoItemsFound from '../Shared/NoItemsFound';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import DebouncedGroupSettingsItem from './DebouncedGroupSettingsItem';
|
||||
const BookmarkSettings = () => {
|
||||
@@ -33,6 +34,7 @@ const BookmarkSettings = () => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedGroupSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewGroup(defaultGroup);
|
||||
@@ -94,6 +96,9 @@ const BookmarkSettings = () => {
|
||||
);
|
||||
setGroups(groupList);
|
||||
}
|
||||
if (debouncedGroupSearch === searchValue) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [groupData, groupsIsFetched, debouncedGroupSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -194,6 +199,8 @@ const BookmarkSettings = () => {
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
searchPlaceholder={'Search Bookmark Groups...'}
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
{dragging ? (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
@@ -285,16 +292,22 @@ const BookmarkSettings = () => {
|
||||
align='center'
|
||||
>
|
||||
<Group w={isMobile ? undefined : '75%'} align='start' justify='center'>
|
||||
{groups?.map((group: BookmarkGroup) => (
|
||||
<DebouncedGroupSettingsItem
|
||||
key={group.id}
|
||||
bookmarkGroup={group}
|
||||
bookmarks={bookmarks
|
||||
.filter((a: Bookmark) => a.category === group.categoryName)
|
||||
.sort((a: Bookmark, b: Bookmark) => (a.index > b.index ? 1 : -1))}
|
||||
searchTerm={searchValue}
|
||||
/>
|
||||
))}
|
||||
{groups.length > 0 ? (
|
||||
groups?.map((group: BookmarkGroup) => (
|
||||
<DebouncedGroupSettingsItem
|
||||
key={group.id}
|
||||
bookmarkGroup={group}
|
||||
bookmarks={bookmarks
|
||||
.filter((a: Bookmark) => a.category === group.categoryName)
|
||||
.sort((a: Bookmark, b: Bookmark) => (a.index > b.index ? 1 : -1))}
|
||||
searchTerm={searchValue}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Group w={'100%'} style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<NoItemsFound />
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -286,7 +286,7 @@ function DebouncedGroupSettingsItem({
|
||||
>
|
||||
<Popover.Target>
|
||||
<Button
|
||||
className={buttonClasses.tableButton}
|
||||
className={buttonClasses.newItemButton}
|
||||
classNames={buttonClasses}
|
||||
w={'min-content'}
|
||||
onClick={() => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
|
||||
import { textInputClasses } from 'styles';
|
||||
import type { Message } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import NoItemsFound from '../Shared/NoItemsFound';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import DebouncedMessage from './DebouncedMessage';
|
||||
|
||||
@@ -23,6 +24,7 @@ const MessageSettings = () => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewMessage('');
|
||||
@@ -38,6 +40,9 @@ const MessageSettings = () => {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (debouncedSearch === searchValue) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [messageData, isMessagesFetched, debouncedSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,6 +72,8 @@ const MessageSettings = () => {
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
searchPlaceholder={'Search Message content...'}
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
<AddItemModal
|
||||
modalOpened={modalOpened}
|
||||
@@ -122,15 +129,25 @@ const MessageSettings = () => {
|
||||
align='center'
|
||||
>
|
||||
<Grid w={'75%'}>
|
||||
{messages?.map((message: Message) => (
|
||||
<GridCol span={isMobile ? 12 : 6} key={message.id}>
|
||||
<DebouncedMessage
|
||||
label={`ID# ${message.id}`}
|
||||
message={message}
|
||||
setMessage={setMessage}
|
||||
/>
|
||||
{messages.length > 0 ? (
|
||||
messages?.map((message: Message) => (
|
||||
<GridCol span={isMobile ? 12 : 6} key={message.id}>
|
||||
<DebouncedMessage
|
||||
label={`ID# ${message.id}`}
|
||||
message={message}
|
||||
setMessage={setMessage}
|
||||
/>
|
||||
</GridCol>
|
||||
))
|
||||
) : (
|
||||
<GridCol
|
||||
w={'100%'}
|
||||
span={12}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<NoItemsFound />
|
||||
</GridCol>
|
||||
))}
|
||||
)}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -17,8 +17,9 @@ const PersonalizationSettings = () => {
|
||||
const [personal, setPersonal] = useState<PersonalData>();
|
||||
const [update, setUpdate] = useState<any>({});
|
||||
const [debounced] = useDebouncedValue(update, 1000);
|
||||
const { mutateAsync: editRow } = useMutationAPI();
|
||||
const { mutateAsync } = useMutationAPI();
|
||||
const { isMobile } = useCommonHooks();
|
||||
|
||||
useEffect(() => {
|
||||
if (personalData && personalIsFetched) {
|
||||
setPersonal(personalData[0]);
|
||||
@@ -28,7 +29,7 @@ const PersonalizationSettings = () => {
|
||||
useEffect(() => {
|
||||
if (Object.keys(debounced).length) {
|
||||
setPersonal(update);
|
||||
editRow({ method: 'PATCH', endpoint: endpoint, body: debounced });
|
||||
mutateAsync({ method: 'PATCH', endpoint: endpoint, body: debounced });
|
||||
setUpdate({});
|
||||
}
|
||||
}, [debounced]);
|
||||
@@ -61,6 +62,8 @@ const PersonalizationSettings = () => {
|
||||
searchValue={undefined}
|
||||
setSearchValue={() => {}}
|
||||
searchPlaceholder={undefined}
|
||||
isLoading={false}
|
||||
setIsLoading={() => {}}
|
||||
/>
|
||||
<Stack
|
||||
align='center'
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useEffect, useState } from 'react';
|
||||
import { popoverClasses, textInputClasses } from 'styles';
|
||||
import type { Provider } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import NoItemsFound from '../Shared/NoItemsFound';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import DebouncedSearchProvider from './DebouncedSearchProvider';
|
||||
import SearchProviderSettingsItem from './SearchProviderSettingsItem';
|
||||
@@ -38,6 +39,7 @@ const SearchProviderSettings = () => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewProvider(defaultProvider);
|
||||
@@ -48,12 +50,17 @@ const SearchProviderSettings = () => {
|
||||
if (providerData && providerDataIsFetched) {
|
||||
setProviders(
|
||||
providerData
|
||||
.filter((a: Provider) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
.filter(
|
||||
(a: Provider) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
|
||||
a.url.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
)
|
||||
.sort((a: Provider, b: Provider) => (a.prefix > b.prefix ? 1 : -1)),
|
||||
);
|
||||
}
|
||||
if (debouncedSearch === searchValue) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [providerData, providerDataIsFetched, debouncedSearch]);
|
||||
|
||||
return (
|
||||
@@ -138,16 +145,18 @@ const SearchProviderSettings = () => {
|
||||
<SettingsHeader
|
||||
setDragging={undefined}
|
||||
openModal={openModal}
|
||||
title={'Search providerData'}
|
||||
title={'Search Providers'}
|
||||
tooltip={{
|
||||
title: 'Search providerData and shortcuts can be configured here.',
|
||||
title: 'Search providers and shortcuts can be configured here.',
|
||||
highlightList: ['providerData', 'search', 'provider', 'prefix'],
|
||||
description:
|
||||
'providerData can be used in the search bar. By starting a search with the prefix, it will be redirected to the url of the provider to complete the search.',
|
||||
'Providers can be used in the search bar. By starting a search with the prefix, it will be redirected to the url of the provider to complete the search.',
|
||||
}}
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
searchPlaceholder={'Search Search providerData...'}
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
<Stack
|
||||
mt={isMobile ? '9rem' : '6rem'}
|
||||
@@ -156,32 +165,42 @@ const SearchProviderSettings = () => {
|
||||
align='center'
|
||||
>
|
||||
<Grid columns={18} w={'75%'}>
|
||||
{providers?.map((provider: Provider) => (
|
||||
<GridCol span={isMobile ? 18 : 6} key={provider.id}>
|
||||
<Popover
|
||||
withArrow
|
||||
withOverlay
|
||||
closeOnClickOutside={!isOpen}
|
||||
classNames={popoverClasses}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Box
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<SearchProviderSettingsItem provider={provider} />
|
||||
</Box>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown w={isMobile ? '100%' : undefined}>
|
||||
<DebouncedSearchProvider
|
||||
searchProvider={provider}
|
||||
setIsOpen={setIsOpen}
|
||||
/>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
{providers.length > 0 ? (
|
||||
providers?.map((provider: Provider) => (
|
||||
<GridCol span={isMobile ? 18 : 6} key={provider.id}>
|
||||
<Popover
|
||||
withArrow
|
||||
withOverlay
|
||||
closeOnClickOutside={!isOpen}
|
||||
classNames={popoverClasses}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Box
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<SearchProviderSettingsItem provider={provider} />
|
||||
</Box>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown w={isMobile ? '100%' : undefined}>
|
||||
<DebouncedSearchProvider
|
||||
searchProvider={provider}
|
||||
setIsOpen={setIsOpen}
|
||||
/>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</GridCol>
|
||||
))
|
||||
) : (
|
||||
<GridCol
|
||||
w={'100%'}
|
||||
span={18}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<NoItemsFound />
|
||||
</GridCol>
|
||||
))}
|
||||
)}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
22
src/pages/settings/components/Shared/NoItemsFound.tsx
Normal file
22
src/pages/settings/components/Shared/NoItemsFound.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Card, Text } from '@mantine/core';
|
||||
import { cardClasses } from 'styles';
|
||||
|
||||
function NoItemsFound() {
|
||||
return (
|
||||
<Card
|
||||
className={cardClasses.settingsItem}
|
||||
classNames={cardClasses}
|
||||
w={'50%'}
|
||||
h={'100%'}
|
||||
p={'xl'}
|
||||
m={'xl'}
|
||||
style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}
|
||||
>
|
||||
<Text fw={300} size={'xl'}>
|
||||
No items found
|
||||
</Text>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoItemsFound;
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { useCommonHooks } from 'hooks';
|
||||
import { FiHelpCircle, FiPlus } from 'react-icons/fi';
|
||||
import { FiHelpCircle, FiLoader, FiPlus } from 'react-icons/fi';
|
||||
import {
|
||||
actionIconClasses,
|
||||
buttonClasses,
|
||||
@@ -28,6 +28,8 @@ function SettingsHeader({
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
searchPlaceholder,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
}: {
|
||||
setDragging: undefined | ((dragging: boolean) => void);
|
||||
openModal: () => void;
|
||||
@@ -40,6 +42,8 @@ function SettingsHeader({
|
||||
searchValue: string | undefined;
|
||||
setSearchValue: (searchValue: string) => void;
|
||||
searchPlaceholder: string | undefined;
|
||||
isLoading: boolean;
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
}) {
|
||||
const { isMobile } = useCommonHooks();
|
||||
return (
|
||||
@@ -105,7 +109,7 @@ function SettingsHeader({
|
||||
</Popover>
|
||||
{searchValue !== undefined && (
|
||||
<Button
|
||||
className={buttonClasses.tableButton}
|
||||
className={buttonClasses.newItemButton}
|
||||
classNames={buttonClasses}
|
||||
onClick={openModal}
|
||||
>
|
||||
@@ -123,11 +127,22 @@ function SettingsHeader({
|
||||
placeholder={searchPlaceholder ?? 'Search...'}
|
||||
value={searchValue}
|
||||
onInput={(event: any) => {
|
||||
setIsLoading(true);
|
||||
setSearchValue(event.target.value);
|
||||
}}
|
||||
style={{
|
||||
zIndex: 100,
|
||||
}}
|
||||
rightSection={
|
||||
isLoading && (
|
||||
<ActionIcon
|
||||
className={actionIconClasses.loadingSpinner}
|
||||
classNames={actionIconClasses}
|
||||
>
|
||||
<FiLoader />
|
||||
</ActionIcon>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{setDragging && (
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Box, Group, Stack, Text } from '@mantine/core';
|
||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FiChevronRight } from 'react-icons/fi';
|
||||
import { textClasses } from 'styles';
|
||||
import type { ThemePlaylist } from 'types';
|
||||
import NoItemsFound from '../Shared/NoItemsFound';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import ThemePlaylistCard from './ThemePlaylistCard';
|
||||
import ThemePlaylistModal from './ThemePlaylistModal';
|
||||
@@ -15,9 +16,10 @@ function ThemePlaylists() {
|
||||
endpoint: themesEndpoint,
|
||||
});
|
||||
const themePlaylistsEndpoint = 'api/themePlaylists';
|
||||
const { data: themePlaylistData } = useQueryAPI({
|
||||
const { data: themePlaylistData, isFetched: isThemePlaylistDataFetched } = useQueryAPI({
|
||||
endpoint: themePlaylistsEndpoint,
|
||||
});
|
||||
const [themePlaylists, setThemePlaylists] = useState<ThemePlaylist[]>([]);
|
||||
const defaultPlaylist = {
|
||||
id: undefined,
|
||||
default: 0,
|
||||
@@ -31,6 +33,7 @@ function ThemePlaylists() {
|
||||
const { currentThemePlaylist, theme, isMobile } = useCommonHooks();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleOpen = (playlist: ThemePlaylist) => {
|
||||
setSelectedPlaylist(playlist);
|
||||
@@ -51,6 +54,19 @@ function ThemePlaylists() {
|
||||
close();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (themePlaylistData && isThemePlaylistDataFetched) {
|
||||
setThemePlaylists(
|
||||
themePlaylistData.filter((a: ThemePlaylist) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (debouncedSearch === searchValue) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [themePlaylistData, isThemePlaylistDataFetched, debouncedSearch]);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<ThemePlaylistModal
|
||||
@@ -77,6 +93,8 @@ function ThemePlaylists() {
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
searchPlaceholder={'Search Theme Playlists...'}
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
<Group mt={'6rem'} justify='center' align='center' pos={'relative'}>
|
||||
<Text p={0} className={textClasses.settingsSubTitle} classNames={textClasses}>
|
||||
@@ -84,41 +102,44 @@ function ThemePlaylists() {
|
||||
</Text>
|
||||
</Group>
|
||||
<Group justify='center' align='center' pos={'relative'}>
|
||||
{themePlaylistData
|
||||
?.filter((playlist: ThemePlaylist) => playlist.default === 1)
|
||||
?.filter((a: ThemePlaylist) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
)
|
||||
.map((cardPlaylist: ThemePlaylist) => {
|
||||
return (
|
||||
<Group key={cardPlaylist.name} pos={'relative'}>
|
||||
{cardPlaylist.name === currentThemePlaylist && (
|
||||
<FiChevronRight
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: -14,
|
||||
zIndex: 10,
|
||||
fontSize: '2em',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--mantine-color-border-0)',
|
||||
color:
|
||||
theme.borderColor === theme.primaryColor
|
||||
? 'var(--mantine-color-accent-0)'
|
||||
: 'var(--mantine-color-background-0)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box>
|
||||
<ThemePlaylistCard
|
||||
cardPlaylist={cardPlaylist}
|
||||
handleOpen={handleOpen}
|
||||
allThemes={allThemeData}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
);
|
||||
})}
|
||||
{themePlaylists.length > 0 ? (
|
||||
themePlaylists
|
||||
.filter((playlist: ThemePlaylist) => playlist.default === 1)
|
||||
.map((cardPlaylist: ThemePlaylist) => {
|
||||
return (
|
||||
<Group key={cardPlaylist.name} pos={'relative'}>
|
||||
{cardPlaylist.name === currentThemePlaylist && (
|
||||
<FiChevronRight
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: -14,
|
||||
zIndex: 10,
|
||||
fontSize: '2em',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--mantine-color-border-0)',
|
||||
color:
|
||||
theme.borderColor === theme.primaryColor
|
||||
? 'var(--mantine-color-accent-0)'
|
||||
: 'var(--mantine-color-background-0)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box>
|
||||
<ThemePlaylistCard
|
||||
cardPlaylist={cardPlaylist}
|
||||
handleOpen={handleOpen}
|
||||
allThemes={allThemeData}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Group w={'100%'} style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<NoItemsFound />
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
<Group justify='center' align='center' pos={'relative'}>
|
||||
<Text p={0} className={textClasses.settingsSubTitle} classNames={textClasses}>
|
||||
@@ -131,42 +152,45 @@ function ThemePlaylists() {
|
||||
pos={'relative'}
|
||||
mb={isMobile ? '6rem' : undefined}
|
||||
>
|
||||
{themePlaylistData
|
||||
?.filter((playlist: ThemePlaylist) => playlist.default === 0)
|
||||
?.filter((a: ThemePlaylist) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
)
|
||||
.sort((a: ThemePlaylist, b: ThemePlaylist) => (a.name > b.name ? 1 : -1))
|
||||
.map((cardPlaylist: ThemePlaylist) => {
|
||||
return (
|
||||
<Group key={cardPlaylist.name} pos={'relative'}>
|
||||
{cardPlaylist.name === currentThemePlaylist && (
|
||||
<FiChevronRight
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: -14,
|
||||
zIndex: 10,
|
||||
fontSize: '2em',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--mantine-color-border-0)',
|
||||
color:
|
||||
theme.borderColor === theme.primaryColor
|
||||
? 'var(--mantine-color-accent-0)'
|
||||
: 'var(--mantine-color-primary-0)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box>
|
||||
<ThemePlaylistCard
|
||||
cardPlaylist={cardPlaylist}
|
||||
handleOpen={handleOpen}
|
||||
allThemes={allThemeData}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
);
|
||||
})}
|
||||
{themePlaylists.length > 0 ? (
|
||||
themePlaylists
|
||||
.filter((playlist: ThemePlaylist) => playlist.default === 0)
|
||||
.sort((a: ThemePlaylist, b: ThemePlaylist) => (a.name > b.name ? 1 : -1))
|
||||
.map((cardPlaylist: ThemePlaylist) => {
|
||||
return (
|
||||
<Group key={cardPlaylist.name} pos={'relative'}>
|
||||
{cardPlaylist.name === currentThemePlaylist && (
|
||||
<FiChevronRight
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: -14,
|
||||
zIndex: 10,
|
||||
fontSize: '2em',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--mantine-color-border-0)',
|
||||
color:
|
||||
theme.borderColor === theme.primaryColor
|
||||
? 'var(--mantine-color-accent-0)'
|
||||
: 'var(--mantine-color-primary-0)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box>
|
||||
<ThemePlaylistCard
|
||||
cardPlaylist={cardPlaylist}
|
||||
handleOpen={handleOpen}
|
||||
allThemes={allThemeData}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Group w={'100%'} style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<NoItemsFound />
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FaDice } from 'react-icons/fa';
|
||||
import { FiChevronRight } from 'react-icons/fi';
|
||||
import { switchClasses } from 'styles';
|
||||
import type { Theme, ThemePlaylist } from 'types';
|
||||
import NoItemsFound from '../Shared/NoItemsFound';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import ThemeCard from './ThemeCard';
|
||||
import ThemeModal from './ThemeModal';
|
||||
@@ -13,9 +14,10 @@ import ThemeModal from './ThemeModal';
|
||||
function ThemesSettings() {
|
||||
const themesEndpoint = 'api/themes';
|
||||
const backgroundsEndpoint = 'api/backgrounds';
|
||||
const { data: allThemeData } = useQueryAPI({
|
||||
const { data: allThemeData, isFetched: isAllThemeDataFetched } = useQueryAPI({
|
||||
endpoint: 'api/themes',
|
||||
});
|
||||
const [allThemes, setAllThemes] = useState<Theme[]>([]);
|
||||
const { data: backgroundData, isFetched: isBackgroundFetched } = useQueryAPI({
|
||||
endpoint: backgroundsEndpoint,
|
||||
});
|
||||
@@ -42,6 +44,7 @@ function ThemesSettings() {
|
||||
const [pickerOpen, { open: openPicker, close: closePicker }] = useDisclosure(false);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { currentTheme, setCurrentTheme, theme, isMobile, setCurrentThemePlaylist } =
|
||||
useCommonHooks();
|
||||
|
||||
@@ -87,6 +90,19 @@ function ThemesSettings() {
|
||||
}
|
||||
}, [themePlaylistData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (allThemeData && isAllThemeDataFetched) {
|
||||
setAllThemes(
|
||||
allThemeData.filter((a: Theme) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (debouncedSearch === searchValue) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [allThemeData, isAllThemeDataFetched, debouncedSearch]);
|
||||
|
||||
return (
|
||||
<Stack w={'100%'} align='center'>
|
||||
<ThemeModal
|
||||
@@ -117,6 +133,8 @@ function ThemesSettings() {
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
searchPlaceholder={'Search Themes...'}
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
<Switch
|
||||
label={
|
||||
@@ -150,7 +168,7 @@ function ThemesSettings() {
|
||||
mb={isMobile ? '6rem' : '1rem'}
|
||||
>
|
||||
<Overlay
|
||||
display={currentTheme === 'random' ? 'flex' : 'none'}
|
||||
display={currentTheme === 'random' && debouncedSearch === '' ? 'flex' : 'none'}
|
||||
blur={5}
|
||||
style={{
|
||||
zIndex: 190,
|
||||
@@ -161,11 +179,8 @@ function ThemesSettings() {
|
||||
}}
|
||||
pos={'absolute'}
|
||||
/>
|
||||
{allThemeData
|
||||
?.filter((a: Theme) =>
|
||||
a.name.toLowerCase().includes(debouncedSearch.toLowerCase()),
|
||||
)
|
||||
.map((cardTheme: Theme) => (
|
||||
{allThemes.length > 0 ? (
|
||||
allThemes.map((cardTheme: Theme) => (
|
||||
<Group key={cardTheme.name} pos={'relative'}>
|
||||
{cardTheme.name === currentTheme && (
|
||||
<FiChevronRight
|
||||
@@ -198,7 +213,15 @@ function ThemesSettings() {
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
))}
|
||||
))
|
||||
) : (
|
||||
<Group
|
||||
w={'100%'}
|
||||
style={{ zIndex: 1000, display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<NoItemsFound />
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useEffect, useState } from 'react';
|
||||
import { popoverClasses, textInputClasses } from 'styles';
|
||||
import type { WolClient } from 'types';
|
||||
import AddItemModal from '../Shared/AddItemModal';
|
||||
import NoItemsFound from '../Shared/NoItemsFound';
|
||||
import SettingsHeader from '../Shared/SettingsHeader';
|
||||
import DebouncedWolClient from './DebouncedWolClient';
|
||||
import WolClientSettingsItem from './WolClientSettingsItem';
|
||||
@@ -36,6 +37,7 @@ const WolClientSettings = () => {
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setNewWolClient(defaultWolClient);
|
||||
@@ -50,6 +52,9 @@ const WolClientSettings = () => {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (debouncedSearch === searchValue) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [wolClientsData, wolClientsIsFetched, debouncedSearch]);
|
||||
|
||||
return (
|
||||
@@ -131,6 +136,8 @@ const WolClientSettings = () => {
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
searchPlaceholder={'Search Clients...'}
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
<Stack
|
||||
mt={isMobile ? '9rem' : '6rem'}
|
||||
@@ -139,30 +146,40 @@ const WolClientSettings = () => {
|
||||
align='center'
|
||||
>
|
||||
<Grid columns={18} w={'75%'}>
|
||||
{wolClients?.map((client: WolClient) => (
|
||||
<GridCol w={'100%'} span={isMobile ? 18 : 6} key={client.id}>
|
||||
<Popover
|
||||
withArrow
|
||||
withOverlay
|
||||
closeOnClickOutside={!isOpen}
|
||||
classNames={popoverClasses}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Group
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
justify='center'
|
||||
>
|
||||
<WolClientSettingsItem client={client} />
|
||||
</Group>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown w={isMobile ? '100%' : undefined}>
|
||||
<DebouncedWolClient wolClient={client} setIsOpen={setIsOpen} />
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
{wolClients.length > 0 ? (
|
||||
wolClients?.map((client: WolClient) => (
|
||||
<GridCol w={'100%'} span={isMobile ? 18 : 6} key={client.id}>
|
||||
<Popover
|
||||
withArrow
|
||||
withOverlay
|
||||
closeOnClickOutside={!isOpen}
|
||||
classNames={popoverClasses}
|
||||
>
|
||||
<Popover.Target>
|
||||
<Group
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
justify='center'
|
||||
>
|
||||
<WolClientSettingsItem client={client} />
|
||||
</Group>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown w={isMobile ? '100%' : undefined}>
|
||||
<DebouncedWolClient wolClient={client} setIsOpen={setIsOpen} />
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</GridCol>
|
||||
))
|
||||
) : (
|
||||
<GridCol
|
||||
w={'100%'}
|
||||
span={18}
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<NoItemsFound />
|
||||
</GridCol>
|
||||
))}
|
||||
)}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -93,3 +93,36 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loadingSpinner {
|
||||
&.root {
|
||||
transition: all .1s ease;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 100%;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
color: var(--mantine-color-primary-0);
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
.loader {
|
||||
display: none;
|
||||
}
|
||||
:hover {
|
||||
background-color: var(--mantine-color-primary-0) !important;
|
||||
color: var(--mantine-color-accent-0) !important;
|
||||
transition: all .1s ease;
|
||||
}
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.tableButton {
|
||||
.newItemButton {
|
||||
&.root {
|
||||
&.loader {
|
||||
display: none;
|
||||
|
||||
Reference in New Issue
Block a user