Files
TriggerFrontend/src/pages/settings/components/WoL/WolClients.tsx
Sean Edmunds 2f8d3ad58e
All checks were successful
Deploy to production / build (push) Successful in 18s
Added persistent sort to more locations
2026-10-20 18:58:52 -05:00

242 lines
6.5 KiB
TypeScript

import {
Button,
Grid,
GridCol,
Group,
LoadingOverlay,
Popover,
Stack,
TextInput,
} from '@mantine/core';
import { isNotEmpty, matches, 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 { buttonClasses, popoverClasses, textInputClasses } from 'styles';
import type { WolClient } from 'types';
import DebouncedWolClient from './DebouncedWolClient';
import WolClientSettingsItem from './WolClientSettingsItem';
export function WolClients() {
const { isMobile, theme, settings, updateSetting } = useCommonHooks();
const defaultWolClient: WolClient = {
id: undefined,
hostname: '',
macAddress: '',
};
const form = useForm({
initialValues: defaultWolClient,
validate: {
hostname: isNotEmpty('Hostname is required'),
macAddress: matches(
/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/,
'Invalid MAC Address',
),
},
});
const endpoint = 'api/wolClients';
const { data: wolClientsData, isFetching: wolClientsIsFetching } = useQueryAPI({
endpoint: endpoint,
});
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()),
)
.sort((a: WolClient, b: WolClient) => {
switch (settings.wolClientsSettingsSort) {
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 [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const handleClose = () => {
closeModal();
form.reset();
};
useEffect(() => {
if (debouncedSearch === searchValue) {
setIsLoading(false);
}
}, [debouncedSearch]);
return (
<Stack>
{modalOpened && (
<AddItemModal
modalOpened={modalOpened}
handleClose={handleClose}
title={'Add a new WoL Client'}
body={
<form
onSubmit={form.onSubmit((values) => {
mutateAsync({
method: 'POST',
endpoint: endpoint,
body: values,
}).catch((error) => {
console.error(error);
});
handleClose();
})}
>
<Stack gap={0} w={'100%'} align='center'>
<Group w={'100%'} justify='center'>
<TextInput
key={form.key('hostname')}
{...form.getInputProps('hostname')}
label={'Hostname'}
placeholder='eg. Workstation'
className={textInputClasses.settingsItem}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
/>
<TextInput
key={form.key('macAddress')}
{...form.getInputProps('macAddress')}
label={'MAC Address'}
placeholder='eg. 00:00:00:00:00:00'
className={textInputClasses.settingsItem}
classNames={textInputClasses}
withAsterisk
styles={
theme.textShadowsEnabled
? {
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
}
: undefined
}
/>
</Group>
<Group mt={'1rem'}>
<Button
type='submit'
className={buttonClasses.saveButton}
classNames={buttonClasses}
>
Save
</Button>
<Button
className={buttonClasses.cancelButton}
classNames={buttonClasses}
onClick={handleClose}
>
Cancel
</Button>
</Group>
</Stack>
</form>
}
/>
)}
<SettingsHeader
setDragging={undefined}
openModal={openModal}
title={'WoL Clients'}
tooltip={{
title: 'Wake on Lan clients can be configured here.',
highlightList: ['Clients', 'homepage'],
description: 'Clients can be powered on from the settings button the homepage.',
}}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchPlaceholder={'Search Clients...'}
isLoading={isLoading}
setIsLoading={setIsLoading}
sortBy={settings.wolClientsSettingsSort}
setSortBy={(value) => {
updateSetting('wolClientsSettingsSort', value);
}}
customSearchEnabled={false}
/>
<Stack
mt={isMobile ? '10rem' : '6rem'}
mb={isMobile ? '6rem' : '1rem'}
w={'100%'}
align='center'
>
<LoadingOverlay
visible={wolClientsIsFetching}
zIndex={180}
overlayProps={{ blur: 5 }}
loaderProps={{
color: 'var(--mantine-color-primary-0)',
type: 'bars',
size: 'xl',
}}
/>
<Grid columns={18} w={'75%'}>
{wolClients.length > 0 ? (
wolClients?.map((client: WolClient) => (
<GridCol w={'100%'} span={isMobile ? 18 : 6} key={client.id}>
<Popover
withArrow
withOverlay
trapFocus
closeOnClickOutside={!isOpen}
classNames={popoverClasses}
middlewares={{ shift: { padding: 20 } }}
position='bottom'
offset={isMobile ? -100 : 0}
>
<Popover.Target>
<Group justify='center'>
<WolClientSettingsItem client={client} />
</Group>
</Popover.Target>
<Popover.Dropdown w={isMobile ? '90%' : 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>
);
}