feat list product and category

This commit is contained in:
ferdiansyah783 2025-08-05 14:34:36 +07:00
parent fffa2ead5c
commit 0fde122ac4
8 changed files with 388 additions and 382 deletions

View File

@ -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
/>

View 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
})
}
}

View File

@ -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'
interface ProductsQueryParams {
page?: number
limit?: number
search?: string
// Add other filter parameters as needed
category_id?: string
is_active?: boolean
}
export const useProductsQuery = {
getProducts: () => {
getProducts: (params: ProductsQueryParams = {}) => {
const { page = 1, limit = 10, search = '', ...filters } = params
return useQuery<Products>({
queryKey: ['products'],
queryKey: ['products', { page, limit, search, ...filters }],
queryFn: async () => {
const res = await api.get('/products')
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
})
}
}

View 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;
}

View File

@ -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,74 +257,103 @@ const ProductCategoryTable = () => {
</div>
</div>
<div className='overflow-x-auto'>
<table className={tableStyles.table}>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<>
<div
className={classnames({
'flex items-center': header.column.getIsSorted(),
'cursor-pointer select-none': header.column.getCanSort()
})}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <i className='tabler-chevron-up text-xl' />,
desc: <i className='tabler-chevron-down text-xl' />
}[header.column.getIsSorted() as 'asc' | 'desc'] ?? null}
</div>
</>
)}
</th>
))}
</tr>
))}
</thead>
{table.getFilteredRowModel().rows.length === 0 ? (
<tbody>
<tr>
<td colSpan={table.getVisibleFlatColumns().length} className='text-center'>
No data available
</td>
</tr>
</tbody>
) : (
<tbody>
{table
.getRowModel()
.rows.slice(0, table.getState().pagination.pageSize)
.map(row => {
return (
<tr key={row.id} className={classnames({ selected: row.getIsSelected() })}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
)
})}
</tbody>
)}
</table>
{isLoading ? (
<Loading />
) : (
<table className={tableStyles.table}>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<>
<div
className={classnames({
'flex items-center': header.column.getIsSorted(),
'cursor-pointer select-none': header.column.getCanSort()
})}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <i className='tabler-chevron-up text-xl' />,
desc: <i className='tabler-chevron-down text-xl' />
}[header.column.getIsSorted() as 'asc' | 'desc'] ?? null}
</div>
</>
)}
</th>
))}
</tr>
))}
</thead>
{table.getFilteredRowModel().rows.length === 0 ? (
<tbody>
<tr>
<td colSpan={table.getVisibleFlatColumns().length} className='text-center'>
No data available
</td>
</tr>
</tbody>
) : (
<tbody>
{table
.getRowModel()
.rows.slice(0, table.getState().pagination.pageSize)
.map(row => {
return (
<tr key={row.id} className={classnames({ selected: row.getIsSelected() })}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
)
})}
</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)}
/>
</>

View File

@ -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>[]>(
() => [
{
@ -226,7 +200,7 @@ const ProductListTable = () => {
}),
columnHelper.accessor('is_active', {
header: 'Status',
cell: ({row}) => (
cell: ({ row }) => (
<Chip
label={row.original.is_active ? 'Active' : 'Inactive'}
variant='tonal'
@ -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,68 +303,98 @@ const ProductListTable = () => {
</div>
</div>
<div className='overflow-x-auto'>
<table className={tableStyles.table}>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<>
<div
className={classnames({
'flex items-center': header.column.getIsSorted(),
'cursor-pointer select-none': header.column.getCanSort()
})}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <i className='tabler-chevron-up text-xl' />,
desc: <i className='tabler-chevron-down text-xl' />
}[header.column.getIsSorted() as 'asc' | 'desc'] ?? null}
</div>
</>
)}
</th>
))}
</tr>
))}
</thead>
{table.getFilteredRowModel().rows.length === 0 ? (
<tbody>
<tr>
<td colSpan={table.getVisibleFlatColumns().length} className='text-center'>
No data available
</td>
</tr>
</tbody>
) : (
<tbody>
{table
.getRowModel()
.rows.slice(0, table.getState().pagination.pageSize)
.map(row => {
return (
<tr key={row.id} className={classnames({ selected: row.getIsSelected() })}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
)
})}
</tbody>
)}
</table>
{isLoading ? (
<Loading />
) : (
<table className={tableStyles.table}>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<>
<div
className={classnames({
'flex items-center': header.column.getIsSorted(),
'cursor-pointer select-none': header.column.getCanSort()
})}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: <i className='tabler-chevron-up text-xl' />,
desc: <i className='tabler-chevron-down text-xl' />
}[header.column.getIsSorted() as 'asc' | 'desc'] ?? null}
</div>
</>
)}
</th>
))}
</tr>
))}
</thead>
{table.getFilteredRowModel().rows.length === 0 ? (
<tbody>
<tr>
<td colSpan={table.getVisibleFlatColumns().length} className='text-center'>
No data available
</td>
</tr>
</tbody>
) : (
<tbody>
{table
.getRowModel()
.rows.slice(0, table.getState().pagination.pageSize)
.map(row => {
return (
<tr key={row.id} className={classnames({ selected: row.getIsSelected() })}>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
))}
</tr>
)
})}
</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>
</>

View File

@ -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('')