387 lines
12 KiB
TypeScript
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>
|
|
);
|
|
}
|