asb_cloud_front/src/components/Table/index.tsx
goodm2ice 0c092aa138 * Добавлено управление столбцами таблиц
* Сменён цвет фона страницы входа
* Лого вынесено вне формы входа
2022-03-02 21:17:27 +05:00

371 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { memo, useEffect, useState, ReactNode } from 'react'
import { InputNumber, Select, Tag, SelectProps } from 'antd'
import { ColumnProps } from 'antd/lib/table'
import { DefaultOptionType, SelectValue } from 'antd/lib/select'
import { Rule } from 'rc-field-form/lib/interface'
import { SimpleTimezoneDto } from '@api'
import { makeNumericSorter, makeStringSorter } from './sorters'
export { makeDateSorter, makeNumericSorter, makeStringSorter } from './sorters'
export { EditableTable, makeActionHandler } from './EditableTable'
export { DatePickerWrapper } from './DatePickerWrapper'
export { Table } from './Table'
export type { BaseTableColumn, TableColumns, TableContainer } from './Table'
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): columnPropsOther => ({
editable: true,
initialValue: 0,
width: 100,
sorter: sorterKey ? makeNumericSorter(sorterKey) : undefined,
formItemRules: [
{
required: true,
message: 'Введите число',
pattern: RegExpIsFloat,
},
],
render: makeNumericRender(fixed),
})
/*
other - объект с дополнительными свойствами колонки
поддерживаются все базовые свойства из описания https://ant.design/components/table/#Column
плю дополнительные для колонок EditableTable: */
type columnPropsOther<T = any> = ColumnProps<T> & {
// редактируемая колонка
editable?: boolean
// react компонента редактора
input?: ReactNode
// значение может быть пустым
isRequired?: boolean
// css класс для <FormItem/>, если требуется
formItemClass?: string
// массив правил валидации значений https://ant.design/components/form/#Rule
formItemRules?: Rule[]
// дефолтное значение при добавлении новой строки
initialValue?: string | number
render?: (...attributes: any) => any
}
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,
fixed: number,
filters: object[],
filterDelegate: (key: string | number) => any,
renderDelegate: (_: any, row: object) => any,
width: string,
) => makeGroupColumn(title, [
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))
})
const rawTimezones = {
'Калининград': 2,
'Москва': 3,
'Самара': 4,
'Екатеринбург': 5,
'Омск': 6,
'Красноярск': 7,
'Новосибирск': 7,
'Иркутск': 8,
'Чита': 9,
'Владивосток': 10,
'Магадан': 11,
'Южно-Сахалинск': 11,
'Среднеколымск': 11,
'Анадырь': 12,
'Петропавловск-Камчатский': 12,
}
const timezoneOptions = Object
.entries(rawTimezones)
.sort((a, b) => a[1] - b[1])
.map(([id, hours]) => ({
label: `UTC${hours > 0 ? '+':''}${('0' + hours).slice(-2)} :: ${id}`,
value: id,
}))
export const TimezoneSelect = memo<SelectProps>(({ onChange, ...other }) => {
const [id, setId] = useState<keyof typeof rawTimezones | null>(null)
useEffect(() => onChange?.({
timezoneId: id,
hours: id ? rawTimezones[id] : 0,
isOverride: false,
}, []), [id, onChange])
return (<Select {...other} onChange={setId} value={id} />)
})
export const makeTimezoneRenderer = () => (timezone?: SimpleTimezoneDto) => {
if (!timezone) return 'UTC~?? :: Неизвестно'
const { hours, timezoneId } = timezone
return `UTC${hours && hours > 0 ? '+':''}${hours ? ('0' + hours).slice(-2) : '~??'} :: ${timezoneId ?? 'Неизвестно'}`
}
export const makeTimezoneColumn = (
title: string = 'Зона',
key: string = 'timezone',
defaultValue: any = null,
allowClear: boolean = true,
other?: columnPropsOther
) => makeColumn(title, key, {
width: 100,
editable: true,
render: makeTimezoneRenderer(),
input: (
<TimezoneSelect
allowClear={allowClear}
options={timezoneOptions}
defaultValue={defaultValue}
/>
),
...other
})