This commit is contained in:
+78
-9
@@ -1,16 +1,26 @@
|
||||
import { type FC } from 'react'
|
||||
import { type FC, useCallback, useState } from 'react'
|
||||
import Table from '@/components/Table'
|
||||
import Filters from '@/components/Filters'
|
||||
import { Add } from 'iconsax-react'
|
||||
import Button from '@/components/Button'
|
||||
import { useGetFoods, useDeleteFood } from './hooks/useFoodData'
|
||||
import { useGetFoods, useDeleteFood, useSetStockForFood } from './hooks/useFoodData'
|
||||
import { useFoodFilters } from './hooks/useFoodFilters'
|
||||
import { getFoodTableColumns } from './components/FoodTableColumns'
|
||||
import { useFoodFiltersFields } from './components/FoodFiltersFields'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { Pages } from '@/config/Pages'
|
||||
import type { ActionType } from '@/components/types/TableTypes'
|
||||
import type { Food } from './types/Types'
|
||||
import StockUpdateModal from './components/StockUpdateModal'
|
||||
import ModalConfrim from '@/components/ModalConfrim'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
|
||||
const FoodsList: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const [stockModalFood, setStockModalFood] = useState<Food | null>(null)
|
||||
const [deleteConfirmFood, setDeleteConfirmFood] = useState<Food | null>(null)
|
||||
|
||||
const {
|
||||
filters,
|
||||
currentPage,
|
||||
@@ -21,18 +31,60 @@ const FoodsList: FC = () => {
|
||||
|
||||
const { data: foodsData, isLoading } = useGetFoods(apiParams, filters)
|
||||
const { mutate: deleteFood, isPending: isDeleting } = useDeleteFood()
|
||||
|
||||
|
||||
const { mutate: setStock, isPending: isUpdatingStock } = useSetStockForFood()
|
||||
|
||||
const foods = foodsData?.data || []
|
||||
const columns = getFoodTableColumns({
|
||||
onDelete: (id: string) => deleteFood(id),
|
||||
isDeleting
|
||||
})
|
||||
const columns = getFoodTableColumns()
|
||||
const filterFields = useFoodFiltersFields()
|
||||
|
||||
const totalPages = foodsData?.meta?.totalPages || 1
|
||||
|
||||
const handleRowActions = useCallback((item: Food): ActionType[] => {
|
||||
return [
|
||||
{
|
||||
label: 'ویرایش',
|
||||
onClick: () => navigate(Pages.foods.update + item.id),
|
||||
},
|
||||
{
|
||||
label: 'بروزرسانی موجودی',
|
||||
onClick: () => setStockModalFood(item),
|
||||
},
|
||||
{
|
||||
label: 'حذف',
|
||||
onClick: () => setDeleteConfirmFood(item),
|
||||
},
|
||||
]
|
||||
}, [navigate])
|
||||
|
||||
const handleStockSubmit = (foodId: string, totalStock: number, availableStock: number) => {
|
||||
setStock(
|
||||
{ foodId, params: { totalStock, availableStock } },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success('موجودی با موفقیت بروزرسانی شد')
|
||||
setStockModalFood(null)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const handleDeleteConfirm = () => {
|
||||
if (!deleteConfirmFood) return
|
||||
|
||||
deleteFood(deleteConfirmFood.id, {
|
||||
onSuccess: () => {
|
||||
toast.success('غذا با موفقیت حذف شد')
|
||||
setDeleteConfirmFood(null)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-5'>
|
||||
<div className='flex justify-between items-center'>
|
||||
@@ -63,12 +115,29 @@ const FoodsList: FC = () => {
|
||||
columns={columns}
|
||||
data={foods}
|
||||
isloading={isLoading}
|
||||
rowActions={handleRowActions}
|
||||
pagination={{
|
||||
currentPage,
|
||||
totalPages,
|
||||
onPageChange: handlePageChange,
|
||||
}}
|
||||
/>
|
||||
|
||||
<StockUpdateModal
|
||||
food={stockModalFood}
|
||||
open={!!stockModalFood}
|
||||
onClose={() => setStockModalFood(null)}
|
||||
onSubmit={handleStockSubmit}
|
||||
isLoading={isUpdatingStock}
|
||||
/>
|
||||
|
||||
<ModalConfrim
|
||||
isOpen={!!deleteConfirmFood}
|
||||
close={() => setDeleteConfirmFood(null)}
|
||||
onConfrim={handleDeleteConfirm}
|
||||
isloading={isDeleting}
|
||||
label={deleteConfirmFood ? `آیا از حذف «${deleteConfirmFood.title}» اطمینان دارید؟` : undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import { Eye } from 'iconsax-react'
|
||||
import type { ColumnType } from '@/components/types/TableTypes'
|
||||
import type { Food } from '../types/Types'
|
||||
import { formatPrice, formatTime } from '../utils/formatters'
|
||||
import Status from '@/components/Status'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Pages } from '@/config/Pages'
|
||||
import TrashWithConfrim from '@/components/TrashWithConfrim'
|
||||
|
||||
interface GetFoodTableColumnsParams {
|
||||
onDelete: (id: string) => void
|
||||
isDeleting: boolean
|
||||
}
|
||||
|
||||
export const getFoodTableColumns = ({ onDelete, isDeleting }: GetFoodTableColumnsParams): ColumnType<Food>[] => {
|
||||
export const getFoodTableColumns = (): ColumnType<Food>[] => {
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -87,23 +78,6 @@ export const getFoodTableColumns = ({ onDelete, isDeleting }: GetFoodTableColumn
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'view',
|
||||
title: '',
|
||||
render: (item: Food) => {
|
||||
return (
|
||||
<div className='flex gap-2 items-center'>
|
||||
<Link to={Pages.foods.update + item.id} className='flex gap-2 items-center'>
|
||||
<Eye size={20} color='#8C90A3' />
|
||||
</Link>
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(item.id)}
|
||||
isloading={isDeleting}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import DefaulModal from '@/components/DefaulModal'
|
||||
import Input from '@/components/Input'
|
||||
import Button from '@/components/Button'
|
||||
import type { Food } from '../types/Types'
|
||||
|
||||
type Props = {
|
||||
food: Food | null
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
onSubmit: (foodId: string, totalStock: number, availableStock: number) => void
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
const StockUpdateModal: FC<Props> = ({
|
||||
food,
|
||||
open,
|
||||
onClose,
|
||||
onSubmit,
|
||||
isLoading = false,
|
||||
}) => {
|
||||
const [totalStock, setTotalStock] = useState(0)
|
||||
const [availableStock, setAvailableStock] = useState(0)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (food) {
|
||||
setTotalStock(food.inventory?.totalStock ?? 0)
|
||||
setAvailableStock(food.inventory?.availableStock ?? 0)
|
||||
setError('')
|
||||
}
|
||||
}, [food])
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (availableStock > totalStock) {
|
||||
setError('موجودی فعلی نمیتواند بیشتر از موجودی روزانه باشد')
|
||||
return
|
||||
}
|
||||
|
||||
if (!food) return
|
||||
|
||||
onSubmit(food.id, totalStock, availableStock)
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaulModal
|
||||
open={open}
|
||||
close={onClose}
|
||||
isHeader
|
||||
title_header='بروزرسانی موجودی'
|
||||
width={480}
|
||||
>
|
||||
{food && (
|
||||
<div className='mt-6'>
|
||||
<p className='text-sm text-gray-600 mb-5'>
|
||||
غذا: <span className='font-medium text-gray-800'>{food.title}</span>
|
||||
</p>
|
||||
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Input
|
||||
name='totalStock'
|
||||
label='موجودی روزانه'
|
||||
placeholder='عدد'
|
||||
type='number'
|
||||
seprator
|
||||
value={totalStock}
|
||||
onChange={(e) => {
|
||||
setTotalStock(Number(e.target.value))
|
||||
setError('')
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
name='availableStock'
|
||||
label='موجودی فعلی'
|
||||
placeholder='عدد'
|
||||
type='number'
|
||||
seprator
|
||||
value={availableStock}
|
||||
onChange={(e) => {
|
||||
setAvailableStock(Number(e.target.value))
|
||||
setError('')
|
||||
}}
|
||||
error_text={error}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-3 mt-6'>
|
||||
<Button
|
||||
label='انصراف'
|
||||
type='button'
|
||||
onClick={onClose}
|
||||
className='bg-gray-100 text-gray-700'
|
||||
/>
|
||||
<Button
|
||||
label='ذخیره'
|
||||
type='button'
|
||||
onClick={handleSubmit}
|
||||
isloading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DefaulModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default StockUpdateModal
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
CreateCategoryType,
|
||||
CreateFoodType,
|
||||
GetFoodsParams,
|
||||
SetStockType,
|
||||
} from "../types/Types";
|
||||
import type { FilterValues } from "@/components/Filters";
|
||||
|
||||
@@ -91,3 +92,14 @@ export const useGetIcons = () => {
|
||||
queryFn: api.getIcons,
|
||||
});
|
||||
};
|
||||
|
||||
export const useSetStockForFood = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ foodId, params }: { foodId: string; params: SetStockType }) =>
|
||||
api.setStockForFood(foodId, params),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["foods"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
GetFoodsParams,
|
||||
GetFoodsResponseType,
|
||||
GetIconsResponseType,
|
||||
SetStockType,
|
||||
} from "../types/Types";
|
||||
import type { FilterValues } from "@/components/Filters";
|
||||
|
||||
@@ -76,3 +77,11 @@ export const getIcons = async (): Promise<GetIconsResponseType> => {
|
||||
const { data } = await axios.get<GetIconsResponseType>(`/admin/groups/icons`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const setStockForFood = async (foodId: string, params: SetStockType) => {
|
||||
const { data } = await axios.patch(
|
||||
`/admin/inventory/food/${foodId}/stock`,
|
||||
params,
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -150,3 +150,8 @@ export type IconGroup = {
|
||||
};
|
||||
|
||||
export type GetIconsResponseType = IResponse<IconGroup[]>;
|
||||
|
||||
export type SetStockType = {
|
||||
totalStock: number;
|
||||
availableStock: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user