Skip to content

Commit 86a2945

Browse files
committed
Created integration with datastore api for table component
1 parent 09daa98 commit 86a2945

File tree

3 files changed

+233
-41
lines changed

3 files changed

+233
-41
lines changed

.changeset/four-walls-warn.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@portaljs/components': patch
3+
---
4+
5+
Created integration with datastore api for table component

packages/components/src/components/Table.tsx

+191-28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
getFilteredRowModel,
77
getPaginationRowModel,
88
getSortedRowModel,
9+
PaginationState,
10+
Table as ReactTable,
911
useReactTable,
1012
} from '@tanstack/react-table';
1113

@@ -25,12 +27,19 @@ import DebouncedInput from './DebouncedInput';
2527
import loadData from '../lib/loadData';
2628
import LoadingSpinner from './LoadingSpinner';
2729

30+
export type TableData = { cols: {key: string, name: string}[]; data: any[]; total: number };
31+
2832
export type TableProps = {
2933
data?: Array<{ [key: string]: number | string }>;
3034
cols?: Array<{ [key: string]: string }>;
3135
csv?: string;
3236
url?: string;
3337
fullWidth?: boolean;
38+
datastoreConfig?: {
39+
dataStoreURI: string;
40+
rowsPerPage?: number;
41+
dataMapperFn: (data) => Promise<TableData> | TableData;
42+
};
3443
};
3544

3645
export const Table = ({
@@ -39,8 +48,28 @@ export const Table = ({
3948
csv = '',
4049
url = '',
4150
fullWidth = false,
51+
datastoreConfig,
4252
}: TableProps) => {
4353
const [isLoading, setIsLoading] = useState<boolean>(false);
54+
const [pageMap, setPageMap] = useState(new Map<number, boolean>());
55+
const {
56+
dataMapperFn,
57+
dataStoreURI,
58+
rowsPerPage = 10,
59+
} = datastoreConfig ?? {};
60+
61+
const [globalFilter, setGlobalFilter] = useState('');
62+
const [isLoadingPage, setIsLoadingPage] = useState<boolean>(false);
63+
const [totalOfRows, setTotalOfRows] = useState<number>(0);
64+
65+
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
66+
pageIndex: 0,
67+
pageSize: rowsPerPage,
68+
});
69+
70+
const [lastIndex, setLastIndex] = useState(pageSize);
71+
const [startIndex, setStartIndex] = useState(0);
72+
const [hasSorted, setHasSorted] = useState(false);
4473

4574
if (csv) {
4675
const out = parseCsv(csv);
@@ -62,21 +91,56 @@ export const Table = ({
6291
);
6392
}, [data, cols]);
6493

65-
const [globalFilter, setGlobalFilter] = useState('');
94+
let table: ReactTable<unknown>;
6695

67-
const table = useReactTable({
68-
data,
69-
columns: tableCols,
70-
getCoreRowModel: getCoreRowModel(),
71-
state: {
72-
globalFilter,
73-
},
74-
globalFilterFn: globalFilterFn,
75-
onGlobalFilterChange: setGlobalFilter,
76-
getFilteredRowModel: getFilteredRowModel(),
77-
getPaginationRowModel: getPaginationRowModel(),
78-
getSortedRowModel: getSortedRowModel(),
79-
});
96+
if (datastoreConfig) {
97+
useEffect(() => {
98+
setIsLoading(true);
99+
fetch(`${dataStoreURI}&limit=${rowsPerPage}&offset=0`)
100+
.then((res) => res.json())
101+
.then(async (res) => {
102+
const { data, cols, total } = await dataMapperFn(res);
103+
setData(data);
104+
setCols(cols);
105+
setTotalOfRows(Math.ceil(total / rowsPerPage));
106+
pageMap.set(0, true);
107+
})
108+
.finally(() => setIsLoading(false));
109+
}, [dataStoreURI]);
110+
111+
table = useReactTable({
112+
data,
113+
pageCount: totalOfRows,
114+
columns: tableCols,
115+
getCoreRowModel: getCoreRowModel(),
116+
state: {
117+
pagination: { pageIndex, pageSize },
118+
},
119+
getFilteredRowModel: getFilteredRowModel(),
120+
manualPagination: true,
121+
onPaginationChange: setPagination,
122+
getSortedRowModel: getSortedRowModel(),
123+
});
124+
125+
useEffect(() => {
126+
if (!hasSorted) return;
127+
queryDataByText(globalFilter);
128+
}, [table.getState().sorting]);
129+
} else {
130+
table = useReactTable({
131+
data,
132+
columns: tableCols,
133+
getCoreRowModel: getCoreRowModel(),
134+
state: {
135+
globalFilter,
136+
},
137+
globalFilterFn: globalFilterFn,
138+
onGlobalFilterChange: setGlobalFilter,
139+
getFilteredRowModel: getFilteredRowModel(),
140+
getPaginationRowModel: getPaginationRowModel(),
141+
getSortedRowModel: getSortedRowModel(),
142+
});
143+
}
80144

81145
useEffect(() => {
82146
if (url) {
@@ -91,6 +155,70 @@ export const Table = ({
91155
}
92156
}, [url]);
93157

158+
const queryDataByText = (filter) => {
159+
setIsLoadingPage(true);
160+
const sortedParam = getSortParam();
161+
fetch(
162+
`${dataStoreURI}&limit=${rowsPerPage}&offset=0&q=${filter}${sortedParam}`
163+
)
164+
.then((res) => res.json())
165+
.then(async (res) => {
166+
const { data, total = 0 } = await dataMapperFn(res);
167+
setTotalOfRows(Math.ceil(total / rowsPerPage));
168+
setData(data);
169+
const newMap = new Map();
170+
newMap.set(0, true);
171+
setPageMap(newMap);
172+
table.setPageIndex(0);
173+
setStartIndex(0);
174+
setLastIndex(pageSize);
175+
})
176+
.finally(() => setIsLoadingPage(false));
177+
};
178+
179+
const getSortParam = () => {
180+
const sort = table.getState().sorting;
181+
return sort.length == 0
182+
? ``
183+
: '&sort=' +
184+
sort
185+
.map(
186+
(x, i) =>
187+
`${x.id}${
188+
i === sort.length - 1 ? (x.desc ? ` desc` : ` asc`) : `,`
189+
}`
190+
)
191+
.reduce((x1, x2) => x1 + x2);
192+
};
193+
194+
const queryPaginatedData = (newPageIndex) => {
195+
let newStartIndex = newPageIndex * pageSize;
196+
setStartIndex(newStartIndex);
197+
setLastIndex(newStartIndex + pageSize);
198+
199+
if (!pageMap.get(newPageIndex)) pageMap.set(newPageIndex, true);
200+
else return;
201+
202+
const sortedParam = getSortParam();
203+
204+
setIsLoadingPage(true);
205+
fetch(
206+
`${dataStoreURI}&limit=${rowsPerPage}&offset=${
207+
newStartIndex + pageSize
208+
}&q=${globalFilter}${sortedParam}`
209+
)
210+
.then((res) => res.json())
211+
.then(async (res) => {
212+
const { data: responseData } = await dataMapperFn(res);
213+
responseData.forEach((e) => {
214+
data[newStartIndex] = e;
215+
newStartIndex++;
216+
});
217+
setData([...data]);
218+
})
219+
.finally(() => setIsLoadingPage(false));
220+
};
221+
94222
return isLoading ? (
95223
<div className="w-full h-full min-h-[500px] flex items-center justify-center">
96224
<LoadingSpinner />
@@ -99,7 +227,10 @@ export const Table = ({
99227
<div className={`${fullWidth ? 'w-[90vw] ml-[calc(50%-45vw)]' : 'w-full'}`}>
100228
<DebouncedInput
101229
value={globalFilter ?? ''}
102-
onChange={(value: any) => setGlobalFilter(String(value))}
230+
onChange={(value: any) => {
231+
if (datastoreConfig) queryDataByText(String(value));
232+
setGlobalFilter(String(value));
233+
}}
103234
className="p-2 text-sm shadow border border-block"
104235
placeholder="Search all columns..."
105236
/>
@@ -114,7 +245,10 @@ export const Table = ({
114245
className: h.column.getCanSort()
115246
? 'cursor-pointer select-none'
116247
: '',
117-
onClick: h.column.getToggleSortingHandler(),
248+
onClick: (v) => {
249+
setHasSorted(true);
250+
h.column.getToggleSortingHandler()(v);
251+
},
118252
}}
119253
>
120254
{flexRender(h.column.columnDef.header, h.getContext())}
@@ -135,23 +269,39 @@ export const Table = ({
135269
))}
136270
</thead>
137271
<tbody>
138-
{table.getRowModel().rows.map((r) => (
139-
<tr key={r.id} className="border-b border-b-slate-200">
140-
{r.getVisibleCells().map((c) => (
141-
<td key={c.id} className="py-2">
142-
{flexRender(c.column.columnDef.cell, c.getContext())}
143-
</td>
144-
))}
272+
{datastoreConfig && isLoadingPage ? (
273+
<tr>
274+
<td colSpan={cols.length} rowSpan={cols.length}>
275+
<div className="w-full h-full flex items-center justify-center pt-6">
276+
<LoadingSpinner />
277+
</div>
278+
</td>
145279
</tr>
146-
))}
280+
) : (
281+
(datastoreConfig
282+
? table.getRowModel().rows.slice(startIndex, lastIndex)
283+
: table.getRowModel().rows
284+
).map((r) => (
285+
<tr key={r.id} className="border-b border-b-slate-200">
286+
{r.getVisibleCells().map((c) => (
287+
<td key={c.id} className="py-2">
288+
{flexRender(c.column.columnDef.cell, c.getContext())}
289+
</td>
290+
))}
291+
</tr>
292+
))
293+
)}
147294
</tbody>
148295
</table>
149296
<div className="flex gap-2 items-center justify-center mt-10">
150297
<button
151298
className={`w-6 h-6 ${
152299
!table.getCanPreviousPage() ? 'opacity-25' : 'opacity-100'
153300
}`}
154-
onClick={() => table.setPageIndex(0)}
301+
onClick={() => {
302+
if (datastoreConfig) queryPaginatedData(0);
303+
table.setPageIndex(0);
304+
}}
155305
disabled={!table.getCanPreviousPage()}
156306
>
157307
<ChevronDoubleLeftIcon />
@@ -160,7 +310,12 @@ export const Table = ({
160310
className={`w-6 h-6 ${
161311
!table.getCanPreviousPage() ? 'opacity-25' : 'opacity-100'
162312
}`}
163-
onClick={() => table.previousPage()}
313+
onClick={() => {
314+
if (datastoreConfig) {
315+
queryPaginatedData(table.getState().pagination.pageIndex - 1);
316+
}
317+
table.previousPage();
318+
}}
164319
disabled={!table.getCanPreviousPage()}
165320
>
166321
<ChevronLeftIcon />
@@ -176,7 +331,11 @@ export const Table = ({
176331
className={`w-6 h-6 ${
177332
!table.getCanNextPage() ? 'opacity-25' : 'opacity-100'
178333
}`}
179-
onClick={() => table.nextPage()}
334+
onClick={() => {
335+
if (datastoreConfig)
336+
queryPaginatedData(table.getState().pagination.pageIndex + 1);
337+
table.nextPage();
338+
}}
180339
disabled={!table.getCanNextPage()}
181340
>
182341
<ChevronRightIcon />
@@ -185,7 +344,11 @@ export const Table = ({
185344
className={`w-6 h-6 ${
186345
!table.getCanNextPage() ? 'opacity-25' : 'opacity-100'
187346
}`}
188-
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
347+
onClick={() => {
348+
const pageIndexToNavigate = table.getPageCount() - 1;
349+
if (datastoreConfig) queryPaginatedData(pageIndexToNavigate);
350+
table.setPageIndex(pageIndexToNavigate);
351+
}}
189352
disabled={!table.getCanNextPage()}
190353
>
191354
<ChevronDoubleRightIcon />

0 commit comments

Comments
 (0)