diff --git a/package-lock.json b/package-lock.json index 3cf958f..8912fac 100755 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,7 @@ "react-dom": "^18.1.0", "react-router-dom": "^6.3.0", "rxjs": "^7.5.5", - "usehooks-ts": "^2.6.0", - "web-vitals": "^2.1.4" + "usehooks-ts": "^2.6.0" }, "devDependencies": { "@babel/core": "^7.18.2", @@ -14786,11 +14785,6 @@ "minimalistic-assert": "^1.0.0" } }, - "node_modules/web-vitals": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz", - "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==" - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -26492,11 +26486,6 @@ "minimalistic-assert": "^1.0.0" } }, - "web-vitals": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz", - "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==" - }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/src/App.tsx b/src/App.tsx index 7498b86..0ac3eb0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,7 @@ export const App = memo(() => ( {/* User pages */} }> - } /> + } /> }> {/* Admin pages */} diff --git a/src/components/LayoutPortal.tsx b/src/components/LayoutPortal.tsx index 97173e3..c8c3f0b 100644 --- a/src/components/LayoutPortal.tsx +++ b/src/components/LayoutPortal.tsx @@ -114,7 +114,7 @@ const _LayoutPortal = memo(() => { setWellsTreeOpen((prev) => !prev)}>{currentWell} )} - {breadcrumb} + {breadcrumb !== true && breadcrumb} )} {topRightBlock} diff --git a/src/components/MenuBreadcrumb.tsx b/src/components/MenuBreadcrumb.tsx index 05cbc74..5299039 100644 --- a/src/components/MenuBreadcrumb.tsx +++ b/src/components/MenuBreadcrumb.tsx @@ -1,10 +1,9 @@ import { Breadcrumb, BreadcrumbItemProps } from 'antd' -import { Link, useLocation } from 'react-router-dom' -import { memo, useMemo } from 'react' +import { Link } from 'react-router-dom' import { join } from 'path' import { PrivateWellMenuItem } from '@components/PrivateWellMenu' -import { FunctionalValue, useFunctionalValue } from '@utils' +import { FunctionalValue, getFunctionalValue, } from '@utils' export const makeBreadcrumbItems = (items: PrivateWellMenuItem[], pathParts: string[], root: string = '/') => { const out = [] @@ -22,35 +21,26 @@ export const makeBreadcrumbItems = (items: PrivateWellMenuItem[], pathParts: str return out } -export type MenuBreadcrumbItemsProps = { - menuItems: PrivateWellMenuItem[] - pathRoot?: RegExp - itemsProps?: FunctionalValue<(item: PrivateWellMenuItem) => BreadcrumbItemProps> - itemRender?: (item: PrivateWellMenuItem) => JSX.Element +export const makeMenuBreadcrumbItems = ( + menuItems: PrivateWellMenuItem[], + path: string, + pathRoot: RegExp = /^\//, + itemsProps?: FunctionalValue<(item: PrivateWellMenuItem) => BreadcrumbItemProps>, + itemRender?: (item: PrivateWellMenuItem) => JSX.Element, +) => { + const getItemProps = getFunctionalValue(itemsProps) + + const rootPart = pathRoot.exec(path) + if (!rootPart || rootPart.length <= 0) return [] + const root = rootPart[0] + const parts = path.trim().slice(root.length).split('/') + const items = makeBreadcrumbItems(menuItems, parts, root) + + return items.map((item) => ( + + {itemRender ? itemRender(item) : ( + {item.title} + )} + + )) } - -export const MenuBreadcrumbItems = memo(({ menuItems, pathRoot = /^\//, itemsProps, itemRender }) => { - const location = useLocation() - const getItemProps = useFunctionalValue(itemsProps) - - const items = useMemo(() => { - const path = location.pathname - const rootPart = pathRoot.exec(path) - if (!rootPart || rootPart.length <= 0) return [] - const root = rootPart[0] - const parts = path.trim().slice(root.length).split('/') - return makeBreadcrumbItems(menuItems, parts, root) - }, [location, menuItems, pathRoot]) - - return ( - <> - {items.map((item) => ( - - {itemRender ? itemRender(item) : ( - {item.title} - )} - - ))} - - ) -}) 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/AdminPanel/index.jsx b/src/pages/AdminPanel/index.jsx index ad338e2..e4c11b8 100755 --- a/src/pages/AdminPanel/index.jsx +++ b/src/pages/AdminPanel/index.jsx @@ -1,9 +1,9 @@ -import { Navigate, Route, Routes } from 'react-router-dom' -import { lazy, memo, useMemo } from 'react' +import { Navigate, Route, Routes, useLocation } from 'react-router-dom' +import { lazy, memo, useEffect, useMemo } from 'react' import { RootPathContext, useLayoutProps, useRootPath } from '@asb/context' import { FastRunMenu } from '@components/FastRunMenu' -import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb' +import { makeMenuBreadcrumbItems } from '@components/MenuBreadcrumb' import { NoAccessComponent, withPermissions } from '@utils' import { AdminNavigationMenu, menuItems } from './AdminNavigationMenu' @@ -21,18 +21,21 @@ const TelemetryViewer = lazy(() => import('./Telemetry/TelemetryViewer')) const TelemetryMerger = lazy(() => import('./Telemetry/TelemetryMerger')) const VisitLog = lazy(() => import('./VisitLog')) -const layoutProps = { - sider: , - title: 'Администраторская панель', - isAdmin: true, - breadcrumb: , -} - const AdminPanel = memo(() => { + const location = useLocation() const root = useRootPath() const rootPath = useMemo(() => `${root}/admin`, [root]) - useLayoutProps(layoutProps) + const setLayoutProps = useLayoutProps() + + useEffect(() => { + setLayoutProps({ + sider: , + title: 'Администраторская панель', + isAdmin: true, + breadcrumb: makeMenuBreadcrumbItems(menuItems, location.pathname, /^\/admin\//), + }) + }, [location.pathname]) return ( diff --git a/src/pages/Cluster/WellOperationsTable.jsx b/src/pages/Cluster/WellOperationsTable.jsx index 1d2b094..1e8ee65 100755 --- a/src/pages/Cluster/WellOperationsTable.jsx +++ b/src/pages/Cluster/WellOperationsTable.jsx @@ -1,7 +1,6 @@ import { memo, useMemo } from 'react' -import { Table } from 'antd' -import { makeTextColumn, makeNumericColumnPlanFact } from '@components/Table' +import { Table, makeTextColumn, makeNumericColumnPlanFact } from '@components/Table' import { getPrecision } from '@utils/functions' const columns = [ diff --git a/src/pages/FileDownload.jsx b/src/pages/FileDownload.jsx index 217edfb..4da1a72 100644 --- a/src/pages/FileDownload.jsx +++ b/src/pages/FileDownload.jsx @@ -1,40 +1,30 @@ -import { Link, useNavigate, useParams } from 'react-router-dom' -import { memo, useCallback, useEffect, useState } from 'react' +import { Link, useLocation, useNavigate, useParams } from 'react-router-dom' +import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { InfoCircleFilled, CloseCircleOutlined } from '@ant-design/icons' import { Button, Result, Typography } from 'antd' import { downloadFile, invokeWebApiWrapperAsync } from '@components/factory' import { withPermissions } from '@utils' -import { FileService, WellService } from '@api' +import { FileService } from '@api' const { Paragraph, Text } = Typography -export const getLinkToFile = (fileInfo) => `/file_download/${fileInfo.idWell}/${fileInfo.id}` +export const getLinkToFile = (fileInfo) => `/file_download/${fileInfo.id}` const FileDownload = memo(function FileDownload() { - const { idWell, idFile } = useParams() - const [well, setWell] = useState({}) + const { idFile } = useParams() const [file, setFile] = useState({}) const [isError, setIsError] = useState(false) const navigate = useNavigate() - - useEffect(() => { - invokeWebApiWrapperAsync( - async () => setWell(await WellService.get(idWell)), - null, - 'Не удалось получить информацию о скважине', - { actionName: 'Получение данных о скважине' } - ) - }, [idWell]) + const location = useLocation() + const isFirstOpenApp = useMemo(() => location.key === 'default', [location]) useEffect(() => { invokeWebApiWrapperAsync( async () => { - const files = await FileService.getFilesInfo(idWell) - // TODO Получается только одна категория файлов. - // Поменять при появлении метода получения инфы о конкретном файле - setFile(files.items.find((file) => file.id === idFile) ?? { id: idFile, idWell, name: `File_${idWell}_${idFile}` }) + const file = await FileService.getFileInfo(idFile) + setFile(file) }, null, () => { @@ -43,10 +33,10 @@ const FileDownload = memo(function FileDownload() { }, { actionName: 'Получение информации о файле' } ) - }, [idWell, idFile]) + }, [idFile]) const download = useCallback(async () => { - if (!await downloadFile(file)) + if (!file || !await downloadFile(file)) setIsError(true) }, [file]) @@ -58,13 +48,16 @@ const FileDownload = memo(function FileDownload() { <> Вы перешли к странице загрузки файла!
- Файл "{file.name ?? ('№' + idFile)}", скважина "{well.caption ?? ('№' + idWell)}". + Файл "{file?.name ?? ('№' + idFile)}". )} // subTitle={} extra={( <> - + {isFirstOpenApp + ? + : + } )} diff --git a/src/pages/Well/Analytics/WellCompositeEditor/WellCompositeSections.jsx b/src/pages/Well/Analytics/WellCompositeEditor/WellCompositeSections.jsx index 426ed21..52931a4 100644 --- a/src/pages/Well/Analytics/WellCompositeEditor/WellCompositeSections.jsx +++ b/src/pages/Well/Analytics/WellCompositeEditor/WellCompositeSections.jsx @@ -1,12 +1,12 @@ import { Link, useLocation } from 'react-router-dom' import { useState, useEffect, memo, useMemo, lazy, Suspense } from 'react' import { LineChartOutlined, ProfileOutlined, TeamOutlined } from '@ant-design/icons' -import { Table, Button, Badge, Divider, Modal, Row, Col } from 'antd' +import { Button, Badge, Divider, Modal, Row, Col } from 'antd' import { useWell } from '@asb/context' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' -import { makeTextColumn, makeNumericColumnPlanFact, makeNumericColumn } from '@components/Table' +import { Table, makeTextColumn, makeNumericColumnPlanFact, makeNumericColumn } from '@components/Table' import { WellCompositeService } from '@api' import { hasPermission, diff --git a/src/pages/Well/Reports/DailyReport/ReportEditor.jsx b/src/pages/Well/Reports/DailyReport/ReportEditor.jsx index e77fc61..05c171a 100644 --- a/src/pages/Well/Reports/DailyReport/ReportEditor.jsx +++ b/src/pages/Well/Reports/DailyReport/ReportEditor.jsx @@ -1,15 +1,15 @@ -import { DatePicker, Descriptions, Form, Input, InputNumber, Modal, Table, Tabs } from 'antd' +import { DatePicker, Descriptions, Form, Input, InputNumber, Modal, Table as RawTable, Tabs } from 'antd' import { memo, useCallback, useEffect, useState } from 'react' import moment from 'moment' import { useWell } from '@asb/context' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' -import { makeColumn, makeGroupColumn } from '@components/Table' +import { Table, makeColumn, makeGroupColumn } from '@components/Table' import { DailyReportService } from '@api' const { Item: RawItem } = Form -const { Summary } = Table +const { Summary } = RawTable const { TabPane } = Tabs const Item = memo(({ style, ...other }) => ) diff --git a/src/pages/Well/Reports/DailyReport/index.jsx b/src/pages/Well/Reports/DailyReport/index.jsx index aa19d13..0c303b1 100644 --- a/src/pages/Well/Reports/DailyReport/index.jsx +++ b/src/pages/Well/Reports/DailyReport/index.jsx @@ -72,8 +72,7 @@ const DailyReport = memo(() => { const filteredData = useMemo(() => { if (!searchDate) return data - const endDate = moment(searchDate[1]).add(1, 'd') - return data.filter((row) => moment(row.reportDate).isBetween(searchDate[0], endDate, 'ms', '[)')) + return data.filter((row) => moment(row.reportDate).isBetween(searchDate[0], searchDate[1], 'day', '[]')) }, [data, searchDate]) return ( diff --git a/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx b/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx index 910aac7..dbe5af1 100644 --- a/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx @@ -1,7 +1,7 @@ -import { Table } from 'antd' import { useState, useEffect, useCallback, memo, useMemo } from 'react' import { useWell } from '@asb/context' +import { Table } from '@components/Table' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' import { Subscribe } from '@services/signalr' @@ -22,7 +22,7 @@ export const ActiveMessagesOnline = memo(({ well: givenWell }) => { if (messages) setMessages(messages.items.splice(0, 4)) }, []) - + const columns = useMemo(() => makeMessageColumns(well.id), [well.id]) useEffect(() => { diff --git a/src/pages/Well/WellCase/index.jsx b/src/pages/Well/WellCase/index.jsx index 3912da6..1eb9709 100644 --- a/src/pages/Well/WellCase/index.jsx +++ b/src/pages/Well/WellCase/index.jsx @@ -1,5 +1,5 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react' -import { Alert, Button, Typography } from 'antd' +import { Alert, Button } from 'antd' import { useWell } from '@asb/context' import { UserView } from '@components/views' 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]) => ({ diff --git a/src/pages/Well/index.jsx b/src/pages/Well/index.jsx index 534b313..398a0b8 100644 --- a/src/pages/Well/index.jsx +++ b/src/pages/Well/index.jsx @@ -4,7 +4,7 @@ import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-do import { WellContext, RootPathContext, useRootPath, useLayoutProps, TopRightBlockContext } from '@asb/context' import { FastRunMenu } from '@components/FastRunMenu' import { invokeWebApiWrapperAsync } from '@components/factory' -import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb' +import { makeMenuBreadcrumbItems } from '@components/MenuBreadcrumb' import { NoAccessComponent, withPermissions } from '@utils' import { WellService } from '@api' @@ -78,9 +78,9 @@ const Well = memo(() => { useEffect(() => setLayoutProps({ sider: , - breadcrumb: , + breadcrumb: makeMenuBreadcrumbItems(menuItems, location.pathname, /^\/well\/[0-9]+\//), topRightBlock: topRightBlock, - }), [well, setLayoutProps, topRightBlock]) + }), [well, location.pathname, setLayoutProps, topRightBlock]) useEffect(() => setTopRightBlock(undefined), [location]) diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts deleted file mode 100755 index 6431bc5..0000000 --- a/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/src/reportWebVitals.ts b/src/reportWebVitals.ts deleted file mode 100644 index 30e6a9d..0000000 --- a/src/reportWebVitals.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getCLS, getFID, getFCP, getLCP, getTTFB, ReportHandler } from 'web-vitals' - -export const reportWebVitals = (onPerfEntry?: ReportHandler) => { - if (!onPerfEntry) return - getCLS(onPerfEntry) - getFID(onPerfEntry) - getFCP(onPerfEntry) - getLCP(onPerfEntry) - getTTFB(onPerfEntry) -} - -export default reportWebVitals