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 (
{val}
)
}
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 = ColumnProps & {
// редактируемая колонка
editable?: boolean
// react компонента редактора
input?: ReactNode
// значение может быть пустым
isRequired?: boolean
// css класс для , если требуется
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: ,
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 = (
title: string,
dataIndex: string,
options: DefaultOptionType[],
defaultValue?: T,
other?: columnPropsOther,
selectOther?: SelectProps
) => makeColumn(title, dataIndex, {
...other,
input: ,
render: (value) => {
const item = options?.find(option => option?.value === value)
return other?.render?.(item?.value) ?? item?.label ?? defaultValue ?? value ?? '--'
}
})
const makeTagInput = >(value_key: string, label_key: string) => memo<{
options: T[],
value?: T[],
onChange?: (values: T[]) => void,
other?: SelectProps,
}>(({ options, value, onChange, other }) => {
const [selectOptions, setSelectOptions] = useState([])
const [selectedValue, setSelectedValue] = useState([])
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 (
)
})
export const makeTagColumn = >(
title: string,
dataIndex: string,
options: T[],
value_key: string,
label_key: string,
other?: columnPropsOther,
tagOther?: SelectProps
) => {
const InputComponent = makeTagInput(value_key, label_key)
return makeColumn(title, dataIndex, {
...other,
render: (item?: T[]) => item?.map((elm: T) => {other?.render?.(elm) ?? elm[label_key]}) ?? '-',
input: ,
})
}
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(({ onChange, ...other }) => {
const [id, setId] = useState(null)
useEffect(() => onChange?.({
timezoneId: id,
hours: id ? rawTimezones[id] : 0,
isOverride: false,
}, []), [id, onChange])
return ()
})
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: (
),
...other
})