Частична улучшена типизация 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 { invokeWebApiWrapperAsync } from '@components/factory'
import { hasPermission } from '@utils'
import { hasPermission, isDev } from '@utils'
import { Table, TableColumns } from '.'
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> & {
columns: ColumnsType<T> & TableColumns<T>
dataSource?: T[]
onChange
onChange?: ((data: T[]) => Promise<void>)
onRowAdd?: TableActionProps<T, S> | ((record: T) => Promise<void>)
onRowEdit?: 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])
const cancel = useCallback(() => {
if (editingKey === newRowKeyValue) {
const newData = [...data]
const index = newData.findIndex((item) => newRowKeyValue === item.key)
newData.splice(index, 1)
setData(newData)
}
setEditingKey('')
}, [data, editingKey])
setEditingKey((prev) => {
if (prev === newRowKeyValue) {
setData((prev) => {
const newData = [...prev]
const index = newData.findIndex((item) => newRowKeyValue === item.key)
newData.splice(index, 1)
return newData
})
}
return ''
})
}, [])
const addNewRow = useCallback(async () => {
const newRow: any = { key: newRowKeyValue } // TODO: Исправить тип
@ -146,43 +151,38 @@ const _EditableTable = <T extends CrudObject, S extends CrudService<T>>({
}, [edit])
const save = useCallback(async (record: T) => {
let row
try {
const 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)
}
row = await form.validateFields()
} 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])
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 { Table as RawTable, TableProps } from 'antd'
import { NamePath } from 'antd/lib/form/interface'
import type { OmitExtends } from '@utils/types'
import { applyTableSettings, getTableSettings, setTableSettings, TableColumnSettings, TableSettings } from '@utils'
import TableSettingsChanger from './TableSettingsChanger'
import { DataType, RenderMethod } from './Columns'
import { tryAddKeys } from './EditableTable'
import '@styles/index.css'
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>
tableName?: string
showSettingsChanger?: boolean
dataSource: DataType<T>[]
}
const _Table = <T extends object>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer<T>) => {
const [newColumns, setNewColumns] = useState<TableColumns<T>>([])
export const keyToString = (key: NamePath): string => Array.isArray(key) ? key.join('.') : String(key)
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 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(() => 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) {
const oldTitle = newColumns[0].title
newColumns[0].title = (props) => (
@ -45,9 +82,9 @@ const _Table = <T extends object>({ columns, dataSource, tableName, showSettings
}), [settings, columns, onSettingsChanged, showSettingsChanger, tableName])
return (
<RawTable<T>
<RawTable<DataType<T>>
columns={newColumns}
dataSource={tryAddKeys<T>(dataSource as any)}
dataSource={tryAddKeys<DataType<T>>(dataSource)}
{...other}
/>
)