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