fix(frontend): enhance contentType logic

This commit is contained in:
yassinedorbozgithub
2025-06-06 06:54:51 +01:00
parent da5200eb1b
commit 2c13f069f9
2 changed files with 61 additions and 70 deletions

View File

@@ -25,26 +25,27 @@ import {
IContentTypeAttributes,
} from "@/types/content-type.types";
import { generateId } from "@/utils/generateId";
import { slugify } from "@/utils/string";
import { FieldInput } from "./components/FieldInput";
import { FIELDS_FORM_DEFAULT_VALUES, READ_ONLY_FIELDS } from "./constants";
import { FIELDS_FORM_DEFAULT_VALUES } from "./constants";
export const ContentTypeForm: FC<ComponentFormProps<IContentType>> = ({
data: { defaultValues: contentTypeWithoutId },
data: { defaultValues: contentTypeWithoutUuid },
Wrapper = Fragment,
WrapperProps,
...rest
}) => {
const contentType = useMemo(
() =>
contentTypeWithoutId && {
...contentTypeWithoutId,
fields: contentTypeWithoutId?.fields?.map((field) => ({
contentTypeWithoutUuid && {
...contentTypeWithoutUuid,
fields: contentTypeWithoutUuid?.fields?.map((field) => ({
...field,
uuid: generateId(),
})),
},
[contentTypeWithoutId],
[contentTypeWithoutUuid],
);
const { toast } = useToast();
const { t } = useTranslate();
@@ -62,6 +63,27 @@ export const ContentTypeForm: FC<ComponentFormProps<IContentType>> = ({
});
const { append, fields, remove } = useFieldArray({
name: "fields",
rules: {
validate: (value) => {
const labelCounts = value.reduce((acc, field) => {
if (!field.label.trim()) return acc;
acc[field.label] = (acc[field.label] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const hasDuplicatedLabels = Object.values(labelCounts).some(
(count: number) => count > 1,
);
if (hasDuplicatedLabels) {
toast.error(t("message.duplicate_labels_not_allowed"));
return false;
}
return true;
},
},
control,
});
const options = {
@@ -83,22 +105,6 @@ export const ContentTypeForm: FC<ComponentFormProps<IContentType>> = ({
options,
);
const onSubmitForm = (params: IContentTypeAttributes) => {
const labelCounts = params.fields?.reduce((acc, field) => {
if (!field.label.trim()) return acc;
acc[field.label] = (acc[field.label] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const hasDuplicates = Object.values(labelCounts || {}).some(
(count: number) => count > 1,
);
if (hasDuplicates) {
toast.error(t("message.duplicate_labels_not_allowed"));
return;
}
if (contentType?.id) {
updateContentType({ id: contentType.id, params });
} else {
@@ -122,23 +128,28 @@ export const ContentTypeForm: FC<ComponentFormProps<IContentType>> = ({
autoFocus
/>
</ContentItem>
{fields.map((f, index) => (
{fields.map((field, idx) => (
<ContentItem
key={f.id}
key={field.id}
display="flex"
justifyContent="space-between"
gap={2}
>
<FieldInput
index={index}
remove={remove}
idx={idx}
control={control}
setValue={setValue}
disabled={READ_ONLY_FIELDS.includes(f.label as any)}
contentTypeField={contentType?.fields?.find(
({ uuid }) => uuid === f.uuid,
)}
onLabelChange={(value) => {
const fieldName = contentType?.fields?.find(
({ uuid }) => uuid === field.uuid,
)?.name;
if (!fieldName) {
setValue(`fields.${idx}.name`, value ? slugify(value) : "");
}
}}
onRemove={() => {
remove(idx);
}}
/>
</ContentItem>
))}

View File

@@ -8,37 +8,27 @@
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import { MenuItem } from "@mui/material";
import {
Control,
Controller,
UseFieldArrayRemove,
UseFormSetValue,
} from "react-hook-form";
import { useMemo } from "react";
import { Control, Controller } from "react-hook-form";
import { IconButton } from "@/app-components/buttons/IconButton";
import { Input } from "@/app-components/inputs/Input";
import { useTranslate } from "@/hooks/useTranslate";
import {
ContentField,
ContentFieldType,
IContentType,
} from "@/types/content-type.types";
import { slugify } from "@/utils/string";
import { ContentFieldType, IContentType } from "@/types/content-type.types";
import { READ_ONLY_FIELDS } from "../constants";
export const FieldInput = ({
setValue,
index,
contentTypeField,
idx,
...props
}: {
index: number;
disabled?: boolean;
remove: UseFieldArrayRemove;
idx: number;
control: Control<IContentType>;
setValue: UseFormSetValue<IContentType>;
contentTypeField?: ContentField;
onRemove?: () => void;
onLabelChange?: (value: string) => void;
}) => {
const { t } = useTranslate();
const isDisabled = useMemo(() => idx < READ_ONLY_FIELDS.length, [idx]);
return (
<>
@@ -46,52 +36,42 @@ export const FieldInput = ({
variant="text"
color="error"
size="medium"
onClick={() => props.remove(index)}
disabled={props.disabled}
onClick={props.onRemove}
disabled={isDisabled}
>
<DeleteOutlineIcon strokeWidth={1} fontSize="medium" />
</IconButton>
<Controller
control={props.control}
name={`fields.${index}.label`}
name={`fields.${idx}.label`}
rules={{ required: t("message.label_is_required") }}
render={({ field, fieldState }) => (
<Input
disabled={props.disabled}
disabled={isDisabled}
{...field}
label={t("label.label")}
error={!!fieldState.error}
helperText={fieldState.error?.message}
onChange={(e) => {
const currentValue = e.target.value;
if (!contentTypeField?.label || !contentTypeField?.name) {
setValue(
`fields.${index}.name`,
currentValue ? slugify(currentValue) : "",
);
}
props?.onLabelChange?.(e.target.value);
field.onChange(e);
}}
/>
)}
/>
<Controller
name={`fields.${index}.name`}
name={`fields.${idx}.name`}
render={({ field }) => (
<Input disabled {...field} label={t("label.name")} />
)}
control={props.control}
/>
<Controller
name={`fields.${index}.type`}
name={`fields.${idx}.type`}
control={props.control}
render={({ field }) => (
<Input
disabled={props.disabled}
disabled={isDisabled}
label={t("label.type")}
{...field}
select