From e0e9ada60fe0ac6a413a4f13ccca3ebaa5e3dc20 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 25 Feb 2026 15:26:32 +0330 Subject: [PATCH] component designer search and user search --- src/pages/admin/service/AdminService.ts | 7 ++ src/pages/designer/DesignerSearch.tsx | 100 ++++++++++++++++++ src/pages/designer/hooks/useDesignerSearch.ts | 15 +++ src/pages/order/List.tsx | 16 ++- src/pages/order/hooks/useOrderData.ts | 6 +- src/pages/order/service/OrderService.ts | 6 +- src/pages/user/UserSearch.tsx | 100 ++++++++++++++++++ src/pages/user/hooks/useUserSearch.ts | 15 +++ src/pages/user/service/UserService.ts | 8 ++ 9 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 src/pages/designer/DesignerSearch.tsx create mode 100644 src/pages/designer/hooks/useDesignerSearch.ts create mode 100644 src/pages/user/UserSearch.tsx create mode 100644 src/pages/user/hooks/useUserSearch.ts diff --git a/src/pages/admin/service/AdminService.ts b/src/pages/admin/service/AdminService.ts index a762077..863e71b 100644 --- a/src/pages/admin/service/AdminService.ts +++ b/src/pages/admin/service/AdminService.ts @@ -13,6 +13,13 @@ export const getAdmins = async (page: number, limit: number = 10) => { return data; }; +export const searchAdmins = async (search: string) => { + const { data } = await axios.get("/admin/admins/search", { + params: { search: search || undefined }, + }); + return data; +}; + export const createAdmin = async (params: CreateAdminType) => { const { data } = await axios.post("/admin/admin", params); return data; diff --git a/src/pages/designer/DesignerSearch.tsx b/src/pages/designer/DesignerSearch.tsx new file mode 100644 index 0000000..e854dd7 --- /dev/null +++ b/src/pages/designer/DesignerSearch.tsx @@ -0,0 +1,100 @@ +import { type FC, useEffect, useRef, useState } from "react"; +import { clx } from "@/helpers/utils"; +import { useDesignerSearch } from "./hooks/useDesignerSearch"; +import type { AdminItemType } from "../admin/types/Types"; + +const DEBOUNCE_MS = 400; + +type DesignerSearchProps = { + label?: string; + placeholder?: string; + className?: string; + onChange?: (designId: string) => void; + value?: string; +}; + +const DesignerSearch: FC = ({ + label, + placeholder = "جستجوی طراح (نام، موبایل، ...)", + className, + onChange, + value = "", +}) => { + const [search, setSearch] = useState(value); + const [debouncedSearch, setDebouncedSearch] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const wrapperRef = useRef(null); + + useEffect(() => { + const t = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_MS); + return () => clearTimeout(t); + }, [search]); + + const { designers, isFetching } = useDesignerSearch(debouncedSearch); + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { + setIsOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const handleSelect = (designer: AdminItemType) => { + setSearch(`${designer.firstName} ${designer.lastName}`); + onChange?.(designer.id); + setIsOpen(false); + }; + + const displayLabel = (designer: AdminItemType) => + `${designer.firstName} ${designer.lastName}`; + + return ( +
+ {label && ( + + )} + { + setSearch(e.target.value); + setIsOpen(true); + }} + onFocus={() => setIsOpen(true)} + placeholder={placeholder} + className="w-full bg-white h-10 text-black block px-4 text-xs rounded-xl border border-border" + /> + {isOpen && debouncedSearch.length > 0 && ( +
+ {isFetching ? ( +
در حال جستجو...
+ ) : designers.length === 0 ? ( +
نتیجه‌ای یافت نشد
+ ) : ( +
    + {designers.map((designer) => ( +
  • + +
  • + ))} +
+ )} +
+ )} +
+ ); +}; + +export default DesignerSearch; diff --git a/src/pages/designer/hooks/useDesignerSearch.ts b/src/pages/designer/hooks/useDesignerSearch.ts new file mode 100644 index 0000000..114c19f --- /dev/null +++ b/src/pages/designer/hooks/useDesignerSearch.ts @@ -0,0 +1,15 @@ +import { useQuery } from "@tanstack/react-query"; +import { searchAdmins } from "../../admin/service/AdminService"; +import type { AdminItemType } from "../../admin/types/Types"; + +export const useDesignerSearch = (debouncedSearch: string) => { + const { data, isFetching } = useQuery({ + queryKey: ["designers", "search", debouncedSearch], + queryFn: () => searchAdmins(debouncedSearch), + enabled: debouncedSearch.length > 0, + }); + + const designers = (data?.data ?? []) as AdminItemType[]; + + return { designers, isFetching }; +}; diff --git a/src/pages/order/List.tsx b/src/pages/order/List.tsx index ebd80a4..2a4129d 100644 --- a/src/pages/order/List.tsx +++ b/src/pages/order/List.tsx @@ -1,5 +1,5 @@ import Filters from '@/components/Filters' -import { type FC } from 'react' +import { type FC, useState } from 'react' import Table from '@/components/Table' import { AddSquare, Edit2, Eye, Printer, Receipt21 } from 'iconsax-react' import { useGetOrders } from './hooks/useOrderData' @@ -8,10 +8,12 @@ import { Link } from 'react-router-dom' import { Paths } from '@/config/Paths' import type { OrderListItemType } from './types/Types' import Button from '@/components/Button' +import UserSearch from '../user/UserSearch' +import DesignerSearch from '../designer/DesignerSearch' const OrdersList: FC = () => { - - const { data } = useGetOrders() + const [designId, setDesignId] = useState() + const { data } = useGetOrders({ designId }) return (
@@ -27,7 +29,7 @@ const OrdersList: FC = () => {
-
+
{ }, ]} onChange={() => { }} /> + + setDesignId(id || undefined)} + />
diff --git a/src/pages/order/hooks/useOrderData.ts b/src/pages/order/hooks/useOrderData.ts index f8fa409..51fe049 100644 --- a/src/pages/order/hooks/useOrderData.ts +++ b/src/pages/order/hooks/useOrderData.ts @@ -6,10 +6,10 @@ import type { OrderPrintFormType, } from "../types/Types"; -export const useGetOrders = () => { +export const useGetOrders = (params?: { designId?: string }) => { return useQuery({ - queryKey: ["orders"], - queryFn: api.getOrders, + queryKey: ["orders", params?.designId], + queryFn: () => api.getOrders(params), }); }; diff --git a/src/pages/order/service/OrderService.ts b/src/pages/order/service/OrderService.ts index 97ed750..b02202a 100644 --- a/src/pages/order/service/OrderService.ts +++ b/src/pages/order/service/OrderService.ts @@ -15,8 +15,10 @@ import type { ProductResponeType, } from "@/pages/product/types/Types"; -export const getOrders = async () => { - const { data } = await axios.get(`/admin/orders`); +export const getOrders = async (params?: { designId?: string }) => { + const { data } = await axios.get(`/admin/orders`, { + params: params?.designId ? { designId: params.designId } : undefined, + }); return data; }; diff --git a/src/pages/user/UserSearch.tsx b/src/pages/user/UserSearch.tsx new file mode 100644 index 0000000..994f73b --- /dev/null +++ b/src/pages/user/UserSearch.tsx @@ -0,0 +1,100 @@ +import { type FC, useEffect, useRef, useState } from "react"; +import { clx } from "@/helpers/utils"; +import { useUserSearch } from "./hooks/useUserSearch"; +import type { UserType } from "./types/Types"; + +const DEBOUNCE_MS = 400; + +type UserSearchProps = { + label?: string; + placeholder?: string; + className?: string; + onChange?: (userId: string) => void; + value?: string; +}; + +const UserSearch: FC = ({ + label, + placeholder = "جستجوی کاربر (نام، موبایل، ...)", + className, + onChange, + value = "", +}) => { + const [search, setSearch] = useState(value); + const [debouncedSearch, setDebouncedSearch] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const wrapperRef = useRef(null); + + useEffect(() => { + const t = setTimeout(() => setDebouncedSearch(search), DEBOUNCE_MS); + return () => clearTimeout(t); + }, [search]); + + const { users, isFetching } = useUserSearch(debouncedSearch); + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { + setIsOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const handleSelect = (user: UserType) => { + setSearch(user.firstName && user.lastName ? `${user.firstName} ${user.lastName}` : user.phone); + onChange?.(user.id); + setIsOpen(false); + }; + + const displayLabel = (user: UserType) => + user.firstName && user.lastName ? `${user.firstName} ${user.lastName}` : user.phone; + + return ( +
+ {label && ( + + )} + { + setSearch(e.target.value); + setIsOpen(true); + }} + onFocus={() => setIsOpen(true)} + placeholder={placeholder} + className="w-full bg-white h-10 text-black block px-4 text-xs rounded-xl border border-border" + /> + {isOpen && debouncedSearch.length > 0 && ( +
+ {isFetching ? ( +
در حال جستجو...
+ ) : users.length === 0 ? ( +
نتیجه‌ای یافت نشد
+ ) : ( +
    + {users.map((user) => ( +
  • + +
  • + ))} +
+ )} +
+ )} +
+ ); +}; + +export default UserSearch; diff --git a/src/pages/user/hooks/useUserSearch.ts b/src/pages/user/hooks/useUserSearch.ts new file mode 100644 index 0000000..7d2684c --- /dev/null +++ b/src/pages/user/hooks/useUserSearch.ts @@ -0,0 +1,15 @@ +import { useQuery } from "@tanstack/react-query"; +import { searchUsers } from "../service/UserService"; +import type { UserType } from "../types/Types"; + +export const useUserSearch = (debouncedSearch: string) => { + const { data, isFetching } = useQuery({ + queryKey: ["users", "search", debouncedSearch], + queryFn: () => searchUsers(debouncedSearch), + enabled: debouncedSearch.length > 0, + }); + + const users = (data?.data ?? []) as UserType[]; + + return { users, isFetching }; +}; diff --git a/src/pages/user/service/UserService.ts b/src/pages/user/service/UserService.ts index 45a5b64..815f979 100644 --- a/src/pages/user/service/UserService.ts +++ b/src/pages/user/service/UserService.ts @@ -7,3 +7,11 @@ export const getUsers = async (page: number) => { ); return data; }; + +export const searchUsers = async (search: string) => { + const { data } = await axios.get( + `/admin/users/search`, + { params: { search: search || undefined } } + ); + return data; +};