From 4f2bf864e45d976d96f35f58bef7e4aaa8c8b75a Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Wed, 22 Jul 2026 11:51:58 +0330 Subject: [PATCH] update user --- src/pages/customers/Create.tsx | 33 ++++++++++++- src/pages/customers/Update.tsx | 51 +++++++++++++++++++-- src/pages/customers/hooks/useUsersData.ts | 11 ++++- src/pages/customers/service/UsersService.ts | 8 ++++ src/pages/customers/types/Types.ts | 5 ++ 5 files changed, 102 insertions(+), 6 deletions(-) diff --git a/src/pages/customers/Create.tsx b/src/pages/customers/Create.tsx index 0f9fc64..cadfb2e 100644 --- a/src/pages/customers/Create.tsx +++ b/src/pages/customers/Create.tsx @@ -1,4 +1,4 @@ -import { type FC, useEffect, useState } from 'react' +import { type FC, useEffect, useMemo, useState } from 'react' import { useFormik } from 'formik' import * as Yup from 'yup' import { TickCircle } from 'iconsax-react' @@ -6,11 +6,13 @@ import { toast } from 'react-toastify' import { useNavigate } from 'react-router-dom' import Button from '@/components/Button' import Input from '@/components/Input' +import MultiSelect from '@/components/MultiSelect' import Select from '@/components/Select' import DatePicker from '@/components/DatePicker' import UploadBox from '@/components/UploadBox' import { useCreateUser } from './hooks/useUsersData' import { useSingleUpload } from '@/pages/uploader/hooks/useUploaderData' +import { useGetUserGroups } from '@/pages/customers/groups/hooks/useUserGroupData' import type { CreateUserType } from './types/Types' import { Pages } from '@/config/Pages' import type { ErrorType } from '@/helpers/types' @@ -23,6 +25,7 @@ type CreateCustomerFormValues = { birthDate: string marriageDate: string gender: string + groupIds: string[] } const buildCreateUserPayload = ( @@ -39,6 +42,7 @@ const buildCreateUserPayload = ( if (values.marriageDate) payload.marriageDate = values.marriageDate if (values.gender !== '') payload.gender = values.gender === 'true' if (avatarUrl) payload.avatarUrl = avatarUrl + if (values.groupIds.length > 0) payload.groupIds = values.groupIds return payload } @@ -47,9 +51,19 @@ const CreateCustomer: FC = () => { const navigate = useNavigate() const { mutate: createUser, isPending } = useCreateUser() const { mutate: singleUpload, isPending: isUploading } = useSingleUpload() + const { data: groupsData } = useGetUserGroups() const [avatarFile, setAvatarFile] = useState([]) const [avatarPreviewUrl, setAvatarPreviewUrl] = useState('') + const groupOptions = useMemo( + () => + (groupsData?.data || []).map((group) => ({ + value: group.id, + label: group.name, + })), + [groupsData], + ) + useEffect(() => { if (avatarFile.length > 0) { const url = URL.createObjectURL(avatarFile[0]) @@ -67,6 +81,7 @@ const CreateCustomer: FC = () => { birthDate: '', marriageDate: '', gender: '', + groupIds: [], }, validationSchema: Yup.object().shape({ phone: Yup.string().required('شماره تلفن الزامی است'), @@ -75,6 +90,7 @@ const CreateCustomer: FC = () => { birthDate: Yup.string(), marriageDate: Yup.string(), gender: Yup.string(), + groupIds: Yup.array().of(Yup.string()), }), onSubmit: (values) => { const submitCustomer = (avatarUrl?: string) => { @@ -197,6 +213,21 @@ const CreateCustomer: FC = () => { /> +
+ formik.setFieldValue('groupIds', values)} + placeholder='انتخاب گروه‌ها' + error_text={ + formik.touched.groupIds && formik.errors.groupIds + ? String(formik.errors.groupIds) + : '' + } + /> +
+
{ const { id } = useParams<{ id: string }>() const { mutate: updateUser, isPending: isUpdating } = useUpdateUser() const { mutate: singleUpload, isPending: isUploading } = useSingleUpload() + const { data: userGroupsData } = useGetUserGroupsForUser(id || '') + const { data: allGroupsData } = useGetUserGroups() const [avatarFile, setAvatarFile] = useState([]) const [avatarPreviewUrl, setAvatarPreviewUrl] = useState('') const customer = (location.state as CustomerUpdateLocationState | null)?.customer + const initialGroupIds = useMemo( + () => (userGroupsData?.data || []).map((group) => group.id), + [userGroupsData], + ) + + const groupOptions = useMemo( + () => + (allGroupsData?.data || []).map((group) => ({ + value: group.id, + label: group.name, + })), + [allGroupsData], + ) + useEffect(() => { if (!customer?.avatarUrl) { setAvatarPreviewUrl('') @@ -64,6 +83,7 @@ const UpdateCustomer: FC = () => { customer?.gender === undefined || customer?.gender === null ? '' : String(customer.gender), + groupIds: initialGroupIds, }, enableReinitialize: true, onSubmit: (values) => { @@ -73,7 +93,9 @@ const UpdateCustomer: FC = () => { } const submitUpdate = (avatarUrl?: string) => { - const payload: UpdateUserType = {} + const payload: UpdateUserType = { + groupIds: values.groupIds, + } if (values.firstName.trim()) payload.firstName = values.firstName.trim() if (values.lastName.trim()) payload.lastName = values.lastName.trim() @@ -82,7 +104,18 @@ const UpdateCustomer: FC = () => { if (values.gender !== '') payload.gender = values.gender === 'true' if (avatarUrl) payload.avatarUrl = avatarUrl - if (Object.keys(payload).length === 0) { + const groupsChanged = + JSON.stringify([...values.groupIds].sort()) !== + JSON.stringify([...initialGroupIds].sort()) + const hasProfileChanges = + Boolean(payload.firstName) || + Boolean(payload.lastName) || + Boolean(payload.birthDate) || + Boolean(payload.marriageDate) || + payload.gender !== undefined || + Boolean(payload.avatarUrl) + + if (!hasProfileChanges && !groupsChanged) { toast.info('حداقل یک فیلد را برای ویرایش وارد کنید') return } @@ -188,6 +221,16 @@ const UpdateCustomer: FC = () => { />
+
+ formik.setFieldValue('groupIds', values)} + placeholder='انتخاب گروه‌ها' + /> +
+
{ }); }; +export const useGetUserGroupsForUser = (userId: string) => { + return useQuery({ + queryKey: ["user-groups-for-user", userId], + queryFn: () => api.getUserGroups(userId), + enabled: !!userId, + }); +}; + export const useSearchUserByPhone = () => { return useMutation({ mutationFn: api.searchUserByPhone, @@ -36,8 +44,9 @@ export const useUpdateUser = () => { return useMutation({ mutationFn: ({ userId, params }: { userId: string; params: UpdateUserType }) => api.updateUser(userId, params), - onSuccess: () => { + onSuccess: (_, { userId }) => { queryClient.invalidateQueries({ queryKey: ["users"] }); + queryClient.invalidateQueries({ queryKey: ["user-groups-for-user", userId] }); }, }); }; diff --git a/src/pages/customers/service/UsersService.ts b/src/pages/customers/service/UsersService.ts index efeb664..0fc05d8 100644 --- a/src/pages/customers/service/UsersService.ts +++ b/src/pages/customers/service/UsersService.ts @@ -5,6 +5,7 @@ import type { CreateUserResponse, CreateUserType, GetCustomersResponse, + GetUserGroupsForUserResponse, GetUsersParams, ImportUsersResponse, SearchUserByPhoneResponse, @@ -40,6 +41,13 @@ export const updateUser = async (userId: string, params: UpdateUserType) => { return data; }; +export const getUserGroups = async (userId: string) => { + const { data } = await axios.get( + `/admin/users/${userId}/groups`, + ); + return data; +}; + export const createUserAddress = async ( userId: string, params: CreateUserAddressType, diff --git a/src/pages/customers/types/Types.ts b/src/pages/customers/types/Types.ts index 1a5e83c..53b2e18 100644 --- a/src/pages/customers/types/Types.ts +++ b/src/pages/customers/types/Types.ts @@ -1,5 +1,6 @@ import type { IResponse } from "@/types/response.types"; import type { RowDataType } from "@/components/types/TableTypes"; +import type { UserGroup } from "@/pages/customers/groups/types/Types"; export type User = { id: string; @@ -111,6 +112,7 @@ export type CreateUserType = { marriageDate?: string; gender?: boolean; avatarUrl?: string; + groupIds?: string[]; }; export type CreateUserResult = { @@ -143,6 +145,9 @@ export type UpdateUserType = { marriageDate?: string; gender?: boolean; avatarUrl?: string; + groupIds?: string[]; }; export type UpdateUserResponse = IResponse; + +export type GetUserGroupsForUserResponse = IResponse;