From 2234c442026eacef9e09fb09ef70b5c2067cb4e5 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Thu, 29 Jan 2026 10:09:51 +0330 Subject: [PATCH] form builder service and hooks --- src/pages/formBuilder/AttributeValues.tsx | 79 ++++++++++ src/pages/formBuilder/Create.tsx | 127 ++++++++++++++++ src/pages/formBuilder/List.tsx | 113 ++++++++++++++ src/pages/formBuilder/Update.tsx | 143 ++++++++++++++++++ src/pages/formBuilder/enum/Enum.ts | 9 ++ .../formBuilder/hooks/useFormBuilderData.ts | 98 ++++++++++++ .../formBuilder/service/FormBuilderService.ts | 76 ++++++++++ src/pages/formBuilder/types/Types.ts | 41 +++++ 8 files changed, 686 insertions(+) create mode 100644 src/pages/formBuilder/AttributeValues.tsx create mode 100644 src/pages/formBuilder/Create.tsx create mode 100644 src/pages/formBuilder/List.tsx create mode 100644 src/pages/formBuilder/Update.tsx create mode 100644 src/pages/formBuilder/enum/Enum.ts create mode 100644 src/pages/formBuilder/hooks/useFormBuilderData.ts create mode 100644 src/pages/formBuilder/service/FormBuilderService.ts create mode 100644 src/pages/formBuilder/types/Types.ts diff --git a/src/pages/formBuilder/AttributeValues.tsx b/src/pages/formBuilder/AttributeValues.tsx new file mode 100644 index 0000000..8576ec7 --- /dev/null +++ b/src/pages/formBuilder/AttributeValues.tsx @@ -0,0 +1,79 @@ +import { type FC } from 'react' +import { useParams } from 'react-router-dom' +import { useDeleteAttributeValue, useGetAttributeValues } from '../hooks/useProductData' +import CreateValue from '../components/CreateValue' +import Table from '@/components/Table' +import UpdateValue from '../components/UpdateValue' +import TrashWithConfrim from '@/components/TrashWithConfrim' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/config/func' + +const AttributeValues: FC = () => { + + const { id } = useParams() + const { data, refetch } = useGetAttributeValues(Number(id)) + const { mutate: deleteValue, isPending } = useDeleteAttributeValue() + + const handleDelete = (id: number) => { + deleteValue(id, { + onSuccess: () => { + refetch() + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + + return ( +
+
+

مقادیر

+ + +
+ +
+ { + return ( +
+ + + handleDelete(item.id)} + colorIcon='red' + isloading={isPending} + /> +
+ ) + } + } + ]} + data={data?.data} + /> + + + + ) +} + +export default AttributeValues \ No newline at end of file diff --git a/src/pages/formBuilder/Create.tsx b/src/pages/formBuilder/Create.tsx new file mode 100644 index 0000000..82c3328 --- /dev/null +++ b/src/pages/formBuilder/Create.tsx @@ -0,0 +1,127 @@ +import { useFormik } from 'formik' +import { type FC } from 'react' +import type { CreateAttributeType } from '../types/Types' +import { FieldTypeEnum } from '../enum/Enum' +import * as Yup from 'yup' +import Button from '@/components/Button' +import { AddSquare } from 'iconsax-react' +import Input from '@/components/Input' +import Select from '@/components/Select' +import SwitchComponent from '@/components/Switch' +import { useCreateAttribute } from '../hooks/useProductData' +import { useNavigate, useParams } from 'react-router-dom' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/config/func' +import { Paths } from '@/config/Paths' + +const CreateAttribute: FC = () => { + + const { id } = useParams() + const navigate = useNavigate() + const { mutate: createAttribute, isPending } = useCreateAttribute() + + const formik = useFormik({ + initialValues: { + name: '', + isRequired: true, + order: 1, + type: FieldTypeEnum.text, + }, + validationSchema: Yup.object({ + name: Yup.string().required('این فیلد اجباری می باشد') + }), + onSubmit: (values) => { + createAttribute({ id: Number(id), params: values }, { + onSuccess: () => { + toast.success('با موفقیت ذخیره شد') + navigate(Paths.product.attribute.list + id) + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + }) + + return ( +
+
+ +

ساخت ویژگی جدید

+ +
+ +
+
+ + + +
+ +
+ formik.setFieldValue('isRequired', value)} + label='اجباری' + /> +
+
+ +
+ ) +} + +export default CreateAttribute \ No newline at end of file diff --git a/src/pages/formBuilder/List.tsx b/src/pages/formBuilder/List.tsx new file mode 100644 index 0000000..cd2137f --- /dev/null +++ b/src/pages/formBuilder/List.tsx @@ -0,0 +1,113 @@ +import Button from '@/components/Button' +import { Paths } from '@/config/Paths' +import { AddSquare, Edit } from 'iconsax-react' +import { type FC } from 'react' +import { Link, useParams } from 'react-router-dom' +import { useDeleteAttribute, useGetAttributes } from '../hooks/useProductData' +import Table from '@/components/Table' +import TrashWithConfrim from '@/components/TrashWithConfrim' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/config/func' + +const AttributeList: FC = () => { + + const { id } = useParams() + const { mutate: deleteAttribute, isPending } = useDeleteAttribute() + const { data, refetch } = useGetAttributes(Number(id)) + + const handleDelete = (id: number) => { + deleteAttribute(id, { + onSuccess: () => { + refetch() + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + + + return ( +
+
+

ویژگی ها

+ + + + +
+ +
+
{ + return ( +
{item.isRequired ? 'اجباری' : 'اختیاری'}
+ ) + } + }, + { + key: 'value', + title: 'مقادیر', + render: (item) => { + return ( + + + + ) + } + }, + { + key: 'actions', + title: '', + render: (item) => { + return ( +
+ + + + + handleDelete(item.id)} + /> +
+ ) + } + } + ]} + data={data?.data} + /> + + + + ) +} + +export default AttributeList \ No newline at end of file diff --git a/src/pages/formBuilder/Update.tsx b/src/pages/formBuilder/Update.tsx new file mode 100644 index 0000000..257ff86 --- /dev/null +++ b/src/pages/formBuilder/Update.tsx @@ -0,0 +1,143 @@ +import { useFormik } from 'formik' +import { useEffect, type FC } from 'react' +import type { CreateAttributeType } from '../types/Types' +import { FieldTypeEnum } from '../enum/Enum' +import * as Yup from 'yup' +import Button from '@/components/Button' +import { AddSquare } from 'iconsax-react' +import Input from '@/components/Input' +import Select from '@/components/Select' +import SwitchComponent from '@/components/Switch' +import { useGetAttributeDetail, useUpdateAttribute } from '../hooks/useProductData' +import { useNavigate, useParams } from 'react-router-dom' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/config/func' + +const UpdateAttribute: FC = () => { + + const { id } = useParams() + const navigate = useNavigate() + const { data, refetch } = useGetAttributeDetail(Number(id)) + const { mutate: createAttribute, isPending } = useUpdateAttribute() + + const formik = useFormik({ + initialValues: { + name: '', + isRequired: true, + order: 1, + type: FieldTypeEnum.text, + }, + validationSchema: Yup.object({ + name: Yup.string().required('این فیلد اجباری می باشد') + }), + onSubmit: (values) => { + createAttribute({ id: Number(id), params: values }, { + onSuccess: () => { + toast.success('با موفقیت ذخیره شد') + refetch() + navigate(-1) + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + }) + + useEffect(() => { + + if (data) { + formik.setValues({ + isRequired: data?.data?.isRequired, + name: data?.data?.name, + order: data?.data?.order, + type: data?.data?.type + }) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]) + + + return ( +
+
+ +

ویرایش ویژگی

+ +
+ +
+
+ + + +
+ +
+ formik.setFieldValue('isRequired', value)} + label='اجباری' + /> +
+
+ +
+ ) +} + +export default UpdateAttribute \ No newline at end of file diff --git a/src/pages/formBuilder/enum/Enum.ts b/src/pages/formBuilder/enum/Enum.ts new file mode 100644 index 0000000..d2330c1 --- /dev/null +++ b/src/pages/formBuilder/enum/Enum.ts @@ -0,0 +1,9 @@ +export const enum FieldTypeEnum { + text = "text", + textarea = "textarea", + number = "number", + select = "select", + radio = "radio", + checkbox = "checkbox", + date = "date", +} diff --git a/src/pages/formBuilder/hooks/useFormBuilderData.ts b/src/pages/formBuilder/hooks/useFormBuilderData.ts new file mode 100644 index 0000000..3a50f09 --- /dev/null +++ b/src/pages/formBuilder/hooks/useFormBuilderData.ts @@ -0,0 +1,98 @@ +import { QueryClient, useMutation, useQuery } from "@tanstack/react-query"; +import * as api from "../service/FormBuilderService"; +import type { CreateFieldType, CreateFieldValueType } from "../types/Types"; + +export const useCreateField = () => { + const queryClient = new QueryClient(); + + return useMutation({ + mutationFn: ({ id, params }: { id: number; params: CreateFieldType }) => + api.createField(id, params), + onSuccess: (_, params) => { + queryClient.refetchQueries({ + queryKey: ["product-Fields", params.id], + }); + }, + }); +}; + +export const useGetFields = (id: number) => { + return useQuery({ + queryKey: ["product-Fields", id], + queryFn: () => api.getFields(id), + enabled: !!id, + }); +}; + +export const useGetFieldDetail = (id: number) => { + return useQuery({ + queryKey: ["product-Field", id], + queryFn: () => api.getFieldDetail(id), + enabled: !!id, + }); +}; + +export const useUpdateField = () => { + const queryClient = new QueryClient(); + return useMutation({ + mutationFn: ({ id, params }: { id: number; params: CreateFieldType }) => + api.updateField(id, params), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["product-Fields"], + }); + }, + }); +}; + +export const useDeleteField = () => { + return useMutation({ + mutationFn: (id: number) => api.deleteFields(id), + }); +}; + +export const useGetFieldValues = (id: number) => { + return useQuery({ + queryKey: ["attrbute-values", id], + queryFn: () => api.getFieldValues(id), + enabled: !!id, + }); +}; + +export const useCreateFieldValue = () => { + return useMutation({ + mutationFn: ({ + id, + params, + }: { + id: number; + params: CreateFieldValueType; + }) => api.createFieldValues(id, params), + }); +}; + +export const useUpdateFieldValue = () => { + return useMutation({ + mutationFn: ({ + id, + params, + }: { + id: number; + params: CreateFieldValueType; + }) => api.updateFieldValues(id, params), + }); +}; + +export const useGetFieldValueDetail = (id: number, enabled: boolean) => { + return useQuery({ + queryKey: ["attrbute-values-detail", id], + queryFn: () => api.getFieldValueDetail(id), + enabled: !!id && enabled, + }); +}; + +export const useDeleteFieldValue = () => { + return useMutation({ + mutationFn: (id: number) => api.deleteFieldValue(id), + }); +}; diff --git a/src/pages/formBuilder/service/FormBuilderService.ts b/src/pages/formBuilder/service/FormBuilderService.ts new file mode 100644 index 0000000..50fb9c0 --- /dev/null +++ b/src/pages/formBuilder/service/FormBuilderService.ts @@ -0,0 +1,76 @@ +import axios from "@/config/axios"; +import type { + CreateFieldType, + CreateFieldValueType, + FieldDetailResponseType, + FieldResponseType, + FieldValueDetailResponseType, + FieldValuesResponseType, +} from "../types/Types"; + +export const createField = async (id: number, params: CreateFieldType) => { + const { data } = await axios.post(`/admin/entity/${id}/field`, params); + return data; +}; + +export const getFields = async (id: number) => { + const { data } = await axios.get( + `/admin/entity/${id}/field`, + ); + return data; +}; + +export const getFieldDetail = async (id: number) => { + const { data } = await axios.get( + `/admin/entity/field/${id}`, + ); + return data; +}; + +export const updateField = async (id: number, params: CreateFieldType) => { + const { data } = await axios.patch(`/admin/entity/field/${id}`, params); + return data; +}; + +export const deleteFields = async (id: number) => { + const { data } = await axios.delete(`/admin/entity/field/${id}`); + return data; +}; + +export const getFieldValues = async (id: number) => { + const { data } = await axios.get( + `/admin/entity/field/${id}/option`, + ); + return data; +}; + +export const createFieldValues = async ( + id: number, + params: CreateFieldValueType, +) => { + const { data } = await axios.post(`/admin/entity/field/${id}/option`, params); + return data; +}; + +export const updateFieldValues = async ( + id: number, + params: CreateFieldValueType, +) => { + const { data } = await axios.patch( + `/admin/entity/field/option/${id}`, + params, + ); + return data; +}; + +export const getFieldValueDetail = async (id: number) => { + const { data } = await axios.get( + `/admin/entity/field/option/${id}`, + ); + return data; +}; + +export const deleteFieldValue = async (id: number) => { + const { data } = await axios.delete(`/admin/entity/field/option/${id}`); + return data; +}; diff --git a/src/pages/formBuilder/types/Types.ts b/src/pages/formBuilder/types/Types.ts new file mode 100644 index 0000000..d129fe9 --- /dev/null +++ b/src/pages/formBuilder/types/Types.ts @@ -0,0 +1,41 @@ +import { type BaseResponse } from "@/shared/types/Types"; +import type { FieldTypeEnum } from "../enum/Enum"; + +export type CreateFieldType = { + name: string, + isRequired: boolean, + type: FieldTypeEnum, + order: number, + multiple: boolean, + entityType: 'product' | 'print' +} + +export type FieldType = { + createdAt: string, + id: number, + isRequired: boolean, + name: string, + order: number, + product: number, + type: FieldTypeEnum, + multiple: boolean, + entityType: 'product' | 'print' +} + +export type FieldResponseType = BaseResponse; +export type FieldDetailResponseType = BaseResponse; + +export type CreateFieldValueType = { + value: string, + order: number, +} + +export type FieldValueType = { + createdAt: string, + id: number, + order: number, + value: string, +} + +export type FieldValuesResponseType = BaseResponse; +export type FieldValueDetailResponseType = BaseResponse;