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,370 +1,55 @@
|
||||
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 {
|
||||
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 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} />,
|
||||
})
|
||||
defaultPageSize: 14,
|
||||
showSizeChanger: true,
|
||||
}
|
||||
|
||||
type PaginationContainer = {
|
||||
skip?: number
|
||||
take?: number
|
||||
count?: number
|
||||
items?: any[] | null
|
||||
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
|
||||
...other,
|
||||
pageSize: сontainer.take,
|
||||
total: сontainer.count ?? сontainer.items?.length ?? 0,
|
||||
current: 1 + Math.floor((сontainer.skip ?? 0) / (сontainer.take ?? 1))
|
||||
})
|
||||
|
@ -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) 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>) => {
|
||||
const date = new Date(a[key])
|
||||
|
||||
if (Number.isNaN(date.getTime()))
|
||||
export const makeDateSorter = <T extends unknown>(key: keyof DataType<T>) => (a: DataType<T>, b: DataType<T>) => {
|
||||
const adate = a[key]
|
||||
const bdate = b[key]
|
||||
if (!isRawDate(adate) || !isRawDate(bdate))
|
||||
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