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

175 lines
7.4 KiB
TypeScript

import { useNavigate } from "react-router";
import { useState, useEffect, useRef } from "react";
import UrlUtils from "~/utils/app/UrlUtils";
import { EntitySimple } from "~/utils/db/entities/entities.db.server";
import { EntityGroupWithDetails } from "~/utils/db/entities/entityGroups.db.server";
import InputText, { RefInputText } from "../ui/input/InputText";
import FormGroup from "../ui/forms/FormGroup";
import { getUserHasPermission } from "~/utils/helpers/PermissionsHelper";
import { useAppOrAdminData } from "~/utils/data/useAppOrAdminData";
import InputCheckboxCards from "../ui/input/InputCheckboxCards";
import { useTranslation } from "react-i18next";
import EntityIcon from "../layouts/icons/EntityIcon";
import QuestionMarkFilledIcon from "../ui/icons/QuestionMarkFilledIcon";
import InputCheckboxWithDescription from "../ui/input/InputCheckboxWithDescription";
import InputSelector from "../ui/input/InputSelector";
import { EntityViewWithTenantAndUser } from "~/utils/db/entities/entityViews.db.server";
import { adminPath } from "~/utils/app/CanonicalUrlUtils";
export function EntityGroupForm({
item,
allEntities,
systemViews,
}: {
item: EntityGroupWithDetails | undefined;
allEntities: EntitySimple[];
systemViews: EntityViewWithTenantAndUser[];
}) {
const { t } = useTranslation();
const appOrAdminData = useAppOrAdminData();
const navigate = useNavigate();
const [title, setTitle] = useState(item?.title ?? "");
const [slug, setSlug] = useState(item?.slug ?? "");
const [icon, setIcon] = useState(
item?.icon ??
`<svg stroke="currentColor" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5"> <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /> </svg>`
);
const [collapsible, setCollapsible] = useState(item?.collapsible ?? false);
const [section, setSection] = useState(item?.section ?? "");
const [entities, setEntities] = useState<{ entityId: string; allViewId: string | null }[]>(item?.entities ?? []);
const input = useRef<RefInputText>(null);
useEffect(() => {
setTimeout(() => {
input.current?.input.current?.focus();
}, 100);
}, []);
useEffect(() => {
if (!item) {
const slug = UrlUtils.slugify(title.toLowerCase());
setSlug(slug);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [title]);
return (
<FormGroup
id={item?.id}
onCancel={() => navigate(adminPath("entities/groups", { mode: "host-aware" }))}
editing={true}
canDelete={getUserHasPermission(appOrAdminData, "admin.entities.delete")}
>
<div className="space-y-2">
<div className="flex justify-between space-x-2">
<InputText ref={input} autoFocus name="title" title={t("shared.title")} value={title} setValue={setTitle} required />
<InputText name="slug" title={t("shared.slug")} value={slug} setValue={setSlug} required onBlur={() => setSlug(UrlUtils.slugify(slug))} />
</div>
<InputText
className="col-span-12"
name="icon"
title={t("models.entity.icon")}
value={icon}
setValue={setIcon}
hint={<div className="text-muted-foreground">{t("models.entityGroup.form.iconHint")}</div>}
button={
<div className="absolute inset-y-0 right-0 flex py-0.5 pr-0.5 ">
<kbd className="border-border text-muted-foreground bg-secondary inline-flex w-10 items-center justify-center rounded border px-1 text-center font-sans text-xs font-medium">
{icon ? (
<EntityIcon className="text-muted-foreground h-7 w-7" icon={icon} title={title} />
) : (
<QuestionMarkFilledIcon className="text-muted-foreground h-5 w-5" aria-hidden="true" />
)}
</kbd>
</div>
}
/>
<InputText name="section" title={t("models.entityGroup.section")} value={section} setValue={setSection} />
<InputCheckboxWithDescription
name="collapsible"
title={t("models.entityGroup.collapsible")}
value={collapsible}
setValue={setCollapsible}
description={t("models.entityGroup.form.collapsibleDescription")}
/>
<InputCheckboxCards
display="name"
columns={allEntities.length === 1 ? 1 : 2}
name="entities"
title={t("models.entity.plural")}
value={entities.map((f) => f.entityId)}
selectAndClearAll
onChange={(e) =>
setEntities(
e.map((v) => ({
entityId: v.toString(),
allViewId: entities.find((f) => f.entityId === v.toString())?.allViewId ?? null,
}))
)
}
options={allEntities.map((v) => ({ value: v.id, name: `${t(v.title)}` }))}
/>
<div>
{/* <label className="mb-1 flex justify-between space-x-2 text-xs font-medium">Layouts</label> */}
<div className="space-y-1">
{entities
.filter((f) => systemViews.some((v) => v.entityId === f.entityId))
.map((f, idx) => {
const entity = allEntities.find((e) => e.id === f.entityId);
if (!entity) {
return <div key={f.entityId}>{t("models.entityGroup.form.invalidEntity")}</div>;
}
const entitySystemViews = systemViews.filter((v) => v.entityId === entity.id);
return (
<InputSelector
withSearch={false}
key={f.entityId}
title={t("models.entityGroup.layoutFor", { entity: t(entity.title) })}
value={f.allViewId ?? undefined}
setValue={(e) => {
const newEntities = [...entities];
newEntities[idx].allViewId = e?.toString() ?? null;
setEntities(newEntities);
}}
options={entitySystemViews.map((f) => {
return {
value: f.id,
name: `${f.title} (${f.layout})`,
};
})}
hint={
<>
{!!f.allViewId && (
<button
type="button"
onClick={() => {
const newEntities = [...entities];
const entity = entities.find((e) => e.entityId === f.entityId);
if (entity) {
entity.allViewId = null;
}
setEntities(newEntities);
}}
className="text-muted-foreground text-xs"
>
{t("shared.clear")}
</button>
)}
</>
}
/>
);
})}
</div>
</div>
{entities.map((item) => {
return <input key={item.entityId} type="hidden" name={"entities[]"} value={JSON.stringify(item)} />;
})}
</div>
</FormGroup>
);
}