ManagementSystem/FRONTEND/src/components/DataTable/DataTable.tsx

1153 lines
33 KiB
TypeScript
Executable File

/* eslint-disable @typescript-eslint/no-explicit-any */
import { get } from '@/rtk/helpers/apiService'
import {
Box,
Button,
Checkbox,
CloseButton,
Container,
Group,
MultiSelect,
Pagination,
RadioGroup,
Select,
Skeleton,
Table,
Text,
TextInput,
Tooltip,
rem,
} from '@mantine/core'
import { DateInput, DateTimePicker } from '@mantine/dates'
import {
IconArrowBadgeLeftFilled,
IconArrowBadgeRightFilled,
IconArrowsSort,
IconFilterExclamation,
IconFilterSearch,
IconSortAscendingLetters,
IconSortDescendingLetters,
IconX,
} from '@tabler/icons-react'
import { ReactNode, useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import classes from './DataTable.module.css'
import { history } from '@/rtk/helpers/history'
export enum TypeFilter {
text = 'text',
boolean = 'boolean',
datetime = 'datetime',
date = 'date',
select = 'select',
MultiSelect = 'MultiSelect',
}
type Column = {
name: string
size: string
header: string
render?: (row: any, data?: any[]) => ReactNode
}
const sortByProperty = (property: string, type: string) => {
switch (type) {
case 'asc':
return (a: any, b: any) => {
if (a[property] < b[property]) {
return -1
} else if (a[property] > b[property]) {
return 1
} else {
return 0
}
}
case 'desc':
return (a: any, b: any) => {
if (a[property] > b[property]) {
return -1
} else if (a[property] < b[property]) {
return 1
} else {
return 0
}
}
}
}
export const DataTableAll = ({
data,
columns,
pagination,
searchInput,
checkBox,
size,
infoTotal,
componentRight,
}: {
data: any[]
columns: Column[]
pagination?: boolean
searchInput?: boolean
checkBox?: boolean
size: string
infoTotal?: React.ReactNode // Set the type to ReactNode to allow JSX elements
componentRight?: React.ReactNode
}) => {
const [Tdata, setTData] = useState<any[]>(data)
// const [tempData, setTempData] = useState<any[]>([])
const [search, setSearch] = useState('')
const [statusSort, setStatusSort] = useState({
name: '',
status: 'clear',
})
const [perPage, setPerPage] = useState(10)
const [page, setPage] = useState(1)
const [selectedRows, setSelectedRows] = useState<any[]>([])
const headers = columns.map((col) => (
<Table.Th w={col.size} key={col.header}>
<Box className={classes.boxTh}>
{col.header}{' '}
<Tooltip
label={
<Text className={classes.labelTooltip} fz={'0.7rem'}>
Desc
</Text>
}
color="gray"
>
<IconArrowsSort
display={statusSort.status === 'clear' ? 'block' : 'none'}
onClick={() => {
setStatusSort({ name: col.name, status: 'desc' })
// localStorage.setItem('package__table_sort', JSON.stringify({ name: col.name, status: 'desc' }))
// setTData(data.slice().sort(sortByProperty(col.name, 'desc')))
}}
className={classes.iconSort}
/>
</Tooltip>
<Tooltip
label={
<Text className={classes.labelTooltip} fz={'0.7rem'}>
Asc
</Text>
}
color="gray"
>
<IconSortDescendingLetters
display={
statusSort.name === col.name && statusSort.status === 'desc'
? 'block'
: 'none'
}
onClick={() => {
setStatusSort({ name: col.name, status: 'asc' })
// localStorage.setItem('package__table_sort', JSON.stringify({ name: col.name, status: 'asc' }))
// setTData(data.slice().sort(sortByProperty(col.name, 'asc')))
}}
className={classes.iconSort}
/>
</Tooltip>
<Tooltip
label={
<Text className={classes.labelTooltip} fz={'0.7rem'}>
Clear
</Text>
}
color="gray"
>
<IconSortAscendingLetters
display={
statusSort.name === col.name && statusSort.status === 'asc'
? 'block'
: 'none'
}
onClick={() => {
setStatusSort({ name: col.name, status: 'clear' })
}}
className={classes.iconSort}
/>
</Tooltip>
</Box>
</Table.Th>
))
const rows = Tdata.map((element, index) => {
const row = columns.map((col) => {
if (col.render) {
return (
<Table.Td key={col.header}>{col.render(element, Tdata)}</Table.Td>
)
} else {
if (element[col.name]) {
return (
<Table.Td key={col.header}>
<span>{element[col.name].toString()}</span>
</Table.Td>
)
} else {
return (
<Table.Td key={col.header}>
<i>null</i>
</Table.Td>
)
}
}
})
return (
<Table.Tr
key={index}
bg={
selectedRows.some((obj) =>
Object.keys(obj).every((key) => obj[key] === element[key]),
)
? 'var(--mantine-color-blue-light)'
: undefined
}
>
<Table.Td display={checkBox ? 'block' : 'none'}>
<Checkbox
aria-label="Select row"
checked={selectedRows.some((obj) =>
Object.keys(obj).every((key) => obj[key] === element[key]),
)}
onChange={(event) =>
setSelectedRows(
event.currentTarget.checked
? [...selectedRows, element]
: selectedRows.filter((position) => position !== element),
)
}
/>
</Table.Td>
{row}
</Table.Tr>
)
})
const [debounceTimer, setDebounceTimer] = useState<any>()
const handleInput = (e: any) => {
clearTimeout(debounceTimer)
setDebounceTimer(
setTimeout(() => {
searchInArray(e)
}, 500),
)
}
const searchInArray = (query: string) => {
if (query !== '') {
setTData(
data.filter((obj) =>
Object.values(obj).some(
(value: any) =>
value !== null &&
value.toString().toLowerCase().includes(query.toLowerCase()),
),
),
)
} else {
if (pagination) {
const temp = data.slice(perPage * (page - 1), perPage * page)
setTData(temp)
} else {
setTData(data)
}
}
}
const areObjectsEqual = (obj1: any, obj2: any) => {
return Object.keys(obj1).every((key) => obj1[key] === obj2[key])
}
const checkSubArray = (arr1: any[], arr2: any[]) => {
return arr1.some((obj1: any) =>
arr2.some((obj2: any) => areObjectsEqual(obj1, obj2)),
)
}
useEffect(() => {
if (pagination) {
setTData(data.slice(perPage * (page - 1), perPage * page))
} else {
if (statusSort.status !== 'clear') {
setTData(
data.slice().sort(sortByProperty(statusSort.name, statusSort.status)),
)
} else {
setTData(data)
}
}
}, [data, statusSort])
useEffect(() => {
if (pagination) {
const temp = data.slice(perPage * (page - 1), perPage * page)
setTData(temp)
}
}, [page, perPage])
return (
<Container className={classes.container} size={size}>
<Box className={classes.menuBox}>
<Box className={classes.boxSearch}>
<TextInput
className={classes.searchInput}
size="xs"
value={search}
display={searchInput ? 'block' : 'none'}
rightSection={
<CloseButton
aria-label="Clear input"
size={'sm'}
onClick={() => {
setSearch('')
handleInput('')
}}
style={{ display: search ? undefined : 'none' }}
/>
}
placeholder="🔍 Search"
onChange={(e) => {
handleInput(e.target.value)
setSearch(e.target.value)
}}
/>
{search !== '' ? (
<Text c={'#b6bec5'} fz={'sm'} ta={'right'} fs={'italic'}>
Found {Tdata.length} matches out of {data.length} entries
</Text>
) : (
<Text c={'#b6bec5'} fz={'sm'} ta={'right'} fs={'italic'}>
Show {perPage * (page - 1) + 1} to {data.length} of {data.length}{' '}
entries
</Text>
)}
</Box>
<Box className={classes.totalBox} display={infoTotal ? 'flex' : 'none'}>
<Text fz={'sm'} ta={'right'}>
{infoTotal}
</Text>
</Box>
<Box
className={classes.paginationBox}
display={pagination ? 'flex' : 'none'}
>
<Pagination
size={'xs'}
color="#a5a2a3"
total={
data.length % perPage !== 0
? data.length / perPage + 1
: data.length / perPage
}
siblings={1}
value={page}
disabled={search !== ''}
onChange={(e) => setPage(e)}
/>
<Select
size="xs"
w={'30%'}
min={10}
max={500}
lh={2}
variant="filled"
ml={'sm'}
label="Per page"
data={['10', '20', '50', '100', '200', '500']}
value={perPage.toString()}
searchable
disabled={search !== ''}
onChange={(e: any) => {
setPerPage(parseInt(e))
}}
/>
</Box>
{componentRight}
</Box>
<Box className={classes.box}>
<Table
stickyHeader
stickyHeaderOffset={-1}
striped
highlightOnHover
withTableBorder
withColumnBorders
>
<Table.Thead className={classes.headers}>
<Table.Tr>
<Table.Th display={checkBox ? 'block' : 'none'}>
<Checkbox
aria-label="Select row"
checked={
checkSubArray(Tdata, selectedRows) &&
Tdata.length === selectedRows.length
}
onChange={(event) =>
setSelectedRows(
event.currentTarget.checked
? (pre) => [...pre, ...Tdata]
: selectedRows.filter(
(item) =>
!Tdata.some((removeItem) =>
areObjectsEqual(item, removeItem),
),
),
)
}
/>
</Table.Th>
{headers}
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
</Box>
</Container>
)
}
export const DataTablePagination = ({
data,
columns,
filterInfo,
searchInput,
checkBox,
size,
}: {
data: any
columns: Column[]
filterInfo: any[]
searchInput?: boolean
checkBox?: boolean
size: string
}) => {
const [flag, setFlag] = useState(false)
const [Tdata, setTData] = useState<any[]>(data.data)
const [baseData, setBaseData] = useState<any>(data)
const [clearState, setClearState] = useState<any>(0)
const initialFilterState = Object.fromEntries(
filterInfo.map((item) => {
const key = item.key
const value = item.value || ''
return [key, value]
}),
)
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
const [skeleton, setSkeletion] = useState(false)
const [search, setSearch] = useState('')
const [openedFilter, setOpenedFilter] = useState(false)
const [dataFilter, setDataFilter] = useState<any>(initialFilterState)
const [perPage, setPerPage] = useState(data.per_page)
const [page, setPage] = useState(data.current_page)
const location = useLocation()
const [statusSort, setStatusSort] = useState({
name: '',
status: 'clear',
})
const [selectedRows, setSelectedRows] = useState<any[]>([])
const navigate = useNavigate()
const urlParams = new URLSearchParams(location.search)
// Render headers
const headers = columns.map((col) => (
<Table.Th w={col.size} key={col.header}>
<Box className={classes.boxTh}>
{col.header} {/* Icon sort */}
<Tooltip
label={
<Text className={classes.labelTooltip} fz={'0.7rem'}>
Desc
</Text>
}
color="gray"
>
<IconArrowsSort
display={
col.name !== '#' && statusSort.status === 'clear'
? 'block'
: 'none'
}
onClick={() => {
setStatusSort({ name: col.name, status: 'desc' })
}}
className={classes.iconSort}
/>
</Tooltip>
<Tooltip
label={
<Text className={classes.labelTooltip} fz={'0.7rem'}>
Asc
</Text>
}
color="gray"
>
<IconSortDescendingLetters
display={
col.name !== '#' &&
statusSort.name === col.name &&
statusSort.status === 'desc'
? 'block'
: 'none'
}
onClick={() => {
setStatusSort({ name: col.name, status: 'asc' })
}}
className={classes.iconSort}
/>
</Tooltip>
<Tooltip
label={
<Text className={classes.labelTooltip} fz={'0.7rem'}>
Clear
</Text>
}
color="gray"
>
<IconSortAscendingLetters
display={
col.name !== '#' &&
statusSort.name === col.name &&
statusSort.status === 'asc'
? 'block'
: 'none'
}
onClick={() => {
setStatusSort({ name: col.name, status: 'clear' })
}}
className={classes.iconSort}
/>
</Tooltip>
</Box>
</Table.Th>
))
// Render rows
const rows =
Tdata.length > 0 &&
Tdata.map((element, index) => {
const row = columns.map((col) => {
// Check 'render' option in config header
if (col.render) {
return (
<Table.Td key={col.header}>{col.render(element, Tdata)}</Table.Td>
)
} else {
// Check data
if (typeof element[col.name] !== 'undefined') {
return <Table.Td key={col.header}>{element[col.name]}</Table.Td>
} else {
return (
<Table.Td key={col.header}>
<i>unknown</i>
</Table.Td>
)
}
}
})
return (
<Table.Tr
key={index}
bg={
selectedRows.some((obj) =>
Object.keys(obj).every((key) => obj[key] === element[key]),
)
? 'var(--mantine-color-blue-light)'
: undefined
}
>
<Table.Td display={checkBox ? 'block' : 'none'}>
<Checkbox
aria-label="Select row"
checked={selectedRows.some((obj) =>
Object.keys(obj).every((key) => obj[key] === element[key]),
)}
onChange={(event) =>
setSelectedRows(
event.currentTarget.checked
? [...selectedRows, element]
: selectedRows.filter((position) => position !== element),
)
}
/>
</Table.Td>
{row}
</Table.Tr>
)
})
const removeParam = (name: string) => {
// Create a URL object
let url = new URL(window.location.href)
// Get the search parameters object
let params = url.searchParams
// Remove specific parameters
params.delete(name)
// Update the URL without reloading the page
window.history.replaceState({}, document.title, url.toString())
}
// Refresh data when useEffect start
const fetchData = async (url: string) => {
try {
setSkeletion(true)
const params = {
search: search,
per_page: perPage,
page: page,
timezone: timeZone,
}
// console.log(statusSort.name)
// Add 'order_by_[field_name] to 'params'
if (statusSort.status !== 'clear') {
Object.assign(params, {
[`order_by_${statusSort.name}`]: statusSort.status,
})
}
// Add all attributes in dataFilter to 'params'
Object.keys(dataFilter).map((key: any) => {
if (dataFilter[key] !== '' && dataFilter[key] !== null) {
Object.assign(params, {
[key]:
typeof dataFilter[key] === 'string' ||
typeof dataFilter[key] === 'number' ||
Array.isArray(dataFilter[key])
? dataFilter[key]
: key === 'to_date'
? Math.floor(dataFilter[key].getTime() / 1000) +
(60 * 60 * 23 + 60 * 59 + 59)
: Math.floor(dataFilter[key].getTime() / 1000),
})
}
})
if (
dataFilter.date_used &&
dataFilter.date_used !== null &&
dataFilter.date_used !== ''
) {
const appendSecond = 60 * 60 * 23 + 60 * 59 + 59
let date_used = dataFilter.date_used
if (typeof date_used === 'string') {
// unix timestamp
if (date_used.length === 10) {
date_used = Math.floor(parseInt(date_used) + appendSecond)
} else if (date_used.length === 13) {
// browser timestamp to unix timestamp
date_used = Math.floor(parseInt(date_used) / 1000 + appendSecond)
}
} else {
date_used = Math.floor(date_used.getTime() / 1000 + appendSecond)
}
// FIXME: append to parameters query to filter
Object.assign(params, {
date_used_to: date_used,
})
}
// Add all attributes in 'params' to URL params
Object.entries(params).forEach((param) => urlParams.set(...param))
Object.entries(dataFilter).forEach(([key, value]) => {
const typeFilter = filterInfo.find((o) => o.key === key).type
const hasType = {
MultiSelect: typeFilter === TypeFilter.MultiSelect,
Date:
typeFilter === TypeFilter.date ||
typeFilter === TypeFilter.datetime,
}
if (hasType.MultiSelect) {
value = Array(value).length ? Array(value).join(',') : []
}
if (hasType.Date) {
value = value ? Date.parse(String(value)) / 1000 : '' // to unix timestamp
}
String(value).length
? urlParams.set(key, String(value))
: urlParams.delete(key)
})
// Request to get data API
const res = await get(url, Object.fromEntries(urlParams.entries()))
if (res.status) {
setBaseData(res)
setTData(res.data)
setSkeletion(false)
navigate({
pathname: location.pathname,
search: urlParams.toString(),
})
}
statusSort.status === 'clear' &&
removeParam(`order_by_${statusSort.name}`)
} catch (error) {
console.warn(error)
}
}
const [debounceTimer, setDebounceTimer] = useState<any>()
const handleData = (time: number, setPage?: any) => {
if (search !== '') {
clearTimeout(debounceTimer)
setDebounceTimer(
setTimeout(() => {
fetchData(baseData.path)
}, time),
)
} else {
fetchData(baseData.path)
}
setPage && setPage(1)
}
const clearFilterData = () => {
setDataFilter(initialFilterState)
setClearState(clearState + 1)
}
const areObjectsEqual = (obj1: any, obj2: any) => {
return Object.keys(obj1).every((key) => obj1[key] === obj2[key])
}
const checkSubArray = (arr1: any[], arr2: any[]) => {
return arr1.some((obj1: any) =>
arr2.some((obj2: any) => areObjectsEqual(obj1, obj2)),
)
}
useEffect(() => {
setBaseData(data)
setTData(data.data)
const updatedDataFilter = { ...dataFilter }
const order_by_ = location.search
.split('&')
.filter((i) => i.includes('order_by_'))[0]
Object.keys(dataFilter).forEach((key) => {
const paramValue = urlParams.get(key)
if (paramValue !== null) {
const typeFilter = filterInfo.find((o: any) => o.key === key).type
// multiple select
if (typeFilter == TypeFilter.MultiSelect) {
updatedDataFilter[key] = String(paramValue).split(',')
} else if (
[TypeFilter.date, TypeFilter.datetime].includes(typeFilter)
) {
const isTimestamp = new Date(parseInt(paramValue)).getTime() > 0
isTimestamp
? (updatedDataFilter[key] = paramValue)
: (updatedDataFilter[key] = null)
} else {
updatedDataFilter[key] = paramValue
}
}
})
urlParams.get('search') !== null && setSearch(urlParams.get('search')!)
setDataFilter(updatedDataFilter)
if (order_by_) {
const sortParam = {
name: order_by_.split('=')[0].split('_')[2],
status: order_by_.split('=')[1],
}
if (JSON.stringify(sortParam) !== JSON.stringify(statusSort)) {
setStatusSort(sortParam)
}
}
}, [data])
useEffect(() => {
if (flag) {
handleData(500)
} else {
setFlag(true)
}
}, [page, perPage, statusSort, clearState])
useEffect(() => {
if (flag && search === '') {
handleData(500, setPage)
} else {
setFlag(true)
}
}, [search])
return (
<Container className={classes.container} size={size}>
<Box
display={openedFilter ? 'block' : 'none'}
style={{ border: 'solid 1px rgb(206, 206, 206)', borderRadius: '5px' }}
>
<Box display={'flex'} className={classes.boxFilter} w={'100%'}>
<IconX
style={{
position: 'absolute',
top: '10%',
right: '1%',
cursor: 'pointer',
}}
size="20"
onClick={() => {
setOpenedFilter(false)
}}
/>
{filterInfo.map((item) => {
switch (item.type) {
case TypeFilter.text:
return (
<TextInput
size="xs"
key={item.key}
value={dataFilter ? dataFilter[item.key] : ''}
className={classes.menuItem}
label={item.name}
onChange={(e) => {
setDataFilter({
...dataFilter,
[item.key]: e.target.value,
})
}}
/>
)
case TypeFilter.boolean:
return (
<RadioGroup
size="xs"
key={item.key}
className={classes.menuItem}
value={dataFilter ? dataFilter[item.key] : ''}
label={item.name}
onDoubleClick={() => {
setDataFilter({ ...dataFilter, [item.key]: '' })
}}
onChange={(e) => {
setDataFilter({ ...dataFilter, [item.key]: e })
}}
>
{item.data}
</RadioGroup>
)
case TypeFilter.datetime:
return (
<DateTimePicker
size="xs"
value={
dataFilter
? dataFilter[item.key] !== null &&
dataFilter[item.key].length === 10
? new Date(dataFilter[item.key] * 1000)
: dataFilter[item.key]
: ''
}
clearable
key={item.key}
className={classes.menuItem}
label={item.name}
onChange={(e) => {
setDataFilter({ ...dataFilter, [item.key]: e })
}}
/>
)
case TypeFilter.date:
return (
<DateInput
size="xs"
value={
dataFilter
? dataFilter[item.key] !== null &&
dataFilter[item.key].length === 10
? new Date(dataFilter[item.key] * 1000)
: dataFilter[item.key]
: ''
}
clearable
valueFormat="YYYY/MM/DD"
key={item.key}
className={classes.menuItem}
label={item.name}
onChange={(e) => {
setDataFilter({ ...dataFilter, [item.key]: e })
}}
/>
)
case TypeFilter.select:
return (
<Select
size="xs"
key={item.key}
value={
dataFilter.discount_type_id !== ''
? dataFilter[item.key]
: null
}
className={classes.menuItem}
label={item.name}
onChange={(e) => {
setDataFilter({ ...dataFilter, [item.key]: e })
}}
data={item.data}
/>
)
case TypeFilter.MultiSelect:
return (
<MultiSelect
size="xs"
key={item.key}
label={item.name}
placeholder={item.placeholder ?? undefined}
data={item.data}
value={dataFilter[item.key]}
onChange={(values) => {
urlParams.set(item.key, values.join(','))
history.push({
pathName: location.pathname,
search: urlParams.toString(),
})
setDataFilter({
...dataFilter,
[item.key]: values,
})
}}
searchable
/>
)
}
})}
</Box>
<Group className={classes.groupBtn}>
<Button
size="xs"
onClick={() => {
handleData(1000)
setPage(1)
}}
>
Filter
</Button>
<Button
size="xs"
onClick={() => {
clearFilterData()
}}
>
Clear
</Button>
</Group>
</Box>
<Box className={classes.menuBox}>
<Box className={classes.boxSearch}>
<Box
style={{
cursor: 'pointer',
display: filterInfo.length > 0 ? 'flex' : 'none',
}}
onClick={() => setOpenedFilter(!openedFilter)}
>
{Object.values(dataFilter).filter(
(value) => value === '' || value === null,
).length === Object.keys(dataFilter).length ? (
<IconFilterSearch style={{ marginBottom: '1vh' }} size={20} />
) : (
<IconFilterExclamation
color="red"
style={{ marginBottom: '1vh' }}
size={20}
/>
)}
<Text
ml={rem('5px')}
fw={600}
fz={'1rem'}
c={
Object.values(dataFilter).filter(
(value) => value === '' || value === null,
).length !== Object.values(dataFilter).length
? 'red'
: ''
}
>
Filter
</Text>
</Box>
<TextInput
className={classes.searchInput}
size="xs"
value={search}
display={searchInput ? 'block' : 'none'}
rightSection={
<CloseButton
aria-label="Clear input"
size={'sm'}
onClick={() => {
setSearch('')
}}
style={{ display: search ? undefined : 'none' }}
/>
}
placeholder="🔍 Search"
onChange={(e) => {
setSearch(e.target.value)
}}
onKeyUp={() => {
handleData(1500, setPage)
}}
/>
<Text className={classes.textSearch}>
Show {baseData.from} to {baseData.to} of {baseData.total} entries
</Text>
</Box>
<Box className={classes.paginationBox}>
{baseData.links.map((element: any, index: number) => {
switch (element.label) {
case '&laquo; Previous':
return (
<button
key={index}
className={classes.btnPagination}
onClick={() => {
page > 1 && setPage(page - 1)
}}
style={{
backgroundColor: element.active && '#739aaf',
color: element.active && 'white',
}}
>
<IconArrowBadgeLeftFilled size={15} />
</button>
)
case 'Next &raquo;':
return (
<button
key={index}
className={classes.btnPagination}
onClick={() => {
page < baseData.last_page && setPage(page + 1)
}}
style={{
backgroundColor: element.active && '#739aaf',
color: element.active && 'white',
}}
>
<IconArrowBadgeRightFilled size={15} />
</button>
)
case '...':
return (
<span key={index}>
{' '}
<b> ... </b>{' '}
</span>
)
default:
return (
<button
key={index}
className={classes.btnPagination}
disabled={element.active}
onClick={() => {
setPage(element.label)
}}
style={{
backgroundColor: element.active && '#739aaf',
color: element.active && 'white',
}}
>
{element.label.toString()}
</button>
)
}
})}
<Select
size="xs"
w={'10%'}
min={10}
max={500}
lh={2}
variant="filled"
ml={'sm'}
label="Per page"
data={['10', '15', '20', '50', '80', '100']}
value={perPage}
onChange={async (e: any) => {
setPage(1)
setPerPage(parseInt(e))
}}
/>
</Box>
</Box>
<Skeleton visible={skeleton}>
<Box className={classes.box}>
<Table
stickyHeader
stickyHeaderOffset={-1}
striped
highlightOnHover
withTableBorder
withColumnBorders
>
<Table.Thead className={classes.headers}>
<Table.Tr>
<Table.Th display={checkBox ? 'block' : 'none'}>
<Checkbox
aria-label="Select row"
checked={
checkSubArray(Tdata, selectedRows) &&
Tdata.length === selectedRows.length
}
onChange={(event) =>
setSelectedRows(
event.currentTarget.checked
? (pre) => [...pre, ...Tdata]
: selectedRows.filter((item) =>
Tdata.some(
(removeItem) =>
!areObjectsEqual(item, removeItem),
),
),
)
}
/>
</Table.Th>
{headers}
</Table.Tr>
</Table.Thead>
{Tdata.length > 0 ? (
<Table.Tbody>{rows}</Table.Tbody>
) : (
<Text
w={'300px'}
p={'lg'}
c={'#dbdee3'}
fw={600}
fz={'1.5rem'}
fs={'italic'}
>
Data Empty
</Text>
)}
</Table>
</Box>
</Skeleton>
</Container>
)
}
export default DataTableAll