Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
components.table.LargeTable.tsx Maven / Gradle / Ivy
import _ from 'lodash'
import { useEffect, useRef, useState } from 'react'
import { useNotifier } from 'hooks/notification'
import { useCustomRef } from 'hooks/customref'
import { useTranslator } from 'hooks/translator'
import Report from 'helpers/report'
import { useDebouncedState } from 'utils/utils'
import { toRegex } from 'utils/option-utils'
import { Box, Typography } from '@mui/material'
import PageStats from 'components/table/page/PageStats'
import MenuBar from 'components/table/page/MenuBar'
import LoadingTable from 'components/table/LoadingTable'
type LargeTableProps = {
description: string
// rendering table data
columns: { headerName: string, field: string, cellProps: any }[]
dataLoader: DataLoader
// styling
containerSx?: any
tableSx?: any
largeTableSx?: any
// configurations
pageSize?: number
filterColumns?: string[]
filterType?: FilterType
disablePaging?: boolean
disableFilter?: boolean
disableReload?: boolean
disableToolbar?: boolean
}
type DataLoader = (
page: Page,
errorCallback: (message: string, reason: string) => void,
dataCallback: (data: Data) => void,
) => Cancellable
type Data = {
rows: any[] // the filtered rows
count: number // the total number of rows
page: Page
//pageCount: number // TODO misleading name; this apparently is the number of rows before local filtering
}
type Page = {
pageNum: number // the current page number
pageSize: number // the number of rows per page
filter: string
filterType: FilterType
action: Action
}
type FilterType = 'remote' | 'local'
type Action = 'main' | 'reload' | 'first' | 'previous' | 'next' | 'last' | 'filter'
type Status = {
busy: boolean
with?: Action
request?: Cancellable
}
type Cancellable = {
cancel: () => void
}
type Setter = (value: T) => void
const LargeTable = ({
description,
// rendering table data
columns = [],
dataLoader,
// styling
containerSx = {},
tableSx = {},
largeTableSx = {},
// configurations
pageSize: givenPageSize = 100,
filterType = 'remote',
filterColumns = [],
disableFilter,
disablePaging,
disableReload,
disableToolbar
}: LargeTableProps) => {
const pageSize = disablePaging && 999999 || givenPageSize
const {translator} = useTranslator()
const notifier = useNotifier()
const [page, setPage] = useState({ pageNum: 0, pageSize, filter: "", filterType, action: 'main' })
const [data, setData] = useState({ rows: [], count: 0, page: {...page, pageNum: 0 }, /*pageCount: -1*/ })
const [pageable, setPageable] = useState(false)
const [filter, setFilter, setCurrentFilter] = useDebouncedState('', 500)
const tableRef = useRef(null)
const [status, setStatus] = useState({ busy: false })
const busyWith = (action: Action): boolean => status.busy && status.with === action
const activeFilterColumns = filterColumns.length === 0 ? columns.filter(column => column.headerName).map(column => column.field) : filterColumns
const toolbarFunctions = createToolbarFunctions(data, status, setStatus, setPage)
const showToolbar = !disableToolbar && ((!disablePaging && pageable) || !disableFilter)
// add functions to be visible from outside of this component
const customRef = useCustomRef()
customRef?.setCurrent({
deleteLine: (id: string) => setData(data => ({...data, rows: data.rows.filter((row: any) => row.id != id)}))
})
// perform a request when the request info changes
useEffect(() => {
const pageChanged = data.page !== page
if (pageChanged || page.action === 'reload') {
const loadingDone = () => {
setStatus({ busy: false, with: undefined })
}
const errorHandler = (message: string, reason: string) => {
loadingDone()
console.error(message, reason)
Report.from(message, translator, { category: Report.backend }).addToNotifier(notifier)
}
const dataHandler = (result: any) => {
loadingDone()
console.log("LargeTable: handle data for %s: result=%o", description, result)
const rows = filterType === 'local' ? filterRows(result.rows, activeFilterColumns, page.filter) : result.rows
setData({ ...result, rows, page })
}
status.request?.cancel() // cancel a running request, if any
console.debug("LargeTable: loading data for page=%o, data.page=%o", page, data.page)
const request = dataLoader(page, errorHandler, dataHandler)
setStatus({ busy: true, with: page.action, request })
}
},[page])
// instigate a page change when the filter changes
useEffect( () => {
if (filter != page.filter) {
setPage({...page, pageNum: 0, filter, action: 'filter' })
}
}, [filter])
// active pagination buttons
useEffect( () => {
const hasCount = Boolean(data.count >= 0)
const enablePaging = data.page.pageNum > 0 ||
(hasCount && data.count > data.page.pageSize) /*||
(!hasCount && data.pageCount === data.page.pageSize*)*/
if (!pageable && enablePaging && !disablePaging)
setPageable(enablePaging)
// scroll to top
if (tableRef.current) {
// @ts-ignore
tableRef.current.scrollTo({top: 0, left: 0})
}
}, [data])
// TODO ref={tableRef} on LoadingTable
return (
)
}
const EmptyTableMessage = ({message, enable}: {message?: string, enable: boolean}) => {
const {t} = useTranslator()
if (!enable) return null
return (
{message || t('table.empty')}
)
}
function filterRows(rows: any[], filterColumns: string[], filter: string) {
if (!filter) return rows
const regex = toRegex(filter)
const check = (value: string) => regex.test(value)
const filterRow = (row: any) => filterColumns.some(column => check(_.get(row.cells, column)))
return rows.filter(filterRow)
}
const createToolbarFunctions = (data: any, status: Status, setStatus: Setter, setPage: Setter) => {
const enabled = data.page.pageNum >= 0
const hasCount = data?.count >= 0
const lastPage = Math.floor(Number(data.count) / Number(data.page.pageSize)) + (Number(data.count) % Number(data.page.pageSize) === 0 ? -1 : 0)
return {
reload: data.page.pageNum >= 0
? () => {
console.log("Reloaded page: %o", data.page)
setPage({...data.page, action: 'reload'})
}
: undefined,
filter: () => {
if (status.busy) setStatus({ busy: false, with: undefined })
},
first: enabled && data.page.pageNum > 0
? () => {
setPage({...data.page, pageNum: 0, action: 'first'})
}
: undefined,
last: enabled && hasCount && lastPage > data.page.pageNum
? () => {
setPage({...data.page, pageNum: lastPage, action: 'last'})
}
: undefined,
previous: enabled && data.page.pageNum > 0
? () => {
setPage({...data.page, pageNum: data.page.pageNum - 1, action: 'previous'})
}
: undefined,
next: enabled && (
(hasCount && data.count > (data.rows.length + data.page.pageNum * data.page.pageSize) ||
(!hasCount && data.rows.length % data.page.pageSize === 0))
)
? () => {
setPage({...data.page, pageNum: data.page.pageNum + 1, action: 'next'})
}
: undefined,
}
}
export default LargeTable