ldc-laravel-base-project/resources/ts/back/Pages/DynamicPages/Form.tsx
2026-07-09 17:32:17 +02:00

412 lines
13 KiB
TypeScript

import { useForm, usePage } from "@inertiajs/react";
import {
LinkIcon,
BookTextIcon,
CogIcon,
EyeIcon,
ShieldAlertIcon,
} from "lucide-react";
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
import DynamicInputs from "@/back/Components/DynamicPages/DynamicInputs";
import FieldsetWrapper from "@/back/Components/FieldsetWrapper";
import GoToFrontView from "@/back/Components/GoToFrontView";
import InputError from "@/back/Components/InputError";
import EnumSelect from "@/back/Components/Inputs/EnumSelect";
import { ModelSelect } from "@/back/Components/Inputs/ModelSelect";
import SimpleString from "@/back/Components/Inputs/SimpleString";
import SimpleSwitch from "@/back/Components/Inputs/SimpleSwitch";
import Slugifycator from "@/back/Components/Inputs/Slugifycator";
import SimpleWysiwyg from "@/back/Components/Inputs/Wysiwyg/SimpleWysiwyg";
import ConfigDetails from "@/back/Components/PageConfigs/ConfigDetails";
import SubmitButton from "@/back/Components/SubmitButton";
import useCrudSubmit from "@/back/hooks/use-crud-submit";
import type {
ConfigField,
DynamicPageModel,
PageConfigModel,
} from "@/back/types/dynamic-pages";
import type { AuthProp, ConfigBackend, UserModel } from "@/back/types/global";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogHeader,
DialogTitle,
DialogDescription,
DialogClose,
DialogContent,
DialogFooter,
DialogTrigger,
} from "@/components/ui/dialog";
import str from "@/hooks/use-string";
import useTrans from "@/hooks/use-translations";
export default function Form({
model,
modelName,
modelRoute,
action,
method,
}: {
model?: DynamicPageModel;
modelName: string;
modelRoute: string;
action: string;
method: number;
}) {
/** Global */
const { locale, url } = usePage().props.appEnv as AppEnv;
const routeName = (usePage().props.configBackend as ConfigBackend).routes
.name;
const dynamicPageUrl = usePage().props.dynamicPageUrl as string | null;
const userModel = (usePage().props.auth as AuthProp).user;
const enumUserRole = usePage().props.enumUserRole as UserModel["role"][];
const getEnumRoleValue = (name: string) =>
enumUserRole.find((role) => role.name === name)?.value;
const isConceptor = userModel.role.value === getEnumRoleValue("conceptor");
const enumDynamicPageStatus = usePage().props
.enumDynamicPageStatus as LaravelEnum;
const pageConfigModels = usePage().props
.pageConfigModels as PageConfigModel[];
// Models filtered based on the presence of the locale.
const filteredPageConfigModels = pageConfigModels.filter(
(pageConfigModel) =>
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
pageConfigModel.label[locale] !== undefined &&
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
pageConfigModel.config[locale] !== undefined,
);
// Set empty config by default.
const defaultConfigId = 0;
const configOptions = [
{
id: defaultConfigId,
label: {},
config: {},
policies: {
view: true,
update: true,
duplicate: true,
delete: true,
},
view: null,
created_at: new Date(),
updated_at: new Date(),
deleted_at: new Date(),
} as PageConfigModel,
...filteredPageConfigModels,
];
configOptions[defaultConfigId].label[locale] = useTrans(
"uwil-dynamic-pages::trans.config_none_selected",
);
// Set translations.
const contentAssociateToModel = str(
useTrans("uwil-backend::back.form_associate_to", {
params: {
attribute: useTrans("uwil-dynamic-pages::trans.attributes.content"),
},
choice: 1,
}),
)
.ucFirst()
.value();
const modalTitle = useTrans("uwil-dynamic-pages::trans.config_modal_title");
const modalDescription = useTrans(
"uwil-dynamic-pages::trans.config_modal_descripion",
);
const modalClose = useTrans("uwil-backend::back.close");
const anchorAccessConfig = useTrans(
"uwil-dynamic-pages::trans.access_model",
{
params: {
model: useTrans("uwil-dynamic-pages::models.classes.page-configs", {
choice: 1,
}),
},
},
);
const transValidationRootLabel = useTrans(
"uwil-dynamic-pages::trans.attributes.root",
);
const transValidationParentPageLabel = useTrans(
"uwil-backend::back.form_associate_to",
{
params: {
attribute: useTrans("uwil-dynamic-pages::trans.attributes.parent_page"),
},
choice: 2,
},
);
const transAssociateToStatus = useTrans(
"uwil-backend::back.form_associate_to",
{
params: {
attribute: useTrans("uwil-dynamic-pages::trans.attributes.status"),
},
choice: 1,
},
);
const transFormTitleIsLocked = useTrans(
"uwil-dynamic-pages::trans.form_title_isLocked",
);
const transFormSubtitleIsLocked = useTrans(
"uwil-dynamic-pages::trans.form_subtitle_isLocked",
);
const getConfig = (id: number): ConfigField[] => {
return configOptions.find((obj) => obj.id === id)?.config[locale] ?? [];
};
/** Form section */
const { post, setData, data, reset } = useForm({
title: model?.title ? model.title[locale] : "",
slug: model?.slug ? model.slug[locale] : "",
content: model?.content ? model.content[locale] : "",
page_id: model?.page_id ?? null,
config_id: model?.config_id ?? configOptions[defaultConfigId].id,
status: model?.status ?? enumDynamicPageStatus.draft.value,
isLocked: model?.isLocked ?? false,
isRoot: model?.isRoot ?? false,
});
const defaultIsRootValue = !data.isRoot && data.page_id ? [data.page_id] : [];
const submit = useCrudSubmit({
method,
modelRoute,
action,
model,
post,
data,
modelNameTrans: useTrans(
`uwil-dynamic-pages::models.classes.${modelName}`,
{
choice: 1,
},
),
});
return (
<form onSubmit={submit} className="grid grid-cols-2 gap-4">
<InputError all />
<FieldsetWrapper
title={useTrans("uwil-backend::back.form_title_global")}
icon={<BookTextIcon className="mr-1" />}
description={useTrans("uwil-backend::back.form_subtitle_global")}
className="sm:grid-cols-2 gap-4"
>
<SimpleString
name="title"
label={str(
useTrans("uwil-backend::back.form_associate_to", {
params: {
attribute: useTrans(
"uwil-dynamic-pages::trans.attributes.title",
),
},
choice: 1,
}),
)
.ucFirst()
.value()}
value={data.title}
setData={setData}
/>
{(!data.isLocked || isConceptor) && (
<EnumSelect
name="status"
options={enumDynamicPageStatus}
label={str(transAssociateToStatus).ucFirst().value()}
placeholder={`ex: ${enumDynamicPageStatus.draft.label}`}
selected={data.status}
setData={setData}
/>
)}
</FieldsetWrapper>
<FieldsetWrapper
title={useTrans(
"uwil-dynamic-pages::trans.form_title_page_accessibility",
)}
icon={<LinkIcon className="mr-1" />}
description={useTrans(
"uwil-dynamic-pages::trans.form_subtitle_page_accessibility",
)}
className="sm:grid-cols-2 gap-4"
>
{isConceptor && (
<div className="col-span-1 md:col-span-2">
<SimpleSwitch
label={str(transValidationRootLabel).ucFirst().value()}
name="isRoot"
checked={data.isRoot}
setData={(name, value) => {
setData("page_id", null);
setData("slug", "");
// @ts-expect-error TS2345: 'name' type is dynamic, TypeScript cannot infer the correct key.
setData(name, value);
}}
/>
</div>
)}
{!data.isRoot && (
<div className="col-span-2 grid grid-cols-1 md:grid-cols-2 gap-4">
<ModelSelect
name="page_id"
targetModel="dynamic_pages"
multiple={false}
field="title"
closeOnSelect
label={str(transValidationParentPageLabel).ucFirst().value()}
placeholder="ex: "
defaultValues={defaultIsRootValue}
exclude={model?.id}
required={false}
setData={setData}
disabled={data.isRoot}
/>
<Slugifycator
value={data.slug}
targetValue={data.title}
setData={setData}
disabled={data.isRoot}
/>
</div>
)}
<div className="col-span-2">
<GoToFrontView
modelName={useTrans(
`uwil-dynamic-pages::models.classes.${modelName}`,
{
choice: 1,
},
)}
pathField={
data.isRoot
? ""
: (dynamicPageUrl ?? "").replace(url, "").replace(/^\/+/, "")
}
isAvailable={
model?.status === enumDynamicPageStatus.published.value
}
/>
</div>
</FieldsetWrapper>
<FieldsetWrapper
title={useTrans("uwil-dynamic-pages::trans.form_title_page")}
icon={<CogIcon className="mr-1" />}
description={useTrans("uwil-dynamic-pages::trans.form_subtitle_page")}
className="gap-y-4"
>
<div className="grid sm:grid-cols-2 gap-4 border-b border-input pb-4">
<div className="flex justify-start items-end gap-1.5">
<ModelSelect
name="config_id"
targetModel="page_configs"
multiple={false}
closeOnSelect
field="label"
responsive
label={str(
useTrans("uwil-backend::back.form_associate_to", {
params: {
attribute: useTrans(
"uwil-dynamic-pages::trans.attributes.config",
),
},
choice: 2,
}),
)
.ucFirst()
.value()}
required={false}
placeholder="ex: John Doe"
defaultValues={data.config_id ? [data.config_id] : []}
setData={(_, value) => {
const id = Array.isArray(value)
? (value[0] ?? defaultConfigId)
: (value ?? defaultConfigId);
setData("config_id", id);
}}
/>
{isConceptor && data.config_id !== defaultConfigId && (
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary" type="button" size="icon">
<EyeIcon />
</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl w-full max-h-[100vh]">
<DialogHeader>
<DialogTitle>{modalTitle}</DialogTitle>
<DialogDescription>{modalDescription}</DialogDescription>
</DialogHeader>
<div className="max-h-[25rem] overflow-y-auto">
<ConfigDetails config={getConfig(data.config_id)} />
</div>
<DialogFooter>
<BtnOptionnalLinkWithTooltip
link={route(
`${routeName}page-configs.show`,
data.config_id,
)}
content={<CogIcon />}
tooltipContent={anchorAccessConfig}
variant="secondary"
/>
<DialogClose asChild>
<Button variant="secondary">{modalClose}</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)}
</div>
</div>
{!data.config_id || data.config_id === defaultConfigId ? (
<SimpleWysiwyg
name="content"
tableName="dynamic_pages"
label={contentAssociateToModel}
value={typeof data.content === "string" ? data.content : ""}
setData={setData}
/>
) : (
<DynamicInputs
name="content"
config={getConfig(data.config_id)}
content={typeof data.content === "object" ? data.content : null}
setData={setData}
// @ts-expect-error Types diff between custom reset and useForm reset.
reset={reset}
/>
)}
</FieldsetWrapper>
{isConceptor && (
<FieldsetWrapper
className="gap-4"
title={transFormTitleIsLocked}
icon={<ShieldAlertIcon className="mr-1" />}
description={transFormSubtitleIsLocked}
inline
>
<SimpleSwitch
name="isLocked"
checked={data.isLocked}
setData={setData}
/>
</FieldsetWrapper>
)}
<div className="col-span-2 text-center">
<SubmitButton
modelTrans={useTrans(
`uwil-dynamic-pages::models.classes.${modelName}`,
{
choice: 1,
},
)}
/>
</div>
</form>
);
}