Form updates

This commit is contained in:
Sean Edmunds
2025-06-13 12:53:17 -05:00
parent 29514d3290
commit c3530a23a4
10 changed files with 472 additions and 436 deletions

View File

@@ -14,6 +14,7 @@ import {
Text,
TextInput,
} from '@mantine/core';
import { isNotEmpty, useForm } from '@mantine/form';
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
import { useEffect, useState } from 'react';
@@ -36,10 +37,19 @@ const ApplicationSettings = () => {
url: '',
icon: '',
baseUrl: '',
filterName: '',
filterName: 'Default',
index: 0,
enabled: 1,
};
const form = useForm({
initialValues: defaultApplication,
validate: {
name: isNotEmpty('Name is required'),
url: isNotEmpty('URL is required'),
icon: isNotEmpty('Icon is required'),
filterName: isNotEmpty('Filter name is required'),
},
});
const appsEndpoint = 'api/applications';
const appsOrderEndpoint = 'api/applicationOrder';
const { data: applicationData, isFetching: isAppsFetching } = useQueryAPI({
@@ -48,7 +58,6 @@ const ApplicationSettings = () => {
const [applications, setApplications] = useState<Application[]>([]);
const [applicationList, setApplicationList] = useState<Application[]>([]);
const { isMobile, reorder } = useCommonHooks();
const [newApplication, setNewApplication] = useState<Application>(defaultApplication);
const { mutateAsync } = useMutationAPI();
const [searchValue, setSearchValue] = useState('');
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
@@ -80,7 +89,7 @@ const ApplicationSettings = () => {
const handleClose = () => {
closeModal();
setNewApplication(defaultApplication);
form.reset();
};
useEffect(() => {
if (applicationData && !isAppsFetching) {
@@ -116,137 +125,124 @@ const ApplicationSettings = () => {
handleClose={handleClose}
title={'Add a new application'}
body={
<Stack gap={0} align='center'>
<Group w={'100%'} justify='center'>
<TextInput
label={'Name'}
className={textInputClasses.settingsItem}
w={'45%'}
classNames={textInputClasses}
defaultValue={newApplication.name}
onChange={(event) => {
setNewApplication({
...newApplication,
name: event.target.value,
});
}}
/>
<TextInput
label={'Base URL'}
className={textInputClasses.settingsItem}
w={'45%'}
classNames={textInputClasses}
defaultValue={newApplication.baseUrl}
onChange={(event) => {
setNewApplication({
...newApplication,
baseUrl: event.target.value,
});
}}
/>
</Group>
<Group w={isMobile ? '94%' : '92%'} justify='center'>
<TextInput
label={'URL'}
className={textInputClasses.settingsItem}
w={'100%'}
classNames={textInputClasses}
defaultValue={newApplication.url}
onChange={(event) => {
setNewApplication({
...newApplication,
url: event.target.value,
});
}}
/>
</Group>
<Group w={'100%'} justify='center'>
<Select
label={'Icon'}
className={selectClasses.settingsItem}
classNames={{
input: selectClasses.menuSelectInput,
label: selectClasses.menuSelectLabel,
options: selectClasses.menuSelectOptions,
option: selectClasses.menuSelectOption,
dropdown: selectClasses.dropdown,
}}
w={'45%'}
mt={'.75rem'}
limit={100}
placeholder='Search for an icon...'
data={Object.keys(allIcons).map((icon) => {
return { value: icon, label: icon };
})}
defaultValue={newApplication.icon}
comboboxProps={{
zIndex: 3000,
}}
onChange={(value: string | null) => {
setNewApplication({
...newApplication,
icon: value ?? '',
});
}}
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
const Icon = allIcons[item.option.value];
return (
<Group>
{item.option.value}
<Icon />
</Group>
);
}}
searchable
maxDropdownHeight={175}
onDropdownOpen={() => setIsOpen(true)}
onDropdownClose={() => setIsOpen(false)}
/>
<TextInput
label={'Filter'}
className={textInputClasses.settingsItem}
pt={'1rem'}
w={'45%'}
classNames={textInputClasses}
defaultValue={newApplication.filterName}
onChange={(event) => {
setNewApplication({
...newApplication,
filterName: event.target.value,
});
}}
/>
</Group>
<form
onSubmit={form.onSubmit((values) => {
mutateAsync({
method: 'POST',
endpoint: appsEndpoint,
body: {
...values,
index: applications.length,
},
});
handleClose();
})}
>
<Stack gap={0} align='center'>
<Group w={'100%'} justify='center'>
<TextInput
key={form.key('name')}
{...form.getInputProps('name')}
label={'Name'}
placeholder='Nextcloud'
className={textInputClasses.settingsItem}
w={'45%'}
classNames={textInputClasses}
withAsterisk
/>
<TextInput
key={form.key('baseUrl')}
{...form.getInputProps('baseUrl')}
label={'Base URL'}
placeholder='/nextcloud'
className={textInputClasses.settingsItem}
w={'45%'}
classNames={textInputClasses}
/>
</Group>
<Group w={isMobile ? '94%' : '92%'} justify='center'>
<TextInput
key={form.key('url')}
{...form.getInputProps('url')}
label={'URL'}
placeholder='https://domain.example.com/nextcloud'
className={textInputClasses.settingsItem}
w={'100%'}
classNames={textInputClasses}
withAsterisk
/>
</Group>
<Group w={'100%'} justify='center'>
<Select
key={form.key('icon')}
{...form.getInputProps('icon')}
label={'Icon'}
className={selectClasses.settingsItem}
classNames={{
input: selectClasses.menuSelectInput,
label: selectClasses.menuSelectLabel,
options: selectClasses.menuSelectOptions,
option: selectClasses.menuSelectOption,
dropdown: selectClasses.dropdown,
}}
w={'45%'}
mt={'.75rem'}
limit={100}
placeholder='Search for an icon...'
data={Object.keys(allIcons).map((icon) => {
return { value: icon, label: icon };
})}
comboboxProps={{
zIndex: 3000,
}}
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
const Icon = allIcons[item.option.value];
return (
<Group>
{item.option.value}
<Icon />
</Group>
);
}}
searchable
maxDropdownHeight={175}
onDropdownOpen={() => setIsOpen(true)}
onDropdownClose={() => setIsOpen(false)}
withAsterisk
/>
<TextInput
key={form.key('filter')}
{...form.getInputProps('filter')}
label={'Filter'}
placeholder='Storage'
className={textInputClasses.settingsItem}
pt={'1rem'}
w={'45%'}
classNames={textInputClasses}
defaultValue={defaultApplication.filterName}
withAsterisk
/>
</Group>
<Divider color='black' my='lg' />
<Group pos={'absolute'} bottom={8}>
<Button
color={'green'}
styles={{ label: { color: 'black' } }}
onClick={() => {
if (newApplication) {
mutateAsync({
method: 'POST',
endpoint: appsEndpoint,
body: {
...newApplication,
index: applications.length,
},
}).then(handleClose);
}
}}
>
Save
</Button>
<Button
color={'red'}
styles={{ label: { color: 'black' } }}
onClick={handleClose}
>
Cancel
</Button>
</Group>
</Stack>
<Divider color='black' my='lg' />
<Group pos={'absolute'} bottom={8}>
<Button
type='submit'
color={'green'}
styles={{ label: { color: 'black' } }}
>
Save
</Button>
<Button
color={'red'}
styles={{ label: { color: 'black' } }}
onClick={handleClose}
>
Cancel
</Button>
</Group>
</Stack>
</form>
}
/>
<SettingsHeader