1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
import React, { useCallback, useEffect, useState } from "react";
import { PluginHook, TableOptions, useTable } from "react-table";
import { LoadingIndicator } from "..";
import { usePageSize } from "../../@storage/local";
import {
ScrollToTop,
useEntityByRange,
useIsEntityLoaded,
} from "../../utilities";
import BaseTable, { TableStyleProps, useStyleAndOptions } from "./BaseTable";
import PageControl from "./PageControl";
import { useDefaultSettings } from "./plugins";
function useEntityPagination<T>(
entity: Async.Entity<T>,
loader: (range: Parameter.Range) => void,
start: number,
end: number
): T[] {
const { state, content } = entity;
const needInit = state === "uninitialized";
const hasEmpty = useIsEntityLoaded(content, start, end) === false;
useEffect(() => {
if (needInit || hasEmpty) {
const length = end - start;
loader({ start, length });
}
});
return useEntityByRange(content, start, end);
}
type Props<T extends object> = TableOptions<T> &
TableStyleProps<T> & {
plugins?: PluginHook<T>[];
entity: Async.Entity<T>;
loader: (params: Parameter.Range) => void;
};
export default function AsyncPageTable<T extends object>(props: Props<T>) {
const { entity, plugins, loader, ...remain } = props;
const { style, options } = useStyleAndOptions(remain);
const {
state,
content: { ids },
} = entity;
// Impl a new pagination system instead of hacking into existing one
const [pageIndex, setIndex] = useState(0);
const [pageSize] = usePageSize();
const totalRows = ids.length;
const pageCount = Math.ceil(totalRows / pageSize);
const pageStart = pageIndex * pageSize;
const pageEnd = pageStart + pageSize;
const data = useEntityPagination(entity, loader, pageStart, pageEnd);
const instance = useTable(
{
...options,
data,
},
useDefaultSettings,
...(plugins ?? [])
);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
instance;
const previous = useCallback(() => {
setIndex((idx) => idx - 1);
}, []);
const next = useCallback(() => {
setIndex((idx) => idx + 1);
}, []);
const goto = useCallback((idx: number) => {
setIndex(idx);
}, []);
useEffect(() => {
ScrollToTop();
}, [pageIndex]);
// Reset page index if we out of bound
useEffect(() => {
if (pageCount === 0) return;
if (pageIndex >= pageCount) {
setIndex(pageCount - 1);
} else if (pageIndex < 0) {
setIndex(0);
}
}, [pageIndex, pageCount]);
if ((state === "loading" && data.length === 0) || state === "uninitialized") {
return <LoadingIndicator></LoadingIndicator>;
}
return (
<React.Fragment>
<BaseTable
{...style}
headers={headerGroups}
rows={rows}
prepareRow={prepareRow}
tableProps={getTableProps()}
tableBodyProps={getTableBodyProps()}
></BaseTable>
<PageControl
count={pageCount}
index={pageIndex}
size={pageSize}
total={totalRows}
canPrevious={pageIndex > 0}
canNext={pageIndex < pageCount - 1}
previous={previous}
next={next}
goto={goto}
></PageControl>
</React.Fragment>
);
}
|