feat: payment & customer

This commit is contained in:
ferdiansyah783
2025-08-07 14:31:42 +07:00
parent a72a64215a
commit a5d22db27b
35 changed files with 2033 additions and 599 deletions
@@ -0,0 +1,203 @@
// React Imports
import { useMemo, useState } from 'react'
// MUI Imports
import Button from '@mui/material/Button'
import Divider from '@mui/material/Divider'
import Drawer from '@mui/material/Drawer'
import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography'
// Third-party Imports
// Type Imports
// Components Imports
import CustomTextField from '@core/components/mui/TextField'
import { Autocomplete, CircularProgress } from '@mui/material'
import { useDebounce } from 'use-debounce'
import { useInventoriesMutation } from '../../../../../services/mutations/inventories'
import { useOutlets } from '../../../../../services/queries/outlets'
import { useProducts } from '../../../../../services/queries/products'
import { InventoryAdjustRequest } from '../../../../../types/services/inventory'
type Props = {
open: boolean
handleClose: () => void
}
const AdjustmentStockDrawer = (props: Props) => {
// Props
const { open, handleClose } = props
const { mutate: adjustInventory, isPending: isCreating } = useInventoriesMutation().adjustInventory
// States
const [productInput, setProductInput] = useState('')
const [productDebouncedInput] = useDebounce(productInput, 500) // debounce for better UX
const [outletInput, setOutletInput] = useState('')
const [outletDebouncedInput] = useDebounce(outletInput, 500) // debounce for better UX
const [formData, setFormData] = useState<InventoryAdjustRequest>({
product_id: '',
outlet_id: '',
delta: 0,
reason: ''
})
const { data: outlets, isLoading: outletsLoading } = useOutlets({
search: outletDebouncedInput
})
const { data: products, isLoading } = useProducts({
search: productDebouncedInput
})
const outletOptions = useMemo(() => outlets?.outlets || [], [outlets])
const options = useMemo(() => products?.products || [], [products])
// Handle Form Submit
const handleFormSubmit = (e: any) => {
e.preventDefault()
adjustInventory(
{ ...formData, delta: Number(formData.delta) },
{
onSuccess: () => {
handleReset()
}
}
)
}
const handleInputChange = (e: any) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
// Handle Form Reset
const handleReset = () => {
handleClose()
setFormData({
product_id: '',
outlet_id: '',
delta: 0,
reason: ''
})
}
return (
<Drawer
open={open}
anchor='right'
variant='temporary'
onClose={handleReset}
ModalProps={{ keepMounted: true }}
sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }}
>
<div className='flex items-center justify-between pli-6 plb-5'>
<Typography variant='h5'>Adjust Inventory</Typography>
<IconButton size='small' onClick={handleReset}>
<i className='tabler-x text-textSecondary text-2xl' />
</IconButton>
</div>
<Divider />
<div className='p-6'>
<form onSubmit={handleFormSubmit} className='flex flex-col gap-5'>
<Autocomplete
options={outletOptions}
loading={outletsLoading}
getOptionLabel={option => option.name}
value={outletOptions.find(p => p.id === formData.outlet_id) || null}
onInputChange={(event, newOutlettInput) => {
setOutletInput(newOutlettInput)
}}
onChange={(event, newValue) => {
setFormData({
...formData,
outlet_id: newValue?.id || ''
})
}}
renderInput={params => (
<CustomTextField
{...params}
className=''
label='Outlet'
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{outletsLoading && <CircularProgress size={18} />}
{params.InputProps.endAdornment}
</>
)
}}
/>
)}
/>
<Autocomplete
options={options}
loading={isLoading}
getOptionLabel={option => option.name}
value={options.find(p => p.id === formData.product_id) || null}
onInputChange={(event, newProductInput) => {
setProductInput(newProductInput)
}}
onChange={(event, newValue) => {
setFormData({
...formData,
product_id: newValue?.id || ''
})
}}
renderInput={params => (
<CustomTextField
{...params}
className=''
label='Product'
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{isLoading && <CircularProgress size={18} />}
{params.InputProps.endAdornment}
</>
)
}}
/>
)}
/>
<CustomTextField
fullWidth
label='Delta'
name='delta'
value={formData.delta}
onChange={handleInputChange}
placeholder='0'
/>
<CustomTextField
fullWidth
label='Reason'
value={formData.reason}
name='reason'
onChange={handleInputChange}
multiline
rows={4}
placeholder='Write a Comment...'
/>
<div className='flex items-center gap-4'>
<Button variant='contained' type='submit' disabled={isCreating}>
{isCreating ? 'Adjusting...' : 'Adjust'}
</Button>
<Button variant='tonal' color='error' type='reset' onClick={handleReset}>
Discard
</Button>
</div>
</form>
</div>
</Drawer>
)
}
export default AdjustmentStockDrawer
@@ -0,0 +1,369 @@
'use client'
// React Imports
import { useCallback, useEffect, useMemo, useState } from 'react'
// Next Imports
// MUI Imports
import Button from '@mui/material/Button'
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import Checkbox from '@mui/material/Checkbox'
import Chip from '@mui/material/Chip'
import Divider from '@mui/material/Divider'
import MenuItem from '@mui/material/MenuItem'
import TablePagination from '@mui/material/TablePagination'
import type { TextFieldProps } from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
// Third-party Imports
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
// Component Imports
import TablePaginationComponent from '@components/TablePaginationComponent'
import CustomTextField from '@core/components/mui/TextField'
// Util Imports
// Style Imports
import tableStyles from '@core/styles/table.module.css'
import { Box, CircularProgress } from '@mui/material'
import AdjustmentStockDrawer from './AdjustmentStockDrawer'
import { Inventory } from '../../../../../types/services/inventory'
import Loading from '../../../../../components/layout/shared/Loading'
import { useInventories } from '../../../../../services/queries/inventories'
declare module '@tanstack/table-core' {
interface FilterFns {
fuzzy: FilterFn<unknown>
}
interface FilterMeta {
itemRank: RankingInfo
}
}
type InventoryWithActionsType = Inventory & {
actions?: string
}
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value)
// Store the itemRank info
addMeta({
itemRank
})
// Return if the item should be filtered in/out
return itemRank.passed
}
const DebouncedInput = ({
value: initialValue,
onChange,
debounce = 500,
...props
}: {
value: string | number
onChange: (value: string | number) => void
debounce?: number
} & Omit<TextFieldProps, 'onChange'>) => {
// States
const [value, setValue] = useState(initialValue)
useEffect(() => {
setValue(initialValue)
}, [initialValue])
useEffect(() => {
const timeout = setTimeout(() => {
onChange(value)
}, debounce)
return () => clearTimeout(timeout)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
return <CustomTextField {...props} value={value} onChange={e => setValue(e.target.value)} />
}
// Column Definitions
const columnHelper = createColumnHelper<InventoryWithActionsType>()
const StockListTable = () => {
const [rowSelection, setRowSelection] = useState({})
const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [addInventoryOpen, setAddInventoryOpen] = useState(false)
// Fetch products with pagination and search
const { data, isLoading, error, isFetching } = useInventories({
page: currentPage,
limit: pageSize
})
const inventories = data?.inventory ?? []
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(1) // Reset to first page
}, [])
const columns = useMemo<ColumnDef<InventoryWithActionsType, any>[]>(
() => [
{
id: 'select',
header: ({ table }) => (
<Checkbox
{...{
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler()
}}
/>
),
cell: ({ row }) => (
<Checkbox
{...{
checked: row.getIsSelected(),
disabled: !row.getCanSelect(),
indeterminate: row.getIsSomeSelected(),
onChange: row.getToggleSelectedHandler()
}}
/>
)
},
columnHelper.accessor('product_id', {
header: 'Product',
cell: ({ row }) => <Typography>{row.original.product_id}</Typography>
}),
columnHelper.accessor('quantity', {
header: 'Quantity',
cell: ({ row }) => <Typography>{row.original.quantity}</Typography>
}),
columnHelper.accessor('reorder_level', {
header: 'Reorder Level',
cell: ({ row }) => <Typography>{row.original.reorder_level}</Typography>
}),
columnHelper.accessor('is_low_stock', {
header: 'Status',
cell: ({ row }) => (
<Chip
label={row.original.is_low_stock ? 'Low' : 'Normal'}
variant='tonal'
color={row.original.is_low_stock ? 'error' : 'success'}
size='small'
/>
)
})
// columnHelper.accessor('actions', {
// header: 'Actions',
// cell: ({ row }) => (
// <div className='flex items-center'>
// <OptionMenu
// iconButtonProps={{ size: 'medium' }}
// iconClassName='text-textSecondary'
// options={[
// { text: 'Download', icon: 'tabler-download' },
// {
// text: 'Delete',
// icon: 'tabler-trash',
// menuItemProps: {
// onClick: () => {
// setOpenConfirm(true)
// setProductId(row.original.id)
// }
// }
// },
// { text: 'Duplicate', icon: 'tabler-copy' }
// ]}
// />
// </div>
// ),
// enableSorting: false
// })
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
const table = useReactTable({
data: inventories as Inventory[],
columns,
filterFns: {
fuzzy: fuzzyFilter
},
state: {
rowSelection,
pagination: {
pageIndex: currentPage, // <= penting!
pageSize
}
},
enableRowSelection: true, //enable row selection for all rows
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
// 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={() => {}} productData={[]} /> */}
<Divider />
<div className='flex flex-wrap justify-between gap-4 p-6'>
<DebouncedInput
value={'search'}
onChange={value => console.log(value)}
placeholder='Search Product'
className='max-sm:is-full'
/>
<div className='flex flex-wrap items-center max-sm:flex-col gap-4 max-sm:is-full is-auto'>
<CustomTextField
select
value={table.getState().pagination.pageSize}
onChange={e => table.setPageSize(Number(e.target.value))}
className='flex-auto is-[70px] max-sm:is-full'
>
<MenuItem value='10'>10</MenuItem>
<MenuItem value='25'>25</MenuItem>
<MenuItem value='50'>50</MenuItem>
</CustomTextField>
<Button
color='secondary'
variant='tonal'
className='max-sm:is-full is-auto'
startIcon={<i className='tabler-upload' />}
>
Export
</Button>
<Button
variant='contained'
className='max-sm:is-full'
onClick={() => setAddInventoryOpen(!addInventoryOpen)}
startIcon={<i className='tabler-plus' />}
>
Adjust Inventory
</Button>
</div>
</div>
<div className='overflow-x-auto'>
{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
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>
<AdjustmentStockDrawer open={addInventoryOpen} handleClose={() => setAddInventoryOpen(!addInventoryOpen)} />
</>
)
}
export default StockListTable
@@ -0,0 +1,191 @@
// React Imports
import { useMemo, useState } from 'react'
// MUI Imports
import Button from '@mui/material/Button'
import Divider from '@mui/material/Divider'
import Drawer from '@mui/material/Drawer'
import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography'
// Third-party Imports
// Type Imports
// Components Imports
import CustomTextField from '@core/components/mui/TextField'
import { Autocomplete, CircularProgress } from '@mui/material'
import { useDebounce } from 'use-debounce'
import { useInventoriesMutation } from '../../../../../services/mutations/inventories'
import { useOutlets } from '../../../../../services/queries/outlets'
import { useProducts } from '../../../../../services/queries/products'
import { InventoryRequest } from '../../../../../types/services/inventory'
type Props = {
open: boolean
handleClose: () => void
}
const AddStockDrawer = (props: Props) => {
// Props
const { open, handleClose } = props
const { mutate: createInventory, isPending: isCreating } = useInventoriesMutation().createInventory
// States
const [productInput, setProductInput] = useState('')
const [productDebouncedInput] = useDebounce(productInput, 500) // debounce for better UX
const [outletInput, setOutletInput] = useState('')
const [outletDebouncedInput] = useDebounce(outletInput, 500) // debounce for better UX
const [formData, setFormData] = useState<InventoryRequest>({
product_id: '',
outlet_id: '',
quantity: 0
})
const { data: outlets, isLoading: outletsLoading } = useOutlets({
search: outletDebouncedInput
})
const { data: products, isLoading } = useProducts({
search: productDebouncedInput
})
const outletOptions = useMemo(() => outlets?.outlets || [], [outlets])
const options = useMemo(() => products?.products || [], [products])
// Handle Form Submit
const handleFormSubmit = (e: any) => {
e.preventDefault()
createInventory(
{ ...formData, quantity: Number(formData.quantity) },
{
onSuccess: () => {
handleReset()
}
}
)
}
const handleInputChange = (e: any) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
// Handle Form Reset
const handleReset = () => {
handleClose()
setFormData({
product_id: '',
outlet_id: '',
quantity: 0
})
}
return (
<Drawer
open={open}
anchor='right'
variant='temporary'
onClose={handleReset}
ModalProps={{ keepMounted: true }}
sx={{ '& .MuiDrawer-paper': { width: { xs: 300, sm: 400 } } }}
>
<div className='flex items-center justify-between pli-6 plb-5'>
<Typography variant='h5'>Add Inventory</Typography>
<IconButton size='small' onClick={handleReset}>
<i className='tabler-x text-textSecondary text-2xl' />
</IconButton>
</div>
<Divider />
<div className='p-6'>
<form onSubmit={handleFormSubmit} className='flex flex-col gap-5'>
<Autocomplete
options={outletOptions}
loading={outletsLoading}
getOptionLabel={option => option.name}
value={outletOptions.find(p => p.id === formData.outlet_id) || null}
onInputChange={(event, newOutlettInput) => {
setOutletInput(newOutlettInput)
}}
onChange={(event, newValue) => {
setFormData({
...formData,
outlet_id: newValue?.id || ''
})
}}
renderInput={params => (
<CustomTextField
{...params}
className=''
label='Outlet'
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{outletsLoading && <CircularProgress size={18} />}
{params.InputProps.endAdornment}
</>
)
}}
/>
)}
/>
<Autocomplete
options={options}
loading={isLoading}
getOptionLabel={option => option.name}
value={options.find(p => p.id === formData.product_id) || null}
onInputChange={(event, newProductInput) => {
setProductInput(newProductInput)
}}
onChange={(event, newValue) => {
setFormData({
...formData,
product_id: newValue?.id || ''
})
}}
renderInput={params => (
<CustomTextField
{...params}
className=''
label='Product'
fullWidth
InputProps={{
...params.InputProps,
endAdornment: (
<>
{isLoading && <CircularProgress size={18} />}
{params.InputProps.endAdornment}
</>
)
}}
/>
)}
/>
<CustomTextField
fullWidth
label='Quantity'
name='quantity'
value={formData.quantity}
onChange={handleInputChange}
placeholder='0'
/>
<div className='flex items-center gap-4'>
<Button variant='contained' type='submit' disabled={isCreating}>
{isCreating ? 'Add...' : 'Add'}
</Button>
<Button variant='tonal' color='error' type='reset' onClick={handleReset}>
Discard
</Button>
</div>
</form>
</div>
</Drawer>
)
}
export default AddStockDrawer
@@ -0,0 +1,391 @@
'use client'
// React Imports
import { useCallback, useEffect, useMemo, useState } from 'react'
// Next Imports
// MUI Imports
import Button from '@mui/material/Button'
import Card from '@mui/material/Card'
import CardHeader from '@mui/material/CardHeader'
import Checkbox from '@mui/material/Checkbox'
import Chip from '@mui/material/Chip'
import Divider from '@mui/material/Divider'
import MenuItem from '@mui/material/MenuItem'
import TablePagination from '@mui/material/TablePagination'
import type { TextFieldProps } from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
// Third-party Imports
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
// Component Imports
import TablePaginationComponent from '@components/TablePaginationComponent'
import CustomTextField from '@core/components/mui/TextField'
import OptionMenu from '@core/components/option-menu'
// Util Imports
// Style Imports
import tableStyles from '@core/styles/table.module.css'
import { Box, CircularProgress } from '@mui/material'
import AddStockDrawer from './AddStockDrawer'
import ConfirmDeleteDialog from '../../../../../components/dialogs/confirm-delete'
import Loading from '../../../../../components/layout/shared/Loading'
import { useInventoriesMutation } from '../../../../../services/mutations/inventories'
import { useInventories } from '../../../../../services/queries/inventories'
import { Inventory } from '../../../../../types/services/inventory'
declare module '@tanstack/table-core' {
interface FilterFns {
fuzzy: FilterFn<unknown>
}
interface FilterMeta {
itemRank: RankingInfo
}
}
type InventoryWithActionsType = Inventory & {
actions?: string
}
const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value)
// Store the itemRank info
addMeta({
itemRank
})
// Return if the item should be filtered in/out
return itemRank.passed
}
const DebouncedInput = ({
value: initialValue,
onChange,
debounce = 500,
...props
}: {
value: string | number
onChange: (value: string | number) => void
debounce?: number
} & Omit<TextFieldProps, 'onChange'>) => {
// States
const [value, setValue] = useState(initialValue)
useEffect(() => {
setValue(initialValue)
}, [initialValue])
useEffect(() => {
const timeout = setTimeout(() => {
onChange(value)
}, debounce)
return () => clearTimeout(timeout)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value])
return <CustomTextField {...props} value={value} onChange={e => setValue(e.target.value)} />
}
// Column Definitions
const columnHelper = createColumnHelper<InventoryWithActionsType>()
const StockListTable = () => {
const [rowSelection, setRowSelection] = useState({})
const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [openConfirm, setOpenConfirm] = useState(false)
const [productId, setProductId] = useState('')
const [addInventoryOpen, setAddInventoryOpen] = useState(false)
// Fetch products with pagination and search
const { data, isLoading, error, isFetching } = useInventories({
page: currentPage,
limit: pageSize
})
const { mutate: deleteInventory, isPending: isDeleting } = useInventoriesMutation().deleteInventory
const inventories = data?.inventory ?? []
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(1) // Reset to first page
}, [])
const handleDelete = () => {
deleteInventory(productId, {
onSuccess: () => setOpenConfirm(false)
})
}
const columns = useMemo<ColumnDef<InventoryWithActionsType, any>[]>(
() => [
{
id: 'select',
header: ({ table }) => (
<Checkbox
{...{
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onChange: table.getToggleAllRowsSelectedHandler()
}}
/>
),
cell: ({ row }) => (
<Checkbox
{...{
checked: row.getIsSelected(),
disabled: !row.getCanSelect(),
indeterminate: row.getIsSomeSelected(),
onChange: row.getToggleSelectedHandler()
}}
/>
)
},
columnHelper.accessor('product_id', {
header: 'Product',
cell: ({ row }) => <Typography>{row.original.product_id}</Typography>
}),
columnHelper.accessor('quantity', {
header: 'Quantity',
cell: ({ row }) => <Typography>{row.original.quantity}</Typography>
}),
columnHelper.accessor('reorder_level', {
header: 'Reorder Level',
cell: ({ row }) => <Typography>{row.original.reorder_level}</Typography>
}),
columnHelper.accessor('is_low_stock', {
header: 'Status',
cell: ({ row }) => (
<Chip
label={row.original.is_low_stock ? 'Low' : 'Normal'}
variant='tonal'
color={row.original.is_low_stock ? 'error' : 'success'}
size='small'
/>
)
}),
columnHelper.accessor('actions', {
header: 'Actions',
cell: ({ row }) => (
<div className='flex items-center'>
<OptionMenu
iconButtonProps={{ size: 'medium' }}
iconClassName='text-textSecondary'
options={[
{ text: 'Download', icon: 'tabler-download' },
{
text: 'Delete',
icon: 'tabler-trash',
menuItemProps: {
onClick: () => {
setOpenConfirm(true)
setProductId(row.original.id)
}
}
},
{ text: 'Duplicate', icon: 'tabler-copy' }
]}
/>
</div>
),
enableSorting: false
})
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
const table = useReactTable({
data: inventories as Inventory[],
columns,
filterFns: {
fuzzy: fuzzyFilter
},
state: {
rowSelection,
pagination: {
pageIndex: currentPage, // <= penting!
pageSize
}
},
enableRowSelection: true, //enable row selection for all rows
onRowSelectionChange: setRowSelection,
getCoreRowModel: getCoreRowModel(),
// 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={() => {}} productData={[]} /> */}
<Divider />
<div className='flex flex-wrap justify-between gap-4 p-6'>
<DebouncedInput
value={'search'}
onChange={value => console.log(value)}
placeholder='Search Product'
className='max-sm:is-full'
/>
<div className='flex flex-wrap items-center max-sm:flex-col gap-4 max-sm:is-full is-auto'>
<CustomTextField
select
value={pageSize}
onChange={handlePageSizeChange}
className='flex-auto is-[70px] max-sm:is-full'
>
<MenuItem value='10'>10</MenuItem>
<MenuItem value='25'>25</MenuItem>
<MenuItem value='50'>50</MenuItem>
</CustomTextField>
<Button
color='secondary'
variant='tonal'
className='max-sm:is-full is-auto'
startIcon={<i className='tabler-upload' />}
>
Export
</Button>
<Button
variant='contained'
className='max-sm:is-full'
onClick={() => setAddInventoryOpen(!addInventoryOpen)}
startIcon={<i className='tabler-plus' />}
>
Add Inventory
</Button>
</div>
</div>
<div className='overflow-x-auto'>
{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
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>
<AddStockDrawer open={addInventoryOpen} handleClose={() => setAddInventoryOpen(!addInventoryOpen)} />
<ConfirmDeleteDialog
open={openConfirm}
onClose={() => setOpenConfirm(false)}
onConfirm={handleDelete}
isLoading={isDeleting}
title='Delete Inventory'
message='Are you sure you want to delete this inventory? This action cannot be undone.'
/>
</>
)
}
export default StockListTable
@@ -0,0 +1,106 @@
// React Imports
import { useEffect, useState } from 'react'
// MUI Imports
import CardContent from '@mui/material/CardContent'
import Grid from '@mui/material/Grid2'
import MenuItem from '@mui/material/MenuItem'
// Type Imports
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
import { Product } from '../../../../../types/services/product'
type ProductStockType = { [key: string]: boolean }
// Vars
const productStockObj: ProductStockType = {
'In Stock': true,
'Out of Stock': false
}
const TableFilters = ({ setData, productData }: { setData: (data: Product[]) => void; productData?: Product[] }) => {
// States
const [category, setCategory] = useState<Product['category_id']>('')
const [stock, setStock] = useState('')
const [status, setStatus] = useState<Product['name']>('')
useEffect(
() => {
const filteredData = productData?.filter(product => {
if (category && product.category_id !== category) return false
if (stock && product.name !== stock) return false
if (status && product.name !== status) return false
return true
})
setData(filteredData ?? [])
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[category, stock, status, productData]
)
return (
<CardContent>
<Grid container spacing={6}>
<Grid size={{ xs: 12, sm: 4 }}>
<CustomTextField
select
fullWidth
id='select-status'
value={status}
onChange={e => setStatus(e.target.value)}
slotProps={{
select: { displayEmpty: true }
}}
>
<MenuItem value=''>Select Status</MenuItem>
<MenuItem value='Scheduled'>Scheduled</MenuItem>
<MenuItem value='Published'>Publish</MenuItem>
<MenuItem value='Inactive'>Inactive</MenuItem>
</CustomTextField>
</Grid>
<Grid size={{ xs: 12, sm: 4 }}>
<CustomTextField
select
fullWidth
id='select-category'
value={category}
onChange={e => setCategory(e.target.value)}
slotProps={{
select: { displayEmpty: true }
}}
>
<MenuItem value=''>Select Category</MenuItem>
<MenuItem value='Accessories'>Accessories</MenuItem>
<MenuItem value='Home Decor'>Home Decor</MenuItem>
<MenuItem value='Electronics'>Electronics</MenuItem>
<MenuItem value='Shoes'>Shoes</MenuItem>
<MenuItem value='Office'>Office</MenuItem>
<MenuItem value='Games'>Games</MenuItem>
</CustomTextField>
</Grid>
<Grid size={{ xs: 12, sm: 4 }}>
<CustomTextField
select
fullWidth
id='select-stock'
value={stock}
onChange={e => setStock(e.target.value as string)}
slotProps={{
select: { displayEmpty: true }
}}
>
<MenuItem value=''>Select Stock</MenuItem>
<MenuItem value='In Stock'>In Stock</MenuItem>
<MenuItem value='Out of Stock'>Out of Stock</MenuItem>
</CustomTextField>
</Grid>
</Grid>
</CardContent>
)
}
export default TableFilters