refactor: migrate modal components

This commit is contained in:
Carlos Valente
2025-07-04 10:34:30 +02:00
committed by Carlos Valente
parent 36223ff49f
commit a8ea1080f3
13 changed files with 288 additions and 307 deletions
@@ -8,5 +8,8 @@
}
.column {
width: 100%;
display: flex;
gap: 1rem;
flex-direction: column;
}
@@ -1,17 +1,9 @@
import { lazy, useEffect, useRef, useState } from 'react';
import {
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from '@chakra-ui/react';
import { getCSSContents, postCSSContents, restoreCSSContents } from '../../../../common/api/assets';
import Button from '../../../../common/components/buttons/Button';
import Info from '../../../../common/components/info/Info';
import Modal from '../../../../common/components/modal/Modal';
import * as Panel from '../../panel-utils/PanelUtils';
import style from './StyleEditorModal.module.scss';
@@ -27,9 +19,7 @@ interface CSSRef {
getCss: () => string;
}
export default function CodeEditorModal(props: CodeEditorModalProps) {
const { isOpen, onClose } = props;
export default function CodeEditorModal({ isOpen, onClose }: CodeEditorModalProps) {
const [css, setCSS] = useState('');
const [isDirty, setIsDirty] = useState(false);
const [saveLoading, setSaveLoading] = useState(false);
@@ -84,46 +74,43 @@ export default function CodeEditorModal(props: CodeEditorModalProps) {
}, [isOpen]);
return (
<Modal isOpen={isOpen} onClose={onClose} variant='ontime' isCentered>
<ModalOverlay />
<ModalContent maxWidth='max(800px, 40vw)'>
<ModalHeader>Edit CSS override</ModalHeader>
<ModalCloseButton />
<ModalBody>
<CodeEditor ref={cssRef} initialValue={css} language='css' isDirty={isDirty} setIsDirty={setIsDirty} />
</ModalBody>
<ModalFooter className={style.column}>
<Modal
title='Edit CSS override'
isOpen={isOpen}
onClose={onClose}
showCloseButton
showBackdrop
bodyElements={
<CodeEditor ref={cssRef} initialValue={css} language='css' isDirty={isDirty} setIsDirty={setIsDirty} />
}
footerElements={
<div className={style.column}>
<Info>Invalid CSS will be refused by the browser</Info>
{error && <Panel.Error className={style.right}>{`Error: ${error}`}</Panel.Error>}
<Panel.InlineElements align='apart' className={style.editorActions}>
<Button
variant='ontime-ghosted'
onClick={handleRestore}
isDisabled={saveLoading || resetLoading}
isLoading={resetLoading}
>
<Button variant='ghosted' size='large' onClick={handleRestore} disabled={saveLoading || resetLoading}>
Reset to example
</Button>
<Panel.InlineElements>
<Button variant='ontime-ghosted' onClick={clear}>
<Button variant='ghosted' size='large' onClick={clear}>
Clear
</Button>
<Button variant='ontime-subtle' onClick={onClose}>
<Button size='large' onClick={onClose}>
Cancel
</Button>
<Button
variant='ontime-filled'
variant='primary'
size='large'
onClick={handleSave}
isDisabled={saveLoading || resetLoading || !isDirty}
isLoading={saveLoading}
disabled={saveLoading || resetLoading || !isDirty}
loading={saveLoading}
>
Save changes
</Button>
</Panel.InlineElements>
</Panel.InlineElements>
</ModalFooter>
</ModalContent>
</Modal>
</div>
}
/>
);
}
@@ -1,8 +0,0 @@
.scrollContainer {
max-height: 70vh;
overflow: auto;
display: flex;
flex-direction: column;
gap: 1rem;
}
@@ -1,45 +1,35 @@
import { Controller, useForm } from 'react-hook-form';
import {
Button,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Select,
Switch,
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { QuickStartData } from 'ontime-types';
import { parseUserTime } from 'ontime-utils';
import { quickProject } from '../../../common/api/db';
import { invalidateAllCaches, maybeAxiosError } from '../../../common/api/utils';
import Button from '../../../common/components/buttons/Button';
import Input from '../../../common/components/input/input/Input';
import TimeInput from '../../../common/components/input/time-input/TimeInput';
import Modal from '../../../common/components/modal/Modal';
import Select from '../../../common/components/select/Select';
import Switch from '../../../common/components/switch/Switch';
import { editorSettingsDefaults, useEditorSettings } from '../../../common/stores/editorSettings';
import * as Panel from '../panel-utils/PanelUtils';
import { quickStartDefaults } from './quickStart.utils';
import style from './QuickStart.module.scss';
interface QuickStartProps {
isOpen: boolean;
onClose: () => void;
}
export default function QuickStart(props: QuickStartProps) {
const { isOpen, onClose } = props;
export default function QuickStart({ isOpen, onClose }: QuickStartProps) {
const { defaultWarnTime, defaultDangerTime, setDangerTime, setWarnTime } = useEditorSettings();
const {
control,
handleSubmit,
register,
formState: { errors, isSubmitting, isValid },
watch,
setError,
setValue,
} = useForm<QuickStartData>({
defaultValues: quickStartDefaults,
values: quickStartDefaults,
@@ -65,123 +55,114 @@ export default function QuickStart(props: QuickStartProps) {
const dangerTimeInMs = parseUserTime(defaultDangerTime);
return (
<Modal isOpen={isOpen} onClose={onClose} closeOnOverlayClick={false} variant='ontime'>
<ModalOverlay />
<ModalCloseButton />
<ModalContent maxWidth='max(640px, 40vw)'>
<Modal
isOpen={isOpen}
onClose={onClose}
showBackdrop
showCloseButton
title='Create new project...'
bodyElements={
<form onSubmit={handleSubmit(onSubmit)} id='quick-start'>
<ModalHeader>Create new project...</ModalHeader>
<ModalBody className={style.scrollContainer}>
<ModalCloseButton />
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Project title' description='Shown as the title in some views' />
<Input
variant='ontime-filled'
size='sm'
maxLength={150}
placeholder='Project title'
autoComplete='off'
width='20rem'
{...register('project.title')}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Time format'
description='Default time format to show in views 12 /24 hours'
error={errors.settings?.timeFormat?.message}
/>
<Select variant='ontime' size='sm' width='auto' isDisabled={false} {...register('settings.timeFormat')}>
<option value='12'>12 hours 11:00:10 PM</option>
<option value='24'>24 hours 23:00:10</option>
</Select>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Views language'
description='Language to be displayed in views'
error={errors.settings?.language?.message}
/>
<Select variant='ontime' size='sm' width='auto' isDisabled={false} {...register('settings.language')}>
<option value='en'>English</option>
<option value='fr'>French</option>
<option value='de'>German</option>
<option value='hu'>Hungarian</option>
<option value='it'>Italian</option>
<option value='no'>Norwegian</option>
<option value='pt'>Portuguese</option>
<option value='es'>Spanish</option>
<option value='sv'>Swedish</option>
<option value='pl'>Polish</option>
<option value='zh'>Chinese (Simplified)</option>
</Select>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Project title' description='Shown as the title in some views' />
<Input maxLength={150} placeholder='Project title' fluid {...register('project.title')} />
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Time format'
description='Default time format to show in views 12 /24 hours'
error={errors.settings?.timeFormat?.message}
/>
<Select
{...register('settings.timeFormat')}
defaultValue='24'
options={[
{ value: '12', label: '12 hours 11:00:10 PM' },
{ value: '24', label: '24 hours 23:00:10' },
]}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='Views language'
description='Language to be displayed in views'
error={errors.settings?.language?.message}
/>
<Select
{...register('settings.language')}
defaultValue='en'
options={[
{ value: 'en', label: 'English' },
{ value: 'fr', label: 'French' },
{ value: 'de', label: 'German' },
{ value: 'hu', label: 'Hungarian' },
{ value: 'it', label: 'Italian' },
{ value: 'no', label: 'Norwegian' },
{ value: 'pt', label: 'Portuguese' },
{ value: 'es', label: 'Spanish' },
{ value: 'sv', label: 'Swedish' },
{ value: 'pl', label: 'Polish' },
{ value: 'zh', label: 'Chinese (Simplified)' },
]}
/>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Warning time' description='Default threshold for warning time in an event' />
<TimeInput<'warnTime'>
name='warnTime'
submitHandler={(_field, value) => setWarnTime(value)}
time={warnTimeInMs}
placeholder={editorSettingsDefaults.warnTime}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field title='Danger time' description='Default threshold for danger time in an event' />
<TimeInput<'dangerTime'>
name='dangerTime'
submitHandler={(_field, value) => setDangerTime(value)}
time={dangerTimeInMs}
placeholder={editorSettingsDefaults.dangerTime}
/>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field title='Warning time' description='Default threshold for warning time in an event' />
<TimeInput<'warnTime'>
name='warnTime'
submitHandler={(_field, value) => setWarnTime(value)}
time={warnTimeInMs}
placeholder={editorSettingsDefaults.warnTime}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field title='Danger time' description='Default threshold for danger time in an event' />
<TimeInput<'dangerTime'>
name='dangerTime'
submitHandler={(_field, value) => setDangerTime(value)}
time={dangerTimeInMs}
placeholder={editorSettingsDefaults.dangerTime}
/>
</Panel.ListItem>
</Panel.ListGroup>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field
title='Freeze timer on end'
description='When a timer hits 00:00:00, it freezes instead of going negative. It invalidates the End Message.'
/>
<Controller
control={control}
name='viewSettings.freezeEnd'
render={({ field: { onChange, value, ref } }) => (
<Switch variant='ontime' size='lg' isChecked={value} onChange={onChange} ref={ref} />
)}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='End message'
description='Message for negative timers; applies only if the timer isn`t frozen on End. If no message is provided, it continues into negative time'
/>
<Input
size='sm'
autoComplete='off'
variant='ontime-filled'
maxLength={150}
width='20rem'
placeholder='Shown when timer reaches end'
{...register('viewSettings.endMessage')}
/>
</Panel.ListItem>
</Panel.ListGroup>
</ModalBody>
<ModalFooter>
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Button variant='ontime-ghosted' size='md' onClick={onClose} isDisabled={false}>
Cancel
</Button>
<Button variant='ontime-filled' size='md' type='submit' isDisabled={!isValid} isLoading={isSubmitting}>
Create project
</Button>
</ModalFooter>
<Panel.ListGroup>
<Panel.ListItem>
<Panel.Field
title='Freeze timer on end'
description='When a timer hits 00:00:00, it freezes instead of going negative. It invalidates the End Message.'
/>
<Switch
name='viewSettings.freezeEnd'
checked={watch('viewSettings.freezeEnd')}
onCheckedChange={(checked) => setValue('viewSettings.freezeEnd', checked)}
/>
</Panel.ListItem>
<Panel.ListItem>
<Panel.Field
title='End message'
description='Message for negative timers; applies only if the timer isn`t frozen on End. If no message is provided, it continues into negative time'
/>
<Input maxLength={150} fluid placeholder='eg: Time is up!' {...register('viewSettings.endMessage')} />
</Panel.ListItem>
</Panel.ListGroup>
</form>
</ModalContent>
</Modal>
}
footerElements={
<>
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Button variant='ghosted' onClick={onClose} disabled={false}>
Cancel
</Button>
<Button variant='primary' type='submit' form='quick-start' disabled={!isValid} loading={isSubmitting}>
Create project
</Button>
</>
}
/>
);
}