asb_cloud_front/src/components/Table/index.tsx

312 lines
9.6 KiB
TypeScript
Raw Normal View History

import { memo, useEffect, useState, ReactNode } from 'react'
import { InputNumber, Select, Table as RawTable, Tag, SelectProps, TableProps } from 'antd'
import { DefaultOptionType, SelectValue } from 'antd/lib/select'
import { Rule } from 'rc-field-form/lib/interface'
import { tryAddKeys } from './EditableTable'
import { makeNumericSorter, makeStringSorter } from './sorters'
export { makeDateSorter, makeNumericSorter, makeStringSorter } from './sorters'
export { EditableTable, makeActionHandler } from './EditableTable'
export { DatePickerWrapper } from './DatePickerWrapper'
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
export const defaultPagination = {
defaultPageSize: 14,
showSizeChanger: true,
}
export const makeNumericRender = (fixed?: number) => (value: any, _: object): ReactNode => {
let val = '-'
if ((value ?? null) !== null && Number.isFinite(+value)) {
val = (fixed ?? null) !== null
? (+value).toFixed(fixed)
: (+value).toPrecision(5)
}
return (
<div className={'text-align-r-container'}>
<span>{val}</span>
</div>
)
}
export const makeNumericColumnOptions = (fixed?: number, sorterKey?: string) => ({
editable: true,
initialValue: 0,
width: 100,
sorter: sorterKey ? makeNumericSorter(sorterKey) : null,
formItemRules: [
{
required: true,
message: 'Введите число',
pattern: RegExpIsFloat,
},
],
render: makeNumericRender(fixed),
})
2021-08-30 10:04:44 +05:00
/*
other - объект с дополнительными свойствами колонки
поддерживаются все базовые свойства из описания https://ant.design/components/table/#Column
2021-08-30 10:04:44 +05:00
плю дополнительные для колонок EditableTable: */
interface columnPropsOther {
// редактируемая колонка
editable?: boolean
// react компонента редактора
input?: ReactNode
2021-08-30 10:04:44 +05:00
// значение может быть пустым
isRequired?: boolean
// css класс для <FormItem/>, если требуется
formItemClass?: string
// массив правил валидации значений https://ant.design/components/form/#Rule
formItemRules?: Rule[]
2021-08-30 10:04:44 +05:00
// дефолтное значение при добавлении новой строки
initialValue?: string | number
render?: (...attributes: any) => any
2021-08-30 10:04:44 +05:00
}
export const makeColumn = (title: string | ReactNode, key: string, other?: columnPropsOther) => ({
title: title,
key: key,
dataIndex: key,
...other,
})
export const makeColumnsPlanFact = (
title: string | ReactNode,
key: string | string[],
columsOther?: any | any[],
gruopOther?: any
) => {
let keyPlanLocal: string
let keyFactLocal: string
if (key instanceof Array) {
keyPlanLocal = key[0]
keyFactLocal = key[1]
} else {
keyPlanLocal = key + 'Plan'
keyFactLocal = key + 'Fact'
}
let columsOtherLocal : any[2]
if (columsOther instanceof Array)
columsOtherLocal = [columsOther[0], columsOther[1]]
else
columsOtherLocal = [columsOther, columsOther]
return {
title: title,
...gruopOther,
children: [
makeColumn('план', keyPlanLocal, columsOtherLocal[0]),
makeColumn('факт', keyFactLocal, columsOtherLocal[1]),
]
}
}
export const makeFilterTextMatch = (key: string | number) => (
(filterValue: string | number, dataItem: any) => dataItem[key] === filterValue
)
export const makeGroupColumn = (title: string, children: object[]) => ({ title, children })
export const makeTextColumn = (
title: string,
dataIndex: string,
filters: object[],
sorter?: (key: string) => any,
render?: any,
other?: any
) => ({
title: title,
dataIndex: dataIndex,
key: dataIndex,
filters: filters,
onFilter: filters ? makeFilterTextMatch(dataIndex) : null,
sorter: sorter ?? makeStringSorter(dataIndex),
render: render,
...other
})
export const makeNumericColumn = (
title: string,
dataIndex: string,
filters: object[],
filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any,
width: string,
other?: columnPropsOther
) => ({
title: title,
dataIndex: dataIndex,
key: dataIndex,
filters: filters,
onFilter: filterDelegate ? filterDelegate(dataIndex) : null,
sorter: makeNumericSorter(dataIndex),
width: width,
input: <InputNumber style={{ width: '100%' }}/>,
render: renderDelegate ?? makeNumericRender(),
align: 'right',
...other
})
export const makeNumericColumnPlanFact = (
title: string,
dataIndex: string,
filters: object[],
filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any,
width: string
) => makeGroupColumn(title, [
makeNumericColumn('п', dataIndex + 'Plan', filters, filterDelegate, renderDelegate, width),
makeNumericColumn('ф', dataIndex + 'Fact', filters, filterDelegate, renderDelegate, width),
])
export const makeNumericStartEnd = (
title: string,
dataIndex: string,
2021-10-12 15:03:43 +05:00
fixed: number,
filters: object[],
filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any,
width: string,
) => makeGroupColumn(title, [
2021-10-12 15:03:43 +05:00
makeNumericColumn('старт', dataIndex + 'Start', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Start')),
makeNumericColumn('конец', dataIndex + 'End', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'End'))
])
export const makeNumericMinMax = (
title: string,
dataIndex: string,
fixed: number,
filters: object[],
filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any,
width: string,
) => makeGroupColumn(title, [
makeNumericColumn('мин', dataIndex + 'Min', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Min')),
makeNumericColumn('макс', dataIndex + 'Max', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Max')),
])
export const makeNumericAvgRange = (
title: string,
dataIndex: string,
fixed: number,
filters: object[],
filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any,
width: string
) => makeGroupColumn(title, [
makeNumericColumn('мин', dataIndex + 'Min', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Min')),
makeNumericColumn('сред', dataIndex + 'Avg', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Avg')),
makeNumericColumn('макс', dataIndex + 'Max', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, dataIndex + 'Max'))
])
export const makeSelectColumn = <T extends unknown = string>(
title: string,
dataIndex: string,
options: DefaultOptionType[],
defaultValue?: T,
other?: columnPropsOther,
selectOther?: SelectProps<SelectValue>
) => makeColumn(title, dataIndex, {
...other,
input: <Select options={options} {...selectOther}/>,
render: (value) => {
const item = options?.find(option => option?.value === value)
return other?.render?.(item?.value) ?? item?.label ?? defaultValue ?? value ?? '--'
}
})
const makeTagInput = <T extends Record<string, any>>(value_key: string, label_key: string) => memo<{
options: T[],
value?: T[],
onChange?: (values: T[]) => void,
other?: SelectProps<SelectValue>,
}>(({ options, value, onChange, other }) => {
const [selectOptions, setSelectOptions] = useState<DefaultOptionType[]>([])
const [selectedValue, setSelectedValue] = useState<SelectValue>([])
useEffect(() => {
setSelectOptions(options.map((elm) => ({
value: String(elm[value_key]),
label: elm[label_key],
})))
}, [options])
useEffect(() => {
setSelectedValue(value?.map((elm) => String(elm[value_key])) ?? [])
}, [value])
const onSelectChange = (rawValues?: SelectValue) => {
let values: any[] = []
if (typeof rawValues === 'string')
values = rawValues.split(',')
else if (Array.isArray(rawValues))
values = rawValues
const objectValues: T[] = values.reduce((out: T[], value: string) => {
const res = options.find((option) => String(option[value_key]) === String(value))
if (res) out.push(res)
return out
}, [])
onChange?.(objectValues)
}
return (
<Select
{...other}
mode={'tags'}
options={selectOptions}
value={selectedValue}
onChange={onSelectChange}
/>
)
})
export const makeTagColumn = <T extends Record<string, any>>(
title: string,
dataIndex: string,
options: T[],
value_key: string,
label_key: string,
other?: columnPropsOther,
tagOther?: SelectProps<SelectValue>
) => {
const InputComponent = makeTagInput<T>(value_key, label_key)
return makeColumn(title, dataIndex, {
...other,
render: (item?: T[]) => item?.map((elm: T) => <Tag key={elm[label_key]} color={'blue'}>{other?.render?.(elm) ?? elm[label_key]}</Tag>) ?? '-',
input: <InputComponent options={options} other={tagOther} />,
})
}
type PaginationContainer = {
skip?: number
take?: number
count?: number
items?: any[] | null
}
export const makePaginationObject = (сontainer: PaginationContainer, ...other: any) => ({
...other,
pageSize: сontainer.take,
total: сontainer.count ?? сontainer.items?.length ?? 0,
current: 1 + Math.floor((сontainer.skip ?? 0) / (сontainer.take ?? 1))
})
export type TableContainer = TableProps<any> & {
dataSource: any[]
children?: ReactNode
}
export const Table = memo<TableContainer>(({dataSource, children, ...other}) => (
<RawTable dataSource={tryAddKeys(dataSource)} {...other}>{children}</RawTable>
))