refactor: models list table columns from props
This commit is contained in:
@@ -4,16 +4,16 @@ import { SealColumnProps } from '../types';
|
||||
import TableHeader from './table-header';
|
||||
|
||||
interface HeaderProps {
|
||||
children: React.ReactNode[];
|
||||
columns: SealColumnProps[];
|
||||
onSort?: (dataIndex: string, order: any) => void;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = (props) => {
|
||||
const { onSort } = props;
|
||||
|
||||
return (
|
||||
<Row className="row">
|
||||
{React.Children.map(props.children, (child, i) => {
|
||||
const { props: columnProps } = child as any;
|
||||
{props.columns?.map((columnProps, i) => {
|
||||
const {
|
||||
title,
|
||||
dataIndex,
|
||||
@@ -24,25 +24,22 @@ const Header: React.FC<HeaderProps> = (props) => {
|
||||
sorter,
|
||||
defaultSortOrder
|
||||
} = columnProps as SealColumnProps;
|
||||
if (React.isValidElement(child)) {
|
||||
return (
|
||||
<Col span={span} key={i}>
|
||||
<TableHeader
|
||||
onSort={onSort}
|
||||
sorter={sorter}
|
||||
dataIndex={dataIndex}
|
||||
sortOrder={sortOrder}
|
||||
defaultSortOrder={defaultSortOrder}
|
||||
title={title}
|
||||
style={headerStyle}
|
||||
firstCell={i === 0}
|
||||
align={align}
|
||||
lastCell={i === props.children.length - 1}
|
||||
></TableHeader>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
return (
|
||||
<Col span={span} key={dataIndex || i}>
|
||||
<TableHeader
|
||||
onSort={onSort}
|
||||
sorter={sorter}
|
||||
dataIndex={dataIndex}
|
||||
sortOrder={sortOrder}
|
||||
defaultSortOrder={defaultSortOrder}
|
||||
title={title}
|
||||
style={headerStyle}
|
||||
firstCell={i === 0}
|
||||
align={align}
|
||||
lastCell={i === props.columns.length - 1}
|
||||
></TableHeader>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Button, Checkbox } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
interface RowPrefixProps {
|
||||
expandable?: boolean | React.ReactNode;
|
||||
enableSelection?: boolean;
|
||||
expanded?: boolean;
|
||||
checked?: boolean;
|
||||
handleRowExpand?: () => void;
|
||||
handleSelectChange?: (e: any) => void;
|
||||
}
|
||||
|
||||
const RowPrefix: React.FC<RowPrefixProps> = (props) => {
|
||||
const {
|
||||
expandable,
|
||||
enableSelection,
|
||||
expanded,
|
||||
checked,
|
||||
handleRowExpand,
|
||||
handleSelectChange
|
||||
} = props;
|
||||
|
||||
if (expandable && enableSelection) {
|
||||
return (
|
||||
<div className="row-prefix-wrapper">
|
||||
<span style={{ marginRight: 5 }}>
|
||||
{_.isBoolean(expandable) ? (
|
||||
<Button type="text" size="small" onClick={handleRowExpand}>
|
||||
{expanded ? <DownOutlined /> : <RightOutlined />}
|
||||
</Button>
|
||||
) : (
|
||||
expandable
|
||||
)}
|
||||
</span>
|
||||
<Checkbox onChange={handleSelectChange} checked={checked}></Checkbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (expandable) {
|
||||
return (
|
||||
<div className="row-prefix-wrapper">
|
||||
{_.isBoolean(expandable) ? (
|
||||
<Button type="text" size="small" onClick={handleRowExpand}>
|
||||
{expanded ? <DownOutlined /> : <RightOutlined />}
|
||||
</Button>
|
||||
) : (
|
||||
expandable
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (enableSelection) {
|
||||
return (
|
||||
<div className="row-prefix-wrapper">
|
||||
{<Checkbox onChange={handleSelectChange} checked={checked}></Checkbox>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default RowPrefix;
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Checkbox } from 'antd';
|
||||
import '../styles/skeleton.less';
|
||||
|
||||
const RowSkeleton = () => {
|
||||
return (
|
||||
<div className="row-skeleton">
|
||||
<div className="holder"></div>
|
||||
<Checkbox></Checkbox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RowSkeleton;
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Empty } from 'antd';
|
||||
import React from 'react';
|
||||
import { SealColumnProps } from '../types';
|
||||
import TableRow from './table-row';
|
||||
|
||||
interface TableBodyProps {
|
||||
dataSource: any[];
|
||||
columns: React.ReactNode[];
|
||||
columns: SealColumnProps[];
|
||||
rowKey: string;
|
||||
rowSelection?: any;
|
||||
expandable?: any;
|
||||
|
||||
@@ -2,14 +2,21 @@ import useSetChunkRequest, {
|
||||
createAxiosToken
|
||||
} from '@/hooks/use-chunk-request';
|
||||
import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Button, Checkbox, Col, Empty, Row, Spin } from 'antd';
|
||||
import { Col, Empty, Row, Spin } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import RowContext from '../row-context';
|
||||
import TableContext from '../table-context';
|
||||
import { RowContextProps, SealTableProps } from '../types';
|
||||
import RowPrefix from './row-prefix';
|
||||
import TableColumn from './seal-column';
|
||||
|
||||
const TableRow: React.FC<
|
||||
RowContextProps &
|
||||
@@ -38,7 +45,7 @@ const TableRow: React.FC<
|
||||
}>(TableContext);
|
||||
const { setChunkRequest } = useSetChunkRequest();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [checked, setChecked] = useState(false);
|
||||
// const [checked, setChecked] = useState(false);
|
||||
const [childrenData, setChildrenData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [firstLoad, setFirstLoad] = useState(true);
|
||||
@@ -56,17 +63,6 @@ const TableRow: React.FC<
|
||||
// callback: (list) => renderChildren?.(list)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (rowSelection) {
|
||||
const { selectedRowKeys } = rowSelection;
|
||||
if (selectedRowKeys.includes(record[rowKey])) {
|
||||
setChecked(true);
|
||||
} else {
|
||||
setChecked(false);
|
||||
}
|
||||
}
|
||||
}, [rowSelection]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (pollTimer.current) {
|
||||
@@ -77,7 +73,23 @@ const TableRow: React.FC<
|
||||
};
|
||||
}, []);
|
||||
|
||||
const checked = useMemo(() => {
|
||||
return rowSelection?.selectedRowKeys?.includes(record[rowKey]);
|
||||
}, [rowSelection?.selectedRowKeys, record, rowKey]);
|
||||
|
||||
const renderChildrenData = () => {
|
||||
if (childrenData.length === 0) {
|
||||
return (
|
||||
<Empty
|
||||
image={loading ? null : Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={loading ? null : undefined}
|
||||
style={{
|
||||
marginBlock: 0,
|
||||
height: 54
|
||||
}}
|
||||
></Empty>
|
||||
);
|
||||
}
|
||||
return renderChildren?.(childrenData, record);
|
||||
};
|
||||
|
||||
@@ -92,7 +104,7 @@ const TableRow: React.FC<
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadChildren = async () => {
|
||||
const handleLoadChildren = useCallback(async () => {
|
||||
try {
|
||||
axiosToken.current?.cancel?.();
|
||||
axiosToken.current = createAxiosToken();
|
||||
@@ -109,7 +121,7 @@ const TableRow: React.FC<
|
||||
} finally {
|
||||
setFirstLoad(false);
|
||||
}
|
||||
};
|
||||
}, [record, loadChildren]);
|
||||
|
||||
const filterUpdateChildrenHandler = () => {
|
||||
if (!expandedRowKeys?.includes(record[rowKey])) {
|
||||
@@ -224,51 +236,6 @@ const TableRow: React.FC<
|
||||
};
|
||||
}, [updateChild, tableContext.allChildren]);
|
||||
|
||||
const renderRowPrefix = () => {
|
||||
if (expandable && rowSelection) {
|
||||
return (
|
||||
<div className="row-prefix-wrapper">
|
||||
<span style={{ marginRight: 5 }}>
|
||||
{_.isBoolean(expandable) ? (
|
||||
<Button type="text" size="small" onClick={handleRowExpand}>
|
||||
{expanded ? <DownOutlined /> : <RightOutlined />}
|
||||
</Button>
|
||||
) : (
|
||||
expandable
|
||||
)}
|
||||
</span>
|
||||
<Checkbox onChange={handleSelectChange} checked={checked}></Checkbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (expandable) {
|
||||
return (
|
||||
<div className="row-prefix-wrapper">
|
||||
{_.isBoolean(expandable) ? (
|
||||
<Button type="text" size="small" onClick={handleRowExpand}>
|
||||
{expanded ? <DownOutlined /> : <RightOutlined />}
|
||||
</Button>
|
||||
) : (
|
||||
expandable
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (rowSelection) {
|
||||
return (
|
||||
<div className="row-prefix-wrapper">
|
||||
{
|
||||
<Checkbox
|
||||
onChange={handleSelectChange}
|
||||
checked={checked}
|
||||
></Checkbox>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<RowContext.Provider value={{ row: { ...record, rowIndex }, onCell }}>
|
||||
<div className="row-box">
|
||||
@@ -277,40 +244,30 @@ const TableRow: React.FC<
|
||||
'row-wrapper-selected': checked
|
||||
})}
|
||||
>
|
||||
{renderRowPrefix()}
|
||||
<RowPrefix
|
||||
expandable={expandable}
|
||||
enableSelection={rowSelection?.enableSelection}
|
||||
expanded={expanded}
|
||||
checked={checked}
|
||||
handleRowExpand={handleRowExpand}
|
||||
handleSelectChange={handleSelectChange}
|
||||
></RowPrefix>
|
||||
<Row className="seal-table-row">
|
||||
{React.Children.map(columns, (child) => {
|
||||
const { props: columnProps } = child as any;
|
||||
if (React.isValidElement(child)) {
|
||||
return (
|
||||
<Col
|
||||
key={`${columnProps.dataIndex}-${rowIndex}`}
|
||||
span={columnProps.span}
|
||||
>
|
||||
{child}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
return <Col span={columnProps.span}></Col>;
|
||||
{columns?.map((columnProps) => {
|
||||
return (
|
||||
<Col
|
||||
key={`${columnProps.dataIndex}-${rowIndex}`}
|
||||
span={columnProps.span}
|
||||
>
|
||||
<TableColumn {...columnProps}></TableColumn>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="expanded-row">
|
||||
<Spin spinning={loading}>
|
||||
{childrenData.length ? (
|
||||
renderChildrenData()
|
||||
) : (
|
||||
<Empty
|
||||
image={loading ? null : Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={loading ? null : undefined}
|
||||
style={{
|
||||
marginBlock: 0,
|
||||
height: 54
|
||||
}}
|
||||
></Empty>
|
||||
)}
|
||||
</Spin>
|
||||
<Spin spinning={loading}>{renderChildrenData()}</Spin>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Checkbox } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import '../styles/skeleton.less';
|
||||
|
||||
const SkeletonItem = () => {
|
||||
return (
|
||||
<div className="row-skeleton">
|
||||
<div className="holder"></div>
|
||||
<Checkbox></Checkbox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
`;
|
||||
|
||||
const TableSkeleton = () => {
|
||||
const dataSource = Array.from({ length: 5 });
|
||||
return (
|
||||
<Wrapper>
|
||||
{dataSource.map((item, index) => (
|
||||
<SkeletonItem key={index} />
|
||||
))}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TableSkeleton);
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Pagination, Spin, type PaginationProps } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import Header from './components/header';
|
||||
import HeaderPrefix from './components/header-prefix';
|
||||
import TableBody from './components/table-body';
|
||||
import './styles/index.less';
|
||||
import { SealTableProps } from './types';
|
||||
import { SealColumnProps, SealTableProps } from './types';
|
||||
|
||||
const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
|
||||
props
|
||||
) => {
|
||||
const {
|
||||
columns,
|
||||
children,
|
||||
rowKey,
|
||||
childParentKey,
|
||||
@@ -29,33 +30,46 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
|
||||
loadChildren,
|
||||
loadChildrenAPI
|
||||
} = props;
|
||||
const [selectState, setSelectState] = useState({
|
||||
selectAll: false,
|
||||
indeterminate: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (rowSelection) {
|
||||
const { selectedRowKeys } = rowSelection;
|
||||
const selectedKeys = new Set(rowSelection.selectedRowKeys);
|
||||
const allRowKeys = props.dataSource.map((record) => record[rowKey]);
|
||||
if (!selectedRowKeys?.length) {
|
||||
setSelectState({
|
||||
selectAll: false,
|
||||
indeterminate: false
|
||||
});
|
||||
} else if (allRowKeys.every((key) => selectedKeys.has(key))) {
|
||||
setSelectState({
|
||||
selectAll: true,
|
||||
indeterminate: false
|
||||
});
|
||||
} else {
|
||||
setSelectState({
|
||||
selectAll: false,
|
||||
indeterminate: true
|
||||
});
|
||||
}
|
||||
const parsedColumns = useMemo(() => {
|
||||
if (columns) return columns;
|
||||
|
||||
return React.Children.toArray(children)
|
||||
.filter(React.isValidElement)
|
||||
.map((child) => {
|
||||
const column = child as React.ReactElement<SealColumnProps>;
|
||||
const { title, dataIndex, key, render, ...restProps } = column.props;
|
||||
|
||||
return {
|
||||
title,
|
||||
dataIndex,
|
||||
key: key || dataIndex,
|
||||
render,
|
||||
...restProps
|
||||
};
|
||||
});
|
||||
}, [columns, children]);
|
||||
|
||||
const selectState = useMemo(() => {
|
||||
const selectedRowKeys = rowSelection?.selectedRowKeys || [];
|
||||
const selectedKeys = new Set(selectedRowKeys);
|
||||
const allRowKeys = props.dataSource.map((record) => record[rowKey]);
|
||||
if (!selectedRowKeys?.length) {
|
||||
return {
|
||||
selectAll: false,
|
||||
indeterminate: false
|
||||
};
|
||||
}
|
||||
if (allRowKeys.every((key) => selectedKeys.has(key))) {
|
||||
return {
|
||||
selectAll: true,
|
||||
indeterminate: false
|
||||
};
|
||||
}
|
||||
return {
|
||||
selectAll: false,
|
||||
indeterminate: true
|
||||
};
|
||||
}, [rowSelection?.selectedRowKeys, props.dataSource, rowKey]);
|
||||
|
||||
const selectAllRows = () => {
|
||||
@@ -103,12 +117,12 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
|
||||
expandable={expandable}
|
||||
enableSelection={rowSelection?.enableSelection}
|
||||
></HeaderPrefix>
|
||||
<Header onSort={onSort}>{children}</Header>
|
||||
<Header onSort={onSort} columns={parsedColumns}></Header>
|
||||
</div>
|
||||
<Spin spinning={loading}>
|
||||
<TableBody
|
||||
dataSource={props.dataSource}
|
||||
columns={children}
|
||||
columns={parsedColumns}
|
||||
rowSelection={rowSelection}
|
||||
expandable={expandable}
|
||||
rowKey={rowKey}
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface SealColumnProps {
|
||||
title: React.ReactNode;
|
||||
render?: (text: any, record: any) => React.ReactNode;
|
||||
dataIndex: string;
|
||||
key?: string;
|
||||
width?: number;
|
||||
span: number;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
@@ -18,6 +19,7 @@ export interface SealColumnProps {
|
||||
};
|
||||
valueType?: 'text' | 'number' | 'date' | 'datetime' | 'time';
|
||||
sortOrder?: 'ascend' | 'descend' | null;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface TableHeaderProps {
|
||||
@@ -26,7 +28,7 @@ export interface TableHeaderProps {
|
||||
sortOrder?: 'ascend' | 'descend' | null;
|
||||
dataIndex: string;
|
||||
onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void;
|
||||
children?: React.ReactNode;
|
||||
children?: React.ReactElement<SealColumnProps>;
|
||||
title: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
firstCell?: boolean;
|
||||
@@ -42,10 +44,11 @@ export interface RowSelectionProps {
|
||||
onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => void;
|
||||
}
|
||||
export interface SealTableProps {
|
||||
columns?: SealColumnProps[];
|
||||
childParentKey?: string;
|
||||
expandedRowKeys?: React.Key[];
|
||||
rowSelection?: RowSelectionProps;
|
||||
children: React.ReactNode[];
|
||||
children?: React.ReactElement<SealColumnProps>[];
|
||||
empty?: React.ReactNode;
|
||||
expandable?: React.ReactNode;
|
||||
dataSource: any[];
|
||||
@@ -65,7 +68,6 @@ export interface SealTableProps {
|
||||
|
||||
export interface RowContextProps {
|
||||
record: Record<string, any>;
|
||||
columns: React.ReactNode[];
|
||||
pollingChildren?: boolean;
|
||||
rowIndex: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user