refactor: restructure settings

refactor: migrate react components
This commit is contained in:
Carlos Valente
2025-07-04 12:13:06 +02:00
parent a8ea1080f3
commit 5600235ba1
93 changed files with 711 additions and 906 deletions
@@ -1,7 +1,7 @@
import { useEffect, useMemo } from 'react';
import { Controller, useFieldArray, useForm } from 'react-hook-form';
import { IoAdd, IoTrash } from 'react-icons/io5';
import { Button, IconButton, Input, Radio, RadioGroup, Select } from '@chakra-ui/react';
import { Radio, RadioGroup, Select } from '@chakra-ui/react';
import {
Automation,
AutomationDTO,
@@ -15,7 +15,10 @@ import {
import { addAutomation, editAutomation, testOutput } from '../../../../common/api/automation';
import { maybeAxiosError } from '../../../../common/api/utils';
import Button from '../../../../common/components/buttons/Button';
import IconButton from '../../../../common/components/buttons/IconButton';
import Info from '../../../../common/components/info/Info';
import Input from '../../../../common/components/input/input/Input';
import ExternalLink from '../../../../common/components/link/external-link/ExternalLink';
import Tag from '../../../../common/components/tag/Tag';
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
@@ -198,10 +201,8 @@ export default function AutomationForm(props: AutomationFormProps) {
Title
<Input
{...register('title', { required: { value: true, message: 'Required field' } })}
variant='ontime-filled'
size='sm'
fluid
placeholder='Load preset'
autoComplete='off'
/>
</label>
<Panel.Error>{errors.title?.message}</Panel.Error>
@@ -272,43 +273,22 @@ export default function AutomationForm(props: AutomationFormProps) {
</label>
<label>
Value to match
<Input
{...register(`filters.${index}.value`)}
variant='ontime-filled'
size='sm'
placeholder='<empty / no value>'
autoComplete='off'
/>
<Input {...register(`filters.${index}.value`)} fluid placeholder='<empty / no value>' />
</label>
<div>
<span>&nbsp;</span>
<div>
<IconButton
aria-label='Delete'
icon={<IoTrash />}
variant='ontime-ghosted'
size='sm'
color='#FA5656' // $red-500
onClick={() => removeFilter(index)}
isDisabled={false}
isLoading={false}
/>
<IconButton aria-label='Delete' variant='ghosted-destructive' onClick={() => removeFilter(index)}>
<IoTrash />
</IconButton>
</div>
</div>
</div>
);
})}
<div>
<Button
variant='ontime-subtle'
size='sm'
type='submit'
rightIcon={<IoAdd />}
onClick={handleAddNewFilter}
isDisabled={false}
isLoading={false}
>
Add filter
<Button type='submit' onClick={handleAddNewFilter}>
Add filter <IoAdd />
</Button>
</div>
</div>
@@ -342,10 +322,8 @@ export default function AutomationForm(props: AutomationFormProps) {
{...register(`outputs.${index}.targetIP`, {
required: { value: true, message: 'Required field' },
})}
variant='ontime-filled'
size='sm'
fluid
placeholder='127.0.0.1'
autoComplete='off'
/>
<Panel.Error>{rowErrors?.targetIP?.message}</Panel.Error>
</label>
@@ -358,51 +336,32 @@ export default function AutomationForm(props: AutomationFormProps) {
max: { value: 65535, message: 'Port must be within range 1024 - 65535' },
min: { value: 1024, message: 'Port must be within range 1024 - 65535' },
})}
variant='ontime-filled'
size='sm'
fluid
type='number'
maxLength={5}
placeholder='8000'
autoComplete='off'
/>
<Panel.Error>{rowErrors?.targetPort?.message}</Panel.Error>
</label>
<label>
Address
<Input
{...register(`outputs.${index}.address`)}
variant='ontime-filled'
size='sm'
placeholder='/cue/start'
autoComplete='off'
/>
<Input {...register(`outputs.${index}.address`)} fluid placeholder='/cue/start' />
<Panel.Error>{rowErrors?.address?.message}</Panel.Error>
</label>
<label>
Arguments
<TemplateInput
{...register(`outputs.${index}.args`)}
value={output.args}
variant='ontime-filled'
size='sm'
placeholder='1'
/>
<TemplateInput {...register(`outputs.${index}.args`)} value={output.args} placeholder='1' />
<Panel.Error>{rowErrors?.args?.message}</Panel.Error>
</label>
<div>
<span>&nbsp;</span>
<Panel.InlineElements relation='inner'>
<Button size='sm' variant='ontime-ghosted-white' onClick={() => handleTestOSCOutput(index)}>
<Button variant='ghosted-white' onClick={() => handleTestOSCOutput(index)}>
Test
</Button>
<IconButton
aria-label='Delete'
icon={<IoTrash />}
variant='ontime-ghosted'
size='sm'
onClick={() => removeOutput(index)}
color='#FA5656' // $red-500
/>
<IconButton aria-label='Delete' variant='ghosted-destructive' onClick={() => removeOutput(index)}>
<IoTrash />
</IconButton>
</Panel.InlineElements>
</div>
</div>
@@ -429,27 +388,20 @@ export default function AutomationForm(props: AutomationFormProps) {
message: 'HTTP messages should target http:// or https://',
},
})}
variant='ontime-filled'
size='sm'
fluid
placeholder='http://127.0.0.1/start/1'
autoComplete='off'
/>
<Panel.Error>{rowErrors?.url?.message}</Panel.Error>
</label>
<div>
<span>&nbsp;</span>
<Panel.InlineElements relation='inner'>
<Button size='sm' variant='ontime-ghosted-white' onClick={() => handleTestHTTPOutput(index)}>
<Button variant='ghosted-white' onClick={() => handleTestHTTPOutput(index)}>
Test
</Button>
<IconButton
aria-label='Delete'
icon={<IoTrash />}
variant='ontime-ghosted'
size='sm'
onClick={() => removeOutput(index)}
color='#FA5656' // $red-500
/>
<IconButton aria-label='Delete' variant='ghosted-destructive' onClick={() => removeOutput(index)}>
<IoTrash />
</IconButton>
</Panel.InlineElements>
</div>
</div>
@@ -479,17 +431,12 @@ export default function AutomationForm(props: AutomationFormProps) {
>
<span>&nbsp;</span>
<Panel.InlineElements relation='inner'>
<Button size='sm' variant='ontime-ghosted-white' onClick={() => handleTestOntimeAction(index)}>
<Button variant='ghosted-white' onClick={() => handleTestOntimeAction(index)}>
Test
</Button>
<IconButton
aria-label='Delete'
icon={<IoTrash />}
variant='ontime-ghosted'
size='sm'
onClick={() => removeOutput(index)}
color='#FA5656' // $red-500
/>
<IconButton aria-label='Delete' variant='ghosted-destructive' onClick={() => removeOutput(index)}>
<IoTrash />
</IconButton>
</Panel.InlineElements>
</OntimeActionForm>
</div>
@@ -500,24 +447,22 @@ export default function AutomationForm(props: AutomationFormProps) {
return null;
})}
<Panel.InlineElements relation='inner'>
<Button variant='ontime-subtle' rightIcon={<IoAdd />} size='sm' onClick={handleAddNewOSCOutput}>
OSC
<Button onClick={handleAddNewOSCOutput}>
OSC <IoAdd />
</Button>
<Button variant='ontime-subtle' rightIcon={<IoAdd />} size='sm' onClick={handleAddNewHTTPOutput}>
HTTP
<Button onClick={handleAddNewHTTPOutput}>
HTTP <IoAdd />
</Button>
<Button variant='ontime-subtle' rightIcon={<IoAdd />} size='sm' onClick={handleAddnewOntimeAction}>
Ontime action
<Button onClick={handleAddnewOntimeAction}>
Ontime action <IoAdd />
</Button>
</Panel.InlineElements>
</div>
<Panel.InlineElements align='end'>
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Button variant='ontime-subtle' size='sm' onClick={onClose}>
Cancel
</Button>
<Button variant='ontime-filled' size='sm' type='submit' isDisabled={!canSubmit} isLoading={isSubmitting}>
<Button onClick={onClose}>Cancel</Button>
<Button variant='primary' type='submit' disabled={!canSubmit} loading={isSubmitting}>
Save
</Button>
</Panel.InlineElements>
@@ -1,9 +1,11 @@
import { Controller, useForm } from 'react-hook-form';
import { Button, Input, Switch } from '@chakra-ui/react';
import { Switch } from '@chakra-ui/react';
import { editAutomationSettings } from '../../../../common/api/automation';
import { maybeAxiosError } from '../../../../common/api/utils';
import Button from '../../../../common/components/buttons/Button';
import Info from '../../../../common/components/info/Info';
import Input from '../../../../common/components/input/input/Input';
import ExternalLink from '../../../../common/components/link/external-link/ExternalLink';
import { preventEscape } from '../../../../common/utils/keyEvent';
import { isOnlyNumbers } from '../../../../common/utils/regex';
@@ -57,16 +59,15 @@ export default function AutomationSettingsForm(props: AutomationSettingsProps) {
<Panel.SubHeader>
Automation settings
<Panel.InlineElements>
<Button variant='ontime-ghosted' size='sm' onClick={onReset} isDisabled={!canSubmit}>
<Button variant='ghosted' onClick={onReset} disabled={!canSubmit}>
Revert to saved
</Button>
<Button
variant='ontime-filled'
size='sm'
variant='primary'
type='submit'
form='automation-settings-form'
isDisabled={!canSubmit}
isLoading={isSubmitting}
disabled={!canSubmit}
loading={isSubmitting}
>
Save
</Button>
@@ -139,13 +140,10 @@ export default function AutomationSettingsForm(props: AutomationSettingsProps) {
<Input
id='oscPortIn'
placeholder='8888'
width='5rem'
maxLength={5}
size='sm'
textAlign='right'
variant='ontime-filled'
style={{ textAlign: 'right', width: '5rem' }}
type='number'
autoComplete='off'
fluid
{...register('oscPortIn', {
required: { value: true, message: 'Required field' },
max: { value: 65535, message: 'Port must be within range 1024 - 65535' },
@@ -1,10 +1,11 @@
import { Fragment, useState } from 'react';
import { IoAdd, IoPencil, IoTrash } from 'react-icons/io5';
import { Button, IconButton } from '@chakra-ui/react';
import { AutomationDTO, NormalisedAutomation } from 'ontime-types';
import { deleteAutomation } from '../../../../common/api/automation';
import { maybeAxiosError } from '../../../../common/api/utils';
import Button from '../../../../common/components/buttons/Button';
import IconButton from '../../../../common/components/buttons/IconButton';
import Tag from '../../../../common/components/tag/Tag';
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
import * as Panel from '../../panel-utils/PanelUtils';
@@ -46,14 +47,11 @@ export default function AutomationsList(props: AutomationsListProps) {
<Panel.SubHeader>
Manage automations
<Button
variant='ontime-subtle'
rightIcon={<IoAdd />}
size='sm'
type='submit'
isDisabled={Boolean(automationFormData)}
disabled={Boolean(automationFormData)}
onClick={() => setAutomationFormData(automationPlaceholder)}
>
New
New <IoAdd />
</Button>
</Panel.SubHeader>
@@ -94,21 +92,19 @@ export default function AutomationsList(props: AutomationsListProps) {
<td>{automations[automationId].outputs.length}</td>
<Panel.InlineElements align='end' relation='inner' as='td'>
<IconButton
size='sm'
variant='ontime-ghosted'
color='#e2e2e2' // $gray-200
icon={<IoPencil />}
variant='ghosted-white'
aria-label='Edit entry'
onClick={() => setAutomationFormData(automations[automationId])}
/>
>
<IoPencil />
</IconButton>
<IconButton
size='sm'
variant='ontime-ghosted'
color='#FA5656' // $red-500
icon={<IoTrash />}
variant='ghosted-destructive'
aria-label='Delete entry'
onClick={() => handleDelete(automationId)}
/>
>
<IoTrash />
</IconButton>
</Panel.InlineElements>
</tr>
{deleteError && (
@@ -1,8 +1,9 @@
import { PropsWithChildren, useState } from 'react';
import { UseFormRegister, UseFormSetValue } from 'react-hook-form';
import { Input, Select } from '@chakra-ui/react';
import { Select } from '@chakra-ui/react';
import { AutomationDTO, OntimeAction } from 'ontime-types';
import Input from '../../../../common/components/input/input/Input';
import { cx } from '../../../../common/utils/styleUtils';
import * as Panel from '../../panel-utils/PanelUtils';
@@ -67,10 +68,8 @@ export default function OntimeActionForm(props: PropsWithChildren<OntimeActionFo
{...register(`outputs.${index}.time`, {
required: { value: true, message: 'Required field' },
})}
variant='ontime-filled'
size='sm'
fluid
placeholder='eg: 10m5s'
autoComplete='off'
/>
<Panel.Error>{rowErrors?.time?.message}</Panel.Error>
</label>
@@ -80,13 +79,7 @@ export default function OntimeActionForm(props: PropsWithChildren<OntimeActionFo
<>
<label>
Text (leave empty for no change)
<Input
{...register(`outputs.${index}.text`)}
variant='ontime-filled'
size='sm'
placeholder='eg: Timer is finished'
autoComplete='off'
/>
<Input {...register(`outputs.${index}.text`)} fluid placeholder='eg: Timer is finished' />
<Panel.Error>{rowErrors?.text?.message}</Panel.Error>
</label>
<label>
@@ -1,10 +1,12 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Button, Input, Select } from '@chakra-ui/react';
import { Select } from '@chakra-ui/react';
import { NormalisedAutomation, TimerLifeCycle, TriggerDTO } from 'ontime-types';
import { addTrigger, editTrigger } from '../../../../common/api/automation';
import { maybeAxiosError } from '../../../../common/api/utils';
import Button from '../../../../common/components/buttons/Button';
import Input from '../../../../common/components/input/input/Input';
import { preventEscape } from '../../../../common/utils/keyEvent';
import * as Panel from '../../panel-utils/PanelUtils';
@@ -87,9 +89,7 @@ export default function TriggerForm(props: TriggerFormProps) {
Title
<Input
{...register('title', { required: { value: true, message: 'Required field' } })}
size='sm'
variant='ontime-filled'
autoComplete='off'
fluid
defaultValue={initialTitle}
/>
<Panel.Error>{errors.title?.message}</Panel.Error>
@@ -127,10 +127,10 @@ export default function TriggerForm(props: TriggerFormProps) {
<Panel.Error>{errors.automationId?.message}</Panel.Error>
</label>
<Panel.InlineElements align='end'>
<Button size='sm' variant='ontime-subtle' isDisabled={isSubmitting} onClick={onCancel}>
<Button disabled={isSubmitting} onClick={onCancel}>
Cancel
</Button>
<Button type='submit' size='sm' variant='ontime-filled' isDisabled={!canSubmit} isLoading={isSubmitting}>
<Button type='submit' variant='primary' disabled={!canSubmit} loading={isSubmitting}>
Save
</Button>
</Panel.InlineElements>
@@ -1,10 +1,10 @@
import { Fragment, useMemo, useState } from 'react';
import { IoAdd } from 'react-icons/io5';
import { Button } from '@chakra-ui/react';
import { NormalisedAutomation, Trigger } from 'ontime-types';
import { deleteTrigger } from '../../../../common/api/automation';
import { maybeAxiosError } from '../../../../common/api/utils';
import Button from '../../../../common/components/buttons/Button';
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
import * as Panel from '../../panel-utils/PanelUtils';
@@ -47,17 +47,8 @@ export default function TriggersList(props: TriggersListProps) {
<Panel.Card>
<Panel.SubHeader>
Manage triggers
<Button
variant='ontime-subtle'
rightIcon={<IoAdd />}
size='sm'
type='submit'
form='trigger-form'
isDisabled={!canAdd}
isLoading={false}
onClick={() => setShowForm(true)}
>
New
<Button type='submit' form='trigger-form' disabled={!canAdd} loading={false} onClick={() => setShowForm(true)}>
New <IoAdd />
</Button>
</Panel.SubHeader>
<Panel.Divider />
@@ -1,8 +1,8 @@
import { useState } from 'react';
import { IoPencil, IoTrash, IoWarningOutline } from 'react-icons/io5';
import { IconButton } from '@chakra-ui/react';
import { NormalisedAutomation, TimerLifeCycle } from 'ontime-types';
import IconButton from '../../../../common/components/buttons/IconButton';
import Tag from '../../../../common/components/tag/Tag';
import * as Panel from '../../panel-utils/PanelUtils';
@@ -62,22 +62,12 @@ export default function TriggersListItem(props: TriggersListItemProps) {
<Tag>{automations?.[automationId]?.title}</Tag>
</td>
<Panel.InlineElements align='end' relation='inner' as='td'>
<IconButton
size='sm'
variant='ontime-ghosted'
color='#e2e2e2' // $gray-200
icon={<IoPencil />}
aria-label='Edit entry'
onClick={() => setIsEditing(true)}
/>
<IconButton
size='sm'
variant='ontime-ghosted'
color='#FA5656' // $red-500
icon={<IoTrash />}
aria-label='Delete entry'
onClick={handleDelete}
/>
<IconButton variant='ghosted-white' aria-label='Edit entry' onClick={() => setIsEditing(true)}>
<IoPencil />
</IconButton>
<IconButton variant='ghosted-destructive' aria-label='Delete entry' onClick={handleDelete}>
<IoTrash />
</IconButton>
</Panel.InlineElements>
</tr>
);
@@ -1,7 +1,7 @@
import { forwardRef, useMemo, useState } from 'react';
import { type InputProps, Input } from '@chakra-ui/react';
import { mergeRefs, useClickOutside } from '@mantine/hooks';
import Input, { type InputProps } from '../../../../../common/components/input/input/Input';
import useCustomFields from '../../../../../common/hooks-query/useCustomFields';
import { makeAutoCompleteList, matchRemaining, selectFromLastTemplate } from './templateInput.utils';
@@ -53,7 +53,7 @@ const TemplateInput = forwardRef(function TemplateInput(props: TemplateInputProp
return (
<div className={style.wrapper} ref={mergeRefs(localRef, ref)}>
<Input value={inputValue} {...rest} onChange={handleInputChange} autoComplete='off' autoCorrect='off' />
<Input value={inputValue} {...rest} onChange={handleInputChange} fluid />
{showSuggestions && suggestions.length > 0 && (
<ul className={style.suggestions}>
{suggestions.map((suggestion) => (