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,
 | 
						|
  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 '« 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
 |