import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useTranslation } from "react-i18next"; import { LuX } from "react-icons/lu"; import { MdAddBox } from "react-icons/md"; export type ModelType = "state" | "object"; export type ObjectClassificationType = "sub_label" | "attribute"; export type Step1FormData = { modelName: string; modelType: ModelType; objectType?: ObjectClassificationType; classes: string[]; }; type Step1NameAndDefineProps = { initialData?: Partial; onNext: (data: Step1FormData) => void; onCancel: () => void; }; export default function Step1NameAndDefine({ initialData, onNext, onCancel, }: Step1NameAndDefineProps) { const { t } = useTranslation(["views/classificationModel"]); const step1FormData = z .object({ modelName: z .string() .min(1, t("wizard.step1.errors.nameRequired")) .max(64, t("wizard.step1.errors.nameLength")) .refine((value) => !/^\d+$/.test(value), { message: t("wizard.step1.errors.nameOnlyNumbers"), }), modelType: z.enum(["state", "object"]), objectType: z.enum(["sub_label", "attribute"]).optional(), classes: z .array(z.string()) .min(1, t("wizard.step1.errors.classRequired")) .refine( (classes) => { const nonEmpty = classes.filter((c) => c.trim().length > 0); return nonEmpty.length >= 1; }, { message: t("wizard.step1.errors.classRequired") }, ) .refine( (classes) => { const nonEmpty = classes.filter((c) => c.trim().length > 0); const unique = new Set(nonEmpty.map((c) => c.toLowerCase())); return unique.size === nonEmpty.length; }, { message: t("wizard.step1.errors.classesUnique") }, ), }) .refine( (data) => { // State models require at least 2 classes if (data.modelType === "state") { const nonEmpty = data.classes.filter((c) => c.trim().length > 0); return nonEmpty.length >= 2; } return true; }, { message: t("wizard.step1.errors.stateRequiresTwoClasses"), path: ["classes"], }, ) .refine( (data) => { // Object models require objectType to be selected if (data.modelType === "object") { return data.objectType !== undefined; } return true; }, { message: t("wizard.step1.errors.objectTypeRequired"), path: ["objectType"], }, ); const form = useForm>({ resolver: zodResolver(step1FormData), defaultValues: { modelName: initialData?.modelName || "", modelType: initialData?.modelType || "state", objectType: initialData?.objectType || "sub_label", classes: initialData?.classes?.length ? initialData.classes : [""], }, mode: "onChange", }); const watchedClasses = form.watch("classes"); const watchedModelType = form.watch("modelType"); const watchedObjectType = form.watch("objectType"); const handleAddClass = () => { const currentClasses = form.getValues("classes"); form.setValue("classes", [...currentClasses, ""], { shouldValidate: true }); }; const handleRemoveClass = (index: number) => { const currentClasses = form.getValues("classes"); const newClasses = currentClasses.filter((_, i) => i !== index); // Ensure at least one field remains (even if empty) if (newClasses.length === 0) { form.setValue("classes", [""], { shouldValidate: true }); } else { form.setValue("classes", newClasses, { shouldValidate: true }); } }; const onSubmit = (data: z.infer) => { // Filter out empty classes const filteredClasses = data.classes.filter((c) => c.trim().length > 0); onNext({ ...data, classes: filteredClasses, }); }; return (
( {t("wizard.step1.name")} )} /> ( {t("wizard.step1.type")}
)} /> {watchedModelType === "object" && ( ( {t("wizard.step1.classificationType")}
)} /> )}
{t("wizard.step1.classes")}
{watchedClasses.map((_, index) => ( (
{watchedClasses.length > 1 && ( )}
)} /> ))}
{form.formState.errors.classes && (

{form.formState.errors.classes.message}

)}
); }