From 85fdf1f7a2aac3ea8571032646a989e9bfe38f36 Mon Sep 17 00:00:00 2001 From: hosseintaromi Date: Sun, 8 Feb 2026 15:05:43 +0330 Subject: [PATCH] feat(wallet-credit): enhance user search functionality and input handling - Integrated user search with debounced input for improved performance and user experience. - Added dropdown for user selection based on search input, displaying user details. - Updated amount input to format currency with thousands separators. - Refactored form handling to improve state management and validation. These changes significantly enhance the Wallet Credit page's usability and functionality. --- .../wallet/wallet-credit/WalletCreditPage.tsx | 121 +++++++++++++++--- 1 file changed, 106 insertions(+), 15 deletions(-) diff --git a/src/pages/wallet/wallet-credit/WalletCreditPage.tsx b/src/pages/wallet/wallet-credit/WalletCreditPage.tsx index cc31b91..2eafe56 100644 --- a/src/pages/wallet/wallet-credit/WalletCreditPage.tsx +++ b/src/pages/wallet/wallet-credit/WalletCreditPage.tsx @@ -1,15 +1,17 @@ -import React, { useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; -import { Wallet, Plus, Loader2 } from 'lucide-react'; +import { Plus } from 'lucide-react'; import { PageContainer, PageTitle } from '@/components/ui/Typography'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { useWalletCredit } from '../core/_hooks'; import { WalletCreditRequest, WalletType } from '../core/_credit-models'; import { formatCurrency } from '@/utils/formatters'; -import { persianToEnglish } from '@/utils/numberUtils'; +import { formatWithThousands, persianToEnglish } from '@/utils/numberUtils'; +import { useSearchUsers, useUsers } from '@/pages/users-admin/core/_hooks'; +import { UserFilters } from '@/pages/users-admin/core/_models'; const schema = yup.object({ user_id: yup.number().required('شناسه کاربر الزامی است').positive('شناسه کاربر باید عدد مثبت باشد'), @@ -24,9 +26,14 @@ type FormData = yup.InferType; const WalletCreditPage = () => { const { mutate: creditWallet, isPending } = useWalletCredit(); const [successData, setSuccessData] = useState<{ new_balance: number; message: string } | null>(null); + const [searchText, setSearchText] = useState(''); + const [debouncedText, setDebouncedText] = useState(''); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [amountDisplay, setAmountDisplay] = useState(''); const { register, + setValue, handleSubmit, formState: { errors }, reset, @@ -37,6 +44,44 @@ const WalletCreditPage = () => { }, }); + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedText(searchText); + }, 300); + return () => clearTimeout(handler); + }, [searchText]); + + const { data: usersData, isLoading: isUsersLoading } = useUsers({ + limit: 20, + offset: 0, + }); + + const searchFilters = useMemo(() => { + if (!debouncedText) return {}; + return { + search_text: debouncedText, + limit: 20, + offset: 0, + }; + }, [debouncedText]); + + const { data: searchData, isLoading: isSearchLoading } = useSearchUsers(searchFilters); + + const userOptions = useMemo(() => { + if (debouncedText) { + return searchData?.users || []; + } + return usersData || []; + }, [debouncedText, searchData, usersData]); + + const isLoadingUsers = debouncedText ? isSearchLoading : isUsersLoading; + + const handleSelectUser = (user: any) => { + setValue('user_id', user.id, { shouldValidate: true, shouldDirty: true }); + setSearchText(user.phone_number); + setIsDropdownOpen(false); + }; + const onSubmit = (data: FormData) => { const payload: WalletCreditRequest = { user_id: data.user_id, @@ -53,6 +98,7 @@ const WalletCreditPage = () => { message: response.message, }); reset(); + setAmountDisplay(''); setTimeout(() => setSuccessData(null), 5000); }, }); @@ -80,14 +126,55 @@ const WalletCreditPage = () => { - (value === '' ? undefined : Number(persianToEnglish(value))), - })} - error={errors.user_id?.message} - placeholder="مثلاً 52" - /> +
+ { + const normalized = persianToEnglish(e.target.value).replace(/[^\d]/g, ''); + setSearchText(normalized); + setIsDropdownOpen(true); + setValue('user_id', undefined as any, { shouldValidate: true, shouldDirty: true }); + }} + onFocus={() => setIsDropdownOpen(true)} + onBlur={() => setTimeout(() => setIsDropdownOpen(false), 150)} + error={errors.user_id?.message} + placeholder="جستجو با شماره موبایل" + /> + + {isDropdownOpen && ( +
+ {isLoadingUsers && ( +
+ در حال بارگذاری... +
+ )} + {!isLoadingUsers && userOptions.length === 0 && ( +
+ کاربری یافت نشد +
+ )} + {!isLoadingUsers && userOptions.map((user: any) => ( + + ))} +
+ )} +
@@ -114,11 +201,15 @@ const WalletCreditPage = () => { (value === '' ? undefined : Number(persianToEnglish(value))), - })} + value={amountDisplay} + onChange={(e) => { + const normalized = persianToEnglish(e.target.value).replace(/[^\d]/g, ''); + const numericValue = normalized ? Number(normalized) : undefined; + setAmountDisplay(normalized ? formatWithThousands(Number(normalized)) : ''); + setValue('amount', numericValue as any, { shouldValidate: true, shouldDirty: true }); + }} error={errors.amount?.message} - placeholder="مثلاً 1000000" + placeholder="مثلاً 1,000,000" numeric />