forked from ddrilling/asb_cloud_front
Компоненты колонок разделены на файлы
This commit is contained in:
parent
0c092aa138
commit
592cfc30e2
63
src/components/Table/Columns/index.ts
Normal file
63
src/components/Table/Columns/index.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
import { Rule } from 'antd/lib/form'
|
||||||
|
import { ColumnProps } from 'antd/lib/table'
|
||||||
|
|
||||||
|
export {
|
||||||
|
RegExpIsFloat,
|
||||||
|
makeNumericRender,
|
||||||
|
makeNumericColumn,
|
||||||
|
makeNumericColumnOptions,
|
||||||
|
makeNumericColumnPlanFact,
|
||||||
|
makeNumericStartEnd,
|
||||||
|
makeNumericMinMax,
|
||||||
|
makeNumericAvgRange
|
||||||
|
} from './numeric'
|
||||||
|
export { makeColumnsPlanFact } from './plan_fact'
|
||||||
|
export { makeSelectColumn } from './select'
|
||||||
|
export { makeTagColumn, makeTagInput } from './tag'
|
||||||
|
export { makeFilterTextMatch, makeTextColumn } from './text'
|
||||||
|
export {
|
||||||
|
rawTimezones,
|
||||||
|
timezoneOptions,
|
||||||
|
TimezoneSelect,
|
||||||
|
makeTimezoneColumn,
|
||||||
|
makeTimezoneRenderer
|
||||||
|
} from './timezone'
|
||||||
|
|
||||||
|
export type { TagInputProps } from './tag'
|
||||||
|
|
||||||
|
export type DataType<T = any> = Record<string, T>
|
||||||
|
export type RenderMethod<T = any> = (value: T, dataset?: DataType<T>, index?: number) => ReactNode
|
||||||
|
export type SorterMethod<T = any> = (a?: DataType<T> | null, b?: DataType<T> | null) => number
|
||||||
|
|
||||||
|
/*
|
||||||
|
other - объект с дополнительными свойствами колонки
|
||||||
|
поддерживаются все базовые свойства из описания https://ant.design/components/table/#Column
|
||||||
|
плю дополнительные для колонок EditableTable: */
|
||||||
|
export 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?: RenderMethod<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeColumn = (title: ReactNode, key: string, other?: columnPropsOther) => ({
|
||||||
|
title: title,
|
||||||
|
key: key,
|
||||||
|
dataIndex: key,
|
||||||
|
...other,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeGroupColumn = (title: ReactNode, children: object[]) => ({ title, children })
|
||||||
|
|
||||||
|
export default makeColumn
|
111
src/components/Table/Columns/numeric.tsx
Normal file
111
src/components/Table/Columns/numeric.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { InputNumber } from 'antd'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { makeNumericSorter } from '../sorters'
|
||||||
|
import { columnPropsOther, makeGroupColumn, RenderMethod } from '.'
|
||||||
|
|
||||||
|
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
|
||||||
|
|
||||||
|
export const makeNumericRender = <T extends unknown>(fixed?: number): RenderMethod<T> => (value) => {
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeNumericColumn = (
|
||||||
|
title: ReactNode,
|
||||||
|
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: ReactNode,
|
||||||
|
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: ReactNode,
|
||||||
|
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: ReactNode,
|
||||||
|
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: ReactNode,
|
||||||
|
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 default makeNumericColumn
|
38
src/components/Table/Columns/plan_fact.tsx
Normal file
38
src/components/Table/Columns/plan_fact.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { columnPropsOther, makeColumn } from '.'
|
||||||
|
|
||||||
|
export const makeColumnsPlanFact = (
|
||||||
|
title: string | ReactNode,
|
||||||
|
key: string | string[],
|
||||||
|
columsOther?: columnPropsOther | [columnPropsOther, columnPropsOther],
|
||||||
|
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 default makeColumnsPlanFact
|
22
src/components/Table/Columns/select.tsx
Normal file
22
src/components/Table/Columns/select.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Select, SelectProps } from 'antd'
|
||||||
|
import { DefaultOptionType, SelectValue } from 'antd/lib/select'
|
||||||
|
|
||||||
|
import { columnPropsOther, makeColumn } from '.'
|
||||||
|
|
||||||
|
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 ?? '--'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default makeSelectColumn
|
74
src/components/Table/Columns/tag.tsx
Normal file
74
src/components/Table/Columns/tag.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { memo, ReactNode, useCallback, useEffect, useState } from 'react'
|
||||||
|
import { Select, SelectProps, Tag } from 'antd'
|
||||||
|
import { DefaultOptionType, SelectValue } from 'antd/lib/select'
|
||||||
|
|
||||||
|
import { OmitExtends } from '@utils'
|
||||||
|
|
||||||
|
import { columnPropsOther, DataType, makeColumn } from '.'
|
||||||
|
|
||||||
|
export type TagInputProps<T extends DataType> = OmitExtends<{
|
||||||
|
options: T[],
|
||||||
|
value?: T[],
|
||||||
|
onChange?: (values: T[]) => void,
|
||||||
|
}, SelectProps<SelectValue>>
|
||||||
|
|
||||||
|
export const makeTagInput = <T extends DataType>(value_key: string, label_key: string) =>
|
||||||
|
memo<TagInputProps<T>>(({ 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 = useCallback((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)
|
||||||
|
}, [onChange, options])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...other}
|
||||||
|
mode={'tags'}
|
||||||
|
options={selectOptions}
|
||||||
|
value={selectedValue}
|
||||||
|
onChange={onSelectChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeTagColumn = <T extends DataType>(
|
||||||
|
title: ReactNode,
|
||||||
|
dataIndex: string,
|
||||||
|
options: T[],
|
||||||
|
value_key: keyof DataType,
|
||||||
|
label_key: keyof DataType,
|
||||||
|
other?: columnPropsOther,
|
||||||
|
tagOther?: TagInputProps<T>
|
||||||
|
) => {
|
||||||
|
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 {...tagOther} options={options} />,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default makeTagColumn
|
27
src/components/Table/Columns/text.tsx
Normal file
27
src/components/Table/Columns/text.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { columnPropsOther, DataType, RenderMethod, SorterMethod } from '.'
|
||||||
|
import { makeStringSorter } from '../sorters'
|
||||||
|
|
||||||
|
export const makeFilterTextMatch = <T extends unknown>(key: keyof DataType<T>) =>
|
||||||
|
(filterValue: T, dataItem: DataType<T>) => dataItem[key] === filterValue
|
||||||
|
|
||||||
|
export const makeTextColumn = <T extends unknown = any>(
|
||||||
|
title: ReactNode,
|
||||||
|
dataIndex: string,
|
||||||
|
filters: object[],
|
||||||
|
sorter?: SorterMethod<T>,
|
||||||
|
render?: RenderMethod<T>,
|
||||||
|
other?: columnPropsOther
|
||||||
|
) => ({
|
||||||
|
title: title,
|
||||||
|
dataIndex: dataIndex,
|
||||||
|
key: dataIndex,
|
||||||
|
filters: filters,
|
||||||
|
onFilter: filters ? makeFilterTextMatch(dataIndex) : null,
|
||||||
|
sorter: sorter ?? makeStringSorter(dataIndex),
|
||||||
|
render: render,
|
||||||
|
...other
|
||||||
|
})
|
||||||
|
|
||||||
|
export default makeTextColumn
|
72
src/components/Table/Columns/timezone.tsx
Normal file
72
src/components/Table/Columns/timezone.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { memo, ReactNode, useEffect, useState } from 'react'
|
||||||
|
import { Select, SelectProps } from 'antd'
|
||||||
|
|
||||||
|
import { SimpleTimezoneDto } from '@api'
|
||||||
|
|
||||||
|
import { columnPropsOther, makeColumn } from '.'
|
||||||
|
|
||||||
|
export const rawTimezones = {
|
||||||
|
'Калининград': 2,
|
||||||
|
'Москва': 3,
|
||||||
|
'Самара': 4,
|
||||||
|
'Екатеринбург': 5,
|
||||||
|
'Омск': 6,
|
||||||
|
'Красноярск': 7,
|
||||||
|
'Новосибирск': 7,
|
||||||
|
'Иркутск': 8,
|
||||||
|
'Чита': 9,
|
||||||
|
'Владивосток': 10,
|
||||||
|
'Магадан': 11,
|
||||||
|
'Южно-Сахалинск': 11,
|
||||||
|
'Среднеколымск': 11,
|
||||||
|
'Анадырь': 12,
|
||||||
|
'Петропавловск-Камчатский': 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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: ReactNode = 'Зона',
|
||||||
|
key: string = 'timezone',
|
||||||
|
defaultValue?: SimpleTimezoneDto,
|
||||||
|
allowClear: boolean = true,
|
||||||
|
other?: columnPropsOther
|
||||||
|
) => makeColumn(title, key, {
|
||||||
|
width: 100,
|
||||||
|
editable: true,
|
||||||
|
render: makeTimezoneRenderer(),
|
||||||
|
input: (
|
||||||
|
<TimezoneSelect
|
||||||
|
allowClear={allowClear}
|
||||||
|
options={timezoneOptions}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
...other
|
||||||
|
})
|
||||||
|
|
||||||
|
export default makeTimezoneColumn
|
@ -1,296 +1,45 @@
|
|||||||
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 { makeDateSorter, makeNumericSorter, makeStringSorter } from './sorters'
|
||||||
export { EditableTable, makeActionHandler } from './EditableTable'
|
export { EditableTable, makeActionHandler } from './EditableTable'
|
||||||
export { DatePickerWrapper } from './DatePickerWrapper'
|
export { DatePickerWrapper } from './DatePickerWrapper'
|
||||||
export { Table } from './Table'
|
export { Table } from './Table'
|
||||||
|
export {
|
||||||
|
RegExpIsFloat,
|
||||||
|
rawTimezones,
|
||||||
|
timezoneOptions,
|
||||||
|
TimezoneSelect,
|
||||||
|
makeGroupColumn,
|
||||||
|
makeColumn,
|
||||||
|
makeColumnsPlanFact,
|
||||||
|
makeFilterTextMatch,
|
||||||
|
makeNumericRender,
|
||||||
|
makeNumericColumn,
|
||||||
|
makeNumericColumnOptions,
|
||||||
|
makeNumericColumnPlanFact,
|
||||||
|
makeNumericStartEnd,
|
||||||
|
makeNumericMinMax,
|
||||||
|
makeNumericAvgRange,
|
||||||
|
makeSelectColumn,
|
||||||
|
makeTagColumn,
|
||||||
|
makeTagInput,
|
||||||
|
makeTextColumn,
|
||||||
|
makeTimezoneColumn,
|
||||||
|
makeTimezoneRenderer,
|
||||||
|
} from './Columns'
|
||||||
|
|
||||||
|
export type {
|
||||||
|
DataType,
|
||||||
|
RenderMethod,
|
||||||
|
SorterMethod,
|
||||||
|
TagInputProps,
|
||||||
|
columnPropsOther,
|
||||||
|
} from './Columns'
|
||||||
export type { BaseTableColumn, TableColumns, TableContainer } from './Table'
|
export type { BaseTableColumn, TableColumns, TableContainer } from './Table'
|
||||||
|
|
||||||
export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/
|
|
||||||
|
|
||||||
export const defaultPagination = {
|
export const defaultPagination = {
|
||||||
defaultPageSize: 14,
|
defaultPageSize: 14,
|
||||||
showSizeChanger: true,
|
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 = {
|
type PaginationContainer = {
|
||||||
skip?: number
|
skip?: number
|
||||||
take?: number
|
take?: number
|
||||||
@ -304,67 +53,3 @@ export const makePaginationObject = (сontainer: PaginationContainer, ...other:
|
|||||||
total: сontainer.count ?? сontainer.items?.length ?? 0,
|
total: сontainer.count ?? сontainer.items?.length ?? 0,
|
||||||
current: 1 + Math.floor((сontainer.skip ?? 0) / (сontainer.take ?? 1))
|
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
|
|
||||||
})
|
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
import { RawDate } from "@asb/utils"
|
import { isRawDate } from '@utils'
|
||||||
|
|
||||||
export const makeNumericSorter = (key: string) => (a: Record<string, number>, b: Record<string, number>) => Number(a[key]) - Number(b[key])
|
import { DataType } from './Columns'
|
||||||
|
|
||||||
export const makeStringSorter = (key: string) => (a: Record<string, string> | null | undefined, b: Record<string, string> | null | undefined) => {
|
export const makeNumericSorter = <T extends unknown>(key: keyof DataType<T>) =>
|
||||||
|
(a: DataType<T>, b: DataType<T>) => Number(a[key]) - Number(b[key])
|
||||||
|
|
||||||
|
export const makeStringSorter = <T extends unknown>(key: keyof DataType<T>) => (a?: DataType<T> | null, b?: DataType<T> | null) => {
|
||||||
if (!a && !b) return 0
|
if (!a && !b) return 0
|
||||||
if (!a) return 1
|
if (!a) return 1
|
||||||
if (!b) return -1
|
if (!b) return -1
|
||||||
|
|
||||||
return ('' + a[key]).localeCompare(b[key])
|
return String(a[key]).localeCompare(String(b[key]))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeDateSorter = (key: string) => (a: Record<string, RawDate>, b: Record<string, RawDate>) => {
|
export const makeDateSorter = <T extends unknown>(key: keyof DataType<T>) => (a: DataType<T>, b: DataType<T>) => {
|
||||||
const date = new Date(a[key])
|
const adate = a[key]
|
||||||
|
const bdate = b[key]
|
||||||
if (Number.isNaN(date.getTime()))
|
if (!isRawDate(adate) || !isRawDate(bdate))
|
||||||
throw new Error('Date column contains not date formatted string(s)')
|
throw new Error('Date column contains not date formatted string(s)')
|
||||||
|
|
||||||
return date.getTime() - new Date(b[key]).getTime()
|
const date = new Date(adate)
|
||||||
|
|
||||||
|
return date.getTime() - new Date(bdate).getTime()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user