1153 lines
33 KiB
TypeScript
Executable File
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,
|
|
}: {
|
|
data: any[]
|
|
columns: Column[]
|
|
pagination?: boolean
|
|
searchInput?: boolean
|
|
checkBox?: boolean
|
|
size: string
|
|
infoTotal?: React.ReactNode // Set the type to ReactNode to allow JSX elements
|
|
}) => {
|
|
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.paginationBox}
|
|
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>
|
|
</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 '« 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 »':
|
|
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
|