diff --git a/src/components/Table/Columns/date.tsx b/src/components/Table/Columns/date.tsx index d4790e6..a401a04 100755 --- a/src/components/Table/Columns/date.tsx +++ b/src/components/Table/Columns/date.tsx @@ -1,26 +1,36 @@ -import { ReactNode } from 'react' +import { Key, ReactNode } from 'react' -import { formatDate } from '@utils' - -import makeColumn, { columnPropsOther } from '.' -import { DatePickerWrapper, makeDateSorter } from '..' +import { makeColumn, ColumnProps, SorterMethod } from '.' +import { DatePickerWrapper, getObjectByDeepKey } from '..' import { DatePickerWrapperProps } from '../DatePickerWrapper' +import { formatDate, isRawDate } from '@utils' -export const makeDateColumn = ( +export const makeDateSorter = (key: Key): SorterMethod => (a, b) => { + const vA = a ? getObjectByDeepKey(key, a) : null + const vB = b ? getObjectByDeepKey(key, b) : null + + if (!isRawDate(vA) || !isRawDate(vB)) return 0 + if (!isRawDate(vA)) return 1 + if (!isRawDate(vB)) return -1 + + return (new Date(vA)).getTime() - (new Date(vB)).getTime() +} + +export const makeDateColumn = ( title: ReactNode, key: string, utc?: boolean, format?: string, - other?: columnPropsOther, + other?: ColumnProps, pickerOther?: DatePickerWrapperProps, -) => makeColumn(title, key, { +) => makeColumn(title, key, { ...other, render: (date) => (
{formatDate(date, utc, format) ?? '-'}
), - sorter: makeDateSorter(key), + sorter: makeDateSorter(key), input: , }) diff --git a/src/components/Table/Columns/index.ts b/src/components/Table/Columns/index.ts index b8cb6ef..778bffd 100755 --- a/src/components/Table/Columns/index.ts +++ b/src/components/Table/Columns/index.ts @@ -1,6 +1,10 @@ -import { ReactNode } from 'react' +import { Key, ReactNode } from 'react' import { Rule } from 'antd/lib/form' -import { ColumnProps } from 'antd/lib/table' +import { ColumnType } from 'antd/lib/table' +import { RenderedCell } from 'rc-table/lib/interface' + +import { DataSet } from '../Table' +import { OmitExtends } from '@utils/types' export * from './date' export * from './time' @@ -12,10 +16,13 @@ export * from './text' export * from './timezone' export type DataType = Record -export type RenderMethod = (value: T, dataset?: DataType, index?: number) => ReactNode -export type SorterMethod = (a?: DataType | null, b?: DataType | null) => number +export type RenderMethod> = (value: T | undefined, dataset: DT, index: number) => ReactNode | RenderedCell | undefined +export type SorterMethod | null | undefined> = (a: DT, b: DT) => number +export type FilterMethod> = (value: string | number | T | undefined, record: DT) => boolean -export type columnPropsOther = ColumnProps> & { +export type FilterGenerator> = (key: Key) => FilterMethod + +export type ColumnProps = OmitExtends<{ // редактируемая колонка editable?: boolean // react компонента редактора @@ -29,10 +36,12 @@ export type columnPropsOther = ColumnProps> & { // дефолтное значение при добавлении новой строки initialValue?: string | number + onFilter?: FilterMethod + sorter?: SorterMethod render?: RenderMethod -} +}, ColumnType>> -export const makeColumn = (title: ReactNode, key: string, other?: columnPropsOther) => ({ +export const makeColumn = (title: ReactNode, key: Key, other?: ColumnProps) => ({ title: title, key: key, dataIndex: key, diff --git a/src/components/Table/Columns/numeric.tsx b/src/components/Table/Columns/numeric.tsx index ad8fdee..36f89b2 100755 --- a/src/components/Table/Columns/numeric.tsx +++ b/src/components/Table/Columns/numeric.tsx @@ -1,20 +1,26 @@ +import { ColumnFilterItem } from 'antd/lib/table/interface' import { InputNumber } from 'antd' -import { ReactNode } from 'react' +import { Key, ReactNode } from 'react' -import { makeNumericSorter } from '../sorters' -import makeColumn, { columnPropsOther, DataType, makeGroupColumn, RenderMethod } from '.' -import { ColumnFilterItem, CompareFn } from 'antd/lib/table/interface' +import makeColumn, { ColumnProps, FilterGenerator, makeGroupColumn, RenderMethod, SorterMethod } from '.' +import { getObjectByDeepKey } from '../Table' export const RegExpIsFloat = /^[-+]?\d+\.?\d*$/ -type FilterMethod = (value: string | number | boolean, record: DataType) => boolean +export const makeNumericSorter = (key: Key): SorterMethod => (a, b) => { + if (!a && !b) return 0 + if (!a) return 1 + if (!b) return -1 -export const makeNumericRender = (fixed?: number): RenderMethod => (value: T) => { - let val = '-' - if ((value ?? null) !== null && Number.isFinite(+value)) { + return Number(getObjectByDeepKey(key, a)) - Number(getObjectByDeepKey(key, b)) +} + +export const makeNumericRender = (fixed?: number, defaultValue: string = '-', precision: number = 5): RenderMethod => (value) => { + let val = defaultValue + if (value !== undefined && value !== null && Number.isFinite(+value)) { val = (fixed ?? null) !== null ? (+value).toFixed(fixed) - : (+value).toPrecision(5) + : (+value).toPrecision(precision) } return ( @@ -24,7 +30,7 @@ export const makeNumericRender = (fixed?: number): RenderMeth ) } -export const makeNumericColumnOptions = (fixed?: number, sorterKey?: string): columnPropsOther => ({ +export const makeNumericColumnOptions = (fixed?: number, sorterKey?: string): ColumnProps => ({ editable: true, initialValue: 0, width: 100, @@ -37,14 +43,14 @@ export const makeNumericColumnOptions = (fixed?: number render: makeNumericRender(fixed), }) -export const makeNumericColumn = ( +export const makeNumericColumn = ( title: ReactNode, - key: string, + key: Key, filters?: ColumnFilterItem[], - filterDelegate?: (key: string | number) => FilterMethod, + filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, width?: string | number, - other?: columnPropsOther, + other?: ColumnProps, ) => makeColumn(title, key, { filters, onFilter: filterDelegate ? filterDelegate(key) : undefined, @@ -56,24 +62,25 @@ export const makeNumericColumn = ( ...other }) -export const makeNumericColumnPlanFact = ( +export const makeNumericColumnPlanFact = ( title: ReactNode, - key: string, + key: Key, filters?: ColumnFilterItem[], - filterDelegate?: (key: string | number) => FilterMethod, + filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, - width?: string | number + width?: string | number, + other?: ColumnProps, ) => makeGroupColumn(title, [ - makeNumericColumn('п', key + 'Plan', filters, filterDelegate, renderDelegate, width), - makeNumericColumn('ф', key + 'Fact', filters, filterDelegate, renderDelegate, width), + makeNumericColumn('п', key + 'Plan', filters, filterDelegate, renderDelegate, width, other), + makeNumericColumn('ф', key + 'Fact', filters, filterDelegate, renderDelegate, width, other), ]) -export const makeNumericStartEnd = ( +export const makeNumericStartEnd = ( title: ReactNode, - key: string, + key: Key, fixed: number, filters?: ColumnFilterItem[], - filterDelegate?: (key: string | number) => FilterMethod, + filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, width?: string | number, ) => makeGroupColumn(title, [ @@ -81,12 +88,12 @@ export const makeNumericStartEnd = ( makeNumericColumn('конец', key + 'End', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, key + 'End')) ]) -export const makeNumericMinMax = ( +export const makeNumericMinMax = ( title: ReactNode, - key: string, + key: Key, fixed: number, filters?: ColumnFilterItem[], - filterDelegate?: (key: string | number) => FilterMethod, + filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, width?: string | number, ) => makeGroupColumn(title, [ @@ -94,4 +101,18 @@ export const makeNumericMinMax = ( makeNumericColumn('макс', key + 'Max', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, key + 'Max')), ]) +export const makeNumericAvgRange = ( + title: ReactNode, + key: Key, + fixed: number, + filters?: ColumnFilterItem[], + filterDelegate?: FilterGenerator, + renderDelegate?: RenderMethod, + width?: string | number, +) => makeGroupColumn(title, [ + makeNumericColumn('мин', `${key}.min`, filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, `${key}.min`)), + makeNumericColumn('сред', `${key}.avg`, filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, `${key}.avg`)), + makeNumericColumn('макс', `${key}.max`, filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, `${key}.max`)), +]) + export default makeNumericColumn diff --git a/src/components/Table/Columns/plan_fact.tsx b/src/components/Table/Columns/plan_fact.tsx index e890eec..852b9e4 100755 --- a/src/components/Table/Columns/plan_fact.tsx +++ b/src/components/Table/Columns/plan_fact.tsx @@ -1,36 +1,20 @@ -import { ReactNode } from 'react' +import { Key, ReactNode } from 'react' -import { columnPropsOther, makeColumn } from '.' +import { ColumnProps, makeColumn } from '.' -export const makeColumnsPlanFact = ( +export const makeColumnsPlanFact = ( title: string | ReactNode, - key: string | string[], - columsOther?: columnPropsOther | [columnPropsOther, columnPropsOther], - gruopOther?: any + key: Key | [Key, Key], + columsOther?: ColumnProps | [ColumnProps, ColumnProps], ) => { - 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] + const keys = Array.isArray(key) ? key : [`${key}Plan`, `${key}Fact`] + const others = Array.isArray(columsOther) ? columsOther : [columsOther, columsOther] return { - title: title, - ...gruopOther, + title, children: [ - makeColumn('план', keyPlanLocal, columsOtherLocal[0]), - makeColumn('факт', keyFactLocal, columsOtherLocal[1]), + makeColumn('план', keys[0], others[0]), + makeColumn('факт', keys[1], others[1]), ] } } diff --git a/src/components/Table/Columns/select.tsx b/src/components/Table/Columns/select.tsx index fefc00f..098d983 100755 --- a/src/components/Table/Columns/select.tsx +++ b/src/components/Table/Columns/select.tsx @@ -1,21 +1,22 @@ import { Select, SelectProps } from 'antd' import { DefaultOptionType, SelectValue } from 'antd/lib/select' +import { Key, ReactNode } from 'react' -import { columnPropsOther, makeColumn } from '.' +import { ColumnProps, makeColumn } from '.' -export const makeSelectColumn = ( - title: string, - dataIndex: string, - options: DefaultOptionType[], +export const makeSelectColumn = ( + title: ReactNode, + key: Key, + options: T[], defaultValue?: T, - other?: columnPropsOther, + other?: ColumnProps, selectOther?: SelectProps -) => makeColumn(title, dataIndex, { +) => makeColumn(title, key, { ...other, input: ) }) -export const makeTimezoneColumn = ( +export const makeTimezoneColumn = ( title: ReactNode = 'Зона', - key: string = 'timezone', - defaultValue?: SimpleTimezoneDto, + key: Key = 'timezone', + defaultValue?: T, allowClear: boolean = true, - other?: columnPropsOther, + other?: ColumnProps, selectOther?: TimezoneSelectProps ) => makeColumn(title, key, { width: 100, editable: true, - render: makeTimezoneRenderer(), + render: makeTimezoneRender(), input: ( , HTMLTableDataCellElement> & { - editing?: boolean - dataIndex?: NamePath - input?: ReactNode - isRequired?: boolean - title: string - formItemClass?: string - formItemRules?: Rule[] - children: ReactNode - initialValue: any +export type EditableCellProps = React.DetailedHTMLProps, HTMLTableDataCellElement> & { + editing?: boolean + dataIndex?: Key + input?: ReactNode + isRequired?: boolean + formItemClass?: string + formItemRules?: Rule[] + children: ReactNode + initialValue: any } +const itemStyle = { margin: 0 } + export const EditableCell = memo(({ - editing, - dataIndex, - input, - isRequired, - formItemClass, - formItemRules, - children, - initialValue, - ...other -}) => ( - - {!editing ? children : ( - - {input ?? } - - )} - -)) + editing, + dataIndex, + input, + isRequired, + formItemClass, + formItemRules, + children, + initialValue, + ...other +}) => { + const rules = useMemo(() => formItemRules || [{ + required: isRequired, + message: `Это обязательное поле!`, + }], [formItemRules, isRequired]) + + const name = useMemo(() => dataIndex ? String(dataIndex).split('.') : undefined, [dataIndex]) + const tdStyle = useMemo(() => editing ? { padding: 0 } : undefined, [editing]) + + const edititngItem = useMemo(() => ( + + {input ?? } + + ), [name, rules, formItemClass, initialValue, input]) + + return ( + + {editing ? edititngItem : children} + + ) +}) diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 3b25a0d..c9ecbbe 100755 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -1,26 +1,65 @@ -import { memo, useCallback, useEffect, useState } from 'react' +import { Key, memo, useCallback, useEffect, useState } from 'react' import { ColumnGroupType, ColumnType } from 'antd/lib/table' import { Table as RawTable, TableProps } from 'antd' +import { RenderMethod } from './Columns' +import { tryAddKeys } from './EditableTable' +import TableSettingsChanger from './TableSettingsChanger' import type { OmitExtends } from '@utils/types' import { applyTableSettings, getTableSettings, setTableSettings, TableColumnSettings, TableSettings } from '@utils' -import TableSettingsChanger from './TableSettingsChanger' -import { tryAddKeys } from './EditableTable' - import '@styles/index.css' export type BaseTableColumn = ColumnGroupType | ColumnType -export type TableColumns = OmitExtends, TableColumnSettings>[] +export type TableColumn = OmitExtends, TableColumnSettings> +export type TableColumns = TableColumn[] export type TableContainer = TableProps & { - columns: TableColumns + columns: TableColumn[] tableName?: string showSettingsChanger?: boolean } -const _Table = ({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer) => { - const [newColumns, setNewColumns] = useState>([]) +export interface DataSet { + [k: Key]: DataSet | T | D +} + +export const getObjectByDeepKey = (key: Key | undefined, data: DataSet): T | undefined => { + if (!key) return undefined + const parts = String(key).split('.') + let out = data + for (let i = 0; i < parts.length && out; i++) { + const key = parts[i] + if (!(key in out)) return undefined // Если ключ не найдем, считаем значение null + out = out[key] as DataSet // Углубляемся внутрь объекта + } + return out as T +} + +export const makeColumnRenderWrapper = >(key: Key | undefined, render: RenderMethod | undefined): RenderMethod => + (_: any, dataset: T, index: number) => { + const renderFunc: RenderMethod = typeof render === 'function' ? render : (record) => String(record) + return renderFunc(getObjectByDeepKey(key, dataset), dataset, index) + } + + +const applyColumnWrappers = >(columns: BaseTableColumn[]): BaseTableColumn[] => { + return columns.map((column) => { + if ('children' in column) { + return { + ...column, + children: applyColumnWrappers(column.children), + } + } + return { + ...column, + render: makeColumnRenderWrapper(column.key, column.render), + } + }) +} + +function _Table>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer) { + const [newColumns, setNewColumns] = useState[]>([]) const [settings, setSettings] = useState({}) const onSettingsChanged = useCallback((settings?: TableSettings | null) => { @@ -31,7 +70,7 @@ const _Table = ({ columns, dataSource, tableName, showSettings useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName]) useEffect(() => setNewColumns(() => { - const newColumns = applyTableSettings(columns, settings) + const newColumns = applyTableSettings(applyColumnWrappers(columns), settings) if (tableName && showSettingsChanger) { const oldTitle = newColumns[0].title newColumns[0].title = (props) => ( diff --git a/src/components/Table/TableSettingsChanger.tsx b/src/components/Table/TableSettingsChanger.tsx index 4ca622f..2bbb805 100755 --- a/src/components/Table/TableSettingsChanger.tsx +++ b/src/components/Table/TableSettingsChanger.tsx @@ -1,11 +1,11 @@ import { memo, useCallback, useEffect, useState } from 'react' import { ColumnsType } from 'antd/lib/table' -import { Button, Modal, Switch, Table } from 'antd' +import { Button, Modal, Switch } from 'antd' import { SettingOutlined } from '@ant-design/icons' import { TableColumnSettings, makeTableSettings, mergeTableSettings, TableSettings } from '@utils' -import { TableColumns } from './Table' -import { makeColumn } from '.' +import { Table, TableColumns } from './Table' +import { makeColumn, makeTextColumn } from '.' const parseSettings = (columns?: TableColumns, settings?: TableSettings | null): TableColumnSettings[] => { const newSettings = mergeTableSettings(makeTableSettings(columns ?? []), settings ?? {}) @@ -46,8 +46,8 @@ const _TableSettingsChanger = ({ title, columns, settings, onC useEffect(() => { setTableColumns([ - makeColumn('Название', 'title'), - makeColumn(null, 'visible', { + makeTextColumn('Название', 'title'), + makeColumn(null, 'visible', { title: () => ( <> Показать @@ -56,7 +56,7 @@ const _TableSettingsChanger = ({ title, columns, settings, onC ), - render: (visible: boolean, _?: TableColumnSettings, index: number = NaN) => ( + render: (visible, _, index = NaN) => ( (key: keyof DataType): CompareFn> => - (a: DataType, b: DataType) => Number(a[key]) - Number(b[key]) - -export const makeNumericObjSorter = (key: [string, string]) => - (a: DataType, b: DataType) => Number(a[key[0]][key[1]]) - Number(b[key[0]][key[1]]) - -export const makeStringSorter = (key: keyof DataType) => (a?: DataType | null, b?: DataType | null) => { - if (!a && !b) return 0 - if (!a) return 1 - if (!b) return -1 - - return String(a[key]).localeCompare(String(b[key])) -} - -export const makeDateSorter = (key: keyof DataType) => (a: DataType, b: DataType) => { - const adate = a[key] - const bdate = b[key] - if (!isRawDate(adate) || !isRawDate(bdate)) - throw new Error('Date column contains not date formatted string(s)') - - const date = new Date(adate) - - return date.getTime() - new Date(bdate).getTime() -} - -export const makeTimeSorter = (key: keyof DataType) => (a: DataType, b: DataType) => { - const elma = a[key] - const elmb = b[key] - - if (!elma && !elmb) return 0 - if (!elma) return 1 - if (!elmb) return -1 - - return timeToMoment(elma).diff(timeToMoment(elmb)) -} diff --git a/src/pages/Well/WellOperations/WellDrillParams.jsx b/src/pages/Well/WellOperations/WellDrillParams.jsx index 486b422..0ae6097 100644 --- a/src/pages/Well/WellOperations/WellDrillParams.jsx +++ b/src/pages/Well/WellOperations/WellDrillParams.jsx @@ -1,88 +1,17 @@ import { useState, useEffect, useCallback, memo, useMemo } from 'react' -import { InputNumber } from 'antd' import { useWell } from '@asb/context' import { EditableTable, makeSelectColumn, - makeGroupColumn, - makeNumericRender, makeNumericSorter, - RegExpIsFloat, + makeNumericAvgRange, } from '@components/Table' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' import { DrillParamsService, WellOperationService } from '@api' import { arrayOrDefault } from '@utils' -import { makeNumericObjSorter } from '@components/Table/sorters' - -const makeNumericObjRender = (fixed, columnKey) => (value, obj) => { - let val = '-' - const isSelected = obj && columnKey && obj[columnKey[0]] ? obj[columnKey[0]][columnKey[1]] : false - - if ((value ?? null) !== null && Number.isFinite(+value)) { - val = (fixed ?? null) !== null - ? (+value).toFixed(fixed) - : (+value).toPrecision(5) - } - - return ( -
- {val} -
- ) -} - -const makeNumericColumnOptionsWithColor = (fixed, sorterKey, columnKey) => ({ - editable: true, - initialValue: 0, - width: 100, - sorter: sorterKey ? makeNumericObjSorter(sorterKey) : undefined, - formItemRules: [{ - required: true, - message: 'Введите число', - pattern: RegExpIsFloat, - }], - render: makeNumericObjRender(fixed, columnKey), -}) - -const makeNumericObjColumn = ( - title, - dataIndex, - filters, - filterDelegate, - renderDelegate, - width, - other -) => ({ - title: title, - dataIndex: dataIndex, - key: dataIndex, - filters: filters, - onFilter: filterDelegate ? filterDelegate(dataIndex) : null, - sorter: makeNumericObjSorter(dataIndex), - width: width, - input: , - render: renderDelegate ?? makeNumericRender(), - align: 'right', - ...other -}) - -const makeNumericAvgRange = ( - title, - dataIndex, - fixed, - filters, - filterDelegate, - renderDelegate, - width -) => makeGroupColumn(title, [ - makeNumericObjColumn('мин', [dataIndex, 'min'], filters, filterDelegate, renderDelegate, width, makeNumericColumnOptionsWithColor(fixed, [dataIndex, 'min'], [dataIndex, 'isMin'])), - makeNumericObjColumn('сред', [dataIndex, 'avg'], filters, filterDelegate, renderDelegate, width, makeNumericColumnOptionsWithColor(fixed, [dataIndex, 'avg'])), - makeNumericObjColumn('макс', [dataIndex, 'max'], filters, filterDelegate, renderDelegate, width, makeNumericColumnOptionsWithColor(fixed, [dataIndex, 'max'], [dataIndex, 'isMax'])) -]) - export const getColumns = async (idWell) => { let sectionTypes = await WellOperationService.getSectionTypes(idWell) sectionTypes = Object.entries(sectionTypes).map(([id, value]) => ({