feat list product and category
This commit is contained in:
parent
fffa2ead5c
commit
0fde122ac4
@ -1,33 +1,29 @@
|
||||
// MUI Imports
|
||||
import Pagination from '@mui/material/Pagination'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { Typography, Pagination } from '@mui/material'
|
||||
|
||||
// Third Party Imports
|
||||
import type { useReactTable } from '@tanstack/react-table'
|
||||
const TablePaginationComponent = ({
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalCount,
|
||||
onPageChange
|
||||
}: {
|
||||
pageIndex: number
|
||||
pageSize: number
|
||||
totalCount: number
|
||||
onPageChange: (event: React.ChangeEvent<unknown>, page: number) => void
|
||||
}) => {
|
||||
const start = pageIndex === 1 ? pageIndex : (pageIndex - 1) * pageSize + 1
|
||||
const end = Math.min((pageIndex) * pageSize, totalCount)
|
||||
|
||||
const TablePaginationComponent = ({ table }: { table: ReturnType<typeof useReactTable> }) => {
|
||||
return (
|
||||
<div className='flex justify-between items-center flex-wrap pli-6 border-bs bs-auto plb-[12.5px] gap-2'>
|
||||
<Typography color='text.disabled'>
|
||||
{`Showing ${
|
||||
table.getFilteredRowModel().rows.length === 0
|
||||
? 0
|
||||
: table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1
|
||||
}
|
||||
to ${Math.min(
|
||||
(table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize,
|
||||
table.getFilteredRowModel().rows.length
|
||||
)} of ${table.getFilteredRowModel().rows.length} entries`}
|
||||
</Typography>
|
||||
<Typography color='text.disabled'>{`Showing ${start} to ${end} of ${totalCount} entries`}</Typography>
|
||||
<Pagination
|
||||
shape='rounded'
|
||||
color='primary'
|
||||
variant='tonal'
|
||||
count={Math.ceil(table.getFilteredRowModel().rows.length / table.getState().pagination.pageSize)}
|
||||
page={table.getState().pagination.pageIndex + 1}
|
||||
onChange={(_, page) => {
|
||||
table.setPageIndex(page - 1)
|
||||
}}
|
||||
count={Math.ceil(totalCount / pageSize)}
|
||||
page={pageIndex}
|
||||
onChange={onPageChange}
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
|
||||
41
src/services/queries/categories.ts
Normal file
41
src/services/queries/categories.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Categories } from '../../types/services/category'
|
||||
import { api } from '../api'
|
||||
|
||||
interface CategoriesQueryParams {
|
||||
page?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
}
|
||||
|
||||
export const useCategoriesQuery = {
|
||||
getCategories: (params: CategoriesQueryParams = {}) => {
|
||||
const { page = 1, limit = 10, search = '', ...filters } = params
|
||||
|
||||
return useQuery<Categories>({
|
||||
queryKey: ['categories', { page, limit, search, ...filters }],
|
||||
queryFn: async () => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
queryParams.append('page', page.toString())
|
||||
queryParams.append('limit', limit.toString())
|
||||
|
||||
if (search) {
|
||||
queryParams.append('search', search)
|
||||
}
|
||||
|
||||
// Add other filters
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
queryParams.append(key, value.toString())
|
||||
}
|
||||
})
|
||||
|
||||
const res = await api.get(`/categories?${queryParams.toString()}`)
|
||||
return res.data.data
|
||||
},
|
||||
// Cache for 5 minutes
|
||||
staleTime: 5 * 60 * 1000
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,44 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Products } from '../../types/services/products'
|
||||
import { Products } from '../../types/services/product'
|
||||
import { api } from '../api'
|
||||
|
||||
export const useProductsQuery = {
|
||||
getProducts: () => {
|
||||
return useQuery<Products>({
|
||||
queryKey: ['products'],
|
||||
queryFn: async () => {
|
||||
const res = await api.get('/products')
|
||||
return res.data.data
|
||||
interface ProductsQueryParams {
|
||||
page?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
// Add other filter parameters as needed
|
||||
category_id?: string
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export const useProductsQuery = {
|
||||
getProducts: (params: ProductsQueryParams = {}) => {
|
||||
const { page = 1, limit = 10, search = '', ...filters } = params
|
||||
|
||||
return useQuery<Products>({
|
||||
queryKey: ['products', { page, limit, search, ...filters }],
|
||||
queryFn: async () => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
queryParams.append('page', page.toString())
|
||||
queryParams.append('limit', limit.toString())
|
||||
|
||||
if (search) {
|
||||
queryParams.append('search', search)
|
||||
}
|
||||
|
||||
// Add other filters
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
queryParams.append(key, value.toString())
|
||||
}
|
||||
})
|
||||
|
||||
const res = await api.get(`/products?${queryParams.toString()}`)
|
||||
return res.data.data
|
||||
},
|
||||
// Cache for 5 minutes
|
||||
staleTime: 5 * 60 * 1000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
18
src/types/services/category.ts
Normal file
18
src/types/services/category.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface Category {
|
||||
id: string;
|
||||
organization_id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
business_type: string;
|
||||
metadata: Record<string, any>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface Categories {
|
||||
categories: Category[];
|
||||
total_count: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
total_pages: number;
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
// MUI Imports
|
||||
import Card from '@mui/material/Card'
|
||||
@ -39,6 +39,10 @@ import TablePaginationComponent from '@components/TablePaginationComponent'
|
||||
|
||||
// Style Imports
|
||||
import tableStyles from '@core/styles/table.module.css'
|
||||
import { useCategoriesQuery } from '../../../../../services/queries/categories'
|
||||
import { Category } from '../../../../../types/services/category'
|
||||
import { Box, CircularProgress } from '@mui/material'
|
||||
import Loading from '../../../../../components/layout/shared/Loading'
|
||||
|
||||
declare module '@tanstack/table-core' {
|
||||
interface FilterFns {
|
||||
@ -49,16 +53,7 @@ declare module '@tanstack/table-core' {
|
||||
}
|
||||
}
|
||||
|
||||
export type categoryType = {
|
||||
id: number
|
||||
categoryTitle: string
|
||||
description: string
|
||||
totalProduct: number
|
||||
totalEarning: number
|
||||
image: string
|
||||
}
|
||||
|
||||
type CategoryWithActionsType = categoryType & {
|
||||
type CategoryWithActionsType = Category & {
|
||||
actions?: string
|
||||
}
|
||||
|
||||
@ -104,106 +99,6 @@ const DebouncedInput = ({
|
||||
return <CustomTextField {...props} value={value} onChange={e => setValue(e.target.value)} />
|
||||
}
|
||||
|
||||
// Vars
|
||||
const categoryData: categoryType[] = [
|
||||
{
|
||||
id: 1,
|
||||
categoryTitle: 'Smart Phone',
|
||||
description: 'Choose from wide range of smartphones online at best prices.',
|
||||
totalProduct: 12548,
|
||||
totalEarning: 98784,
|
||||
image: '/images/apps/ecommerce/product-1.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
categoryTitle: 'Clothing, Shoes, and jewellery',
|
||||
description: 'Fashion for a wide selection of clothing, shoes, jewellery and watches.',
|
||||
totalProduct: 4689,
|
||||
totalEarning: 45627,
|
||||
image: '/images/apps/ecommerce/product-9.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
categoryTitle: 'Home and Kitchen',
|
||||
description: 'Browse through the wide range of Home and kitchen products.',
|
||||
totalProduct: 11297,
|
||||
totalEarning: 51097,
|
||||
image: '/images/apps/ecommerce/product-10.png'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
categoryTitle: 'Beauty and Personal Care',
|
||||
description: 'Explore beauty and personal care products, shop makeup and etc.',
|
||||
totalProduct: 9474,
|
||||
totalEarning: 74829,
|
||||
image: '/images/apps/ecommerce/product-19.png'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
categoryTitle: 'Books',
|
||||
description: 'Over 25 million titles across categories such as business and etc.',
|
||||
totalProduct: 10257,
|
||||
totalEarning: 63618,
|
||||
image: '/images/apps/ecommerce/product-25.png'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
categoryTitle: 'Games',
|
||||
description: 'Every month, get exclusive in-game loot, free games, a free subscription.',
|
||||
totalProduct: 14501,
|
||||
totalEarning: 65920,
|
||||
image: '/images/apps/ecommerce/product-12.png'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
categoryTitle: 'Baby Products',
|
||||
description: 'Buy baby products across different categories from top brands.',
|
||||
totalProduct: 8624,
|
||||
totalEarning: 38838,
|
||||
image: '/images/apps/ecommerce/product-14.png'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
categoryTitle: 'Growsari',
|
||||
description: 'Shop grocery Items through at best prices in India.',
|
||||
totalProduct: 7389,
|
||||
totalEarning: 72652,
|
||||
image: '/images/apps/ecommerce/product-26.png'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
categoryTitle: 'Computer Accessories',
|
||||
description: 'Enhance your computing experience with our range of computer accessories.',
|
||||
totalProduct: 9876,
|
||||
totalEarning: 65421,
|
||||
image: '/images/apps/ecommerce/product-17.png'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
categoryTitle: 'Fitness Tracker',
|
||||
description: 'Monitor your health and fitness goals with our range of advanced fitness trackers.',
|
||||
totalProduct: 1987,
|
||||
totalEarning: 32067,
|
||||
image: '/images/apps/ecommerce/product-10.png'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
categoryTitle: 'Smart Home Devices',
|
||||
description: 'Transform your home into a smart home with our innovative smart home devices.',
|
||||
totalProduct: 2345,
|
||||
totalEarning: 87654,
|
||||
image: '/images/apps/ecommerce/product-11.png'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
categoryTitle: 'Audio Speakers',
|
||||
description: 'Immerse yourself in rich audio quality with our wide range of speakers.',
|
||||
totalProduct: 5678,
|
||||
totalEarning: 32145,
|
||||
image: '/images/apps/ecommerce/product-2.png'
|
||||
}
|
||||
]
|
||||
|
||||
// Column Definitions
|
||||
const columnHelper = createColumnHelper<CategoryWithActionsType>()
|
||||
|
||||
@ -211,8 +106,28 @@ const ProductCategoryTable = () => {
|
||||
// States
|
||||
const [addCategoryOpen, setAddCategoryOpen] = useState(false)
|
||||
const [rowSelection, setRowSelection] = useState({})
|
||||
const [data, setData] = useState(...[categoryData])
|
||||
const [globalFilter, setGlobalFilter] = useState('')
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
|
||||
// Fetch products with pagination and search
|
||||
const { data, isLoading, error, isFetching } = useCategoriesQuery.getCategories({
|
||||
page: currentPage,
|
||||
limit: pageSize
|
||||
})
|
||||
|
||||
const categories = data?.categories ?? []
|
||||
const totalCount = data?.total_count ?? 0
|
||||
|
||||
const handlePageChange = useCallback((event: unknown, newPage: number) => {
|
||||
setCurrentPage(newPage)
|
||||
}, [])
|
||||
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newPageSize = parseInt(event.target.value, 10)
|
||||
setPageSize(newPageSize)
|
||||
setCurrentPage(0) // Reset to first page
|
||||
}, [])
|
||||
|
||||
const columns = useMemo<ColumnDef<CategoryWithActionsType, any>[]>(
|
||||
() => [
|
||||
@ -238,31 +153,27 @@ const ProductCategoryTable = () => {
|
||||
/>
|
||||
)
|
||||
},
|
||||
columnHelper.accessor('categoryTitle', {
|
||||
header: 'Categories',
|
||||
columnHelper.accessor('name', {
|
||||
header: 'Name',
|
||||
cell: ({ row }) => (
|
||||
<div className='flex items-center gap-3'>
|
||||
<img src={row.original.image} width={38} height={38} className='rounded bg-actionHover' />
|
||||
{/* <img src={row.original.image} width={38} height={38} className='rounded bg-actionHover' /> */}
|
||||
<div className='flex flex-col items-start'>
|
||||
<Typography className='font-medium' color='text.primary'>
|
||||
{row.original.categoryTitle}
|
||||
{row.original.name}
|
||||
</Typography>
|
||||
<Typography variant='body2'>{row.original.description}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor('totalProduct', {
|
||||
header: 'Total Products',
|
||||
cell: ({ row }) => <Typography>{row.original.totalProduct.toLocaleString()}</Typography>
|
||||
columnHelper.accessor('description', {
|
||||
header: 'Decription',
|
||||
cell: ({ row }) => <Typography>{row.original.description || '-'}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('totalEarning', {
|
||||
header: 'Total Earning',
|
||||
cell: ({ row }) => (
|
||||
<Typography>
|
||||
{row.original.totalEarning.toLocaleString('en-IN', { style: 'currency', currency: 'USD' })}
|
||||
</Typography>
|
||||
)
|
||||
columnHelper.accessor('business_type', {
|
||||
header: 'Business Type',
|
||||
cell: ({ row }) => <Typography>{row.original.business_type}</Typography>
|
||||
}),
|
||||
columnHelper.accessor('actions', {
|
||||
header: 'Actions',
|
||||
@ -279,7 +190,7 @@ const ProductCategoryTable = () => {
|
||||
{
|
||||
text: 'Delete',
|
||||
icon: 'tabler-trash',
|
||||
menuItemProps: { onClick: () => setData(data.filter(category => category.id !== row.original.id)) }
|
||||
menuItemProps: { onClick: () => console.log('click') }
|
||||
},
|
||||
{ text: 'Duplicate', icon: 'tabler-copy' }
|
||||
]}
|
||||
@ -294,32 +205,24 @@ const ProductCategoryTable = () => {
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data: data as categoryType[],
|
||||
data: categories as Category[],
|
||||
columns,
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter
|
||||
},
|
||||
state: {
|
||||
rowSelection,
|
||||
globalFilter
|
||||
},
|
||||
initialState: {
|
||||
pagination: {
|
||||
pageSize: 10
|
||||
pageIndex: currentPage, // <= penting!
|
||||
pageSize
|
||||
}
|
||||
},
|
||||
enableRowSelection: true, //enable row selection for all rows
|
||||
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
|
||||
globalFilterFn: fuzzyFilter,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onGlobalFilterChange: setGlobalFilter,
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
getFacetedMinMaxValues: getFacetedMinMaxValues()
|
||||
// Disable client-side pagination since we're handling it server-side
|
||||
manualPagination: true,
|
||||
pageCount: Math.ceil(totalCount / pageSize)
|
||||
})
|
||||
|
||||
return (
|
||||
@ -327,9 +230,9 @@ const ProductCategoryTable = () => {
|
||||
<Card>
|
||||
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
||||
<DebouncedInput
|
||||
value={globalFilter ?? ''}
|
||||
onChange={value => setGlobalFilter(String(value))}
|
||||
placeholder='Search'
|
||||
value={'search'}
|
||||
onChange={value => console.log(value)}
|
||||
placeholder='Search Product'
|
||||
className='max-sm:is-full'
|
||||
/>
|
||||
<div className='flex max-sm:flex-col items-start sm:items-center gap-4 max-sm:is-full'>
|
||||
@ -354,6 +257,9 @@ const ProductCategoryTable = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className='overflow-x-auto'>
|
||||
{isLoading ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<table className={tableStyles.table}>
|
||||
<thead>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
@ -407,21 +313,47 @@ const ProductCategoryTable = () => {
|
||||
</tbody>
|
||||
)}
|
||||
</table>
|
||||
)}
|
||||
|
||||
{isFetching && !isLoading && (
|
||||
<Box
|
||||
position='absolute'
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
bgcolor='rgba(255,255,255,0.7)'
|
||||
zIndex={1}
|
||||
>
|
||||
<CircularProgress size={24} />
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
<TablePagination
|
||||
component={() => <TablePaginationComponent table={table} />}
|
||||
count={table.getFilteredRowModel().rows.length}
|
||||
rowsPerPage={table.getState().pagination.pageSize}
|
||||
page={table.getState().pagination.pageIndex}
|
||||
onPageChange={(_, page) => {
|
||||
table.setPageIndex(page)
|
||||
}}
|
||||
component={() => (
|
||||
<TablePaginationComponent
|
||||
pageIndex={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalCount={totalCount}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
)}
|
||||
count={totalCount}
|
||||
rowsPerPage={pageSize}
|
||||
page={currentPage}
|
||||
onPageChange={handlePageChange}
|
||||
onRowsPerPageChange={handlePageSizeChange}
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Card>
|
||||
<AddCategoryDrawer
|
||||
open={addCategoryOpen}
|
||||
categoryData={data}
|
||||
setData={setData}
|
||||
categoryData={categories}
|
||||
setData={() => {}}
|
||||
handleClose={() => setAddCategoryOpen(!addCategoryOpen)}
|
||||
/>
|
||||
</>
|
||||
|
||||
@ -1,63 +1,50 @@
|
||||
'use client'
|
||||
|
||||
// React Imports
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
// Next Imports
|
||||
import Link from 'next/link'
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
// MUI Imports
|
||||
import Button from '@mui/material/Button'
|
||||
import Card from '@mui/material/Card'
|
||||
import CardHeader from '@mui/material/CardHeader'
|
||||
import Button from '@mui/material/Button'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Checkbox from '@mui/material/Checkbox'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import IconButton from '@mui/material/IconButton'
|
||||
import Switch from '@mui/material/Switch'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import TablePagination from '@mui/material/TablePagination'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import type { TextFieldProps } from '@mui/material/TextField'
|
||||
import Typography from '@mui/material/Typography'
|
||||
|
||||
// Third-party Imports
|
||||
import classnames from 'classnames'
|
||||
import { rankItem } from '@tanstack/match-sorter-utils'
|
||||
import {
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
getFilteredRowModel,
|
||||
getFacetedRowModel,
|
||||
getFacetedUniqueValues,
|
||||
getFacetedMinMaxValues,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel
|
||||
} from '@tanstack/react-table'
|
||||
import type { ColumnDef, FilterFn } from '@tanstack/react-table'
|
||||
import type { RankingInfo } from '@tanstack/match-sorter-utils'
|
||||
import { rankItem } from '@tanstack/match-sorter-utils'
|
||||
import type { ColumnDef, FilterFn } from '@tanstack/react-table'
|
||||
import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
|
||||
import classnames from 'classnames'
|
||||
|
||||
// Type Imports
|
||||
import type { ThemeColor } from '@core/types'
|
||||
import type { Locale } from '@configs/i18n'
|
||||
import type { ProductType } from '@/types/apps/ecommerceTypes'
|
||||
|
||||
// Component Imports
|
||||
import TableFilters from './TableFilters'
|
||||
import CustomAvatar from '@core/components/mui/Avatar'
|
||||
import TablePaginationComponent from '@components/TablePaginationComponent'
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
import OptionMenu from '@core/components/option-menu'
|
||||
import TablePaginationComponent from '@components/TablePaginationComponent'
|
||||
import TableFilters from './TableFilters'
|
||||
|
||||
// Util Imports
|
||||
import { getLocalizedUrl } from '@/utils/i18n'
|
||||
|
||||
// Style Imports
|
||||
import tableStyles from '@core/styles/table.module.css'
|
||||
import { Box, CircularProgress } from '@mui/material'
|
||||
import Loading from '../../../../../components/layout/shared/Loading'
|
||||
import { useProductsQuery } from '../../../../../services/queries/products'
|
||||
import { Product } from '../../../../../types/services/products'
|
||||
import { Product } from '../../../../../types/services/product'
|
||||
|
||||
declare module '@tanstack/table-core' {
|
||||
interface FilterFns {
|
||||
@ -72,20 +59,6 @@ type ProductWithActionsType = Product & {
|
||||
actions?: string
|
||||
}
|
||||
|
||||
type ProductCategoryType = {
|
||||
[key: string]: {
|
||||
icon: string
|
||||
color: ThemeColor
|
||||
}
|
||||
}
|
||||
|
||||
type productStatusType = {
|
||||
[key: string]: {
|
||||
title: string
|
||||
color: ThemeColor
|
||||
}
|
||||
}
|
||||
|
||||
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
|
||||
// Rank the item
|
||||
const itemRank = rankItem(row.getValue(columnId), value)
|
||||
@ -128,36 +101,37 @@ const DebouncedInput = ({
|
||||
return <CustomTextField {...props} value={value} onChange={e => setValue(e.target.value)} />
|
||||
}
|
||||
|
||||
// Vars
|
||||
const productCategoryObj: ProductCategoryType = {
|
||||
Accessories: { icon: 'tabler-headphones', color: 'error' },
|
||||
'Home Decor': { icon: 'tabler-smart-home', color: 'info' },
|
||||
Electronics: { icon: 'tabler-device-laptop', color: 'primary' },
|
||||
Shoes: { icon: 'tabler-shoe', color: 'success' },
|
||||
Office: { icon: 'tabler-briefcase', color: 'warning' },
|
||||
Games: { icon: 'tabler-device-gamepad-2', color: 'secondary' }
|
||||
}
|
||||
|
||||
const productStatusObj: productStatusType = {
|
||||
Scheduled: { title: 'Scheduled', color: 'warning' },
|
||||
Published: { title: 'Publish', color: 'success' },
|
||||
Inactive: { title: 'Inactive', color: 'error' }
|
||||
}
|
||||
|
||||
// Column Definitions
|
||||
const columnHelper = createColumnHelper<ProductWithActionsType>()
|
||||
|
||||
const ProductListTable = () => {
|
||||
// States
|
||||
const { data } = useProductsQuery.getProducts()
|
||||
const [rowSelection, setRowSelection] = useState({})
|
||||
const [filteredData, setFilteredData] = useState(data?.products ?? [])
|
||||
const [globalFilter, setGlobalFilter] = useState('')
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
|
||||
// Hooks
|
||||
const { lang: locale } = useParams()
|
||||
|
||||
// Fetch products with pagination and search
|
||||
const { data, isLoading, error, isFetching } = useProductsQuery.getProducts({
|
||||
page: currentPage,
|
||||
limit: pageSize
|
||||
})
|
||||
|
||||
const products = data?.products ?? []
|
||||
const totalCount = data?.total_count ?? 0
|
||||
|
||||
const handlePageChange = useCallback((event: unknown, newPage: number) => {
|
||||
setCurrentPage(newPage)
|
||||
}, [])
|
||||
|
||||
// Handle page size change
|
||||
const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newPageSize = parseInt(event.target.value, 10)
|
||||
setPageSize(newPageSize)
|
||||
setCurrentPage(0) // Reset to first page
|
||||
}, [])
|
||||
|
||||
const columns = useMemo<ColumnDef<ProductWithActionsType, any>[]>(
|
||||
() => [
|
||||
{
|
||||
@ -261,48 +235,40 @@ const ProductListTable = () => {
|
||||
})
|
||||
],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[data, filteredData]
|
||||
[]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data: filteredData as Product[],
|
||||
data: products as Product[],
|
||||
columns,
|
||||
filterFns: {
|
||||
fuzzy: fuzzyFilter
|
||||
},
|
||||
state: {
|
||||
rowSelection,
|
||||
globalFilter
|
||||
},
|
||||
initialState: {
|
||||
pagination: {
|
||||
pageSize: 10
|
||||
pageIndex: currentPage, // <= penting!
|
||||
pageSize
|
||||
}
|
||||
},
|
||||
enableRowSelection: true, //enable row selection for all rows
|
||||
// enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
|
||||
globalFilterFn: fuzzyFilter,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onGlobalFilterChange: setGlobalFilter,
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
getFacetedMinMaxValues: getFacetedMinMaxValues()
|
||||
// Disable client-side pagination since we're handling it server-side
|
||||
manualPagination: true,
|
||||
pageCount: Math.ceil(totalCount / pageSize)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader title='Filters' />
|
||||
<TableFilters setData={setFilteredData} productData={data?.products} />
|
||||
<TableFilters setData={() => {}} productData={[]} />
|
||||
<Divider />
|
||||
<div className='flex flex-wrap justify-between gap-4 p-6'>
|
||||
<DebouncedInput
|
||||
value={globalFilter ?? ''}
|
||||
onChange={value => setGlobalFilter(String(value))}
|
||||
value={'search'}
|
||||
onChange={value => console.log(value)}
|
||||
placeholder='Search Product'
|
||||
className='max-sm:is-full'
|
||||
/>
|
||||
@ -337,6 +303,9 @@ const ProductListTable = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className='overflow-x-auto'>
|
||||
{isLoading ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<table className={tableStyles.table}>
|
||||
<thead>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
@ -390,15 +359,42 @@ const ProductListTable = () => {
|
||||
</tbody>
|
||||
)}
|
||||
</table>
|
||||
)}
|
||||
|
||||
{isFetching && !isLoading && (
|
||||
<Box
|
||||
position='absolute'
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
bgcolor='rgba(255,255,255,0.7)'
|
||||
zIndex={1}
|
||||
>
|
||||
<CircularProgress size={24} />
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TablePagination
|
||||
component={() => <TablePaginationComponent table={table} />}
|
||||
count={table.getFilteredRowModel().rows.length}
|
||||
rowsPerPage={table.getState().pagination.pageSize}
|
||||
page={table.getState().pagination.pageIndex}
|
||||
onPageChange={(_, page) => {
|
||||
table.setPageIndex(page)
|
||||
}}
|
||||
component={() => (
|
||||
<TablePaginationComponent
|
||||
pageIndex={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalCount={totalCount}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
)}
|
||||
count={totalCount}
|
||||
rowsPerPage={pageSize}
|
||||
page={currentPage}
|
||||
onPageChange={handlePageChange}
|
||||
onRowsPerPageChange={handlePageSizeChange}
|
||||
rowsPerPageOptions={[10, 25, 50]}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Card>
|
||||
</>
|
||||
|
||||
@ -11,7 +11,7 @@ import type { ProductType } from '@/types/apps/ecommerceTypes'
|
||||
|
||||
// Component Imports
|
||||
import CustomTextField from '@core/components/mui/TextField'
|
||||
import { Product } from '../../../../../types/services/products'
|
||||
import { Product } from '../../../../../types/services/product'
|
||||
|
||||
type ProductStockType = { [key: string]: boolean }
|
||||
|
||||
@ -21,13 +21,7 @@ const productStockObj: ProductStockType = {
|
||||
'Out of Stock': false
|
||||
}
|
||||
|
||||
const TableFilters = ({
|
||||
setData,
|
||||
productData
|
||||
}: {
|
||||
setData: (data: Product[]) => void
|
||||
productData?: Product[]
|
||||
}) => {
|
||||
const TableFilters = ({ setData, productData }: { setData: (data: Product[]) => void; productData?: Product[] }) => {
|
||||
// States
|
||||
const [category, setCategory] = useState<Product['category_id']>('')
|
||||
const [stock, setStock] = useState('')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user