product list
This commit is contained in:
Generated
+1
@@ -11,6 +11,7 @@
|
|||||||
"@headlessui/react": "^2.2.7",
|
"@headlessui/react": "^2.2.7",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tailwindcss/vite": "^4.1.12",
|
"@tailwindcss/vite": "^4.1.12",
|
||||||
"@tanstack/react-query": "^5.85.5",
|
"@tanstack/react-query": "^5.85.5",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"@headlessui/react": "^2.2.7",
|
"@headlessui/react": "^2.2.7",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@tailwindcss/vite": "^4.1.12",
|
"@tailwindcss/vite": "^4.1.12",
|
||||||
"@tanstack/react-query": "^5.85.5",
|
"@tanstack/react-query": "^5.85.5",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { type FC } from 'react';
|
||||||
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationEllipsis,
|
||||||
|
} from './ui/pagination';
|
||||||
|
import { type PagerType } from '../pages/products/types/Types';
|
||||||
|
|
||||||
|
interface PaginationUiProps {
|
||||||
|
pager: PagerType | null | undefined;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PaginationUi: FC<PaginationUiProps> = ({ pager, onPageChange }) => {
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
onPageChange(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPageNumbers = () => {
|
||||||
|
if (!pager) return [];
|
||||||
|
|
||||||
|
const pageNumbers: (number | 'ellipsis')[] = [];
|
||||||
|
const maxVisiblePages = 5;
|
||||||
|
|
||||||
|
if (pager.totalPages <= maxVisiblePages) {
|
||||||
|
for (let i = 1; i <= pager.totalPages; i++) {
|
||||||
|
pageNumbers.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pager.page > 3) {
|
||||||
|
pageNumbers.push(1);
|
||||||
|
if (pager.page > 4) {
|
||||||
|
pageNumbers.push('ellipsis');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Math.max(2, pager.page - 1);
|
||||||
|
const end = Math.min(pager.totalPages - 1, pager.page + 1);
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
pageNumbers.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pager.page < pager.totalPages - 2) {
|
||||||
|
if (pager.page < pager.totalPages - 3) {
|
||||||
|
pageNumbers.push('ellipsis');
|
||||||
|
}
|
||||||
|
pageNumbers.push(pager.totalPages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageNumbers;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!pager || pager.totalPages <= 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex ی justify-center mt-6">
|
||||||
|
<Pagination>
|
||||||
|
<PaginationContent>
|
||||||
|
{getPageNumbers().map((page, index) => (
|
||||||
|
<PaginationItem key={index}>
|
||||||
|
{page === 'ellipsis' ? (
|
||||||
|
<PaginationEllipsis />
|
||||||
|
) : (
|
||||||
|
<PaginationLink
|
||||||
|
href="#"
|
||||||
|
isActive={page === pager.page}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handlePageChange(page);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</PaginationLink>
|
||||||
|
)}
|
||||||
|
</PaginationItem>
|
||||||
|
))}
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PaginationUi;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost:
|
||||||
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
|
icon: "size-9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Button({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"button"> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}) {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="button"
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
MoreHorizontalIcon,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
data-slot="pagination"
|
||||||
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaginationContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"ul">) {
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
data-slot="pagination-content"
|
||||||
|
className={cn("flex flex-row items-center gap-1", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
||||||
|
return <li data-slot="pagination-item" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean
|
||||||
|
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
||||||
|
React.ComponentProps<"a">
|
||||||
|
|
||||||
|
function PaginationLink({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = "icon",
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? "page" : undefined}
|
||||||
|
data-slot="pagination-link"
|
||||||
|
data-active={isActive}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? "outline" : "ghost",
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaginationPrevious({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) {
|
||||||
|
return (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon />
|
||||||
|
<span className="hidden sm:block">Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaginationNext({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) {
|
||||||
|
return (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="hidden sm:block">Next</span>
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaginationEllipsis({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
data-slot="pagination-ellipsis"
|
||||||
|
className={cn("flex size-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontalIcon className="size-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationPrevious,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationEllipsis,
|
||||||
|
}
|
||||||
+133
-1
@@ -1,9 +1,141 @@
|
|||||||
import { type FC } from 'react'
|
import { type FC, useState } from 'react'
|
||||||
|
import { useGetProducts } from './hooks/useProductData';
|
||||||
|
import { type ProductListType } from './types/Types';
|
||||||
|
import { ProductStatus } from './enum/ProductEnum';
|
||||||
|
import PageLoading from '../../components/PageLoading';
|
||||||
|
import Error from '../../components/Error';
|
||||||
|
import PaginationUi from '../../components/PaginationUi';
|
||||||
|
import StatusWithText from '../../components/StatusWithText';
|
||||||
|
import Td from '../../components/Td';
|
||||||
|
import TrashWithConfrim from '../../components/TrashWithConfrim';
|
||||||
|
import { Edit, Eye } from 'iconsax-react';
|
||||||
|
|
||||||
const List: FC = () => {
|
const List: FC = () => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const { data: productsData, isLoading, error } = useGetProducts(currentPage);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[400px]">
|
||||||
|
<PageLoading />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center min-h-[400px]">
|
||||||
|
<Error errorText="خطا در بارگذاری محصولات" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const products = productsData?.results?.products || [];
|
||||||
|
const pager = productsData?.results?.pager;
|
||||||
|
|
||||||
|
const getStatusVariant = (status: ProductStatus) => {
|
||||||
|
switch (status) {
|
||||||
|
case ProductStatus.Approved: return 'success';
|
||||||
|
case ProductStatus.Rejected: return 'error';
|
||||||
|
case ProductStatus.Pending: return 'warning';
|
||||||
|
case ProductStatus.Draft: return 'warning';
|
||||||
|
default: return 'warning';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusText = (status: ProductStatus) => {
|
||||||
|
switch (status) {
|
||||||
|
case ProductStatus.Approved: return 'تایید شده';
|
||||||
|
case ProductStatus.Rejected: return 'رد شده';
|
||||||
|
case ProductStatus.Pending: return 'در انتظار';
|
||||||
|
case ProductStatus.Draft: return 'پیشنویس';
|
||||||
|
default: return status;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
|
||||||
|
<table className='w-full text-sm'>
|
||||||
|
<thead className='thead'>
|
||||||
|
<tr>
|
||||||
|
<Td text={'تصویر'} />
|
||||||
|
<Td text={'عنوان'} />
|
||||||
|
<Td text={'برند'} />
|
||||||
|
<Td text={'دستهبندی'} />
|
||||||
|
<Td text={'وضعیت'} />
|
||||||
|
<Td text={'قیمت'} />
|
||||||
|
<Td text={'موجودی'} />
|
||||||
|
<Td text={'عملیات'} />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{products.length === 0 ? (
|
||||||
|
<tr className='tr'>
|
||||||
|
<td colSpan={8} className="text-center py-8 text-gray-500">
|
||||||
|
هیچ محصولی یافت نشد
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
products.map((product: ProductListType) => (
|
||||||
|
<tr key={product._id} className='tr'>
|
||||||
|
<Td text="">
|
||||||
|
<div className="w-12 h-12 rounded-lg overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={product.imagesUrl?.cover || '/placeholder-image.png'}
|
||||||
|
alt={product.title_fa}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text="">
|
||||||
|
<div>
|
||||||
|
<div className="font-medium text-gray-900">
|
||||||
|
{product.title_fa}
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-500 text-xs">
|
||||||
|
{product.title_en}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td text={product.brand_title_fa} />
|
||||||
|
<Td text={product.category_title_fa} />
|
||||||
|
<Td text="">
|
||||||
|
<StatusWithText
|
||||||
|
variant={getStatusVariant(product.status)}
|
||||||
|
text={getStatusText(product.status)}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
<Td text={`${product.default_variant?.price?.selling_price?.toLocaleString('fa-IR') || 0} تومان`} />
|
||||||
|
<Td text={String(product.default_variant?.stock || 0)} />
|
||||||
|
<Td text="">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Eye color='#8C90A3' size={19} />
|
||||||
|
|
||||||
|
<Edit color='#8C90A3' size={16} />
|
||||||
|
|
||||||
|
<TrashWithConfrim
|
||||||
|
onDelete={() => {
|
||||||
|
// TODO: Implement delete product
|
||||||
|
console.log('Delete product:', product._id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PaginationUi
|
||||||
|
pager={pager}
|
||||||
|
onPageChange={(page) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
export enum ProductMarketStatus {
|
||||||
|
Out_of_stock = "out_of_stock",
|
||||||
|
Marketable = "Marketable",
|
||||||
|
stop_production = "stopProduction",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProductStatus {
|
||||||
|
Draft = "Draft",
|
||||||
|
Pending = "Pending",
|
||||||
|
Approved = "Approved",
|
||||||
|
Rejected = "Rejected",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProductSource {
|
||||||
|
LOCAL = "local",
|
||||||
|
IMPORT = "import",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProductDiscountType {
|
||||||
|
Fixed = "fixed",
|
||||||
|
Percent = "percent",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CreateProductStep {
|
||||||
|
Detail = 1,
|
||||||
|
Attribute,
|
||||||
|
Image,
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import * as api from "../service/ProductService";
|
import * as api from "../service/ProductService";
|
||||||
import {
|
import {
|
||||||
type CreateProductDetailRequestType,
|
type CreateProductDetailRequestType,
|
||||||
@@ -6,23 +6,27 @@ import {
|
|||||||
type SaveProductRequestType
|
type SaveProductRequestType
|
||||||
} from "../types/Types";
|
} from "../types/Types";
|
||||||
|
|
||||||
// Hook برای مرحله اول: ایجاد جزئیات محصول
|
|
||||||
export const useCreateProductDetail = () => {
|
export const useCreateProductDetail = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: CreateProductDetailRequestType) => api.createProductDetail(variables),
|
mutationFn: (variables: CreateProductDetailRequestType) => api.createProductDetail(variables),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook برای مرحله دوم: افزودن ویژگیها و توضیحات
|
|
||||||
export const useCreateProductAttribute = () => {
|
export const useCreateProductAttribute = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: CreateProductAttributeRequestType) => api.createProductAttribute(variables),
|
mutationFn: (variables: CreateProductAttributeRequestType) => api.createProductAttribute(variables),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook برای مرحله سوم: ذخیره نهایی محصول
|
|
||||||
export const useSaveProduct = () => {
|
export const useSaveProduct = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (variables: SaveProductRequestType) => api.saveProduct(variables),
|
mutationFn: (variables: SaveProductRequestType) => api.saveProduct(variables),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetProducts = (page: number = 1, limit: number = 10) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['products', page, limit],
|
||||||
|
queryFn: () => api.getProducts(page, limit),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,23 +5,28 @@ import {
|
|||||||
type CreateProductAttributeRequestType,
|
type CreateProductAttributeRequestType,
|
||||||
type CreateProductAttributeResponseType,
|
type CreateProductAttributeResponseType,
|
||||||
type SaveProductRequestType,
|
type SaveProductRequestType,
|
||||||
type SaveProductResponseType
|
type SaveProductResponseType,
|
||||||
|
type GetProductsResponseType
|
||||||
} from "../types/Types";
|
} from "../types/Types";
|
||||||
|
|
||||||
// مرحله اول: ایجاد جزئیات محصول
|
|
||||||
export const createProductDetail = async (params: CreateProductDetailRequestType): Promise<CreateProductDetailResponseType> => {
|
export const createProductDetail = async (params: CreateProductDetailRequestType): Promise<CreateProductDetailResponseType> => {
|
||||||
const { data } = await axios.post(`/admin/products/creation/detail`, params);
|
const { data } = await axios.post(`/admin/products/creation/detail`, params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// مرحله دوم: افزودن ویژگیها و توضیحات
|
|
||||||
export const createProductAttribute = async (params: CreateProductAttributeRequestType): Promise<CreateProductAttributeResponseType> => {
|
export const createProductAttribute = async (params: CreateProductAttributeRequestType): Promise<CreateProductAttributeResponseType> => {
|
||||||
const { data } = await axios.post(`/admin/products/creation/attribute`, params);
|
const { data } = await axios.post(`/admin/products/creation/attribute`, params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// مرحله سوم: ذخیره نهایی محصول
|
|
||||||
export const saveProduct = async (params: SaveProductRequestType): Promise<SaveProductResponseType> => {
|
export const saveProduct = async (params: SaveProductRequestType): Promise<SaveProductResponseType> => {
|
||||||
const { data } = await axios.post(`/admin/products/creation/save`, params);
|
const { data } = await axios.post(`/admin/products/creation/save`, params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getProducts = async (page: number = 1, limit: number = 10): Promise<GetProductsResponseType> => {
|
||||||
|
const { data } = await axios.get(`/admin/products/native`, {
|
||||||
|
params: { page, limit }
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// تایپهای مربوط به ایجاد محصول سه مرحلهای
|
// تایپهای مربوط به ایجاد محصول سه مرحلهای
|
||||||
|
import { ProductStatus } from "../enum/ProductEnum";
|
||||||
|
|
||||||
// مرحله اول: جزئیات محصول
|
// مرحله اول: جزئیات محصول
|
||||||
export type CreateProductDetailRequestType = {
|
export type CreateProductDetailRequestType = {
|
||||||
@@ -104,3 +105,95 @@ export type ProductType = {
|
|||||||
imagesList?: string[];
|
imagesList?: string[];
|
||||||
coverImage?: string;
|
coverImage?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// تایپهای مربوط به لیست محصولات
|
||||||
|
|
||||||
|
export type PagerType = {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
totalItems: number;
|
||||||
|
totalPages: number;
|
||||||
|
prevPage: boolean | null;
|
||||||
|
nextPage: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProductPriceType = {
|
||||||
|
order_limit: number;
|
||||||
|
retailPrice: number;
|
||||||
|
selling_price: number;
|
||||||
|
is_specialSale: boolean;
|
||||||
|
discount_percent: number;
|
||||||
|
specialSale_order_limit: number | null;
|
||||||
|
specialSale_quantity: number | null;
|
||||||
|
specialSale_endDate: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProductVariantType = {
|
||||||
|
_id: string;
|
||||||
|
market_status: string;
|
||||||
|
price: ProductPriceType;
|
||||||
|
stock: number;
|
||||||
|
postingTime: number;
|
||||||
|
isFreeShip: boolean;
|
||||||
|
isWholeSale: boolean;
|
||||||
|
shop: {
|
||||||
|
_id: string;
|
||||||
|
};
|
||||||
|
warranty: number;
|
||||||
|
size?: number;
|
||||||
|
meterage?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProductImagesType = {
|
||||||
|
cover: string;
|
||||||
|
list: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProductListType = {
|
||||||
|
_id: number;
|
||||||
|
title_fa: string;
|
||||||
|
title_en: string;
|
||||||
|
seoTitle: string | null;
|
||||||
|
seoDescription: string | null;
|
||||||
|
description: string;
|
||||||
|
metaDescription: string;
|
||||||
|
shopName: string;
|
||||||
|
category_title_fa: string;
|
||||||
|
category_id: string;
|
||||||
|
brand_title_fa: string;
|
||||||
|
status: ProductStatus;
|
||||||
|
adminComments: string | null;
|
||||||
|
step: number;
|
||||||
|
isFake: boolean;
|
||||||
|
imagesUrl: ProductImagesType;
|
||||||
|
variantCount: number;
|
||||||
|
default_variant: ProductVariantType;
|
||||||
|
popular: boolean;
|
||||||
|
incredible: boolean;
|
||||||
|
voice: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BrandListType = {
|
||||||
|
_id: string;
|
||||||
|
status: string;
|
||||||
|
title_en: string;
|
||||||
|
title_fa: string;
|
||||||
|
images: string[];
|
||||||
|
logoUrl: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CategoryListType = {
|
||||||
|
_id: string;
|
||||||
|
title_fa: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetProductsResponseType = {
|
||||||
|
status: number;
|
||||||
|
success: boolean;
|
||||||
|
results: {
|
||||||
|
pager: PagerType;
|
||||||
|
products: ProductListType[];
|
||||||
|
brands: BrandListType[];
|
||||||
|
categories: CategoryListType[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user