From 59e2e62c562b2a2d65df08d98233f321d6abe35b Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 15 Nov 2025 12:26:31 +0330 Subject: [PATCH] list foods --- src/pages/food/List.tsx | 122 ++++++------------ .../food/components/FoodFiltersFields.tsx | 13 ++ .../food/components/FoodTableColumns.tsx | 81 ++++++++++++ src/pages/food/hooks/useFoodData.ts | 8 ++ src/pages/food/hooks/useFoodFilters.ts | 44 +++++++ src/pages/food/service/FoodService.ts | 23 +++- src/pages/food/types/Types.ts | 32 ++++- src/pages/food/utils/formatters.ts | 12 ++ 8 files changed, 250 insertions(+), 85 deletions(-) create mode 100644 src/pages/food/components/FoodFiltersFields.tsx create mode 100644 src/pages/food/components/FoodTableColumns.tsx create mode 100644 src/pages/food/hooks/useFoodFilters.ts create mode 100644 src/pages/food/utils/formatters.ts diff --git a/src/pages/food/List.tsx b/src/pages/food/List.tsx index 0b9f9b6..846cb6f 100644 --- a/src/pages/food/List.tsx +++ b/src/pages/food/List.tsx @@ -1,10 +1,31 @@ - import { type FC } from 'react' import Table from '@/components/Table' -import { Add, Eye } from 'iconsax-react' +import Filters from '@/components/Filters' +import { Add } from 'iconsax-react' import Button from '@/components/Button' +import { useGetFoods } from './hooks/useFoodData' +import { useFoodFilters } from './hooks/useFoodFilters' +import { getFoodTableColumns } from './components/FoodTableColumns' +import { useFoodFiltersFields } from './components/FoodFiltersFields' const FoodsList: FC = () => { + const { + filters, + currentPage, + apiParams, + handleFiltersChange, + handlePageChange, + limit, + } = useFoodFilters() + + const { data: foodsData, isLoading } = useGetFoods(apiParams) + + const foods = foodsData?.data || [] + const columns = getFoodTableColumns() + const filterFields = useFoodFiltersFields() + + const totalPages = Math.ceil(foods.length / limit) || 1 + return (
@@ -18,89 +39,24 @@ const FoodsList: FC = () => {
- { - return ( -
- {item.name} -
- ) - } - }, - { - key: 'name', - title: 'نام غذا', - }, - { - key: 'category', - title: 'دسته بندی', - }, - { - key: 'price', - title: 'قیمت ', - }, - { - key: 'preparationTime', - title: 'زمان آماده سازی', - }, - { - key: 'status', - title: 'وضعیت', - render: (item) => { - return ( -
- {item.status} -
- ) - } - }, - { - key: 'view', - title: '', - render: () => { - return ( -
- -
- ) - } - }, - ]} - data={[ - { - id: 1, - image: 'https://picsum.photos/seed/food1/200', - name: 'کباب کوبیده', - category: 'غذای ایرانی', - price: '۱۵۰,۰۰۰ تومان', - preparationTime: '۳۰ دقیقه', - status: 'موجود', - }, - { - id: 2, - image: 'https://picsum.photos/seed/food2/200', - name: 'پیتزا پپرونی', - category: 'فست فود', - price: '۲۰۰,۰۰۰ تومان', - preparationTime: '۴۵ دقیقه', - status: 'موجود', - }, - { - id: 3, - image: 'https://picsum.photos/seed/food3/200', - name: 'سالاد سزار', - category: 'پیش غذا', - price: '۸۰,۰۰۰ تومان', - preparationTime: '۱۵ دقیقه', - status: 'ناموجود', - } - ]} + + +
) } diff --git a/src/pages/food/components/FoodFiltersFields.tsx b/src/pages/food/components/FoodFiltersFields.tsx new file mode 100644 index 0000000..9b1cd49 --- /dev/null +++ b/src/pages/food/components/FoodFiltersFields.tsx @@ -0,0 +1,13 @@ +import { useMemo } from 'react' +import type { FieldType } from '@/components/Filters' + +export const useFoodFiltersFields = (): FieldType[] => { + return useMemo(() => [ + { + type: 'input', + name: 'search', + placeholder: 'جستجو', + }, + ], []) +} + diff --git a/src/pages/food/components/FoodTableColumns.tsx b/src/pages/food/components/FoodTableColumns.tsx new file mode 100644 index 0000000..932c45c --- /dev/null +++ b/src/pages/food/components/FoodTableColumns.tsx @@ -0,0 +1,81 @@ +import { Eye } from 'iconsax-react' +import type { ColumnType } from '@/components/types/TableTypes' +import type { Food } from '../types/Types' +import { formatPrice, formatTime } from '../utils/formatters' + +export const getFoodTableColumns = (): ColumnType[] => { + return [ + { + key: 'image', + title: 'تصویر', + render: (item: Food) => { + const imageUrl = item.images && item.images.length > 0 ? item.images[0] : '' + return ( +
+ {imageUrl ? ( + {item.title} + ) : ( +
+ بدون تصویر +
+ )} +
+ ) + } + }, + { + key: 'title', + title: 'نام غذا', + }, + { + key: 'categories', + title: 'دسته بندی', + render: (item: Food) => { + return item.categories && item.categories.length > 0 + ? item.categories.map(cat => cat.title).join(', ') + : '-' + } + }, + { + key: 'price', + title: 'قیمت', + render: (item: Food) => { + return formatPrice(item.price) + } + }, + { + key: 'prepareTime', + title: 'زمان آماده سازی', + render: (item: Food) => { + return formatTime(item.prepareTime) + } + }, + { + key: 'isActive', + title: 'وضعیت', + render: (item: Food) => { + return ( +
+ {item.isActive ? 'فعال' : 'غیرفعال'} +
+ ) + } + }, + { + key: 'view', + title: '', + render: () => { + return ( +
+ +
+ ) + } + }, + ] +} + diff --git a/src/pages/food/hooks/useFoodData.ts b/src/pages/food/hooks/useFoodData.ts index a6a561b..9a179f7 100644 --- a/src/pages/food/hooks/useFoodData.ts +++ b/src/pages/food/hooks/useFoodData.ts @@ -1,5 +1,6 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import * as api from "../service/FoodService"; +import type { GetFoodsParams } from "../service/FoodService"; export const useCreateFood = () => { return useMutation({ @@ -7,6 +8,13 @@ export const useCreateFood = () => { }); }; +export const useGetFoods = (params?: GetFoodsParams) => { + return useQuery({ + queryKey: ["foods", params], + queryFn: () => api.getFoods(params), + }); +}; + export const useGetCategories = () => { return useQuery({ queryKey: ["categories"], diff --git a/src/pages/food/hooks/useFoodFilters.ts b/src/pages/food/hooks/useFoodFilters.ts new file mode 100644 index 0000000..130bb1d --- /dev/null +++ b/src/pages/food/hooks/useFoodFilters.ts @@ -0,0 +1,44 @@ +import { useState, useMemo } from 'react' +import type { FilterValues } from '@/components/Filters' +import type { GetFoodsParams } from '../service/FoodService' + +const DEFAULT_PAGE = 1 +const DEFAULT_LIMIT = 10 + +export const useFoodFilters = () => { + const [filters, setFilters] = useState({}) + const [currentPage, setCurrentPage] = useState(DEFAULT_PAGE) + const [limit] = useState(DEFAULT_LIMIT) + + const apiParams = useMemo(() => { + const params: GetFoodsParams = { + page: currentPage, + limit: limit, + } + + if (filters.search) { + params.search = filters.search + } + + return params + }, [filters, currentPage, limit]) + + const handleFiltersChange = (newFilters: FilterValues) => { + setFilters(newFilters) + setCurrentPage(DEFAULT_PAGE) + } + + const handlePageChange = (page: number) => { + setCurrentPage(page) + } + + return { + filters, + currentPage, + apiParams, + handleFiltersChange, + handlePageChange, + limit, + } +} + diff --git a/src/pages/food/service/FoodService.ts b/src/pages/food/service/FoodService.ts index 9d06056..ee1555d 100644 --- a/src/pages/food/service/FoodService.ts +++ b/src/pages/food/service/FoodService.ts @@ -1,11 +1,32 @@ import axios from "@/config/axios"; -import type { CreateFoodType, GetCategoriesResponseType } from "../types/Types"; +import type { + CreateFoodType, + GetCategoriesResponseType, + GetFoodsResponseType, +} from "../types/Types"; export const createFood = async (params: CreateFoodType) => { const { data } = await axios.post(`/foods`, params); return data; }; +export type GetFoodsParams = { + page?: number; + limit?: number; + search?: string; + orderBy?: string; + order?: "asc" | "desc"; + categoryId?: string; + isActive?: boolean; +}; + +export const getFoods = async ( + params?: GetFoodsParams +): Promise => { + const { data } = await axios.get(`/foods`, { params }); + return data; +}; + export const getCategories = async (): Promise => { const { data } = await axios.get(`/categories`); return data; diff --git a/src/pages/food/types/Types.ts b/src/pages/food/types/Types.ts index 43c1122..41072e8 100644 --- a/src/pages/food/types/Types.ts +++ b/src/pages/food/types/Types.ts @@ -29,13 +29,43 @@ export type CreateFoodType = { export type Category = { id: string; + createdAt: string; + updatedAt: string; + deletedAt: string | null; title: string; isActive: boolean; restId: string; avatarUrl: string; +}; + +export type Food = { + id: string; createdAt: string; updatedAt: string; - foods: unknown[]; + deletedAt: string | null; + title: string; + desc: string | null; + content: string[]; + price: number; + points: number; + order: number | null; + prepareTime: number; + sat: boolean; + sun: boolean; + mon: boolean; + breakfast: boolean; + noon: boolean; + dinner: boolean; + stock: number; + stockDefault: number; + isActive: boolean; + images: string[]; + inPlaceServe: boolean; + pickupServe: boolean; + rate: number; + discount: number; + categories: Category[]; }; export type GetCategoriesResponseType = IResponse; +export type GetFoodsResponseType = IResponse; diff --git a/src/pages/food/utils/formatters.ts b/src/pages/food/utils/formatters.ts new file mode 100644 index 0000000..9273436 --- /dev/null +++ b/src/pages/food/utils/formatters.ts @@ -0,0 +1,12 @@ +export const formatPrice = (price: number): string => { + return new Intl.NumberFormat('fa-IR', { + style: 'currency', + currency: 'IRR', + minimumFractionDigits: 0, + }).format(price) +} + +export const formatTime = (minutes: number): string => { + return `${minutes} دقیقه` +} +