feat: add custom field type of image

This commit is contained in:
Carlos Valente
2025-02-19 20:29:13 +01:00
committed by Carlos Valente
parent d0b4c6a279
commit 570ff59cfc
18 changed files with 306 additions and 56 deletions
@@ -6,6 +6,7 @@ import { CustomField, CustomFieldLabel } from 'ontime-types';
import CopyTag from '../../../../../common/components/copy-tag/CopyTag';
import Swatch from '../../../../../common/components/input/colour-input/Swatch';
import Tag from '../../../../../common/components/tag/Tag';
import * as Panel from '../../../panel-utils/PanelUtils';
import CustomFieldForm from './CustomFieldForm';
@@ -13,19 +14,20 @@ import CustomFieldForm from './CustomFieldForm';
import style from '../FeatureSettings.module.scss';
interface CustomFieldEntryProps {
field: string;
colour: string;
label: string;
fieldKey: string;
type: 'string' | 'image';
onEdit: (label: CustomFieldLabel, patch: CustomField) => Promise<void>;
onDelete: (label: CustomFieldLabel) => Promise<void>;
}
export default function CustomFieldEntry(props: CustomFieldEntryProps) {
const { colour, label, onEdit, onDelete, field } = props;
const { colour, label, fieldKey, type, onEdit, onDelete } = props;
const [isEditing, setIsEditing] = useState(false);
const handleEdit = async (patch: CustomField) => {
await onEdit(field, patch);
await onEdit(fieldKey, patch);
setIsEditing(false);
};
@@ -38,7 +40,7 @@ export default function CustomFieldEntry(props: CustomFieldEntryProps) {
onSubmit={handleEdit}
initialColour={colour}
initialLabel={label}
initialKey={field}
initialKey={fieldKey}
/>
</td>
</tr>
@@ -50,10 +52,13 @@ export default function CustomFieldEntry(props: CustomFieldEntryProps) {
<td>
<Swatch color={colour} />
</td>
<td>
<Tag>{type}</Tag>
</td>
<td className={style.halfWidth}>{label}</td>
<td className={style.fullWidth}>
<CopyTag label='Copy key to use in integrations' copyValue={field}>
{field}
<CopyTag label='Copy key to use in integrations' copyValue={fieldKey}>
{fieldKey}
</CopyTag>
</td>
<Panel.InlineElements relation='inner' as='td'>
@@ -71,7 +76,7 @@ export default function CustomFieldEntry(props: CustomFieldEntryProps) {
color='#FA5656' // $red-500
icon={<IoTrash />}
aria-label='Delete entry'
onClick={() => onDelete(field)}
onClick={() => onDelete(fieldKey)}
/>
</Panel.InlineElements>
</tr>
@@ -1,10 +1,11 @@
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { Button, Input } from '@chakra-ui/react';
import { Controller, useForm } from 'react-hook-form';
import { Button, Input, Radio, RadioGroup } from '@chakra-ui/react';
import { CustomField } from 'ontime-types';
import { customFieldLabelToKey, isAlphanumericWithSpace } from 'ontime-utils';
import { maybeAxiosError } from '../../../../../common/api/utils';
import Info from '../../../../../common/components/info/Info';
import SwatchSelect from '../../../../../common/components/input/colour-input/SwatchSelect';
import useCustomFields from '../../../../../common/hooks-query/useCustomFields';
import { preventEscape } from '../../../../../common/utils/keyEvent';
@@ -20,6 +21,8 @@ interface CustomFieldsFormProps {
initialKey?: string;
}
type CustomFieldFormData = CustomField & { key: string };
export default function CustomFieldForm(props: CustomFieldsFormProps) {
const { onSubmit, onCancel, initialColour, initialLabel, initialKey } = props;
const { data } = useCustomFields();
@@ -28,6 +31,7 @@ export default function CustomFieldForm(props: CustomFieldsFormProps) {
const [_, setColour] = useState(initialColour || '');
const {
control,
handleSubmit,
register,
setFocus,
@@ -35,17 +39,17 @@ export default function CustomFieldForm(props: CustomFieldsFormProps) {
setValue,
getValues,
formState: { errors, isSubmitting, isValid, isDirty },
} = useForm({
defaultValues: { label: initialLabel || '', colour: initialColour || '', key: initialKey || '' },
} = useForm<CustomFieldFormData>({
defaultValues: { type: 'string', label: initialLabel || '', colour: initialColour || '' },
resetOptions: {
keepDirtyValues: true,
},
});
const setupSubmit = async (values: { label: string; colour: string }) => {
const { label, colour } = values;
const setupSubmit = async (values: CustomFieldFormData) => {
const { type, label, colour } = values;
const newField: CustomField = {
type: 'string', // type is not user definable yet
type,
colour,
label,
};
@@ -77,6 +81,26 @@ export default function CustomFieldForm(props: CustomFieldsFormProps) {
className={style.fieldForm}
onKeyDown={(event) => preventEscape(event, onCancel)}
>
<Info>
Please note that images can quickly deteriorate your app&apos;s performance.
<br />
Prefer using small, and compressed images.
</Info>
<div>
<Panel.Description>Type</Panel.Description>
<Controller
name='type'
control={control}
render={({ field }) => (
<RadioGroup {...field} size='sm' isDisabled={isEditMode} variant='ontime'>
<Panel.InlineElements relation='component'>
<Radio value='string'>Text</Radio>
<Radio value='image'>Image</Radio>
</Panel.InlineElements>
</RadioGroup>
)}
/>
</div>
<div className={style.twoCols}>
<div>
<Panel.Description>Label (only alphanumeric characters are allowed)</Panel.Description>
@@ -105,12 +129,10 @@ export default function CustomFieldForm(props: CustomFieldsFormProps) {
<Input {...register('key')} disabled size='sm' variant='ontime-filled' autoComplete='off' />
</div>
</div>
<div>
<Panel.Description>Colour</Panel.Description>
<SwatchSelect name='colour' value={colour} handleChange={(_field, value) => handleSelectColour(value)} />
</div>
{errors.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Panel.InlineElements relation='inner' align='end'>
<Button size='sm' variant='ontime-ghosted' onClick={onCancel}>
@@ -74,19 +74,21 @@ export default function CustomFields() {
<thead>
<tr>
<th>Colour</th>
<th>Type</th>
<th>Name</th>
<th>Key (used in Integrations)</th>
<th />
</tr>
</thead>
<tbody>
{Object.entries(data).map(([key, { colour, label }]) => {
{Object.entries(data).map(([key, { colour, label, type }]) => {
return (
<CustomFieldEntry
key={key}
field={key}
fieldKey={key}
colour={colour}
label={label}
type={type}
onEdit={handleEditField}
onDelete={handleDelete}
/>