Частична улучшена типизация Editable table

This commit is contained in:
goodmice 2022-10-21 10:12:26 +05:00
parent 32ec3c22d2
commit 4ad6f91c8a
No known key found for this signature in database
GPG Key ID: 63EA771203189CF1
2 changed files with 89 additions and 52 deletions

View File

@ -3,7 +3,7 @@ import { Form, Button, Popconfirm, TableProps } from 'antd'
import { EditOutlined, SaveOutlined, PlusOutlined, CloseCircleOutlined, DeleteOutlined } from '@ant-design/icons' import { EditOutlined, SaveOutlined, PlusOutlined, CloseCircleOutlined, DeleteOutlined } from '@ant-design/icons'
import { invokeWebApiWrapperAsync } from '@components/factory' import { invokeWebApiWrapperAsync } from '@components/factory'
import { hasPermission } from '@utils' import { hasPermission, isDev } from '@utils'
import { Table, TableColumns } from '.' import { Table, TableColumns } from '.'
import { EditableCell } from './EditableCell' import { EditableCell } from './EditableCell'
@ -87,7 +87,7 @@ const EditableTableComponents = { body: { cell: EditableCell }}
export type EditableTableProps<T extends CrudObject, S extends CrudService<T>> = TableProps<T> & { export type EditableTableProps<T extends CrudObject, S extends CrudService<T>> = TableProps<T> & {
columns: ColumnsType<T> & TableColumns<T> columns: ColumnsType<T> & TableColumns<T>
dataSource?: T[] dataSource?: T[]
onChange onChange?: ((data: T[]) => Promise<void>)
onRowAdd?: TableActionProps<T, S> | ((record: T) => Promise<void>) onRowAdd?: TableActionProps<T, S> | ((record: T) => Promise<void>)
onRowEdit?: TableActionProps<T, S> | ((record: T) => Promise<void>) onRowEdit?: TableActionProps<T, S> | ((record: T) => Promise<void>)
onRowDelete?: TableActionProps<T, S> | ((record: T) => Promise<void>) onRowDelete?: TableActionProps<T, S> | ((record: T) => Promise<void>)
@ -130,14 +130,19 @@ const _EditableTable = <T extends CrudObject, S extends CrudService<T>>({
}, [form]) }, [form])
const cancel = useCallback(() => { const cancel = useCallback(() => {
if (editingKey === newRowKeyValue) { setEditingKey((prev) => {
const newData = [...data] if (prev === newRowKeyValue) {
const index = newData.findIndex((item) => newRowKeyValue === item.key) setData((prev) => {
newData.splice(index, 1) const newData = [...prev]
setData(newData) const index = newData.findIndex((item) => newRowKeyValue === item.key)
} newData.splice(index, 1)
setEditingKey('') return newData
}, [data, editingKey]) })
}
return ''
})
}, [])
const addNewRow = useCallback(async () => { const addNewRow = useCallback(async () => {
const newRow: any = { key: newRowKeyValue } // TODO: Исправить тип const newRow: any = { key: newRowKeyValue } // TODO: Исправить тип
@ -146,43 +151,38 @@ const _EditableTable = <T extends CrudObject, S extends CrudService<T>>({
}, [edit]) }, [edit])
const save = useCallback(async (record: T) => { const save = useCallback(async (record: T) => {
let row
try { try {
const row = await form.validateFields() row = await form.validateFields()
const newData = [...data]
const index = newData.findIndex((item) => record.key === item.key)
const item = newData[index]
const newItem = { ...item, ...row }
newData.splice(index, 1, newItem)
if (item.key === newRowKeyValue)
item.key = newRowKeyValue + newData.length
const isAdding = editingKey === newRowKeyValue
setEditingKey('')
setData(newData)
if (isAdding)
try {
onAdd?.(newItem)
} catch (err) {
console.log('callback onRowAdd fault:', err)
}
else
try {
onEdit?.(newItem)
} catch (err) {
console.log('callback onRowEdit fault:', err)
}
try {
onChange?.(newData)
} catch (err) {
console.log('callback onChange fault:', err)
}
} catch (errInfo) { } catch (errInfo) {
console.log('Validate Failed:', errInfo) if (isDev())
console.warn('Validate Failed:', errInfo)
return
} }
const newData = [...data]
const index = newData.findIndex((item) => record.key === item.key)
const item = newData[index]
const newItem = { ...item, ...row }
newData.splice(index, 1, newItem)
if (item.key === newRowKeyValue)
item.key += newData.length
try {
if (editingKey === newRowKeyValue)
await onAdd?.(newItem)
else
await onEdit?.(newItem)
await onChange?.(newData)
} catch (err) {
if (isDev())
console.warn(err)
}
setEditingKey('')
setData(newData)
}, [data, editingKey, form, onChange, onAdd, onEdit]) }, [data, editingKey, form, onChange, onAdd, onEdit])
const deleteRow = useCallback((record: T) => { const deleteRow = useCallback((record: T) => {

View File

@ -1,26 +1,58 @@
import { memo, useCallback, useEffect, useState } from 'react' import { memo, ReactNode, useCallback, useEffect, useState } from 'react'
import { ColumnGroupType, ColumnType } from 'antd/lib/table' import { ColumnGroupType, ColumnType } from 'antd/lib/table'
import { Table as RawTable, TableProps } from 'antd' import { Table as RawTable, TableProps } from 'antd'
import { NamePath } from 'antd/lib/form/interface'
import type { OmitExtends } from '@utils/types' import type { OmitExtends } from '@utils/types'
import { applyTableSettings, getTableSettings, setTableSettings, TableColumnSettings, TableSettings } from '@utils' import { applyTableSettings, getTableSettings, setTableSettings, TableColumnSettings, TableSettings } from '@utils'
import TableSettingsChanger from './TableSettingsChanger' import TableSettingsChanger from './TableSettingsChanger'
import { DataType, RenderMethod } from './Columns'
import { tryAddKeys } from './EditableTable' import { tryAddKeys } from './EditableTable'
import '@styles/index.css' import '@styles/index.css'
export type BaseTableColumn<T> = ColumnGroupType<T> | ColumnType<T> export type BaseTableColumn<T> = ColumnGroupType<T> | ColumnType<T>
export type TableColumns<T> = OmitExtends<BaseTableColumn<T>, TableColumnSettings>[] export type TableColumns<T> = (OmitExtends<Omit<BaseTableColumn<T>, 'key' | 'render'>, TableColumnSettings> & {
key: NamePath
render: RenderMethod<T | null>
})[]
export type TableContainer<T> = Omit<TableProps<T>, 'columns'> & { type MergedTableColumns<T> = (OmitExtends<Omit<BaseTableColumn<T>, 'render'>, TableColumnSettings> & {
render: RenderMethod<T | null>
})[]
export type TableContainer<T> = Omit<TableProps<T>, 'columns' | 'dataSource'> & {
columns: TableColumns<T> columns: TableColumns<T>
tableName?: string tableName?: string
showSettingsChanger?: boolean showSettingsChanger?: boolean
dataSource: DataType<T>[]
} }
const _Table = <T extends object>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer<T>) => { export const keyToString = (key: NamePath): string => Array.isArray(key) ? key.join('.') : String(key)
const [newColumns, setNewColumns] = useState<TableColumns<T>>([])
const defaultRender = <T,>(data: T): ReactNode => <>{data}</>
const getValueFromKey = <T,>(key: NamePath, record: DataType<T>): T | null => {
if (!key) return null
if (!Array.isArray(key))
return record[String(key)]
if (key.length <= 0) return null
const child = record[key[0]]
if (key.length === 1) return child
if (typeof child !== 'object') return null
return getValueFromKey(key.slice(1), child as DataType<T>)
}
const columnRenderWrapper = <T,>(key: NamePath, render?: RenderMethod<T | null>): RenderMethod<T | null> => (_: T | null, record?: DataType<T | null>, index?: number) => {
if (!record) return '-'
if (render)
return render(getValueFromKey(key, record), record, index)
return defaultRender(getValueFromKey(key, record))
}
const _Table = <T extends Record<string, any>>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer<T>) => {
const [newColumns, setNewColumns] = useState<MergedTableColumns<T>>([])
const [settings, setSettings] = useState<TableSettings>({}) const [settings, setSettings] = useState<TableSettings>({})
const onSettingsChanged = useCallback((settings?: TableSettings | null) => { const onSettingsChanged = useCallback((settings?: TableSettings | null) => {
@ -31,7 +63,12 @@ const _Table = <T extends object>({ columns, dataSource, tableName, showSettings
useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName]) useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName])
useEffect(() => setNewColumns(() => { useEffect(() => setNewColumns(() => {
const newColumns = applyTableSettings(columns, settings) const mergedColumns = applyTableSettings(columns, settings)
const newColumns = mergedColumns.map((column) => ({
...column,
render: columnRenderWrapper(column.key, column.render),
key: keyToString(column.key),
}))
if (tableName && showSettingsChanger) { if (tableName && showSettingsChanger) {
const oldTitle = newColumns[0].title const oldTitle = newColumns[0].title
newColumns[0].title = (props) => ( newColumns[0].title = (props) => (
@ -45,9 +82,9 @@ const _Table = <T extends object>({ columns, dataSource, tableName, showSettings
}), [settings, columns, onSettingsChanged, showSettingsChanger, tableName]) }), [settings, columns, onSettingsChanged, showSettingsChanger, tableName])
return ( return (
<RawTable<T> <RawTable<DataType<T>>
columns={newColumns} columns={newColumns}
dataSource={tryAddKeys<T>(dataSource as any)} dataSource={tryAddKeys<DataType<T>>(dataSource)}
{...other} {...other}
/> />
) )