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

387 lines
12 KiB
TypeScript

import { useForm, usePage } from "@inertiajs/react";
import { BookTextIcon, CogIcon, InfoIcon } from "lucide-react";
import { useEffect, useState } from "react";
import FieldsetWrapper from "@/back/Components/FieldsetWrapper";
import InputError from "@/back/Components/InputError";
import SimpleString from "@/back/Components/Inputs/SimpleString";
import PageConfigDynamicFields from "@/back/Components/PageConfigs/DynamicFields";
import ExportConfigJson from "@/back/Components/PageConfigs/ExportConfigJson";
import ImportConfigJson from "@/back/Components/PageConfigs/ImportConfigJson";
import SubmitButton from "@/back/Components/SubmitButton";
import useCrudSubmit from "@/back/hooks/use-crud-submit";
import type {
PageConfigModelWithRelations,
ConfigField,
} from "@/back/types/dynamic-pages";
import type { AuthProp, ConfigBackend, UserModel } from "@/back/types/global";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import str from "@/hooks/use-string";
import useTrans from "@/hooks/use-translations";
import { cn } from "@/lib/utils";
export default function Form({
model,
modelName,
modelRoute,
action,
method,
}: {
model?: PageConfigModelWithRelations;
modelName: string;
modelRoute: string;
action: string;
method: number;
}) {
const { appEnv, auth, enumUserRole, configBackend } = usePage()
.props as unknown as {
appEnv: AppEnv;
auth: AuthProp;
enumUserRole: UserModel["role"][];
configBackend: ConfigBackend;
};
const getEnumRoleValue = (name: string) =>
enumUserRole.find((role) => role.name === name)?.value;
const isConceptor = auth.user.role.value === getEnumRoleValue("conceptor");
// Hack to actualize states.
const [resetState, setResetState] = useState<number>(1);
/** Get configs in others locales */
const [selectedConfig, setSelectedConfig] = useState<string | null>(null);
const otherConfigs = Object.entries(model?.config ?? {}).filter(
([configLocale]) => configLocale !== appEnv.locale,
);
// Translations.
const transSelectConfigLocale = useTrans(
"uwil-dynamic-pages::trans.config_select_fields",
);
const transDialogTitle = useTrans("uwil-backend::back.are_you_sure");
const transDialogParagraph = useTrans(
"uwil-dynamic-pages::trans.config_save_warning",
{
params: { locale: str(appEnv.locale).upperCase().value() },
},
);
const transDialogParagraphList = useTrans(
"uwil-dynamic-pages::trans.config_save_warning_list",
{
params: {
models: useTrans(`uwil-dynamic-pages::models.classes.${modelName}`, {
choice: 2,
}),
},
choice: 2,
},
);
const transDialogCancel = useTrans("uwil-backend::back.no_cancel");
const transDialogSave = useTrans("uwil-backend::back.yes_update");
const transJsonExportTooltip = useTrans(
"uwil-dynamic-pages::trans.json_editor.export_tooltip",
);
const transModelName = useTrans(
`uwil-dynamic-pages::models.classes.${modelName}`,
{
choice: 1,
},
);
const transSubmitBtn = useTrans(
`uwil-dynamic-pages::models.classes.${modelName}`,
{
choice: 1,
},
);
/** Form section */
const { post, setData, data } = useForm({
label: model?.label ? model.label[appEnv.locale] : "",
view: model?.view,
config: model?.config ? (model.config[appEnv.locale] ?? []) : [],
});
const submit = useCrudSubmit({
method,
modelRoute,
action,
model,
post,
data,
modelNameTrans: useTrans(
`uwil-dynamic-pages::models.classes.${modelName}`,
{
choice: 1,
},
),
});
const resetConfig = (localeData: string) => {
const customLocaleData = str(localeData).lowerCase().value();
setData(
"config",
model?.config ? (model.config[customLocaleData] ?? []) : [],
);
};
/**
* Check if auth user must be notified when the config is updated.
* @return boolean
*/
const mustBeNotified = () => {
return (
!route().current(`${configBackend.routes.name}${modelName}.create`) &&
!route().current(`${configBackend.routes.name}${modelName}.duplicate`) &&
model?.pages.length &&
data.config !== model.config[appEnv.locale]
);
};
useEffect(() => {
if (data.config.length === 0 && selectedConfig !== null) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setSelectedConfig(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data.config]);
useEffect(() => {
if (selectedConfig !== null) {
const lowerSelectedLocale = str(selectedConfig)
.slug()
.lowerCase()
.value();
setData("config", model?.config[lowerSelectedLocale] ?? []);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedConfig]);
return (
<form id="config-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="label"
label={str(
useTrans("uwil-backend::back.form_associate_to", {
params: {
attribute: useTrans(
"uwil-dynamic-pages::trans.attributes.label",
),
},
choice: 1,
}),
)
.ucFirst()
.value()}
placeholder="ex: John Doe"
value={data.label}
setData={setData}
/>
<SimpleString
name="view"
label={
<div className="flex items-center gap-1.5">
<span>
{str(
useTrans("uwil-backend::back.form_associate_to", {
params: {
attribute: useTrans(
"uwil-dynamic-pages::trans.attributes.view",
),
},
choice: 2,
}),
)
.ucFirst()
.value()}
</span>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon size={15} />
</TooltipTrigger>
<TooltipContent>
<div className="space-y-1 text-center">
<p>
{useTrans(
"uwil-dynamic-pages::trans.respect_format_page",
)}
</p>
<code className="bg-muted px-2 py-1 rounded text-xs">
front/Pages/FileName
</code>
</div>
</TooltipContent>
</Tooltip>
</div>
}
placeholder="ex: John Doe"
pattern="^front\/Pages\/[A-Z][a-zA-Z0-9]+$"
required={false}
value={data.view ?? ""}
setData={setData}
/>
</FieldsetWrapper>
<FieldsetWrapper
title={useTrans("uwil-dynamic-pages::trans.form_title_config")}
icon={<CogIcon className="mr-1" />}
description={useTrans("uwil-dynamic-pages::trans.form_subtitle_config")}
>
{/* Import configuration from json file. */}
{isConceptor && (
<div
className={cn("mb-3", {
"border-b pb-3 mb-3": !route().current(
`${configBackend.routes.name}${modelName}.edit`,
),
})}
>
<ImportConfigJson
onConfirm={(value, overwrite) => {
const newConfig = !overwrite
? [...data.config, ...value]
: value;
setData("config", newConfig as ConfigField[]);
setResetState((prev) => prev + 1);
}}
/>
</div>
)}
{/* Show only on editing page. */}
{!route().current(`${configBackend.routes.name}${modelName}.create`) &&
!route().current(
`${configBackend.routes.name}${modelName}.duplicate`,
) && (
<div className="flex justify-center items-center border-b pb-3 mb-3 gap-1.5">
{/* Select config from another locale */}
<Select
onValueChange={(value) => {
setSelectedConfig(value);
resetConfig(value);
}}
disabled={!otherConfigs.length}
>
<SelectTrigger className="text-muted-foreground w-full">
<SelectValue placeholder={transSelectConfigLocale} />
</SelectTrigger>
<SelectContent
position="popper"
className="w-[var(--radix-select-trigger-width)]"
>
{otherConfigs.map(([configLocale, config]) => {
const strConfigLocale = str(configLocale)
.upperCase()
.value();
const localeFields = Object.values(config)
.map((field) => field.label)
.join(", ");
return (
<SelectItem
key={strConfigLocale}
value={strConfigLocale}
className="truncate"
title={`${strConfigLocale}: ${localeFields}`}
>
<b>{strConfigLocale}:</b> {localeFields}
</SelectItem>
);
})}
</SelectContent>
</Select>
{/* Button to export config */}
{isConceptor && (
<ExportConfigJson
data={data.config}
filename={`${str(data.label || "configuration")
.slug()
.value()}.json`}
tooltipContent={transJsonExportTooltip}
disabled={!data.config.length}
/>
)}
</div>
)}
<PageConfigDynamicFields
key={resetState}
name="config"
value={data.config}
setData={setData}
selectedConfig={selectedConfig}
/>
</FieldsetWrapper>
<div className="col-span-2 text-center">
{mustBeNotified() ? (
<Dialog>
<DialogTrigger asChild>
<div className="w-fit mx-auto">
<SubmitButton isInDialog modelTrans={transModelName} />
</div>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{transDialogTitle}</DialogTitle>
<DialogDescription asChild>
<div className="flex flex-col gap-y-1.5">
<p>{transDialogParagraph}</p>
<p>{transDialogParagraphList}</p>
<div className="max-h-[20rem] overflow-y-auto">
<ul className="list-disc pl-6 space-y-1">
{model?.pages.map((page) => (
<li key={page.id}>
{page.title[appEnv.locale] ??
page.title[appEnv.fallbackLocale]}
</li>
))}
</ul>
</div>
</div>
</DialogDescription>
</DialogHeader>
<DialogFooter className="items-center sm:justify-start">
<DialogClose asChild>
<Button variant="secondary" type="button">
{transDialogCancel}
</Button>
</DialogClose>
<DialogClose asChild>
<Button form="config-form" type="submit">
{transDialogSave}
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
) : (
<SubmitButton modelTrans={transSubmitBtn} />
)}
</div>
</form>
);
}