Files
ontime/apps/client/src/common/components/view-params-editor/ParamInput.tsx
T
Carlos Valente 1849b4d39f Deps migration (#1988)
* chore: migrate eslint to oxlint

* chore: migrate prettier to oxfmt

* chore: migrate typescript

* chore: toThrow should have a expected value

* chore: cast test value as Day

* chore: small title fix

* chore: mocks should be hoisted

* chore: incorrect async useage

* chore: test should be inside description

* chore: test sohuld include an expeced

* chore: oxfmt

---------

Co-authored-by: alex-Arc <omnivox@LAPTOP-RC5SNBVV.localdomain>
2026-03-08 16:22:12 +01:00

197 lines
5.2 KiB
TypeScript

import { ComponentProps, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router';
import { isStringBoolean } from '../../../views/common/viewUtils';
import Checkbox from '../checkbox/Checkbox';
import Input from '../input/input/Input';
import Select, { SelectOption } from '../select/Select';
import Switch from '../switch/Switch';
import InlineColourPicker from './InlineColourPicker';
import { ParamField } from './viewParams.types';
import style from './ParamInput.module.scss';
interface ParamInputProps {
paramField: ParamField;
}
export default function ParamInput({ paramField }: ParamInputProps) {
const [searchParams] = useSearchParams();
const { id, type, defaultValue } = paramField;
if (type === 'persist') {
if (!paramField.values || !paramField.values.length) {
return null;
}
return <input hidden name={id} readOnly value={paramField.values.join(',')} />;
}
if (type === 'option') {
const optionFromParams = searchParams.get(id);
const defaultOptionValue = optionFromParams || defaultValue;
if (paramField.values.length === 0) {
return <span className={style.empty}>No options available</span>;
}
return <ControlledSelect id={id} initialValue={defaultOptionValue} options={paramField.values} />;
}
if (type === 'multi-option') {
const optionFromParams = searchParams.getAll(id);
return (
<MultiOption
paramField={paramField}
options={optionFromParams.length ? optionFromParams : (paramField.defaultValue ?? [''])}
/>
);
}
if (type === 'boolean') {
return <ControlledSwitch id={id} initialValue={isStringBoolean(searchParams.get(id)) ?? defaultValue} />;
}
if (type === 'number') {
const { placeholder } = paramField;
const defaultNumberValue = searchParams.get(id) ?? defaultValue;
return (
<Input
height='large'
type='number'
step='any'
name={id}
defaultValue={defaultNumberValue}
placeholder={placeholder}
/>
);
}
if (type === 'colour') {
return <InlineColourPicker name={id} value={searchParams.get(id) ?? defaultValue} />;
}
const defaultStringValue = searchParams.get(id) ?? defaultValue ?? '';
const { placeholder } = paramField;
return <ControlledInput id={id} initialValue={defaultStringValue} placeholder={placeholder} />;
}
interface EditFormMultiOptionProps {
paramField: ParamField & { type: 'multi-option' };
options: string[];
}
function MultiOption({ paramField, options }: EditFormMultiOptionProps) {
const { id, values } = paramField;
const [paramState, setParamState] = useState<string[]>(options);
// synchronise options
useEffect(() => {
setParamState(options);
}, [options]);
const toggleValue = (value: string, checked: boolean) => {
if (checked) {
setParamState((prev) => [...prev, value]);
} else {
setParamState((prev) => prev.filter((v) => v !== value));
}
};
if (values.length === 0) {
return <span className={style.empty}>No options available</span>;
}
return (
<>
<input name={id} hidden readOnly value={paramState.join(',')} />
<div className={style.inline}>
{values.map((option) => {
return (
<label
key={option.value}
className={style.toggleSelect}
style={{
'--user-bg': option.colour,
}}
>
<Checkbox
checked={paramState.includes(option.value)}
onCheckedChange={(checked) => toggleValue(option.value, checked as boolean)}
/>
{option.label}
</label>
);
})}
</div>
</>
);
}
interface ControlledSwitchProps {
id: string;
initialValue: boolean;
}
function ControlledSwitch({ id, initialValue }: ControlledSwitchProps) {
const [checked, setChecked] = useState(initialValue);
// synchronise checked state
useEffect(() => {
setChecked(initialValue);
}, [initialValue]);
return <Switch size='large' name={id} checked={checked} onCheckedChange={setChecked} />;
}
interface ControlledSelectProps {
id: string;
initialValue?: string;
options: SelectOption[];
}
function ControlledSelect({ id, initialValue, options }: ControlledSelectProps) {
const [selected, setSelected] = useState(initialValue);
// synchronise selected state
useEffect(() => {
setSelected(initialValue);
}, [initialValue]);
return (
<Select
size='large'
name={id}
options={options}
value={selected}
onValueChange={(value) => {
if (value === null) return;
setSelected(value);
}}
/>
);
}
interface ControlledInputProps<T extends number | string> extends ComponentProps<typeof Input> {
id: string;
initialValue: T;
}
function ControlledInput<T extends number | string>({ id, initialValue, ...inputProps }: ControlledInputProps<T>) {
const [value, setValue] = useState(initialValue);
// synchronise selected state
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return (
<Input
height='large'
name={id}
value={value}
onChange={(event) => setValue(event.target.value as T)}
{...inputProps}
/>
);
}