Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/entities/rows/RowValueInput.tsx
T

493 lines
23 KiB
TypeScript

import { RowMedia, RowValueMultiple, RowValueRange } from "@toolctrl/db/prisma-browser";
import { Ref, useImperativeHandle, useRef, useState, useEffect, forwardRef } from "react";
import { useTranslation } from "react-i18next";
import { MediaDto } from "~/application/dtos/entities/MediaDto";
import { PropertyType } from "~/application/enums/entities/PropertyType";
import InputDate, { RefInputDate } from "~/components/ui/input/InputDate";
import InputNumber, { RefInputNumber } from "~/components/ui/input/InputNumber";
import InputText, { RefInputText } from "~/components/ui/input/InputText";
import { PropertyWithDetails, EntityWithDetails } from "~/utils/db/entities/entities.db.server";
import InputCheckbox from "~/components/ui/input/InputCheckbox";
import InputMedia from "~/components/ui/input/InputMedia";
import PropertyAttributeHelper from "~/utils/helpers/PropertyAttributeHelper";
import { PropertyAttributeName } from "~/application/enums/entities/PropertyAttributeName";
import { RowValueMultipleDto } from "~/application/dtos/entities/RowValueMultipleDto";
import InputMultiText, { RefInputMultiText } from "~/components/ui/input/InputMultiText";
import PropertyMultiSelector from "../properties/PropertyMultiSelector";
import InputRangeNumber from "~/components/ui/input/ranges/InputRangeNumber";
import { RowValueRangeDto } from "~/application/dtos/entities/RowValueRangeDto";
import InputRangeDate from "~/components/ui/input/ranges/InputRangeDate";
import { getSeparator } from "~/utils/shared/SeparatorUtils";
import InputTextSubtype from "~/components/ui/input/subtypes/InputTextSubtype";
import InputSelect from "~/components/ui/input/InputSelect";
import InputRadioGroupCards from "~/components/ui/input/InputRadioGroupCards";
import { renderSanitizedMarkdown } from "~/utils/html-sanitizer";
import { translatePropertyTitle, translateSelectOptionLabel } from "~/utils/i18n/translateLabel";
export interface RefRowValueInput {
focus: () => void;
}
interface Props {
selected: PropertyWithDetails | undefined;
entity: EntityWithDetails;
textValue: string | undefined;
numberValue: number | undefined;
dateValue: Date | undefined;
booleanValue: boolean | undefined;
multiple: RowValueMultiple[] | undefined;
range: RowValueRange | undefined;
initialMedia?: RowMedia[] | undefined;
initialOption?: string | undefined;
onChange?: (value: string | number | Date | boolean | undefined | null) => void;
onChangeOption?: (option: string | undefined) => void;
onChangeMedia?: (option: MediaDto[]) => void;
onChangeMultiple?: (option: RowValueMultipleDto[]) => void;
onChangeRange?: (option: RowValueRangeDto | undefined) => void;
readOnly: boolean;
className?: string;
autoFocus?: boolean;
}
const RowValueInput = (
{
entity,
selected,
textValue,
numberValue,
dateValue,
booleanValue,
multiple,
range,
initialMedia,
initialOption,
onChange,
onChangeOption,
onChangeMedia,
onChangeMultiple,
onChangeRange,
className,
readOnly,
autoFocus,
}: Props,
ref: Ref<RefRowValueInput>
) => {
useImperativeHandle(ref, () => ({ focus }));
const { t, i18n } = useTranslation();
const numberInput = useRef<RefInputNumber>(null);
const textInput = useRef<RefInputText>(null);
const dateInput = useRef<RefInputDate>(null);
const multipleInput = useRef<RefInputMultiText>(null);
const [media, setMedia] = useState<MediaDto[]>([]);
function focus() {
if (selected?.type === PropertyType.TEXT) {
textInput.current?.input.current?.focus();
} else if (selected?.type === PropertyType.NUMBER) {
numberInput.current?.input.current?.focus();
} else if (selected?.type === PropertyType.DATE) {
dateInput.current?.input.current?.focus();
}
}
function getSelectedTitle() {
return selected ? translatePropertyTitle(t, i18n, entity.name, selected) : "";
}
useEffect(() => {
if (selected?.type === PropertyType.MEDIA) {
if (onChangeMedia) {
onChangeMedia(media);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [media]);
return (
<>
{selected?.type === PropertyType.TEXT ? (
<>
{["monaco", "wysiwyg", "iframe"].includes(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Editor) ?? "") &&
readOnly ? (
<></>
) : (
<>
<InputTextSubtype
subtype={selected.subtype as any}
name={selected.name}
title={getSelectedTitle()}
value={textValue}
setValue={(e) => (onChange ? onChange(e.toString()) : undefined)}
required={selected.isRequired}
className={className}
readOnly={readOnly}
disabled={readOnly}
pattern={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Pattern)}
minLength={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Min)}
maxLength={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Max)}
rows={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Rows)}
placeholder={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Placeholder)}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
uppercase={PropertyAttributeHelper.getPropertyAttributeValue_Boolean(selected, PropertyAttributeName.Uppercase)}
lowercase={PropertyAttributeHelper.getPropertyAttributeValue_Boolean(selected, PropertyAttributeName.Lowercase)}
password={PropertyAttributeHelper.getPropertyAttributeValue_Boolean(selected, PropertyAttributeName.Password)}
editor={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Editor)}
editorLanguage={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.EditorLanguage)}
editorSize={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.EditorSize) as any}
autoFocus={autoFocus}
/>
</>
)}
{["iframe"].includes(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Editor) ?? "") && readOnly && (
<div className="overflow-auto">
{getSafeIframePreviewUrl(textValue) ? (
<iframe title={getSelectedTitle()} src={getSafeIframePreviewUrl(textValue)} sandbox="allow-scripts" className="h-96 w-full" />
) : null}
</div>
)}
{["monaco", "wysiwyg"].includes(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Editor) ?? "") &&
readOnly && (
<div className="overflow-auto">
<label className="mb-1 flex justify-between space-x-2 truncate text-xs font-medium">
<span>{getSelectedTitle()}</span>
</label>
<div className="prose border-border bg-secondary h-auto rounded-md border p-3">
<div
dangerouslySetInnerHTML={{
__html: renderSanitizedMarkdown(textValue),
}}
/>
</div>
</div>
)}
</>
) : selected?.type === PropertyType.NUMBER ? (
<InputNumber
ref={numberInput}
name={selected.name}
title={getSelectedTitle()}
required={selected.isRequired}
value={numberValue}
setValue={(e) => (onChange ? onChange(Number(e)) : undefined)}
disabled={readOnly}
className={className}
readOnly={readOnly}
min={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Min)}
max={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Max)}
step={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Step)}
placeholder={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Placeholder)}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
autoFocus={autoFocus}
canUnset={true}
/>
) : selected?.type === PropertyType.DATE ? (
<>
<InputDate
ref={dateInput}
required={selected.isRequired}
name={selected.name}
title={getSelectedTitle()}
value={dateValue}
onChange={(e) => (onChange ? onChange(e) : undefined)}
className={className}
readOnly={readOnly}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
autoFocus={autoFocus}
/>
</>
) : selected?.type === PropertyType.SELECT ? (
<>
{/* <PropertyOptionSelector
property={selected}
initial={initialOption}
onSelected={(e) => {
if (onChange) {
onChange(e ?? "");
}
if (onChangeOption) {
onChangeOption(e);
}
}}
className={className}
disabled={readOnly}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
display={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.SelectOptions) as SelectOptionsDisplay}
autoFocus={autoFocus}
/> */}
{!selected.subtype || selected.subtype === "dropdown" ? (
<InputSelect
name={selected.name}
title={getSelectedTitle()}
required={selected.isRequired}
value={initialOption}
defaultValue={textValue}
setValue={(e) => {
if (onChange) {
onChange(e?.toString() ?? "");
}
if (onChangeOption) {
onChangeOption(e?.toString());
}
}}
disabled={readOnly}
className={className}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
autoFocus={autoFocus}
options={selected.options.map((f) => ({
name: translateSelectOptionLabel(t, i18n, entity.name, selected.name, f),
value: f.value,
}))}
/>
) : selected.subtype === "radioGroupCards" ? (
<InputRadioGroupCards
title={getSelectedTitle()}
name={selected.name}
value={initialOption}
defaultValue={textValue}
disabled={readOnly}
required={selected.isRequired}
options={selected.options.map((f) => {
return {
value: f.value,
name: translateSelectOptionLabel(t, i18n, entity.name, selected.name, f),
};
})}
onChange={(e) => {
if (onChange) {
onChange(e);
}
if (onChangeOption) {
onChangeOption(e);
}
}}
/>
) : null}
</>
) : selected?.type === PropertyType.BOOLEAN ? (
<>
<InputCheckbox
asToggle={true}
name={selected.name}
title={getSelectedTitle()}
required={selected.isRequired}
value={booleanValue}
setValue={(e) => (onChange ? onChange(e as boolean) : undefined)}
disabled={readOnly}
className={className}
readOnly={readOnly}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
autoFocus={autoFocus}
/>
</>
) : selected?.type === PropertyType.MEDIA ? (
<InputMedia
name={selected.name}
title={getSelectedTitle()}
initialMedia={initialMedia}
className={className}
disabled={readOnly}
onSelected={(e) => setMedia(e)}
readOnly={readOnly}
required={selected.isRequired || (PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Min) ?? 0) > 0}
min={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Min)}
max={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Max)}
accept={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.AcceptFileTypes)}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
autoFocus={autoFocus}
/>
) : selected?.type === PropertyType.MULTI_SELECT ? (
<>
<PropertyMultiSelector
subtype={selected.subtype as any}
name={selected.name}
title={getSelectedTitle()}
required={selected.isRequired}
options={selected.options}
value={multiple}
onSelected={(e) => (onChangeMultiple ? onChangeMultiple(e) : undefined)}
disabled={readOnly}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
autoFocus={autoFocus}
/>
</>
) : selected?.type === PropertyType.MULTI_TEXT ? (
<>
<InputMultiText
ref={multipleInput}
name={selected.name}
title={getSelectedTitle()}
value={multiple}
// onSelected={(e) => (onChangeMultiple ? onChangeMultiple(e) : undefined)}
required={selected.isRequired}
className={className}
readOnly={readOnly}
disabled={readOnly}
pattern={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Pattern)}
minLength={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Min)}
maxLength={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Max)}
rows={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Rows)}
placeholder={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Placeholder)}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
uppercase={PropertyAttributeHelper.getPropertyAttributeValue_Boolean(selected, PropertyAttributeName.Uppercase)}
lowercase={PropertyAttributeHelper.getPropertyAttributeValue_Boolean(selected, PropertyAttributeName.Lowercase)}
autoFocus={autoFocus}
separator={getSeparator(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Separator))}
/>
</>
) : selected?.type === PropertyType.RANGE_NUMBER ? (
<>
<InputRangeNumber
name={selected.name}
title={getSelectedTitle()}
required={selected.isRequired}
valueMin={range?.numberMin ? Number(range.numberMin) : undefined}
valueMax={range?.numberMax ? Number(range.numberMax) : undefined}
onChangeMin={(e) => {
if (onChangeRange) {
onChangeRange({
dateMin: null,
dateMax: null,
numberMin: Number(e),
numberMax: range?.numberMax ? Number(range.numberMax) : null,
});
}
}}
onChangeMax={(e) => {
if (onChangeRange) {
onChangeRange({
dateMin: null,
dateMax: null,
numberMax: Number(e),
numberMin: range?.numberMin ? Number(range.numberMin) : null,
});
}
}}
disabled={readOnly}
className={className}
readOnly={readOnly}
min={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Min)}
max={PropertyAttributeHelper.getPropertyAttributeValue_Number(selected, PropertyAttributeName.Max)}
step={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Step)}
placeholder={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Placeholder)}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
autoFocus={autoFocus}
/>
</>
) : selected?.type === PropertyType.RANGE_DATE ? (
<>
<InputRangeDate
required={selected.isRequired}
name={selected.name}
title={getSelectedTitle()}
valueMin={range?.dateMin ?? undefined}
valueMax={range?.dateMax ?? undefined}
onChangeMin={(e) => {
if (onChangeRange) {
onChangeRange({
dateMin: e,
dateMax: range?.dateMax ?? null,
numberMin: null,
numberMax: null,
});
}
}}
onChangeMax={(e) => {
if (onChangeRange) {
onChangeRange({
dateMin: range?.dateMin ?? null,
dateMax: e,
numberMin: null,
numberMax: null,
});
}
}}
className={className}
readOnly={readOnly}
hint={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HintText) ?? "")}
help={t(PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.HelpText) ?? "")}
icon={PropertyAttributeHelper.getPropertyAttributeValue_String(selected, PropertyAttributeName.Icon)}
autoFocus={autoFocus}
/>
</>
) : selected?.type === PropertyType.FORMULA ? (
<>
{!selected.formula ? (
<div className="text-red-500">{t("models.row.values.unknownFormula")}</div>
) : (
<InputFormulaValue
property={selected}
title={translatePropertyTitle(t, i18n, entity.name, selected)}
textValue={textValue}
numberValue={numberValue}
dateValue={dateValue}
booleanValue={booleanValue}
/>
)}
</>
) : (
<div className={className}>{t("models.row.values.notSupported")}</div>
)}
</>
);
};
function InputFormulaValue(props: {
property: PropertyWithDetails;
title: string;
textValue?: string;
numberValue?: number;
dateValue?: Date;
booleanValue?: boolean;
}) {
return (
<>
{props.property.formula?.resultAs === "string" ? (
<InputText name={props.property.name} title={props.title} defaultValue={props.textValue} readOnly={true} />
) : props.property.formula?.resultAs === "number" ? (
<InputNumber name={props.property.name} title={props.title} defaultValue={props.numberValue} readOnly={true} canUnset={true} />
) : props.property.formula?.resultAs === "date" ? (
<InputDate name={props.property.name} title={props.title} value={props.dateValue} readOnly={true} />
) : props.property.formula?.resultAs === "boolean" ? (
<InputCheckbox asToggle={true} name={props.property.name} title={props.title} value={props.booleanValue} readOnly={true} />
) : null}
</>
);
}
function getSafeIframePreviewUrl(value: string | undefined) {
if (!value) {
return undefined;
}
try {
const url = new URL(value.trim());
return url.protocol === "https:" ? url.toString() : undefined;
} catch {
return undefined;
}
}
export default forwardRef(RowValueInput);