diff --git a/apps/client/src/externals.ts b/apps/client/src/externals.ts index ea971bfc9..a456611f9 100644 --- a/apps/client/src/externals.ts +++ b/apps/client/src/externals.ts @@ -3,3 +3,4 @@ export const apiRepoLatest = 'https://api.github.com/repos/cpvalente/ontime/rele export const websiteUrl = 'https://www.getontime.no'; export const documentationUrl = 'https://docs.getontime.no'; +export const customFieldsDocsUrl = 'https://docs.getontime.no/features/custom-fields/'; diff --git a/apps/client/src/features/app-settings/panel/feature-settings-panel/FeatureSettings.module.scss b/apps/client/src/features/app-settings/panel/feature-settings-panel/FeatureSettings.module.scss index c9c8a13f2..704cf483a 100644 --- a/apps/client/src/features/app-settings/panel/feature-settings-panel/FeatureSettings.module.scss +++ b/apps/client/src/features/app-settings/panel/feature-settings-panel/FeatureSettings.module.scss @@ -1,3 +1,7 @@ +.halfWidth { + width: 50%; +} + .fullWidth { width: 100%; } @@ -37,3 +41,9 @@ .flex { display: flex; } + +.twoCols { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} diff --git a/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFieldEntry.tsx b/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFieldEntry.tsx index 18a787392..a22694ee8 100644 --- a/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFieldEntry.tsx +++ b/apps/client/src/features/app-settings/panel/feature-settings-panel/custom-fields/CustomFieldEntry.tsx @@ -4,6 +4,7 @@ import { IoPencil } from '@react-icons/all-files/io5/IoPencil'; import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; import { CustomField, CustomFieldLabel } from 'ontime-types'; +import CopyTag from '../../../../../common/components/copy-tag/CopyTag'; import Swatch from '../../../../../common/components/input/colour-input/Swatch'; import CustomFieldForm from './CustomFieldForm'; @@ -36,6 +37,7 @@ export default function CustomFieldEntry(props: CustomFieldEntryProps) { onSubmit={handleEdit} initialColour={colour} initialLabel={label} + initialKey={field} /> @@ -47,7 +49,12 @@ export default function CustomFieldEntry(props: CustomFieldEntryProps) { - {label} + {label} + + + {field} + + void; initialColour?: string; initialLabel?: string; + initialKey?: string; } export default function CustomFieldForm(props: CustomFieldsFormProps) { - const { onSubmit, onCancel, initialColour, initialLabel } = props; + const { onSubmit, onCancel, initialColour, initialLabel, initialKey } = props; + const { data } = useCustomFields(); + // we use this to force an update const [_, setColour] = useState(initialColour || ''); @@ -31,7 +35,7 @@ export default function CustomFieldForm(props: CustomFieldsFormProps) { getValues, formState: { errors, isSubmitting, isValid, isDirty }, } = useForm({ - defaultValues: { label: initialLabel || '', colour: initialColour || '' }, + defaultValues: { label: initialLabel || '', colour: initialColour || '', key: initialKey || '' }, resetOptions: { keepDirtyValues: true, }, @@ -66,28 +70,38 @@ export default function CustomFieldForm(props: CustomFieldsFormProps) { return (
-
- Label (only alphanumeric characters are allowed) - {errors.label && {errors.label.message}} - { - if (value.trim().length === 0) return 'Required field'; - if (!isAlphanumeric(value)) return 'Only alphanumeric characters are allowed'; - return true; - }, - })} - size='sm' - variant='ontime-filled' - autoComplete='off' - /> +
+
+ Label (only alphanumeric characters are allowed) + {errors.label && {errors.label.message}} + setValue('key', customFieldLabelToKey(getValues('label')) ?? 'N/A'), + validate: (value) => { + if (value.trim().length === 0) return 'Required field'; + if (!isAlphanumericWithSpace(value)) return 'Only alphanumeric characters and space are allowed'; + if (Object.keys(data).includes(value)) return 'Custom fields must be unique'; + return true; + }, + })} + size='sm' + variant='ontime-filled' + autoComplete='off' + /> +
+ +
+ Key (auto-generated value for use in Integrations and API) + +
Colour handleSelectColour(value)} />
+ {errors.root && {errors.root.message}}