feat(roles): add role form page for create/edit

- Unified form for both create and edit operations
- Form validation with Yup schema
- Auto-populate fields in edit mode
- Proper navigation and loading states
- Persian validation messages
This commit is contained in:
hosseintaromi 2025-07-18 14:02:58 +03:30
parent 0ee448d9be
commit 4b4fe84cee
1 changed files with 149 additions and 0 deletions

View File

@ -0,0 +1,149 @@
import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useRole, useCreateRole, useUpdateRole } from "../core/_hooks";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
import { RoleFormData } from "../core/_models";
import { ArrowRight } from "lucide-react";
const roleSchema = yup.object({
title: yup
.string()
.required("نام نقش الزامی است")
.min(2, "نام نقش باید حداقل ۲ کاراکتر باشد"),
description: yup
.string()
.required("توضیحات الزامی است")
.min(5, "توضیحات باید حداقل ۵ کاراکتر باشد"),
});
const RoleFormPage = () => {
const navigate = useNavigate();
const { id } = useParams();
const isEdit = !!id;
const { data: role, isLoading: roleLoading } = useRole(id || "");
const { mutate: createRole, isPending: creating } = useCreateRole();
const { mutate: updateRole, isPending: updating } = useUpdateRole();
const {
register,
handleSubmit,
formState: { errors, isValid },
reset,
} = useForm<RoleFormData>({
resolver: yupResolver(roleSchema),
mode: 'onChange',
});
useEffect(() => {
if (isEdit && role) {
reset({
title: role.title,
description: role.description,
});
}
}, [isEdit, role, reset]);
const onSubmit = (data: RoleFormData) => {
if (isEdit && id) {
updateRole({
id: parseInt(id),
...data,
}, {
onSuccess: () => {
navigate('/roles');
}
});
} else {
createRole(data, {
onSuccess: () => {
navigate('/roles');
}
});
}
};
if (isEdit && roleLoading) {
return <LoadingSpinner />;
}
const isLoading = creating || updating;
return (
<div className="p-6">
<div className="mb-6">
<div className="flex items-center gap-4 mb-4">
<Button
variant="secondary"
onClick={() => navigate('/roles')}
className="flex items-center gap-2"
>
<ArrowRight className="h-4 w-4" />
بازگشت
</Button>
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
{isEdit ? 'ویرایش نقش' : 'ایجاد نقش جدید'}
</h1>
</div>
</div>
<div className="max-w-2xl">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<Input
label="نام نقش"
type="text"
placeholder="نام نقش را وارد کنید"
error={errors.title?.message}
{...register('title')}
/>
<div className="space-y-1">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
توضیحات
</label>
<textarea
placeholder="توضیحات نقش را وارد کنید"
className={`w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 resize-none h-24 ${errors.description
? 'border-red-500 focus:ring-red-500 focus:border-red-500'
: 'border-gray-300 dark:border-gray-600'
} dark:bg-gray-700 dark:text-gray-100`}
{...register('description')}
/>
{errors.description && (
<p className="text-sm text-red-600 dark:text-red-400">
{errors.description.message}
</p>
)}
</div>
<div className="flex justify-end gap-3 pt-4">
<Button
type="button"
variant="secondary"
onClick={() => navigate('/roles')}
>
انصراف
</Button>
<Button
type="submit"
variant="primary"
loading={isLoading}
disabled={!isValid}
>
{isEdit ? 'به‌روزرسانی' : 'ایجاد'}
</Button>
</div>
</form>
</div>
</div>
</div>
);
};
export default RoleFormPage;