Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/jsonProperties/components/JsonPropertyForm.tsx
T

330 lines
11 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import InputText, { RefInputText } from "~/components/ui/input/InputText";
import StringUtils from "~/utils/shared/StringUtils";
import TableSimple from "~/components/ui/tables/TableSimple";
import { useTranslation } from "react-i18next";
import ButtonSecondary from "~/components/ui/buttons/ButtonSecondary";
import ButtonPrimary from "~/components/ui/buttons/ButtonPrimary";
import InputSelect from "~/components/ui/input/InputSelect";
import { Form, useFetcher } from "react-router";
import { JsonPropertyDto, JsonPropertyType, JsonPropertyTypes } from "~/modules/jsonProperties/dtos/JsonPropertyTypeDto";
import InputCheckbox from "~/components/ui/input/InputCheckbox";
// import {toast} from "sonner";
import toast from "react-hot-toast";
import SlideOverWideEmpty from "~/components/ui/slideOvers/SlideOverWideEmpty";
import { Button } from "~/components/ui/button";
import TrashIcon from "~/components/ui/icons/TrashIcon";
import PencilIcon from "~/components/ui/icons/PencilIcon";
import ConfirmModal, { RefConfirmModal } from "~/components/ui/modals/ConfirmModal";
interface Props {
item: JsonPropertyDto | null;
// onUpdate?: (item: JsonPropertyDto) => void;
// onAdd?: (item: JsonPropertyDto) => void;
onClose: () => void;
}
export default function JsonPropertyForm({ item, onClose }: Props) {
const { t } = useTranslation();
const fetcher = useFetcher<{ error?: string; success?: string }>();
const confirmDelete = useRef<RefConfirmModal>(null);
const [selectedOption, setSelectedOption] = useState<{ value: string; name: string } | null>();
const [name, setName] = useState<string>(item ? item.name : "");
const [title, setTitle] = useState<string>(item ? item.title : "");
const [type, setType] = useState<JsonPropertyType>(item ? (item.type as any) : "string");
const [options, setOptions] = useState<{ value: string; name: string }[]>(item?.options || []);
const [isRequired, setIsRequired] = useState<boolean>(item ? item.required : false);
useEffect(() => {
if (fetcher.data?.success) {
toast.success(fetcher.data.success);
onClose();
} else if (fetcher.data?.error) {
toast.error(fetcher.data.error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fetcher.data]);
const mainInput = useRef<RefInputText>(null);
useEffect(() => {
setTimeout(() => {
mainInput.current?.input.current?.focus();
}, 100);
}, []);
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]);
function onAccept() {
if (item === null) {
const form = new FormData();
form.append("action", "properties-add");
form.set(
"property",
JSON.stringify({
name,
title,
type,
required: isRequired,
options: type === "select" || type === "multiselect" ? options : null,
})
);
fetcher.submit(form, {
method: "post",
});
// if (onAdd) {
// onAdd({
// name,
// title,
// type,
// required: isRequired,
// options: type === "select" || type === "multiselect" ? options : null,
// });
// }
} else {
const form = new FormData();
form.append("action", "properties-edit");
form.set("name", item.name);
form.set(
"property",
JSON.stringify({
...item,
name,
title,
type,
options: type === "select" || type === "multiselect" ? options : null,
required: isRequired,
})
);
fetcher.submit(form, {
method: "post",
});
// if (onUpdate) {
// onUpdate({
// ...item,
// name,
// title,
// type,
// options: type === "select" || type === "multiselect" ? options : null,
// required: isRequired,
// });
// }
}
}
function onSubmit(e: React.FormEvent) {
e.preventDefault();
onAccept();
}
function onDelete(item: JsonPropertyDto) {
confirmDelete.current?.setValue(item);
confirmDelete.current?.show(t("shared.confirmDelete"), t("shared.delete"), t("shared.cancel"), t("shared.warningCannotUndo"));
}
function onDeleteConfirmed(item: JsonPropertyDto) {
const form = new FormData();
form.set("action", "properties-delete");
form.set("name", item.name);
fetcher.submit(form, {
method: "post",
});
}
return (
<div>
<Form onSubmit={onSubmit}>
<div className="mb-4 space-y-2">
<div>
<InputText ref={mainInput} name="title" title={t("models.jsonProperty.title")} value={title} setValue={setTitle} required />
</div>
<div>
<InputText name="name" title={t("models.jsonProperty.name")} value={name} setValue={setName} required />
</div>
<div>
<InputSelect
name="type"
title={t("models.jsonProperty.type")}
value={type}
setValue={(e) => setType(e as JsonPropertyType)}
options={JsonPropertyTypes.map((property) => ({
name: t(`models.jsonProperty.types.${property.value}`),
value: property.value,
}))}
required
/>
</div>
{(type === "select" || type === "multiselect") && (
<div className="space-y-1">
{/* Caption for an options table, not a single form control. */}
<div className="text-xs font-medium">{t("models.jsonProperty.options")}</div>
<div>
<TableSimple
items={options}
actions={[
{
title: t("shared.edit"),
onClick: (_, i) => setSelectedOption(i),
renderTitle: (_) => <PencilIcon className="group-hover:text-muted-foreground h-4 w-4 text-gray-300" />,
},
{
title: t("shared.remove"),
onClick: (_, i) => setOptions((prev) => prev.filter((o) => o.value !== i.value)),
renderTitle: (_) => <TrashIcon className="text-muted-foreground group-hover:text-foreground h-4 w-4" />,
},
]}
headers={[
{
name: "name",
title: t("shared.name"),
value: (item) => item.name,
},
{
name: "value",
title: t("shared.value"),
className: "w-full",
value: (item) => item.value,
},
]}
/>
<div className="mt-2">
<Button type="button" variant="outline" onClick={() => setSelectedOption(null)}>
{t("shared.add")}
</Button>
</div>
</div>
</div>
)}
<div>
<InputCheckbox name="required" title={t("models.jsonProperty.required")} value={isRequired} setValue={setIsRequired} />
</div>
</div>
<div className="border-border mt-3 flex justify-between border-t pt-3">
<div>
{item && (
<ButtonSecondary destructive onClick={() => onDelete(item)}>
{t("shared.delete")}
</ButtonSecondary>
)}
</div>
<div className="flex items-center space-x-2">
<ButtonSecondary onClick={onClose}>{t("shared.back")}</ButtonSecondary>
<ButtonPrimary type="submit">{t("shared.accept")}</ButtonPrimary>
</div>
</div>
</Form>
<SlideOverWideEmpty
title={selectedOption === null ? t("shared.new") : selectedOption ? t("shared.edit") : ""}
open={selectedOption !== undefined}
onClose={() => setSelectedOption(undefined)}
size="sm"
>
{selectedOption !== undefined && (
<PropertyOptionForm
item={selectedOption}
onClose={() => setSelectedOption(undefined)}
onUpdate={(option) => {
if (selectedOption) {
// onChange(items.map((item) => (item === selectedProperty ? property : item)));
setOptions(options.map((o) => (o === selectedOption ? option : o)));
} else {
setOptions([...options, option]);
}
setSelectedOption(undefined);
}}
onAdd={(option) => {
setOptions([...options, option]);
setSelectedOption(undefined);
}}
/>
)}
</SlideOverWideEmpty>
<ConfirmModal ref={confirmDelete} destructive onYes={onDeleteConfirmed} />
</div>
);
}
function PropertyOptionForm({
item,
onUpdate,
onAdd,
onClose,
}: {
item: { name: string; value: string } | null;
onUpdate: (option: { name: string; value: string }) => void;
onAdd: (option: { name: string; value: string }) => void;
onClose: () => void;
}) {
const { t } = useTranslation();
const [name, setName] = useState<string>(item ? item.name : "");
const [value, setValue] = useState<string>(item ? item.value : "");
const mainInput = useRef<RefInputText>(null);
useEffect(() => {
setTimeout(() => {
mainInput.current?.input.current?.focus();
}, 100);
}, []);
useEffect(() => {
if (!item) {
if (name.includes(".")) {
const keys = name.split(".");
setValue(StringUtils.toCamelCase(keys[keys.length - 1].toLowerCase()));
} else {
setValue(StringUtils.toCamelCase(name.toLowerCase()));
}
}
}, [item, name]);
function onAccept() {
if (item === null) {
onAdd({
name,
value,
});
} else {
onUpdate({
...item,
name,
value,
});
}
}
function onSubmit(e: React.FormEvent) {
e.preventDefault();
onAccept();
}
return (
<Form onSubmit={onSubmit} className="space-y-2">
<div>
<InputText ref={mainInput} name="name" title={t("shared.name")} value={name} setValue={setName} required />
</div>
<div>
<InputText name="value" title={t("shared.value")} value={value} setValue={setValue} required />
</div>
<div className="flex justify-between">
<div>
<ButtonSecondary onClick={onClose}>{t("shared.back")}</ButtonSecondary>
</div>
<ButtonPrimary type="submit">{t("shared.accept")}</ButtonPrimary>
</div>
</Form>
);
}