Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/entities/properties/PropertyForm.tsx
T

378 lines
16 KiB
TypeScript

import clsx from "clsx";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { PropertyType } from "~/application/enums/entities/PropertyType";
import ButtonTertiary from "~/components/ui/buttons/ButtonTertiary";
import FormGroup from "~/components/ui/forms/FormGroup";
import PencilAltIcon from "~/components/ui/icons/PencilAltIcon";
import ViewListIcon from "~/components/ui/icons/ViewListIcon";
import InputCheckboxWithDescription from "~/components/ui/input/InputCheckboxWithDescription";
import InputNumber from "~/components/ui/input/InputNumber";
import InputText from "~/components/ui/input/InputText";
import { PropertyWithDetails, EntitySimple } from "~/utils/db/entities/entities.db.server";
import StringUtils from "~/utils/shared/StringUtils";
import PropertyTypeSelector from "./PropertyTypeSelector";
import PropertyOptionsForm, { OptionValue, RefPropertyOptionsForm } from "./PropertyOptionsForm";
import PropertyAttributeHelper from "~/utils/helpers/PropertyAttributeHelper";
import PropertyAttribute from "./PropertyAttribute";
import PropertySubtypeSelector from "./PropertySubtypeSelector";
import PropertyFormulaSelector from "./PropertyFormulaSelector";
import { FormulaDto } from "~/modules/formulas/dtos/FormulaDto";
import { Link } from "react-router";
interface Props {
item?: PropertyWithDetails;
properties: PropertyWithDetails[];
entities: EntitySimple[];
formulas: FormulaDto[];
formulaListPath?: string;
formulaDetailsPath?: (formulaId: string) => string;
}
export default function PropertyForm({ item, properties, formulas, formulaListPath, formulaDetailsPath }: Props) {
const { t } = useTranslation();
const selectOptionsForm = useRef<RefPropertyOptionsForm>(null);
const [showAdvancedOptions, setShowAdvancedOptions] = useState(true);
const [order, setOrder] = useState<number | undefined>(item?.order ?? Math.max(...properties.map((o) => o.order)) + 1);
const [name, setName] = useState<string>(item?.name ?? "");
const [title, setTitle] = useState<string>(item?.title ?? "");
const [type, setType] = useState<PropertyType>(item?.type ?? PropertyType.TEXT);
const [subtype, setSubtype] = useState<string>(item?.subtype ?? "");
const [formulaId, setFormulaId] = useState(item?.formulaId ?? undefined);
const [options, setOptions] = useState<OptionValue[]>(
item?.options?.map((o) => {
return {
id: o.id,
order: o.order,
value: o.value,
name: o.name,
color: o.color,
};
}) ?? []
);
const [isRequired, setIsRequired] = useState<boolean>(item?.isRequired ?? true);
const [showInCreate, setShowInCreate] = useState<boolean>(item?.showInCreate ?? true);
const [isHidden, setIsHidden] = useState<boolean>(item?.isHidden ?? false);
const [isDisplay, setIsDisplay] = useState<boolean>(item?.isDisplay ?? properties.filter((f) => !f.isDefault).length === 0);
const [isReadOnly, setIsReadOnly] = useState<boolean>(item?.isReadOnly ?? false);
const [canUpdate, setCanUpdate] = useState<boolean>(item ? item.canUpdate : true);
const [attributes, setAttributes] = useState<{ name: string; value: string | undefined }[]>(item?.attributes ?? []);
// const [formula, setFormula] = useState<string>();
const [titleEnabled, setTitleEnabled] = useState(false);
useEffect(() => {
if (!item) {
if (title.includes(".")) {
const keys = title.split(".");
setName(StringUtils.toCamelCase(keys[keys.length - 1].toLowerCase()));
} else {
setName(StringUtils.toCamelCase(title.toLowerCase()));
}
}
}, [item, title, type]);
useEffect(() => {
const formula = formulas.find((f) => f.id === formulaId);
if (!item && formula) {
setTitle(formula.name);
setName(StringUtils.toCamelCase(formula.name.toLowerCase()));
}
}, [item, formulaId, formulas]);
useEffect(() => {
setTitleEnabled(true);
if (!item) {
if (type === PropertyType.TEXT) {
setSubtype("singleLine");
} else if (type === PropertyType.SELECT) {
setSubtype("dropdown");
} else if (type === PropertyType.MULTI_SELECT) {
setSubtype("combobox");
} else {
// setSubtype("");
}
} else {
if (type === PropertyType.TEXT) {
if (!subtype || !["singleLine", "email", "phone", "url"].includes(subtype)) {
setSubtype("singleLine");
}
} else if (type === PropertyType.SELECT) {
if (!subtype || !["dropdown", "radioGroupCards"].includes(subtype)) {
setSubtype("dropdown");
}
} else if (type === PropertyType.MULTI_SELECT) {
if (!subtype || !["combobox", "checkboxCards"].includes(subtype)) {
setSubtype("combobox");
}
} else {
// setSubtype("");
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item, type]);
useEffect(() => {
if (type === PropertyType.FORMULA) {
setShowAdvancedOptions(false);
setIsRequired(false);
setIsReadOnly(true);
setShowInCreate(false);
}
}, [type]);
return (
<>
<FormGroup
id={item?.id}
editing={true}
canDelete={item !== undefined && !item?.isDefault && showAdvancedOptions}
className="space-y-2 pb-4"
classNameFooter="px-4"
>
<input type="hidden" name="order" value={order} hidden readOnly />
<div className="mt-4">
<div className="space-y-3 px-4">
<div className="w-full">
<label htmlFor="type" className="text-foreground/80 block text-xs font-medium">
{t("models.property.type")}
</label>
<div className="mt-1">
<PropertyTypeSelector selected={type} onSelected={(e) => setType(e)} />
</div>
</div>
{type === PropertyType.TEXT && (
<div>
<label htmlFor="subtype" className="text-foreground/80 block text-xs font-medium">
{t("models.property.subtype")}
</label>
<div className="mt-1">
<PropertySubtypeSelector types={["singleLine", "email", "phone", "url"]} selected={subtype} onSelected={(e) => setSubtype(e)} />
</div>
</div>
)}
{type === PropertyType.SELECT && (
<div>
<label htmlFor="subtype" className="text-foreground/80 block text-xs font-medium">
{t("models.property.subtype")}
</label>
<div className="mt-1">
<PropertySubtypeSelector types={["dropdown", "radioGroupCards"]} selected={subtype} onSelected={(e) => setSubtype(e)} />
</div>
</div>
)}
{type === PropertyType.MULTI_SELECT && (
<div>
<label htmlFor="subtype" className="text-foreground/80 block text-xs font-medium">
{t("models.property.subtype")}
</label>
<div className="mt-1">
<PropertySubtypeSelector types={["combobox", "checkboxCards"]} selected={subtype} onSelected={(e) => setSubtype(e)} />
</div>
</div>
)}
{type === PropertyType.FORMULA && (
<div>
<div className="flex justify-between space-x-2">
<label htmlFor="formula" className="text-foreground/80 block text-xs font-medium">
{t("models.property.formula")}
</label>
{item?.formulaId && formulaDetailsPath ? (
<Link to={formulaDetailsPath(item.formulaId)} className="text-xs text-blue-500 hover:underline">
{t("models.property.goToFormula")}
</Link>
) : formulaListPath ? (
<Link to={formulaListPath} className="text-xs text-blue-500 hover:underline">
{t("models.property.viewFormulas")}
</Link>
) : null}
</div>
<div className="mt-1">
<PropertyFormulaSelector name="formula-id" items={formulas} selected={formulaId} onSelected={(e) => setFormulaId(e)} />
</div>
</div>
)}
{titleEnabled && (
<InputText
name="title"
title={t("models.property.title")}
value={title}
setValue={(e) => setTitle(e)}
disabled={!titleEnabled}
required
withTranslation
placeholder={t("models.property.form.placeholders.title")}
autoFocus
/>
)}
<InputText
name="name"
title={t("models.property.name")}
value={name}
setValue={(e) => setName(e)}
required
placeholder={t("models.property.form.placeholders.name")}
pattern="[a-z]+((\d)|([A-Z0-9][a-z0-9]+))*([A-Z])?"
hint={<span className="text-muted-foreground font-normal italic">{t("models.property.form.hints.camelCase")}</span>}
/>
<div className="border-border w-full rounded-md border border-dotted px-2 py-0">
<InputCheckboxWithDescription
name="is-display"
title={t("models.property.isDisplay")}
description={t("models.property.form.descriptions.isDisplay")}
value={isDisplay}
setValue={setIsDisplay}
/>
</div>
{/* {type === PropertyType.FORMULA && (
<div className="w-full">
<label htmlFor="formula" className="block text-xs font-medium text-foreground/80">
{t("entities.fields.FORMULA")}
</label>
<div className="mt-1">
<InputText
name="formula"
title={t("models.property.formula")}
value={formula}
setValue={(e) => setFormula(e.toString())}
disabled={!titleEnabled}
required
/>
</div>
</div>
)} */}
{[PropertyType.SELECT, PropertyType.MULTI_SELECT].includes(type) && (
<div className="w-full">
<label htmlFor="property-options-display" className="text-foreground/80 block text-sm font-medium">
{t("models.property.options")}
</label>
<div className="mt-1 flex rounded-md shadow-2xs">
<div className="relative flex grow items-stretch focus-within:z-10">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<ViewListIcon className="text-muted-foreground h-4 w-4" aria-hidden="true" />
</div>
{options.map((option) => {
return <input key={option.order} hidden readOnly type="text" id="options[]" name="options[]" value={JSON.stringify(option)} />;
})}
<input
id="property-options-display"
disabled
className="focus:border-border focus:ring-ring border-border text-foreground bg-secondary/90 block w-full rounded-none rounded-l-md border pl-10 sm:text-sm"
value={options.length === 0 ? t("models.property.form.noDropdownValues") : options.map((f) => f.value).join(", ")}
/>
</div>
<button
type="button"
onClick={() => selectOptionsForm.current?.set(options)}
className="focus:border-border focus:ring-ring border-border bg-secondary hover:bg-secondary/90 text-foreground/80 relative -ml-px inline-flex items-center space-x-2 rounded-r-md border px-4 py-1.5 text-sm font-medium focus:outline-hidden focus:ring-1"
>
<PencilAltIcon className="text-muted-foreground h-4 w-4" aria-hidden="true" />
<span>{t("models.property.form.setOptions")}</span>
</button>
</div>
</div>
)}
<div className="flex">
<ButtonTertiary onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}>
{!showAdvancedOptions ? t("models.property.form.showAdvancedOptions") : t("models.property.form.hideAdvancedOptions")}
</ButtonTertiary>
</div>
</div>
<div className={clsx("border-border bg-secondary my-2 space-y-3 border border-dashed px-4 py-3", showAdvancedOptions ? "" : "hidden")}>
<div className="w-full">
<InputNumber name="order" title={t("models.property.order")} value={order} setValue={setOrder} />
</div>
<div className="w-full">
<InputCheckboxWithDescription
name="show-in-create"
title={t("models.property.showInCreate")}
description={t("models.property.form.descriptions.showInCreate")}
value={showInCreate}
setValue={setShowInCreate}
/>
</div>
<div className="w-full">
<InputCheckboxWithDescription
name="is-required"
title={t("models.property.isRequired")}
description={t("models.property.form.descriptions.isRequired")}
value={isRequired}
setValue={setIsRequired}
/>
</div>
<div className="w-full">
<InputCheckboxWithDescription
name="is-hidden"
title={t("models.property.isHidden")}
description={t("models.property.form.descriptions.isHidden")}
value={isHidden}
setValue={setIsHidden}
/>
</div>
<div className="w-full">
<InputCheckboxWithDescription
name="is-read-only"
title={t("models.property.isReadOnly")}
description={t("models.property.form.descriptions.isReadOnly")}
value={isReadOnly}
setValue={setIsReadOnly}
/>
</div>
<div className="w-full">
<InputCheckboxWithDescription
name="can-update"
title={t("models.property.canUpdate")}
description={t("models.property.form.descriptions.canUpdate")}
value={canUpdate}
setValue={setCanUpdate}
/>
</div>
{/* <div className="font-bold">{t("models.propertyAttribute.plural")}</div> */}
{attributes.map((attribute) => {
return <input key={attribute.name} hidden readOnly type="text" id="attributes[]" name="attributes[]" value={JSON.stringify(attribute)} />;
})}
{PropertyAttributeHelper.getAttributesByType(type, attributes).map((item) => {
return (
<PropertyAttribute
className="mb-2"
key={item}
name={item}
title={PropertyAttributeHelper.getAttributeTitle(t, item)}
value={attributes.find((f) => f.name === item)?.value ?? undefined}
setValue={(e) => {
const value = { name: item, value: e?.toString() };
const found = attributes.find((f) => f.name === item);
if (found) {
setAttributes([...attributes.filter((f) => f.name !== item), value]);
} else {
setAttributes([...attributes, value]);
}
}}
/>
);
})}
</div>
</div>
</FormGroup>
<PropertyOptionsForm ref={selectOptionsForm} title={title} onSet={(e) => setOptions(e)} />
</>
);
}