· 4 years ago · Jun 08, 2021, 10:54 AM
1import React from 'react';
2import { v4 as uuidv4 } from 'uuid';
3
4// components
5import Table from 'antd/es/table';
6import Empty from 'antd/es/empty';
7import { Loader } from 'gmoc-frontend-core/common/components/ui/loader';
8import GridFooter from './grid-footer';
9import GridTableDetails from './table-details/GridTableDetails';
10import CellContentFactory from './CellContentFactory';
11import ContextMenuWrapper from 'gmoc-grid-builder/components/context-menu';
12import DragabbleTableWrapper from './DragabbleTableWrapper';
13import GridTableTools from './table-tools';
14import GridTableNested from 'gmoc-grid-builder/components/table/table-nested/GridTableNested';
15
16// actions
17import { setDetailsData, setSorters, setIsDetailsLoading } from '../../actions';
18
19// api
20import { apiInstance } from 'gmoc-frontend-core/core/api/config';
21
22// custom hooks
23import useMultiRowSelection from 'gmoc-grid-builder/custom-hooks/useMultiRowSelection';
24
25// types
26import { ISelectedRow } from 'gmoc-grid-builder/types';
27import { IFactoryComponent } from 'gmoc-components-factory/types';
28
29// styles
30import { TableStyled } from './styles';
31
32type Props = {
33 readonly data: any;
34 readonly columns: any;
35 readonly detailsData: any;
36 readonly options: any;
37 readonly total: number;
38 readonly itemsPerPage: number;
39 readonly pageNum: number;
40 readonly paginationPositions: any;
41 readonly selectable: boolean;
42 readonly isLoading: boolean;
43 readonly onPaginationChange: any;
44 readonly onSorterChange: (targetPageNum: number) => void;
45 readonly onDetailsShow: any;
46 readonly dispatch: any;
47 readonly gridState: any;
48 readonly getData: (targetPageNum: number, filterArgs?: any) => Promise<void>;
49 readonly args?: string[];
50 readonly showDetails?: boolean;
51 readonly isFirstRender?: boolean;
52 readonly isToolRefresh?: boolean;
53 readonly outerContextMenuComponents?: IFactoryComponent
54 readonly outerCellGridComponents?: IFactoryComponent
55 readonly isContextMenuVisible?: boolean;
56 readonly multiSelect?: boolean;
57 readonly selectionDisabledIds?: number[];
58 readonly showFooter?: boolean;
59 readonly scrollDetectSelector?: string,
60 readonly onSelectedElementsChange?: (selectedElements: ISelectedRow[])=> void;
61 readonly onContextMenuError?: (err: any) => void;
62 readonly customSortFunction?: (a: any, b: any, key: string) => number;
63}
64
65const columns2 = [
66 {
67 title: 'Name',
68 dataIndex: 'name',
69 sorter: true,
70 width: '20%',
71 },
72 {
73 title: 'Surname',
74 dataIndex: 'surname',
75 sorter: true,
76 },
77 {
78 title: 'Email',
79 dataIndex: 'email',
80 },
81];
82
83const dataS = [
84 {
85 name: 'Ania',
86 surname: 'Gołdzia',
87 email: 'anieczka@gmail.com',
88 },
89 {
90 name: 'Piotr',
91 surname: 'Dendys',
92 email: 'dendi181@gmail.com',
93 },
94 {
95 name: 'Janusz',
96 surname: 'Januszewski',
97 email: 'januszek@gmail.com',
98 },
99 {
100 name: 'Jan',
101 surname: 'Kowal',
102 email: 'kowal@gmail.com',
103 },
104];
105
106const GridTable: React.FC<Props> = (props: Props) => {
107 const {
108 data,
109 columns,
110 detailsData,
111 options,
112 total,
113 itemsPerPage,
114 pageNum,
115 paginationPositions,
116 isLoading,
117 showDetails,
118 isFirstRender,
119 onPaginationChange,
120 onSorterChange,
121 onDetailsShow,
122 dispatch,
123 isContextMenuVisible,
124 getData,
125 isToolRefresh,
126 gridState,
127 multiSelect,
128 selectionDisabledIds,
129 showFooter,
130 scrollDetectSelector,
131 selectable,
132 outerContextMenuComponents,
133 outerCellGridComponents,
134 onSelectedElementsChange,
135 onContextMenuError,
136 customSortFunction,
137 } = props;
138
139 const mainTableId: string = React.useMemo(() => uuidv4(), []);
140
141 React.useEffect(() => {
142 if (onDetailsShow) {
143 // TODO: rozgraniczyć entity i preview data
144 const { entity } = detailsData || {};
145 onDetailsShow(detailsData);
146 }
147 }, [detailsData]);
148
149 const handlePaginationChange = (targetPageNum: number): void => {
150 if (onPaginationChange) {
151 onPaginationChange(targetPageNum);
152 }
153 };
154
155 const handleSortingChange = (sorter: any): void => {
156 console.log('sorter', sorter);
157 if (Object.keys(sorter).length) {
158 const { column, order } = sorter;
159 const { dataIndex } = column;
160
161 const [key] = dataIndex;
162 /**
163 * We have to change parameter name in order to adjust it to API`s one
164 */
165 const mappedOrder = order === 'ascend' ? 'ASC' : 'DESC';
166
167 const args = { _order: `${key}|${mappedOrder}` };
168
169 dispatch(setSorters(args));
170 dispatch(setIsDetailsLoading(true));
171 onSorterChange(1);
172 }
173 };
174
175 const getPaginationConfig = () => {
176 type PaginationSize = 'small' | 'default' | undefined;
177 return {
178 hideOnSinglePage: true,
179 current: pageNum,
180 total,
181 pageSize: itemsPerPage,
182 position: paginationPositions,
183 showSizeChanger: false,
184 size: 'small' as PaginationSize,
185 onChange: (currentIndex) => handlePaginationChange(currentIndex),
186 };
187 };
188
189 const getDetails = async (record): Promise<void> => {
190 dispatch(setIsDetailsLoading(true));
191 const { options: { preview } } = record;
192 const res = await apiInstance.get(preview);
193 const { data: resData } = res.data;
194
195 dispatch(setDetailsData(resData));
196 dispatch(setIsDetailsLoading(false));
197 };
198
199 const {
200 handleClick, handleRightClick, isLoading: isMenuLoading, menuData, setMenuData, selectedElements,
201 } = useMultiRowSelection({
202 multiSelect,
203 dispatchTable: dispatch,
204 getDetails,
205 showDetails,
206 isContextMenuVisible,
207 selectionDisabledIds,
208 onContextMenuError,
209 onSelectedElementsChange,
210 data,
211 });
212
213 const refreshGrid = async (): Promise<void> => {
214 await getData(pageNum, gridState.filters);
215 };
216
217 const getRowConfig = (record, rowIndex) => {
218 return {
219 onContextMenu: (event) => {
220 if (!isContextMenuVisible) {
221 return;
222 }
223 handleRightClick(record, rowIndex, mainTableId, null, refreshGrid);
224 },
225
226 onClick: (event) => {
227 if (!selectable) {
228 return;
229 }
230 handleClick(record, rowIndex, mainTableId, null, data);
231 },
232 };
233 };
234
235 const renderColumns = React.useMemo(() => {
236 return (
237 columns && columns.map((column, idx) => {
238 return (
239 <Table.Column
240 key={idx}
241 title={column.title}
242 dataIndex={...column.dataIndex}
243 align={column.align}
244 width="auto"
245 ellipsis
246 fixed={column.fixed}
247 // sorter={column.sortable && customSortFunction ? (a, b) => customSortFunction(a, b, column.key) : false}
248 sorter
249 sortDirections={['ascend', 'descend']}
250 render={(cellValue) => (
251 <CellContentFactory
252 getGridData={getData}
253 pageNum={pageNum}
254 tableWrapperClass={mainTableId}
255 cellData={cellValue}
256 gridState={gridState}
257 outerCellGridComponents={outerCellGridComponents}
258 />
259 )}
260 />
261 );
262 })
263 );
264 }, [columns]);
265
266 /**
267 * Loader should be visible when:
268 *
269 * Columns length is less than 1 it`s mean that grid was not fetched yet and during 'isFirstRender'
270 * any other render is caseud by change in filters form and is processed by native ant.design table loader
271 */
272 if (columns.length < 1 && isFirstRender) {
273 return <Loader minHeight="0px" />;
274 }
275
276 return (
277 <div className="row">
278 <div className={showDetails ? 'col-8' : 'col-12'}>
279 <GridTableTools
280 refreshGrid={refreshGrid}
281 isRefreshBtnVisible={isToolRefresh}
282 />
283 <ContextMenuWrapper
284 setMenuData={setMenuData}
285 isContextMenuVisible={isContextMenuVisible}
286 isLoading={isMenuLoading}
287 menuData={menuData}
288 parentDivId={mainTableId}
289 scrollDetectSelector={scrollDetectSelector}
290 outerContextMenuComponents={outerContextMenuComponents}
291 elementsToIgnoreOnRightClick={[
292 'ant-table-thead',
293 'ant-pagination ant-table-pagination ant-table-pagination-right',
294 'ant-table-footer',
295 ]}
296 parentElementsToIgnoreWithoutChildrenOnRightClick={['ant-table-wrapper']}
297 >
298 <TableStyled
299 tableLayout="auto"
300 loading={isLoading}
301 pagination={getPaginationConfig()}
302 dataSource={dataS}
303 scroll={{ x: 'max-content' }}
304 columns={columns2}
305 onChange={(pagination, filters, sorter) => {
306 // console.log('pagination', pagination);
307 // console.log('filters', filters);
308 handleSortingChange(sorter);
309 }}
310 rowSelection={{
311 columnWidth: 0,
312 renderCell: () => '',
313 selectedRowKeys: selectedElements
314 .filter(
315 (row: ISelectedRow) =>
316 row.mainTableId === mainTableId
317 && row.innerTableId === null,
318 )
319 .map((inner) => inner.id),
320 hideSelectAll: true,
321 }}
322 onRow={getRowConfig}
323 components={{
324 body: {
325 wrapper: DragabbleTableWrapper,
326 // row: DragabbleTableRow,
327 },
328 }}
329 locale={{
330 emptyText: (
331 <Empty
332 description="Brak wyników wyszukiwania."
333 image={Empty.PRESENTED_IMAGE_DEFAULT}
334 />
335 ),
336 triggerDesc: 'Kliknij aby posortować malejąco',
337 triggerAsc: 'Kliknij aby posortować rosnąco',
338 cancelSort: 'Kliknij aby anulować sortowanie',
339 }}
340 expandable={{
341 expandedRowRender: (record: any, index: number, indent:number, isExpanded: boolean) => {
342 return (
343 <GridTableNested
344 record={record}
345 isExpanded={isExpanded}
346 handleClick={handleClick}
347 handleRightClick={handleRightClick}
348 mainTableId={mainTableId}
349 selectedElements={selectedElements}
350 outerCellGridComponents={outerCellGridComponents}
351 />
352 );
353 },
354 rowExpandable: (record: any) => {
355 return record.hasOwnProperty('expandable');
356 },
357 expandIconColumnIndex:
358 options && options.expandable ? 1 : -1,
359 }}
360 footer={() => showFooter && (
361 <GridFooter
362 total={total}
363 selectedRowKeys={selectedElements}
364 />
365 )}
366 />
367 {/* { renderColumns } */}
368 {/* </TableStyled> */}
369 </ContextMenuWrapper>
370 </div>
371 {showDetails && (
372 <div className="col-4">
373 <GridTableDetails />
374 </div>
375 )}
376 </div>
377 );
378};
379
380export default React.memo(GridTable);
381