From 20b271d91eeaaba26f214ac63edd1e9475c49536 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 28 Nov 2022 10:15:01 +0500 Subject: [PATCH 01/32] =?UTF-8?q?=D0=9B=D0=BE=D0=B3=D0=BE=D1=82=D0=B8?= =?UTF-8?q?=D0=BF=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20SVG.?= =?UTF-8?q?=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=D1=82=D0=B8=D0=BF=20=D0=90=D0=A1=D0=91=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B9=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/images/AsbLogo.svg | 40 ++++++++++++++++++++++++++++++++++++++++ src/images/Logo.svg | 33 +++++++++++++++++++++++++++++++++ src/images/Logo.tsx | 36 +++--------------------------------- 3 files changed, 76 insertions(+), 33 deletions(-) create mode 100644 src/images/AsbLogo.svg create mode 100644 src/images/Logo.svg diff --git a/src/images/AsbLogo.svg b/src/images/AsbLogo.svg new file mode 100644 index 0000000..1707292 --- /dev/null +++ b/src/images/AsbLogo.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/Logo.svg b/src/images/Logo.svg new file mode 100644 index 0000000..dbafbae --- /dev/null +++ b/src/images/Logo.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/Logo.tsx b/src/images/Logo.tsx index 2dd724b..32f9634 100755 --- a/src/images/Logo.tsx +++ b/src/images/Logo.tsx @@ -1,44 +1,14 @@ import { memo } from 'react' +import { ReactComponent as RawLogo } from './Logo.svg' + export type LogoProps = React.SVGProps & { size?: number onlyIcon?: boolean } export const Logo = memo(({ size = 170, onlyIcon, ...props }) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + )) export default Logo From cb8be79274945fc862d3d205a60a136416123982 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 5 Dec 2022 20:35:29 +0500 Subject: [PATCH 02/32] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=D0=BD=20PrivateMenu=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=BB?= =?UTF-8?q?=D1=8E=D0=B1=D1=8B=D0=BC=D0=B8=20=D1=83=D0=BA=D0=B0=D0=B7=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=BC=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=8B=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/FastRunMenu.tsx | 8 +++--- src/components/MenuBreadcrumb.tsx | 14 +++++----- .../{PrivateWellMenu.tsx => PrivateMenu.tsx} | 26 +++++++++---------- src/pages/AdminPanel/AdminNavigationMenu.jsx | 4 +-- ...igationMenu.jsx => WellNavigationMenu.jsx} | 10 +++---- src/pages/Well/index.jsx | 4 +-- 6 files changed, 33 insertions(+), 33 deletions(-) rename src/components/{PrivateWellMenu.tsx => PrivateMenu.tsx} (73%) rename src/pages/Well/{NavigationMenu.jsx => WellNavigationMenu.jsx} (94%) diff --git a/src/components/FastRunMenu.tsx b/src/components/FastRunMenu.tsx index dfaed0c..30d78b2 100644 --- a/src/components/FastRunMenu.tsx +++ b/src/components/FastRunMenu.tsx @@ -5,11 +5,11 @@ import { AutoComplete } from 'antd' import { join } from 'path' import { useWell } from '@asb/context' -import { makeItem, PrivateWellMenuItem } from './PrivateWellMenu' +import { makeItem, PrivateMenuItem } from './PrivateMenu' import { hasPermission, isURLAvailable } from '@utils' import { menuItems as adminMenuItems } from '@pages/AdminPanel/AdminNavigationMenu' -import { menuItems as wellMenuItems } from '@pages/Well/NavigationMenu' +import { menuItems as wellMenuItems } from '@pages/Well/WellNavigationMenu' import '@styles/fast_run_menu.less' @@ -29,7 +29,7 @@ const transliterateToEn = (text: string) => Object.entries(transliterationTable) const applyVars = (route: string, vars?: object): string => !vars ? route : Object.entries(vars).reduce((out, [key, value]) => out.replaceAll(`{${key}}`, value), route) -const makeOptions = (items: PrivateWellMenuItem[], vars?: object): OptionType[] => { +const makeOptions = (items: PrivateMenuItem[], vars?: object): OptionType[] => { const out: OptionType[] = [] items.forEach((item) => { if (!hasPermission(item.permissions)) return @@ -78,7 +78,7 @@ export const FastRunMenu = memo(() => { ] if (isURLAvailable('/admin')) - menus.push(makeItem('Панель администратора', '/admin', [], undefined, adminMenuItems as PrivateWellMenuItem[])) + menus.push(makeItem('Панель администратора', '/admin', [], undefined, adminMenuItems as PrivateMenuItem[])) if (well.id) menus.push( diff --git a/src/components/MenuBreadcrumb.tsx b/src/components/MenuBreadcrumb.tsx index 5299039..3623f68 100644 --- a/src/components/MenuBreadcrumb.tsx +++ b/src/components/MenuBreadcrumb.tsx @@ -2,16 +2,16 @@ import { Breadcrumb, BreadcrumbItemProps } from 'antd' import { Link } from 'react-router-dom' import { join } from 'path' -import { PrivateWellMenuItem } from '@components/PrivateWellMenu' +import { PrivateMenuItem } from '@components/PrivateMenu' import { FunctionalValue, getFunctionalValue, } from '@utils' -export const makeBreadcrumbItems = (items: PrivateWellMenuItem[], pathParts: string[], root: string = '/') => { +export const makeBreadcrumbItems = (items: PrivateMenuItem[], pathParts: string[], root: string = '/') => { const out = [] const parts = [...pathParts] let route = root - let arr: PrivateWellMenuItem[] | undefined = items + let arr: PrivateMenuItem[] | undefined = items while (arr && parts.length > 0) { - const child: PrivateWellMenuItem | undefined = arr.find(elm => elm.route.toLowerCase() === parts[0].toLowerCase()) + const child: PrivateMenuItem | undefined = arr.find(elm => elm.route.toLowerCase() === parts[0].toLowerCase()) if (!child) break route = join(route, child.route) out.push({ ...child, route }) @@ -22,11 +22,11 @@ export const makeBreadcrumbItems = (items: PrivateWellMenuItem[], pathParts: str } export const makeMenuBreadcrumbItems = ( - menuItems: PrivateWellMenuItem[], + menuItems: PrivateMenuItem[], path: string, pathRoot: RegExp = /^\//, - itemsProps?: FunctionalValue<(item: PrivateWellMenuItem) => BreadcrumbItemProps>, - itemRender?: (item: PrivateWellMenuItem) => JSX.Element, + itemsProps?: FunctionalValue<(item: PrivateMenuItem) => BreadcrumbItemProps>, + itemRender?: (item: PrivateMenuItem) => JSX.Element, ) => { const getItemProps = getFunctionalValue(itemsProps) diff --git a/src/components/PrivateWellMenu.tsx b/src/components/PrivateMenu.tsx similarity index 73% rename from src/components/PrivateWellMenu.tsx rename to src/components/PrivateMenu.tsx index 78e6d6c..f19fddf 100644 --- a/src/components/PrivateWellMenu.tsx +++ b/src/components/PrivateMenu.tsx @@ -1,21 +1,21 @@ import { ItemType } from 'antd/lib/menu/hooks/useItems' +import { Menu, MenuProps } from 'antd' import { memo, ReactNode, useMemo } from 'react' import { Link, useLocation } from 'react-router-dom' import { join } from 'path' -import { Menu, MenuProps } from 'antd' import { hasPermission, Permission } from '@utils' -export type PrivateWellMenuItem = { +export type PrivateMenuItem = { title: string route: string permissions: Permission | Permission[] icon?: ReactNode visible?: boolean - children?: PrivateWellMenuItem[] + children?: PrivateMenuItem[] } -const makeItems = (items: PrivateWellMenuItem[], parentRoute: string, pathParser?: (path: string, parent: string) => string): ItemType[] => { +const makeItems = (items: PrivateMenuItem[], parentRoute: string, pathParser?: (path: string, parent: string) => string): ItemType[] => { return items.map((item) => { if (item.visible === false || !(item.visible === true || hasPermission(item.permissions))) return null @@ -43,11 +43,11 @@ const makeItems = (items: PrivateWellMenuItem[], parentRoute: string, pathParser }).filter(Boolean) } -const makeItemList = (items: PrivateWellMenuItem[], rootPath: string, wellId?: number): ItemType[] => { +const makeItemList = (items: PrivateMenuItem[], rootPath: string, variables: Record): ItemType[] => { const parser = (path: string, parent: string) => { if (!path.startsWith('/')) path = join(parent, path) - return path.replace(/\{wellId\}/, String(wellId)) + return Object.entries(variables).reduce((out, [key, value]) => out.replaceAll(`{${key}}`, String(value)), path) } return makeItems(items, rootPath, parser) @@ -58,9 +58,9 @@ export const makeItem = ( route: string, permissions: Permission | Permission[], icon?: ReactNode, - children?: PrivateWellMenuItem[], + children?: PrivateMenuItem[], visible?: boolean -): PrivateWellMenuItem => ({ +): PrivateMenuItem => ({ title, route, icon, @@ -69,16 +69,16 @@ export const makeItem = ( visible, }) -export type PrivateWellMenuProps = Omit & { - idWell?: number - items: PrivateWellMenuItem[] +export type PrivateMenuProps = Omit & { + variables?: Record + items: PrivateMenuItem[] rootPath?: string } -export const PrivateWellMenu = memo(({ idWell, items, rootPath = '/', ...other }) => { +export const PrivateMenu = memo(({ variables, items, rootPath = '/', ...other }) => { const location = useLocation() - const menuItems = useMemo(() => makeItemList(items, rootPath, idWell), [items, rootPath, idWell]) + const menuItems = useMemo(() => makeItemList(items, rootPath, variables || {}), [items, rootPath, variables]) const tabKeys = useMemo(() => { const out = [] diff --git a/src/pages/AdminPanel/AdminNavigationMenu.jsx b/src/pages/AdminPanel/AdminNavigationMenu.jsx index 0474af2..98a098a 100644 --- a/src/pages/AdminPanel/AdminNavigationMenu.jsx +++ b/src/pages/AdminPanel/AdminNavigationMenu.jsx @@ -12,7 +12,7 @@ import { UserOutlined, } from '@ant-design/icons' -import { makeItem, PrivateWellMenu } from '@components/PrivateWellMenu' +import { makeItem, PrivateMenu } from '@components/PrivateMenu' import { isDev } from '@utils' export const menuItems = [ @@ -33,7 +33,7 @@ export const menuItems = [ ].filter(Boolean) export const AdminNavigationMenu = memo((props) => ( - , [ @@ -59,15 +59,15 @@ export const menuItems = [ makeItem('Дело скважины', 'well_case', [], ), ] -export const NavigationMenu = memo((props) => ( - ( + )) -export default NavigationMenu +export default WellNavigationMenu diff --git a/src/pages/Well/index.jsx b/src/pages/Well/index.jsx index 44b22d9..ca1cd2e 100644 --- a/src/pages/Well/index.jsx +++ b/src/pages/Well/index.jsx @@ -8,7 +8,7 @@ import { makeMenuBreadcrumbItems } from '@components/MenuBreadcrumb' import { NoAccessComponent, withPermissions } from '@utils' import { WellService } from '@api' -import { NavigationMenu, menuItems } from './NavigationMenu' +import { WellNavigationMenu, menuItems } from './WellNavigationMenu' import '@styles/index.css' @@ -81,7 +81,7 @@ const Well = memo(() => { }, [idWell]) useEffect(() => setLayoutProps({ - sider: , + sider: , breadcrumb: makeMenuBreadcrumbItems(menuItems, location.pathname, /^\/well\/[0-9]+\//), topRightBlock: topRightBlock?.(well), }), [well, location.pathname, setLayoutProps, topRightBlock]) From dc0f80fee52ec12a8724ddc0382ec3cc22b150eb Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 5 Dec 2022 20:39:14 +0500 Subject: [PATCH 03/32] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=81=D1=82=D0=B0=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BC=D0=B5=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=B6=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/outlets/DepositsOutlet.tsx | 6 +++--- src/components/selectors/WellSelector.jsx | 4 ++-- src/components/selectors/WellTreeSelector.tsx | 4 ++-- src/context.ts | 4 ++-- src/pages/Deposit.jsx | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/outlets/DepositsOutlet.tsx b/src/components/outlets/DepositsOutlet.tsx index d191222..80e2303 100644 --- a/src/components/outlets/DepositsOutlet.tsx +++ b/src/components/outlets/DepositsOutlet.tsx @@ -1,7 +1,7 @@ import { memo, useEffect, useState } from 'react' import { Outlet } from 'react-router-dom' -import { DepositsContext } from '@asb/context' +import { DepositListContext } from '@asb/context' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' import { DepositDto, DepositService } from '@api' @@ -24,11 +24,11 @@ export const DepositsOutlet = memo(() => { }, []) return ( - + - + ) }) diff --git a/src/components/selectors/WellSelector.jsx b/src/components/selectors/WellSelector.jsx index da7d95c..c0e18bd 100755 --- a/src/components/selectors/WellSelector.jsx +++ b/src/components/selectors/WellSelector.jsx @@ -1,7 +1,7 @@ import { Tag, TreeSelect } from 'antd' import { memo, useEffect, useState } from 'react' -import { useDeposits } from '@asb/context' +import { useDepositList } from '@asb/context' import { invokeWebApiWrapperAsync } from '@components/factory' import { hasPermission } from '@utils' @@ -39,7 +39,7 @@ export const WellSelector = memo(({ value, onChange, treeData, treeLabels, ...ot const [wellsTree, setWellsTree] = useState([]) const [wellLabels, setWellLabels] = useState([]) - const deposits = useDeposits() + const deposits = useDepositList() useEffect(() => { invokeWebApiWrapperAsync( diff --git a/src/components/selectors/WellTreeSelector.tsx b/src/components/selectors/WellTreeSelector.tsx index 231cdcf..ceb1b1f 100755 --- a/src/components/selectors/WellTreeSelector.tsx +++ b/src/components/selectors/WellTreeSelector.tsx @@ -2,7 +2,7 @@ import { Drawer, Tree, TreeDataNode, TreeProps } from 'antd' import { useState, useEffect, useCallback, memo, Key, useMemo } from 'react' import { useNavigate, useLocation } from 'react-router-dom' -import { useDeposits } from '@asb/context' +import { useDepositList } from '@asb/context' import { WellIcon, WellIconState } from '@components/icons' import { DepositDto, WellDto } from '@api' import { isRawDate } from '@utils' @@ -126,7 +126,7 @@ export const WellTreeSelector = memo(({ expand, current, const navigate = useNavigate() const location = useLocation() - const deposits = useDeposits() + const deposits = useDepositList() const wellsTree = useMemo(() => makeWellsTreeData(deposits), [deposits]) diff --git a/src/context.ts b/src/context.ts index d719031..3d8cf90 100644 --- a/src/context.ts +++ b/src/context.ts @@ -14,7 +14,7 @@ export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => vo /** Контекст для блока справа от крошек на страницах скважин и админки */ export const TopRightBlockContext = createContext<(block: JSX.Element) => void>(() => {}) /** Контекст со списком месторождений */ -export const DepositsContext = createContext([]) +export const DepositListContext = createContext([]) /** * Получить текущую скважину @@ -42,7 +42,7 @@ export const useUser = () => useContext(UserContext) * * @returns Список скважин */ -export const useDeposits = () => useContext(DepositsContext) +export const useDepositList = () => useContext(DepositListContext) /** * Получить метод задания элементов справа от крошек diff --git a/src/pages/Deposit.jsx b/src/pages/Deposit.jsx index f0eb9c3..8d37569 100755 --- a/src/pages/Deposit.jsx +++ b/src/pages/Deposit.jsx @@ -3,7 +3,7 @@ import { Link, useLocation } from 'react-router-dom' import { Map, Overlay } from 'pigeon-maps' import { Popover, Badge } from 'antd' -import { useDeposits, useLayoutProps } from '@asb/context' +import { useDepositList, useLayoutProps } from '@asb/context' import { PointerIcon } from '@components/icons' import { FastRunMenu } from '@components/FastRunMenu' import { limitValue, withPermissions } from '@utils' @@ -38,7 +38,7 @@ const calcViewParams = (clusters) => { } const Deposit = memo(() => { - const deposits = useDeposits() + const deposits = useDepositList() const setLayoutProps = useLayoutProps() const location = useLocation() From ebe3a50fbed18979fe7075bbd9228c04a7a7cd55 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 5 Dec 2022 21:07:17 +0500 Subject: [PATCH 04/32] =?UTF-8?q?WellTreeSelector=20=D0=B4=D0=BE=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BD=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=BD=D0=B5?= =?UTF-8?q?=D1=86=D0=B8=D1=84=D1=80=D0=BE=D0=B2=D1=8B=D0=BC=D0=B8=20=D0=B7?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=D0=BC=D0=B8=20=D0=B2?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=81=D1=82=D0=B0=D1=85=20=D1=81=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/selectors/WellTreeSelector.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/selectors/WellTreeSelector.tsx b/src/components/selectors/WellTreeSelector.tsx index ceb1b1f..a5b8962 100755 --- a/src/components/selectors/WellTreeSelector.tsx +++ b/src/components/selectors/WellTreeSelector.tsx @@ -12,12 +12,19 @@ import { ReactComponent as ClusterIcon } from '@images/ClusterIcon.svg' import '@styles/wellTreeSelect.css' +/** + * Для поиска в URL текущего раздела по шаблону `/{type}/{id}` + * + * Если найдено совпадение может вернуть 1 или 2 группы соответственно + */ +const URL_REGEX = /^\/([^\/?#]+)(?:\/([^\/?#]+))?/ + export const getWellState = (idState?: number): WellIconState => idState === 1 ? 'active' : 'unknown' export const checkIsWellOnline = (lastTelemetryDate: unknown): boolean => isRawDate(lastTelemetryDate) && (Date.now() - +new Date(lastTelemetryDate) < 600_000) const getKeyByUrl = (url?: string): [Key | null, string | null] => { - const result = url?.match(/^\/([^\/]+)\/([^\/?]+)/) // pattern "/:type/:id" + const result = url?.match(URL_REGEX) // pattern "/:type/:id" if (!result) return [null, null] return [result[0], result[1]] } @@ -137,8 +144,8 @@ export const WellTreeSelector = memo(({ expand, current, }, [wellsTree]) const onSelect = useCallback((value: Key[]): void => { - const newRoot = /\/(\w+)\/\d+/.exec(String(value)) - const oldRoot = /\/(\w+)(?:\/\d+)?/.exec(location.pathname) + const newRoot = URL_REGEX.exec(String(value)) + const oldRoot = URL_REGEX.exec(location.pathname) if (!newRoot || !oldRoot) return let newPath = newRoot[0] From 4dd57aff98776e49f06084c39cb3421ee60fac34 Mon Sep 17 00:00:00 2001 From: goodmice Date: Mon, 5 Dec 2022 22:49:55 +0500 Subject: [PATCH 05/32] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=B3=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B0=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/context.ts | 11 +- src/pages/Deposit.jsx | 106 -------------------- src/pages/Deposit/DepositNavigationMenu.jsx | 23 +++++ src/pages/Deposit/Map.jsx | 85 ++++++++++++++++ src/pages/Deposit/index.jsx | 64 ++++++++++++ 5 files changed, 182 insertions(+), 107 deletions(-) delete mode 100755 src/pages/Deposit.jsx create mode 100644 src/pages/Deposit/DepositNavigationMenu.jsx create mode 100644 src/pages/Deposit/Map.jsx create mode 100644 src/pages/Deposit/index.jsx diff --git a/src/context.ts b/src/context.ts index 3d8cf90..c638ae6 100644 --- a/src/context.ts +++ b/src/context.ts @@ -3,6 +3,8 @@ import { createContext, useContext, useEffect } from 'react' import { LayoutPortalProps } from '@components/LayoutPortal' import { DepositDto, UserTokenDto, WellDto } from '@api' +/** Контекст текущего месторождения */ +export const DepositContext = createContext(null) /** Контекст текущей скважины */ export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}, () => {}]) /** Контекст текущего корневого пути */ @@ -19,10 +21,17 @@ export const DepositListContext = createContext([]) /** * Получить текущую скважину * - * @returns Текущая скважина, либо `null` + * @returns Текущая скважина, либо пустой объект */ export const useWell = () => useContext(WellContext) +/** + * Получить текущее месторождение + * + * @returns Текущее месторождение, либо `null` + */ +export const useDeposit = () => useContext(DepositContext) + /** * Получить текущий корневой путь * diff --git a/src/pages/Deposit.jsx b/src/pages/Deposit.jsx deleted file mode 100755 index 8d37569..0000000 --- a/src/pages/Deposit.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useEffect, memo, useMemo, useCallback } from 'react' -import { Link, useLocation } from 'react-router-dom' -import { Map, Overlay } from 'pigeon-maps' -import { Popover, Badge } from 'antd' - -import { useDepositList, useLayoutProps } from '@asb/context' -import { PointerIcon } from '@components/icons' -import { FastRunMenu } from '@components/FastRunMenu' -import { limitValue, withPermissions } from '@utils' - -import '@styles/index.css' - -const zoomLimit = limitValue(5, 15) - -const calcViewParams = (clusters) => { - if ((clusters?.length ?? 0) <= 0) - return { center: [60.81226, 70.0562], zoom: 5 } - - const center = clusters.reduce((sum, cluster) => { - sum[0] += cluster.latitude - sum[1] += cluster.longitude - return sum - }, [0, 0]).map((elm) => elm / clusters.length) - - const maxDeg = clusters.reduce((max, cluster) => { - const dLatitude = Math.abs(center[0] - cluster.latitude) - const dLongitude = Math.abs(center[1] - cluster.longitude) - return Math.max(Math.max(dLatitude, dLongitude), max) - }, 0) - - // zoom max = 20 (too close) - // zoom min = 1 (mega far) - // 4 - full Russia (161.6 deg) - // 13.5 - Khanty-Mansiysk - const zoom = zoomLimit(5 + 5 / (maxDeg + 0.5)) - - return { center, zoom } -} - -const Deposit = memo(() => { - const deposits = useDepositList() - const setLayoutProps = useLayoutProps() - const location = useLocation() - - const makeDepositLinks = useCallback((clusters) => ( -
- {clusters.map(cluster => ( - -
{cluster.caption}
- - ))} -
- ), [location.pathname]) - - const viewParams = useMemo(() => calcViewParams(deposits), [deposits]) - - useEffect(() => { - const hasId = location.pathname.length > '/deposit/'.length - - const selectorProps = { - expand: hasId ? [location.pathname] : true, - current: hasId ? location.pathname : undefined, - } - - setLayoutProps({ - sheet: false, - showSelector: true, - selectorProps, - title: 'Месторождение', - }) - }, [setLayoutProps, location.pathname]) - - return ( - <> - -
- - {deposits.map(deposit => { - const anchor = [deposit.latitude, deposit.longitude] - const links = makeDepositLinks(deposit.clusters) - - return ( - - -
- - - -
-
-
- ) - })} -
-
- - ) -}) - -export default withPermissions(Deposit, ['Cluster.get']) diff --git a/src/pages/Deposit/DepositNavigationMenu.jsx b/src/pages/Deposit/DepositNavigationMenu.jsx new file mode 100644 index 0000000..304f062 --- /dev/null +++ b/src/pages/Deposit/DepositNavigationMenu.jsx @@ -0,0 +1,23 @@ +import { memo } from 'react' +import { + HeatMapOutlined, +} from '@ant-design/icons' + +import { makeItem, PrivateMenu } from '@components/PrivateMenu' + +export const menuItems = [ + makeItem('Карта', 'map', [], ), +] + +export const DepositNavigationMenu = memo((props) => ( + +)) + +export default DepositNavigationMenu diff --git a/src/pages/Deposit/Map.jsx b/src/pages/Deposit/Map.jsx new file mode 100644 index 0000000..b849dae --- /dev/null +++ b/src/pages/Deposit/Map.jsx @@ -0,0 +1,85 @@ +import { memo, useMemo, useCallback } from 'react' +import { Link, useLocation } from 'react-router-dom' +import { Map as PigeonMap, Overlay } from 'pigeon-maps' +import { Popover, Badge } from 'antd' + +import { useDepositList } from '@asb/context' +import { PointerIcon } from '@components/icons' +import { limitValue, withPermissions } from '@utils' + +import '@styles/index.css' + +const zoomLimit = limitValue(5, 15) + +const calcViewParams = (clusters) => { + if ((clusters?.length ?? 0) <= 0) + return { center: [60.81226, 70.0562], zoom: 5 } + + const center = clusters.reduce((sum, cluster) => { + sum[0] += cluster.latitude + sum[1] += cluster.longitude + return sum + }, [0, 0]).map((elm) => elm / clusters.length) + + const maxDeg = clusters.reduce((max, cluster) => { + const dLatitude = Math.abs(center[0] - cluster.latitude) + const dLongitude = Math.abs(center[1] - cluster.longitude) + return Math.max(Math.max(dLatitude, dLongitude), max) + }, 0) + + // zoom max = 20 (too close) + // zoom min = 1 (mega far) + // 4 - full Russia (161.6 deg) + // 13.5 - Khanty-Mansiysk + const zoom = zoomLimit(5 + 5 / (maxDeg + 0.5)) + + return { center, zoom } +} + +const Map = memo(() => { + const deposits = useDepositList() + const location = useLocation() + + const makeDepositLinks = useCallback((clusters) => ( +
+ {clusters.map(cluster => ( + +
{cluster.caption}
+ + ))} +
+ ), [location.pathname]) + + const viewParams = useMemo(() => calcViewParams(deposits), [deposits]) + + return ( +
+ + {deposits.map(deposit => { + const anchor = [deposit.latitude, deposit.longitude] + const links = makeDepositLinks(deposit.clusters) + + return ( + + +
+ + + +
+
+
+ ) + })} +
+
+ ) +}) + +export default withPermissions(Map, ['Cluster.get']) diff --git a/src/pages/Deposit/index.jsx b/src/pages/Deposit/index.jsx new file mode 100644 index 0000000..b342af2 --- /dev/null +++ b/src/pages/Deposit/index.jsx @@ -0,0 +1,64 @@ +import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom' +import { lazy, memo, useEffect, useMemo } from 'react' + +import { DepositContext, RootPathContext, useDepositList, useLayoutProps, useRootPath } from '@asb/context' +import FastRunMenu from '@components/FastRunMenu' +import { NoAccessComponent, withPermissions } from '@utils' + +const Map = lazy(() => import('./Map')) +const DepositNavigationMenu = lazy(() => import('./DepositNavigationMenu')) + +const Deposit = memo(() => { + const { '*': param } = useParams() + const location = useLocation() + + const setLayoutProps = useLayoutProps() + const deposits = useDepositList() + + const idDeposit = useMemo(() => { + const result = /^([^\/#?]+)/.exec(param) + return result && result[1] !== 'null' ? Number(result[1]) : null + }, [param]) + + const deposit = useMemo(() => deposits.find((deposit) => deposit.id === idDeposit) || null, [deposits, idDeposit]) + + const root = useRootPath() + const rootPath = useMemo(() => `${root}/deposit`, [root]) + + useEffect(() => { + const hasId = location.pathname.length > '/deposit/'.length + + const selectorProps = { + expand: hasId ? [location.pathname] : true, + current: hasId ? location.pathname : undefined, + } + + setLayoutProps({ + sheet: false, + sider: , + showSelector: true, + selectorProps, + title: 'Месторождение', + }) + }, [setLayoutProps, location.pathname, deposit]) + + return ( + + + + + } /> + + + } /> + } /> + + } /> + + + + + ) +}) + +export default withPermissions(Deposit, []) From 17d7b7c41d91d56f8d8604b15b062610504320d9 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 6 Dec 2022 00:23:45 +0500 Subject: [PATCH 06/32] =?UTF-8?q?*=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D0=B0=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D0=BC=D0=B5=D1=81=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D1=81=D0=BA=D0=B0=D0=B7=D0=BA=D0=B5=20=D0=B7=D0=BD=D0=B0?= =?UTF-8?q?=D1=87=D0=BA=D0=B0=20=D0=BD=D0=B0=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=B5=20*=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B2=D1=8B=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BC=D0=B5=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=B6=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D1=81=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Deposit/Map.jsx | 10 +++++++++- src/pages/Deposit/index.jsx | 17 ++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/pages/Deposit/Map.jsx b/src/pages/Deposit/Map.jsx index b849dae..911c137 100644 --- a/src/pages/Deposit/Map.jsx +++ b/src/pages/Deposit/Map.jsx @@ -67,7 +67,15 @@ const Map = memo(() => { return ( - + + {deposit.caption} + + )} + >
diff --git a/src/pages/Deposit/index.jsx b/src/pages/Deposit/index.jsx index b342af2..b0af444 100644 --- a/src/pages/Deposit/index.jsx +++ b/src/pages/Deposit/index.jsx @@ -1,4 +1,4 @@ -import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom' +import { Navigate, Route, Routes, useParams } from 'react-router-dom' import { lazy, memo, useEffect, useMemo } from 'react' import { DepositContext, RootPathContext, useDepositList, useLayoutProps, useRootPath } from '@asb/context' @@ -10,11 +10,13 @@ const DepositNavigationMenu = lazy(() => import('./DepositNavigationMenu')) const Deposit = memo(() => { const { '*': param } = useParams() - const location = useLocation() const setLayoutProps = useLayoutProps() const deposits = useDepositList() + const root = useRootPath() + const rootPath = useMemo(() => `${root}/deposit`, [root]) + const idDeposit = useMemo(() => { const result = /^([^\/#?]+)/.exec(param) return result && result[1] !== 'null' ? Number(result[1]) : null @@ -22,15 +24,12 @@ const Deposit = memo(() => { const deposit = useMemo(() => deposits.find((deposit) => deposit.id === idDeposit) || null, [deposits, idDeposit]) - const root = useRootPath() - const rootPath = useMemo(() => `${root}/deposit`, [root]) - useEffect(() => { - const hasId = location.pathname.length > '/deposit/'.length + const key = idDeposit ? `/deposit/${idDeposit}` : null const selectorProps = { - expand: hasId ? [location.pathname] : true, - current: hasId ? location.pathname : undefined, + expand: key ? [key] : true, + current: key || undefined, } setLayoutProps({ @@ -40,7 +39,7 @@ const Deposit = memo(() => { selectorProps, title: 'Месторождение', }) - }, [setLayoutProps, location.pathname, deposit]) + }, [setLayoutProps, idDeposit]) return ( From 1a737b6afe7b95dd5d03827f75afadc3da357b61 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 6 Dec 2022 00:51:41 +0500 Subject: [PATCH 07/32] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B2=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B5,?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=B3=D0=B4=D0=B0=20=D0=BD=D0=B5=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=BD=D0=BE=20=D0=BC=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5/=D0=BA=D1=83?= =?UTF-8?q?=D1=81=D1=82/=D1=81=D0=BA=D0=B2=D0=B0=D0=B6=D0=B8=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/selectors/WellTreeSelector.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/selectors/WellTreeSelector.tsx b/src/components/selectors/WellTreeSelector.tsx index a5b8962..c9e2c34 100755 --- a/src/components/selectors/WellTreeSelector.tsx +++ b/src/components/selectors/WellTreeSelector.tsx @@ -23,26 +23,28 @@ export const getWellState = (idState?: number): WellIconState => idState === 1 ? export const checkIsWellOnline = (lastTelemetryDate: unknown): boolean => isRawDate(lastTelemetryDate) && (Date.now() - +new Date(lastTelemetryDate) < 600_000) -const getKeyByUrl = (url?: string): [Key | null, string | null] => { +const getKeyByUrl = (url?: string): [Key | null, string | null, number | null] => { const result = url?.match(URL_REGEX) // pattern "/:type/:id" - if (!result) return [null, null] - return [result[0], result[1]] + if (!result) return [null, null, null] + return [result[0], result[1], result[2] && result[2] !== 'null' ? Number(result[2]) : null] } const getLabel = (wellsTree: TreeDataNode[], value?: string): string | undefined => { - const [url, type] = getKeyByUrl(value) + const [url, type, key] = getKeyByUrl(value) if (!url) return let deposit: TreeDataNode | undefined let cluster: TreeDataNode | undefined let well: TreeDataNode | undefined switch (type) { case 'deposit': + if (key === null) return 'Месторождение не выбрано' deposit = wellsTree.find((deposit) => deposit.key === url) if (deposit) return `${deposit.title}` return 'Ошибка! Месторождение не найдено!' case 'cluster': + if (key === null) return 'Куст не выбран' deposit = wellsTree.find((deposit) => ( cluster = deposit.children?.find((cluster: TreeDataNode) => cluster.key === url) )) @@ -51,6 +53,7 @@ const getLabel = (wellsTree: TreeDataNode[], value?: string): string | undefined return 'Ошибка! Куст не найден!' case 'well': + if (key === null) return 'Скважина не выбрана' deposit = wellsTree.find((deposit) => ( cluster = deposit.children?.find((cluster: TreeDataNode) => ( well = cluster.children?.find((well: TreeDataNode) => well.key === url) From 043f73fde329f7ad0ff66473729fd5759c9c2ec5 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 6 Dec 2022 00:56:02 +0500 Subject: [PATCH 08/32] =?UTF-8?q?=D0=9A=D1=80=D0=BE=D1=88=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LayoutPortal.tsx | 9 ++++++--- src/components/MenuBreadcrumb.tsx | 5 ++--- src/pages/AdminPanel/index.jsx | 23 ++++++++++------------- src/pages/Well/index.jsx | 8 +++++--- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/components/LayoutPortal.tsx b/src/components/LayoutPortal.tsx index c8c3f0b..c81daf3 100644 --- a/src/components/LayoutPortal.tsx +++ b/src/components/LayoutPortal.tsx @@ -1,7 +1,7 @@ import { Breadcrumb, Layout, LayoutProps, Menu, SiderProps } from 'antd' import { Key, memo, ReactNode, Suspense, useCallback, useEffect, useMemo, useState } from 'react' import { ItemType } from 'antd/lib/menu/hooks/useItems' -import { Link, Outlet } from 'react-router-dom' +import { Link, Outlet, useLocation } from 'react-router-dom' import { ApartmentOutlined, CodeOutlined, @@ -31,7 +31,7 @@ export type LayoutPortalProps = Omit & { siderProps?: SiderProps & { userMenuProps?: UserMenuProps } isAdmin?: boolean fallback?: JSX.Element - breadcrumb?: boolean | JSX.Element + breadcrumb?: boolean | ((path: string) => JSX.Element) topRightBlock?: JSX.Element } @@ -49,6 +49,7 @@ const _LayoutPortal = memo(() => { const [userMenuOpen, setUserMenuOpen] = useState(false) const [currentWell, setCurrentWell] = useState('') const [props, setProps] = useState(defaultProps) + const location = useLocation() const { isAdmin, title, sheet, showSelector, selectorProps, sider, siderProps, fallback, breadcrumb, topRightBlock, ...other } = useMemo(() => props, [props]) @@ -66,6 +67,8 @@ const _LayoutPortal = memo(() => { makeItem('Профиль', 'profile', , null, () => setUserMenuOpen((prev) => !prev)), ].filter(Boolean) as ItemType[], [isAdmin, currentWell]) + const breadcrumbItems = useMemo(() => typeof breadcrumb === 'function' && breadcrumb(location.pathname), [breadcrumb, location.pathname]) + return ( {(sider || siderProps) && ( @@ -114,7 +117,7 @@ const _LayoutPortal = memo(() => { setWellsTreeOpen((prev) => !prev)}>{currentWell} )} - {breadcrumb !== true && breadcrumb} + {breadcrumbItems} )} {topRightBlock} diff --git a/src/components/MenuBreadcrumb.tsx b/src/components/MenuBreadcrumb.tsx index 3623f68..ccbda20 100644 --- a/src/components/MenuBreadcrumb.tsx +++ b/src/components/MenuBreadcrumb.tsx @@ -21,13 +21,12 @@ export const makeBreadcrumbItems = (items: PrivateMenuItem[], pathParts: string[ return out } -export const makeMenuBreadcrumbItems = ( +export const makeMenuBreadcrumbItemsRender = ( menuItems: PrivateMenuItem[], - path: string, pathRoot: RegExp = /^\//, itemsProps?: FunctionalValue<(item: PrivateMenuItem) => BreadcrumbItemProps>, itemRender?: (item: PrivateMenuItem) => JSX.Element, -) => { +) => (path: string) => { const getItemProps = getFunctionalValue(itemsProps) const rootPart = pathRoot.exec(path) diff --git a/src/pages/AdminPanel/index.jsx b/src/pages/AdminPanel/index.jsx index e4c11b8..98c8e65 100755 --- a/src/pages/AdminPanel/index.jsx +++ b/src/pages/AdminPanel/index.jsx @@ -1,9 +1,9 @@ -import { Navigate, Route, Routes, useLocation } from 'react-router-dom' +import { Navigate, Route, Routes } from 'react-router-dom' import { lazy, memo, useEffect, useMemo } from 'react' import { RootPathContext, useLayoutProps, useRootPath } from '@asb/context' import { FastRunMenu } from '@components/FastRunMenu' -import { makeMenuBreadcrumbItems } from '@components/MenuBreadcrumb' +import { makeMenuBreadcrumbItemsRender } from '@components/MenuBreadcrumb' import { NoAccessComponent, withPermissions } from '@utils' import { AdminNavigationMenu, menuItems } from './AdminNavigationMenu' @@ -21,21 +21,18 @@ const TelemetryViewer = lazy(() => import('./Telemetry/TelemetryViewer')) const TelemetryMerger = lazy(() => import('./Telemetry/TelemetryMerger')) const VisitLog = lazy(() => import('./VisitLog')) +const layoutProps = { + sider: , + title: 'Администраторская панель', + isAdmin: true, + breadcrumb: makeMenuBreadcrumbItemsRender(menuItems, /^\/admin\//), +} + const AdminPanel = memo(() => { - const location = useLocation() const root = useRootPath() const rootPath = useMemo(() => `${root}/admin`, [root]) - const setLayoutProps = useLayoutProps() - - useEffect(() => { - setLayoutProps({ - sider: , - title: 'Администраторская панель', - isAdmin: true, - breadcrumb: makeMenuBreadcrumbItems(menuItems, location.pathname, /^\/admin\//), - }) - }, [location.pathname]) + useLayoutProps(layoutProps) return ( diff --git a/src/pages/Well/index.jsx b/src/pages/Well/index.jsx index ca1cd2e..322dbf8 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 { makeMenuBreadcrumbItems } from '@components/MenuBreadcrumb' +import { makeMenuBreadcrumbItemsRender } from '@components/MenuBreadcrumb' import { NoAccessComponent, withPermissions } from '@utils' import { WellService } from '@api' @@ -40,6 +40,8 @@ const DiagramReport = lazy(() => import('./Reports/DiagramReport')) const Statistics = lazy(() => import('./Analytics/Statistics')) const WellCompositeEditor = lazy(() => import('./Analytics/WellCompositeEditor')) +const breadcrumb = makeMenuBreadcrumbItemsRender(menuItems, /^\/well\/[^\/#?]+\//) + const Well = memo(() => { const { idWell } = useParams() @@ -82,9 +84,9 @@ const Well = memo(() => { useEffect(() => setLayoutProps({ sider: , - breadcrumb: makeMenuBreadcrumbItems(menuItems, location.pathname, /^\/well\/[0-9]+\//), + breadcrumb, topRightBlock: topRightBlock?.(well), - }), [well, location.pathname, setLayoutProps, topRightBlock]) + }), [well, setLayoutProps, topRightBlock]) return ( From 4b20a44d8857ef9ed52f3d81dc58b11954c18454 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 6 Dec 2022 04:44:03 +0500 Subject: [PATCH 09/32] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=B0=D1=8F?= =?UTF-8?q?=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=86=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/views/WellView.tsx | 12 +- src/pages/Deposit/DepositNavigationMenu.jsx | 2 + .../Deposit/GeneralSubsystemStatistics.jsx | 104 ++++++++++++++++++ src/pages/Deposit/index.jsx | 28 +++-- 4 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 src/pages/Deposit/GeneralSubsystemStatistics.jsx diff --git a/src/components/views/WellView.tsx b/src/components/views/WellView.tsx index 316edb8..614dc52 100644 --- a/src/components/views/WellView.tsx +++ b/src/components/views/WellView.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react' +import { DetailedHTMLProps, HTMLAttributes, memo } from 'react' import { Tooltip, TooltipProps } from 'antd' import { Grid, GridItem } from '@components/Grid' @@ -15,9 +15,11 @@ const wellState: Record = { export type WellViewProps = TooltipProps & { well?: WellDto + iconProps?: DetailedHTMLProps, HTMLSpanElement> + labelProps?: DetailedHTMLProps, HTMLSpanElement> } -export const WellView = memo(({ well, ...other }) => well ? ( +export const WellView = memo(({ well, iconProps, labelProps, ...other }) => well ? ( Название: @@ -47,10 +49,12 @@ export const WellView = memo(({ well, ...other }) => well ? ( {well.id ?? '---'} )}> - + - {well.caption} + + {well.deposit} / {well.cluster} / {well.caption} + ) : ( - diff --git a/src/pages/Deposit/DepositNavigationMenu.jsx b/src/pages/Deposit/DepositNavigationMenu.jsx index 304f062..b1c2965 100644 --- a/src/pages/Deposit/DepositNavigationMenu.jsx +++ b/src/pages/Deposit/DepositNavigationMenu.jsx @@ -1,5 +1,6 @@ import { memo } from 'react' import { + FundOutlined, HeatMapOutlined, } from '@ant-design/icons' @@ -7,6 +8,7 @@ import { makeItem, PrivateMenu } from '@components/PrivateMenu' export const menuItems = [ makeItem('Карта', 'map', [], ), + makeItem('Наработка АКБ', 'statistics', [], ), ] export const DepositNavigationMenu = memo((props) => ( diff --git a/src/pages/Deposit/GeneralSubsystemStatistics.jsx b/src/pages/Deposit/GeneralSubsystemStatistics.jsx new file mode 100644 index 0000000..b544157 --- /dev/null +++ b/src/pages/Deposit/GeneralSubsystemStatistics.jsx @@ -0,0 +1,104 @@ +import { memo, useCallback, useEffect, useState } from 'react' +import { QuestionCircleOutlined } from '@ant-design/icons' +import { Card } from 'antd' + +import { WellView } from '@components/views' +import LoaderPortal from '@components/LoaderPortal' +import { DateRangeWrapper, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table' +import { invokeWebApiWrapperAsync } from '@components/factory' +import { arrayOrDefault, withPermissions } from '@utils' +import { SubsystemOperationTimeService } from '@api' + +const numericRender = makeNumericRender(2) + +const columns = [ + makeTextColumn('Подсистема', 'subsystemName', undefined, undefined, (value, row) => value || row.key), + makeNumericColumn('Проходка, м', 'sumDepthInterval'), + makeNumericColumn('Время работы, ч', 'usedTimeHours'), + makeNumericColumn('Кол-во запусков', 'operationCount'), + makeNumericColumn('Коэф. использования, %', 'kUsage', undefined, undefined, (value) => numericRender(value * 100)), +] + +const getSubsystemIcon = (subsystem) => { + return +} + +const GeneralSubsystemStatistics = memo(() => { + const [data, setData] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [selected, setSelected] = useState(null) + const [dates, setDates] = useState([null, null]) + + const onRow = useCallback((record) => { + + }, [selected]) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const data = await SubsystemOperationTimeService.getStatByWell(dates?.[0]?.toISOString(), dates?.[1]?.toISOString()) + const out = arrayOrDefault(data).map(({ well, ...subsystems }) => ({ + well, + subsystems: Object.entries(subsystems).map(([key, value]) => ({ key, ...value })), + })) + setData(out) + }, + setIsLoading, + 'Не удалось загрузить статистику наработки подсистем', + { actionName: 'Загрузка статистики наработки подсистем' }, + ) + }, [dates]) + + const makeOnCardClick = useCallback((row) => () => { + setSelected((prev) => (prev?.well.id === row.well.id) ? null : row) + }, []) + + return ( + +
+
+ Диапазон дат: + +
+
+ {data.map((row) => { + const cardStyle = { + boxShadow: row.well.id === selected?.well.id ? '0 0 5px 2px gray' : null, + } + + return ( + } onClick={makeOnCardClick(row)} style={cardStyle}> +
+ {row.subsystems.map((ss) => ( +
+ {getSubsystemIcon(ss)} + {ss.subsystemName || ss.key} +
+ ))} +
+
+ ) + })} +
+ {selected && ( +
+
+ Детальная информация по скважине + +
+ + + )} + + + ) +}) + +export default withPermissions(GeneralSubsystemStatistics, []) diff --git a/src/pages/Deposit/index.jsx b/src/pages/Deposit/index.jsx index b0af444..c17a951 100644 --- a/src/pages/Deposit/index.jsx +++ b/src/pages/Deposit/index.jsx @@ -3,10 +3,15 @@ import { lazy, memo, useEffect, useMemo } from 'react' import { DepositContext, RootPathContext, useDepositList, useLayoutProps, useRootPath } from '@asb/context' import FastRunMenu from '@components/FastRunMenu' +import { makeMenuBreadcrumbItemsRender } from '@components/MenuBreadcrumb' import { NoAccessComponent, withPermissions } from '@utils' +import { DepositNavigationMenu, menuItems } from './DepositNavigationMenu' + const Map = lazy(() => import('./Map')) -const DepositNavigationMenu = lazy(() => import('./DepositNavigationMenu')) +const GeneralSubsystemStatistics = lazy(() => import('./GeneralSubsystemStatistics')) + +const breadcrumb = makeMenuBreadcrumbItemsRender(menuItems, /^\/deposit\/[^\/#?]+\//) const Deposit = memo(() => { const { '*': param } = useParams() @@ -17,9 +22,14 @@ const Deposit = memo(() => { const root = useRootPath() const rootPath = useMemo(() => `${root}/deposit`, [root]) - const idDeposit = useMemo(() => { - const result = /^([^\/#?]+)/.exec(param) - return result && result[1] !== 'null' ? Number(result[1]) : null + const [idDeposit, isMap] = useMemo(() => { + const result = /^([^\/#?]+)(:?\/([^\/#?]+))?/.exec(param) + if (!result) return [null, false] + console.log(result) + return [ + result[1] !== 'null' ? Number(result[1]) : null, + result[3] === 'map', + ] }, [param]) const deposit = useMemo(() => deposits.find((deposit) => deposit.id === idDeposit) || null, [deposits, idDeposit]) @@ -33,13 +43,14 @@ const Deposit = memo(() => { } setLayoutProps({ - sheet: false, - sider: , - showSelector: true, + breadcrumb: !isMap && breadcrumb, + sheet: !isMap, + sider: , + showSelector: isMap, selectorProps, title: 'Месторождение', }) - }, [setLayoutProps, idDeposit]) + }, [setLayoutProps, idDeposit, isMap]) return ( @@ -53,6 +64,7 @@ const Deposit = memo(() => { } /> } /> + } /> From 8f98cc066c866e32a95fa6b92fc77ef00d6ae3e4 Mon Sep 17 00:00:00 2001 From: goodmice Date: Thu, 8 Dec 2022 08:26:10 +0500 Subject: [PATCH 10/32] =?UTF-8?q?=D0=A4=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20range=20=D1=83=D1=81=D0=BA=D0=BE=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B2=205=20=D1=80=D0=B0=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/functions/numbers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/functions/numbers.tsx b/src/utils/functions/numbers.tsx index 0a686f1..6efb69c 100644 --- a/src/utils/functions/numbers.tsx +++ b/src/utils/functions/numbers.tsx @@ -23,7 +23,7 @@ export const limitValue = (min: T, max: T) => (value: T) => { * * @returns Массив чисел в диапазоне от `start` до `end` */ -export const range = (end: number, start: number = 0) => Array.from({ length: end - start }, (_, i) => start + i) +export const range = (end: number, start: number = 0) => Array(end - start).fill(undefined).map((_, i) => start + i) export const pretify = (n: number): number | null => { if (!Number.isFinite(n)) return null From 28a096279360f3441d5756290b220ac22804d0d4 Mon Sep 17 00:00:00 2001 From: goodmice Date: Thu, 8 Dec 2022 10:55:11 +0500 Subject: [PATCH 11/32] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=D1=8B=20=D0=9D=D0=B0=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=90=D0=9A=D0=91=20*=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=86=D0=B2=D0=B5=D1=82=D0=B0=20*?= =?UTF-8?q?=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=B8=D0=BA=D0=BE=D0=BD=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=20*=20?= =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BD?= =?UTF-8?q?=D0=B0=20null?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Deposit/GeneralSubsystemStatistics.jsx | 108 +++++++++++++----- 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/src/pages/Deposit/GeneralSubsystemStatistics.jsx b/src/pages/Deposit/GeneralSubsystemStatistics.jsx index b544157..2f9d4cd 100644 --- a/src/pages/Deposit/GeneralSubsystemStatistics.jsx +++ b/src/pages/Deposit/GeneralSubsystemStatistics.jsx @@ -1,6 +1,11 @@ -import { memo, useCallback, useEffect, useState } from 'react' -import { QuestionCircleOutlined } from '@ant-design/icons' -import { Card } from 'antd' +import { memo, useCallback, useEffect, useMemo, useState } from 'react' +import { + CheckOutlined, + StopOutlined, + QuestionCircleOutlined, + WarningOutlined, +} from '@ant-design/icons' +import { Card, Empty } from 'antd' import { WellView } from '@components/views' import LoaderPortal from '@components/LoaderPortal' @@ -19,8 +24,48 @@ const columns = [ makeNumericColumn('Коэф. использования, %', 'kUsage', undefined, undefined, (value) => numericRender(value * 100)), ] -const getSubsystemIcon = (subsystem) => { - return +const getSubsystemState = (subsystem) => { + if (!subsystem || typeof subsystem.kUsage !== 'number') return null + if (subsystem.kUsage <= 0.2) return 'error' + if (subsystem.kUsage <= 0.7) return 'warn' + return 'ok' +} + +const getSubsystemColor = (state) => { + switch (state) { + case 'ok': return '#52c41a' + case 'warn': return '#faad14' + case 'error': return '#ff4d4f' + default: return null + } +} + +const getSubsystemIcon = (state) => { + switch (state) { + case 'ok': return + case 'warn': return + case 'error': return + default: return + } +} + +const getCardState = (subsystems) => { + if (subsystems.length <= 0) return null + const states = subsystems.map((ss) => getSubsystemState(ss)) + if (states.some((state) => state === 'error')) return 'error' + if (states.some((state) => state === 'warn')) return 'warn' + return 'ok' +} + +const generateSubsystem = (subsystem) => { + const state = getSubsystemState(subsystem) + + return ( +
+ {getSubsystemIcon(state)} + {subsystem.subsystemName || subsystem.key} +
+ ) } const GeneralSubsystemStatistics = memo(() => { @@ -30,7 +75,10 @@ const GeneralSubsystemStatistics = memo(() => { const [dates, setDates] = useState([null, null]) const onRow = useCallback((record) => { - + const state = getSubsystemState(record) + if (state === null) return null + const color = getSubsystemColor(state) + return { style: { backgroundColor: color } } }, [selected]) useEffect(() => { @@ -39,7 +87,7 @@ const GeneralSubsystemStatistics = memo(() => { const data = await SubsystemOperationTimeService.getStatByWell(dates?.[0]?.toISOString(), dates?.[1]?.toISOString()) const out = arrayOrDefault(data).map(({ well, ...subsystems }) => ({ well, - subsystems: Object.entries(subsystems).map(([key, value]) => ({ key, ...value })), + subsystems: Object.entries(subsystems).filter(([_, v]) => v).map(([key, v]) => ({ key, ...v })) })) setData(out) }, @@ -53,32 +101,37 @@ const GeneralSubsystemStatistics = memo(() => { setSelected((prev) => (prev?.well.id === row.well.id) ? null : row) }, []) + const cards = useMemo(() => { + return data.map((row) => { + const state = getCardState(row.subsystems) + const isSelected = row.well.id === selected?.well.id + + const cardStyle = { + boxShadow: `0 0 5px 2px ${isSelected ? 'gray' : getSubsystemColor(state)}`, + userSelect: 'none', + } + + return ( + } onClick={state ? makeOnCardClick(row) : null} style={cardStyle}> +
+ {state ? row.subsystems.map((ss) => generateSubsystem(ss)) : } +
+
+ ) + }) + }, [data, selected]) + return (
-
+
Диапазон дат:
-
- {data.map((row) => { - const cardStyle = { - boxShadow: row.well.id === selected?.well.id ? '0 0 5px 2px gray' : null, - } - - return ( - } onClick={makeOnCardClick(row)} style={cardStyle}> -
- {row.subsystems.map((ss) => ( -
- {getSubsystemIcon(ss)} - {ss.subsystemName || ss.key} -
- ))} -
-
- ) - })} +
+
+ {cards} +
{selected && (
@@ -87,7 +140,6 @@ const GeneralSubsystemStatistics = memo(() => {
Date: Thu, 8 Dec 2022 11:14:37 +0500 Subject: [PATCH 12/32] =?UTF-8?q?=D0=A2=D0=B0=D0=B1=D0=BB=D0=B8=D1=86?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=86=D0=B5=20=D0=9D=D0=B0=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=90=D0=9A=D0=91=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BD=D0=B0=20=D0=B2=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D1=81=D0=BA=D0=B0=D0=B7=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/views/WellView.tsx | 4 +- .../Deposit/GeneralSubsystemStatistics.jsx | 76 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/components/views/WellView.tsx b/src/components/views/WellView.tsx index 614dc52..125303a 100644 --- a/src/components/views/WellView.tsx +++ b/src/components/views/WellView.tsx @@ -19,6 +19,8 @@ export type WellViewProps = TooltipProps & { labelProps?: DetailedHTMLProps, HTMLSpanElement> } +export const getWellTitle = (well: WellDto) => `${well.deposit || '-'} / ${well.cluster || '-'} / ${well.caption || '-'}` + export const WellView = memo(({ well, iconProps, labelProps, ...other }) => well ? ( @@ -53,7 +55,7 @@ export const WellView = memo(({ well, iconProps, labelProps, ...o - {well.deposit} / {well.cluster} / {well.caption} + {getWellTitle(well)} ) : ( diff --git a/src/pages/Deposit/GeneralSubsystemStatistics.jsx b/src/pages/Deposit/GeneralSubsystemStatistics.jsx index 2f9d4cd..ddde54e 100644 --- a/src/pages/Deposit/GeneralSubsystemStatistics.jsx +++ b/src/pages/Deposit/GeneralSubsystemStatistics.jsx @@ -1,13 +1,13 @@ -import { memo, useCallback, useEffect, useMemo, useState } from 'react' +import { memo, useEffect, useMemo, useState } from 'react' import { CheckOutlined, StopOutlined, QuestionCircleOutlined, WarningOutlined, } from '@ant-design/icons' -import { Card, Empty } from 'antd' +import { Card, Empty, Popover } from 'antd' -import { WellView } from '@components/views' +import { getWellTitle, WellView } from '@components/views' import LoaderPortal from '@components/LoaderPortal' import { DateRangeWrapper, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table' import { invokeWebApiWrapperAsync } from '@components/factory' @@ -68,19 +68,18 @@ const generateSubsystem = (subsystem) => { ) } +const onRow = (record) => { + const state = getSubsystemState(record) + if (state === null) return null + const color = getSubsystemColor(state) + return { style: { backgroundColor: color, color: 'white' } } +} + const GeneralSubsystemStatistics = memo(() => { const [data, setData] = useState([]) const [isLoading, setIsLoading] = useState(false) - const [selected, setSelected] = useState(null) const [dates, setDates] = useState([null, null]) - const onRow = useCallback((record) => { - const state = getSubsystemState(record) - if (state === null) return null - const color = getSubsystemColor(state) - return { style: { backgroundColor: color } } - }, [selected]) - useEffect(() => { invokeWebApiWrapperAsync( async () => { @@ -97,29 +96,45 @@ const GeneralSubsystemStatistics = memo(() => { ) }, [dates]) - const makeOnCardClick = useCallback((row) => () => { - setSelected((prev) => (prev?.well.id === row.well.id) ? null : row) - }, []) - const cards = useMemo(() => { return data.map((row) => { const state = getCardState(row.subsystems) - const isSelected = row.well.id === selected?.well.id const cardStyle = { - boxShadow: `0 0 5px 2px ${isSelected ? 'gray' : getSubsystemColor(state)}`, + boxShadow: `0 0 5px 2px ${getSubsystemColor(state)}`, userSelect: 'none', } - return ( - } onClick={state ? makeOnCardClick(row) : null} style={cardStyle}> + const card = ( +
{state ? row.subsystems.map((ss) => generateSubsystem(ss)) : }
) + + if (!state) return card + + const detailTitle = ( + <> + Детальная информация по скважине + + + ) + + const detailContent = ( +
+ ) + + return {card} }) - }, [data, selected]) + }, [data]) return ( @@ -128,26 +143,9 @@ const GeneralSubsystemStatistics = memo(() => { Диапазон дат: -
-
- {cards} -
+
+ {cards}
- {selected && ( -
-
- Детальная информация по скважине - -
-
- - )} ) From 65b9ede58063d3151f863d10fd9d9f03dd7e500d Mon Sep 17 00:00:00 2001 From: goodmice Date: Thu, 8 Dec 2022 11:18:42 +0500 Subject: [PATCH 13/32] =?UTF-8?q?=D0=A4=D0=B0=D0=B9=D0=BB=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B=20=D0=9D=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=90=D0=9A=D0=91=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B8=D0=BC=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Deposit/DepositNavigationMenu.jsx | 2 +- .../{GeneralSubsystemStatistics.jsx => StatisticsADW.jsx} | 0 src/pages/Deposit/index.jsx | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/pages/Deposit/{GeneralSubsystemStatistics.jsx => StatisticsADW.jsx} (100%) diff --git a/src/pages/Deposit/DepositNavigationMenu.jsx b/src/pages/Deposit/DepositNavigationMenu.jsx index b1c2965..af138bc 100644 --- a/src/pages/Deposit/DepositNavigationMenu.jsx +++ b/src/pages/Deposit/DepositNavigationMenu.jsx @@ -8,7 +8,7 @@ import { makeItem, PrivateMenu } from '@components/PrivateMenu' export const menuItems = [ makeItem('Карта', 'map', [], ), - makeItem('Наработка АКБ', 'statistics', [], ), + makeItem('Наработка АКБ', 'statistics_adw', [], ), ] export const DepositNavigationMenu = memo((props) => ( diff --git a/src/pages/Deposit/GeneralSubsystemStatistics.jsx b/src/pages/Deposit/StatisticsADW.jsx similarity index 100% rename from src/pages/Deposit/GeneralSubsystemStatistics.jsx rename to src/pages/Deposit/StatisticsADW.jsx diff --git a/src/pages/Deposit/index.jsx b/src/pages/Deposit/index.jsx index c17a951..a4cadf6 100644 --- a/src/pages/Deposit/index.jsx +++ b/src/pages/Deposit/index.jsx @@ -9,7 +9,7 @@ import { NoAccessComponent, withPermissions } from '@utils' import { DepositNavigationMenu, menuItems } from './DepositNavigationMenu' const Map = lazy(() => import('./Map')) -const GeneralSubsystemStatistics = lazy(() => import('./GeneralSubsystemStatistics')) +const StatisticsADW = lazy(() => import('./StatisticsADW')) const breadcrumb = makeMenuBreadcrumbItemsRender(menuItems, /^\/deposit\/[^\/#?]+\//) @@ -64,7 +64,7 @@ const Deposit = memo(() => { } /> } /> - } /> + } /> From 5af996f9e5931254a6fc882aa3b7ccac5f88e52f Mon Sep 17 00:00:00 2001 From: goodmice Date: Thu, 8 Dec 2022 11:48:30 +0500 Subject: [PATCH 14/32] =?UTF-8?q?=D0=A1=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Deposit/StatisticsADW.jsx | 115 +++++++++++----------------- src/styles/statistics_adw.less | 59 ++++++++++++++ 2 files changed, 103 insertions(+), 71 deletions(-) create mode 100644 src/styles/statistics_adw.less diff --git a/src/pages/Deposit/StatisticsADW.jsx b/src/pages/Deposit/StatisticsADW.jsx index ddde54e..c26d948 100644 --- a/src/pages/Deposit/StatisticsADW.jsx +++ b/src/pages/Deposit/StatisticsADW.jsx @@ -1,19 +1,16 @@ +import { CheckOutlined, StopOutlined, QuestionCircleOutlined, WarningOutlined } from '@ant-design/icons' import { memo, useEffect, useMemo, useState } from 'react' -import { - CheckOutlined, - StopOutlined, - QuestionCircleOutlined, - WarningOutlined, -} from '@ant-design/icons' import { Card, Empty, Popover } from 'antd' -import { getWellTitle, WellView } from '@components/views' import LoaderPortal from '@components/LoaderPortal' -import { DateRangeWrapper, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table' +import { getWellTitle, WellView } from '@components/views' import { invokeWebApiWrapperAsync } from '@components/factory' +import { DateRangeWrapper, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table' import { arrayOrDefault, withPermissions } from '@utils' import { SubsystemOperationTimeService } from '@api' +import '@styles/statistics_adw.less' + const numericRender = makeNumericRender(2) const columns = [ @@ -31,15 +28,6 @@ const getSubsystemState = (subsystem) => { return 'ok' } -const getSubsystemColor = (state) => { - switch (state) { - case 'ok': return '#52c41a' - case 'warn': return '#faad14' - case 'error': return '#ff4d4f' - default: return null - } -} - const getSubsystemIcon = (state) => { switch (state) { case 'ok': return @@ -51,7 +39,7 @@ const getSubsystemIcon = (state) => { const getCardState = (subsystems) => { if (subsystems.length <= 0) return null - const states = subsystems.map((ss) => getSubsystemState(ss)) + const states = subsystems.map(getSubsystemState) if (states.some((state) => state === 'error')) return 'error' if (states.some((state) => state === 'warn')) return 'warn' return 'ok' @@ -61,33 +49,26 @@ const generateSubsystem = (subsystem) => { const state = getSubsystemState(subsystem) return ( -
+
{getSubsystemIcon(state)} {subsystem.subsystemName || subsystem.key}
) } -const onRow = (record) => { - const state = getSubsystemState(record) - if (state === null) return null - const color = getSubsystemColor(state) - return { style: { backgroundColor: color, color: 'white' } } -} +const onRow = (record) => ({ className: `status-${getSubsystemState(record)}` }) +const objectToArray = (obj) => Object.entries(obj).filter(([_, v]) => v).map(([key, v]) => ({ key, ...v })) const GeneralSubsystemStatistics = memo(() => { - const [data, setData] = useState([]) const [isLoading, setIsLoading] = useState(false) const [dates, setDates] = useState([null, null]) + const [data, setData] = useState([]) useEffect(() => { invokeWebApiWrapperAsync( async () => { const data = await SubsystemOperationTimeService.getStatByWell(dates?.[0]?.toISOString(), dates?.[1]?.toISOString()) - const out = arrayOrDefault(data).map(({ well, ...subsystems }) => ({ - well, - subsystems: Object.entries(subsystems).filter(([_, v]) => v).map(([key, v]) => ({ key, ...v })) - })) + const out = arrayOrDefault(data).map(({ well, ...ss }) => ({ well, subsystems: objectToArray(ss) })) setData(out) }, setIsLoading, @@ -96,56 +77,48 @@ const GeneralSubsystemStatistics = memo(() => { ) }, [dates]) - const cards = useMemo(() => { - return data.map((row) => { - const state = getCardState(row.subsystems) + const cards = useMemo(() => data.map((row) => { + const state = getCardState(row.subsystems) - const cardStyle = { - boxShadow: `0 0 5px 2px ${getSubsystemColor(state)}`, - userSelect: 'none', - } + const card = ( + +
+ {state ? row.subsystems.map((ss) => generateSubsystem(ss)) : } +
+
+ ) - const card = ( - -
- {state ? row.subsystems.map((ss) => generateSubsystem(ss)) : } -
-
- ) + if (!state) return card - if (!state) return card - - const detailTitle = ( - <> - Детальная информация по скважине - - - ) - - const detailContent = ( -
- ) - - return {card} - }) - }, [data]) + return ( + + Детальная информация по скважине + + + )} + content={( +
+ )} + >{card} + ) + }), [data]) return ( -
-
+
+
Диапазон дат:
-
- {cards} -
+
{cards}
) diff --git a/src/styles/statistics_adw.less b/src/styles/statistics_adw.less new file mode 100644 index 0000000..b03fed3 --- /dev/null +++ b/src/styles/statistics_adw.less @@ -0,0 +1,59 @@ +@ok-color: #52c41a; +@warn-color: #faad14; +@error-color: #ff4d4f; + +.statistics-adw-page { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 20px; + height: 100%; + + & .filter-block { + align-items: center; + padding-left: 15px; + display: flex; + gap: 10px; + } + + & .well-cards { + padding: 15px; + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 15px; + + & .subsystem-card { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + &.status-ok { box-shadow: 0 0 5px 2px @ok-color; } + &.status-warn { box-shadow: 0 0 5px 2px @warn-color; } + &.status-error { box-shadow: 0 0 5px 2px @error-color; } + + & .subsystem-card-body { + display: flex; + flex-direction: column; + gap: 10px; + + & .subsystem-status { + display: flex; + gap: 5px; + align-items: center; + + &.status-ok { color: @ok-color; } + &.status-warn { color: @warn-color; } + &.status-error { color: @error-color; } + } + } + } + } +} + +.ant-table-row { + &.status-ok { background-color: @ok-color; } + &.status-warn { background-color: @warn-color; } + &.status-error { background-color: @error-color; } +} From d235b01c809f8a44d81b00305a70647f4c87a84c Mon Sep 17 00:00:00 2001 From: goodmice Date: Thu, 8 Dec 2022 12:13:58 +0500 Subject: [PATCH 15/32] =?UTF-8?q?=D0=A1=D1=82=D0=B8=D0=BB=D0=B8=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D0=B4=D0=B8=D1=80=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D1=8F?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 +- src/components/FastRunMenu.tsx | 2 +- src/components/LayoutPortal.tsx | 2 +- src/components/UserMenu.tsx | 2 +- src/components/d3/D3Chart.tsx | 2 +- .../d3/D3HorizontalPercentChart.tsx | 4 +-- src/components/d3/D3MouseZone.tsx | 2 +- .../d3/monitoring/D3HorizontalCursor.tsx | 2 +- src/components/d3/plugins/D3Cursor.tsx | 2 +- src/components/d3/plugins/D3Tooltip.tsx | 2 +- src/components/selectors/WellTreeSelector.tsx | 2 +- src/pages/Deposit/StatisticsADW.jsx | 2 +- src/pages/Well/Analytics/Statistics.jsx | 2 +- .../Analytics/WellCompositeEditor/index.jsx | 2 +- .../Well/DrillingProgram/CategoryAdder.jsx | 2 +- .../Well/DrillingProgram/CategoryHistory.jsx | 2 +- .../Well/DrillingProgram/CategoryRender.jsx | 2 +- src/pages/Well/DrillingProgram/index.jsx | 2 +- src/pages/Well/Measure/MeasureTable.jsx | 2 +- src/pages/Well/Measure/View.jsx | 2 +- src/pages/Well/Measure/columnsCommon.jsx | 2 +- .../Well/Telemetry/DashboardNNB/index.jsx | 2 +- src/pages/Well/Telemetry/Messages.jsx | 4 +-- .../Well/Telemetry/OperationTime/index.jsx | 4 +-- .../Telemetry/Operations/OperationsChart.jsx | 2 +- .../Telemetry/Operations/OperationsTable.jsx | 2 +- src/pages/Well/Telemetry/Operations/index.jsx | 2 +- .../TelemetryView/ActiveMessagesOnline.jsx | 2 +- .../Well/Telemetry/TelemetryView/index.jsx | 4 +-- src/pages/Well/WellCase/HistoryTable.jsx | 2 +- src/pages/Well/WellCase/WellCaseEditor.jsx | 2 +- src/pages/Well/WellCase/index.jsx | 2 +- .../WellOperations/Tvd/AdditionalTables.jsx | 2 +- .../Well/WellOperations/Tvd/NptTable.jsx | 2 +- src/pages/Well/WellOperations/Tvd/TLChart.jsx | 6 ++--- src/pages/Well/WellOperations/Tvd/TLPie.jsx | 2 +- src/pages/Well/WellOperations/Tvd/index.jsx | 26 ++++--------------- src/pages/public/Login.jsx | 3 ++- src/styles/{ => components}/charts.less | 0 src/styles/{ => components}/d3.less | 0 src/styles/{ => components}/display.less | 0 .../{ => components}/fast_run_menu.less | 0 src/styles/{ => components}/filter.less | 0 src/styles/{ => components}/layout.less | 0 src/styles/{ => components}/loader.css | 0 src/styles/{ => components}/user_menu.less | 0 .../well_tree_select.css} | 0 src/styles/{ => pages}/App.less | 2 +- src/styles/{ => pages}/dashboard_nnb.less | 0 .../{ => pages}/detected_operations.less | 0 src/styles/{ => pages}/drilling_program.less | 0 src/styles/{ => pages}/equipment_details.css | 0 src/styles/{ => pages}/measure.css | 0 src/styles/{ => pages}/message.less | 0 src/styles/{ => pages}/message_telemetry.css | 0 src/styles/{ => pages}/operation_time.less | 0 src/styles/{ => pages}/smbo.css | 0 src/styles/{ => pages}/statistics.less | 0 src/styles/{ => pages}/statistics_adw.less | 0 src/styles/{ => pages}/telemetry_view.less | 0 src/styles/{ => pages}/tvd.less | 0 src/styles/{ => pages}/well_case.less | 0 src/styles/{ => pages}/well_composite.less | 0 63 files changed, 50 insertions(+), 65 deletions(-) rename src/styles/{ => components}/charts.less (100%) mode change 100755 => 100644 rename src/styles/{ => components}/d3.less (100%) rename src/styles/{ => components}/display.less (100%) rename src/styles/{ => components}/fast_run_menu.less (100%) rename src/styles/{ => components}/filter.less (100%) rename src/styles/{ => components}/layout.less (100%) rename src/styles/{ => components}/loader.css (100%) mode change 100755 => 100644 rename src/styles/{ => components}/user_menu.less (100%) rename src/styles/{wellTreeSelect.css => components/well_tree_select.css} (100%) mode change 100755 => 100644 rename src/styles/{ => pages}/App.less (97%) mode change 100755 => 100644 rename src/styles/{ => pages}/dashboard_nnb.less (100%) rename src/styles/{ => pages}/detected_operations.less (100%) rename src/styles/{ => pages}/drilling_program.less (100%) mode change 100755 => 100644 rename src/styles/{ => pages}/equipment_details.css (100%) mode change 100755 => 100644 rename src/styles/{ => pages}/measure.css (100%) mode change 100755 => 100644 rename src/styles/{ => pages}/message.less (100%) rename src/styles/{ => pages}/message_telemetry.css (100%) mode change 100755 => 100644 rename src/styles/{ => pages}/operation_time.less (100%) rename src/styles/{ => pages}/smbo.css (100%) mode change 100755 => 100644 rename src/styles/{ => pages}/statistics.less (100%) mode change 100755 => 100644 rename src/styles/{ => pages}/statistics_adw.less (100%) rename src/styles/{ => pages}/telemetry_view.less (100%) rename src/styles/{ => pages}/tvd.less (100%) mode change 100755 => 100644 rename src/styles/{ => pages}/well_case.less (100%) rename src/styles/{ => pages}/well_composite.less (100%) diff --git a/src/App.tsx b/src/App.tsx index 0d84e96..8f11027 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,7 @@ import { RootPathContext } from '@asb/context' import SuspenseFallback from '@components/SuspenseFallback' import { NoAccessComponent } from '@utils' -import '@styles/App.less' +import '@styles/pages/App.less' const UserOutlet = lazy(() => import('@components/outlets/UserOutlet')) const DepositsOutlet = lazy(() => import('@components/outlets/DepositsOutlet')) diff --git a/src/components/FastRunMenu.tsx b/src/components/FastRunMenu.tsx index 30d78b2..b21db2f 100644 --- a/src/components/FastRunMenu.tsx +++ b/src/components/FastRunMenu.tsx @@ -11,7 +11,7 @@ import { hasPermission, isURLAvailable } from '@utils' import { menuItems as adminMenuItems } from '@pages/AdminPanel/AdminNavigationMenu' import { menuItems as wellMenuItems } from '@pages/Well/WellNavigationMenu' -import '@styles/fast_run_menu.less' +import '@styles/components/fast_run_menu.less' const transliterationTable = { 'q': 'й', 'w': 'ц', 'e': 'у', 'r': 'к', 't': 'е', 'y': 'н', 'u': 'г', 'i': 'ш', 'o': 'щ', 'p': 'з', '[': 'х', ']': 'ъ', '{': 'х', '}': 'ъ', diff --git a/src/components/LayoutPortal.tsx b/src/components/LayoutPortal.tsx index c81daf3..ef88f30 100644 --- a/src/components/LayoutPortal.tsx +++ b/src/components/LayoutPortal.tsx @@ -18,7 +18,7 @@ import SuspenseFallback from './SuspenseFallback' import Logo from '@images/Logo' -import '@styles/layout.less' +import '@styles/components/layout.less' const { Content, Sider } = Layout diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index f222420..f9a380b 100755 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -10,7 +10,7 @@ import { invokeWebApiWrapperAsync } from '@components/factory' import { isURLAvailable, removeUser } from '@utils' import { AuthService } from '@api' -import '@styles/user_menu.less' +import '@styles/components/user_menu.less' export type UserMenuProps = DrawerProps & { isAdmin?: boolean diff --git a/src/components/d3/D3Chart.tsx b/src/components/d3/D3Chart.tsx index fa7822d..c6d37ab 100644 --- a/src/components/d3/D3Chart.tsx +++ b/src/components/d3/D3Chart.tsx @@ -36,7 +36,7 @@ import type { ChartTicks } from './types' -import '@styles/d3.less' +import '@styles/components/d3.less' const defaultOffsets: ChartOffset = { top: 10, diff --git a/src/components/d3/D3HorizontalPercentChart.tsx b/src/components/d3/D3HorizontalPercentChart.tsx index 0b04bd0..17ea77b 100644 --- a/src/components/d3/D3HorizontalPercentChart.tsx +++ b/src/components/d3/D3HorizontalPercentChart.tsx @@ -4,10 +4,10 @@ import { Property } from 'csstype' import * as d3 from 'd3' import LoaderPortal from '@components/LoaderPortal' +import { usePartialProps } from '@utils' import { ChartOffset } from './types' -import '@styles/d3.less' -import { usePartialProps } from '@asb/utils' +import '@styles/components/d3.less' export type PercentChartDataType = { name: string diff --git a/src/components/d3/D3MouseZone.tsx b/src/components/d3/D3MouseZone.tsx index 464d814..72b1730 100644 --- a/src/components/d3/D3MouseZone.tsx +++ b/src/components/d3/D3MouseZone.tsx @@ -3,7 +3,7 @@ import * as d3 from 'd3' import { ChartOffset } from './types' -import '@styles/d3.less' +import '@styles/components/d3.less' export type D3MouseState = { /** Позиция мыши по оси X */ diff --git a/src/components/d3/monitoring/D3HorizontalCursor.tsx b/src/components/d3/monitoring/D3HorizontalCursor.tsx index 97dea97..d1caa2e 100644 --- a/src/components/d3/monitoring/D3HorizontalCursor.tsx +++ b/src/components/d3/monitoring/D3HorizontalCursor.tsx @@ -9,7 +9,7 @@ import { getChartIcon, isDev, usePartialProps } from '@utils' import { BaseDataType } from '../types' import { ChartGroup, ChartSizes } from './D3MonitoringCharts' -import '@styles/d3.less' +import '@styles/components/d3.less' type D3GroupRenderFunction = (group: ChartGroup, data: DataType[], flowData: DataType[] | undefined) => ReactNode diff --git a/src/components/d3/plugins/D3Cursor.tsx b/src/components/d3/plugins/D3Cursor.tsx index 2b2adab..3769981 100644 --- a/src/components/d3/plugins/D3Cursor.tsx +++ b/src/components/d3/plugins/D3Cursor.tsx @@ -6,7 +6,7 @@ import { usePartialProps } from '@utils' import { wrapPlugin } from './base' -import '@styles/d3.less' +import '@styles/components/d3.less' export type D3CursorSettings = { /** Параметры стиля линии */ diff --git a/src/components/d3/plugins/D3Tooltip.tsx b/src/components/d3/plugins/D3Tooltip.tsx index 3517b5f..1d16cb7 100644 --- a/src/components/d3/plugins/D3Tooltip.tsx +++ b/src/components/d3/plugins/D3Tooltip.tsx @@ -8,7 +8,7 @@ import { BaseDataType, ChartRegistry } from '@components/d3/types' import { D3MouseState, useD3MouseZone } from '@components/d3/D3MouseZone' import { getTouchedElements, wrapPlugin } from './base' -import '@styles/d3.less' +import '@styles/components/d3.less' export type D3TooltipPosition = 'bottom' | 'top' | 'left' | 'right' | 'none' diff --git a/src/components/selectors/WellTreeSelector.tsx b/src/components/selectors/WellTreeSelector.tsx index c9e2c34..57a5a12 100755 --- a/src/components/selectors/WellTreeSelector.tsx +++ b/src/components/selectors/WellTreeSelector.tsx @@ -10,7 +10,7 @@ import { isRawDate } from '@utils' import { ReactComponent as DepositIcon } from '@images/DepositIcon.svg' import { ReactComponent as ClusterIcon } from '@images/ClusterIcon.svg' -import '@styles/wellTreeSelect.css' +import '@styles/components/well_tree_select.css' /** * Для поиска в URL текущего раздела по шаблону `/{type}/{id}` diff --git a/src/pages/Deposit/StatisticsADW.jsx b/src/pages/Deposit/StatisticsADW.jsx index c26d948..f81864e 100644 --- a/src/pages/Deposit/StatisticsADW.jsx +++ b/src/pages/Deposit/StatisticsADW.jsx @@ -9,7 +9,7 @@ import { DateRangeWrapper, makeNumericColumn, makeNumericRender, makeTextColumn, import { arrayOrDefault, withPermissions } from '@utils' import { SubsystemOperationTimeService } from '@api' -import '@styles/statistics_adw.less' +import '@styles/pages/statistics_adw.less' const numericRender = makeNumericRender(2) diff --git a/src/pages/Well/Analytics/Statistics.jsx b/src/pages/Well/Analytics/Statistics.jsx index 68d4244..99315e8 100644 --- a/src/pages/Well/Analytics/Statistics.jsx +++ b/src/pages/Well/Analytics/Statistics.jsx @@ -10,7 +10,7 @@ import { OperationStatService, WellOperationService } from '@api' import { arrayOrDefault, withPermissions } from '@utils' import '@styles/index.css' -import '@styles/statistics.less' +import '@styles/pages/statistics.less' const { Text } = Typography const { Summary } = RawTable diff --git a/src/pages/Well/Analytics/WellCompositeEditor/index.jsx b/src/pages/Well/Analytics/WellCompositeEditor/index.jsx index b76fd84..9464b46 100644 --- a/src/pages/Well/Analytics/WellCompositeEditor/index.jsx +++ b/src/pages/Well/Analytics/WellCompositeEditor/index.jsx @@ -12,7 +12,7 @@ import { OperationStatService, WellCompositeService } from '@api' import WellCompositeSections from './WellCompositeSections' -import '@styles/well_composite.less' +import '@styles/pages/well_composite.less' const ClusterWells = lazy(() => import('@pages/Cluster/ClusterWells')) diff --git a/src/pages/Well/DrillingProgram/CategoryAdder.jsx b/src/pages/Well/DrillingProgram/CategoryAdder.jsx index bdecf0d..2c95341 100644 --- a/src/pages/Well/DrillingProgram/CategoryAdder.jsx +++ b/src/pages/Well/DrillingProgram/CategoryAdder.jsx @@ -8,7 +8,7 @@ import { invokeWebApiWrapperAsync } from '@components/factory' import { DrillingProgramService } from '@api' -import '@styles/drilling_program.less' +import '@styles/pages/drilling_program.less' const catSelectorRules = [{ required: true, diff --git a/src/pages/Well/DrillingProgram/CategoryHistory.jsx b/src/pages/Well/DrillingProgram/CategoryHistory.jsx index a80acc5..f1605da 100644 --- a/src/pages/Well/DrillingProgram/CategoryHistory.jsx +++ b/src/pages/Well/DrillingProgram/CategoryHistory.jsx @@ -12,7 +12,7 @@ import { FileService } from '@api' import MarksCard from './MarksCard' -import '@styles/drilling_program.less' +import '@styles/pages/drilling_program.less' const { RangePicker } = DatePicker const { Search } = Input diff --git a/src/pages/Well/DrillingProgram/CategoryRender.jsx b/src/pages/Well/DrillingProgram/CategoryRender.jsx index db5b2d4..3f21adf 100644 --- a/src/pages/Well/DrillingProgram/CategoryRender.jsx +++ b/src/pages/Well/DrillingProgram/CategoryRender.jsx @@ -18,7 +18,7 @@ import { formatDate, MimeTypes } from '@utils' import MarksCard from './MarksCard' -import '@styles/drilling_program.less' +import '@styles/pages/drilling_program.less' const CommentPrompt = memo(({ isRequired = true, ...props }) => ( cols.map(col => col.map(col => ({ render: renderDelegate, ...col }))) diff --git a/src/pages/Well/Measure/View.jsx b/src/pages/Well/Measure/View.jsx index 59cbca7..6f61143 100644 --- a/src/pages/Well/Measure/View.jsx +++ b/src/pages/Well/Measure/View.jsx @@ -4,7 +4,7 @@ import { Empty, Form } from 'antd' import { Grid, GridItem } from '@components/Grid' import '@styles/index.css' -import '@styles/measure.css' +import '@styles/pages/measure.css' export const View = memo(({ columns, item }) => !item || !columns?.length ? ( diff --git a/src/pages/Well/Measure/columnsCommon.jsx b/src/pages/Well/Measure/columnsCommon.jsx index 8b512bd..1ee6bd4 100644 --- a/src/pages/Well/Measure/columnsCommon.jsx +++ b/src/pages/Well/Measure/columnsCommon.jsx @@ -2,7 +2,7 @@ import { Input } from 'antd' import { RegExpIsFloat } from '@components/Table' -import '@styles/measure.css' +import '@styles/pages/measure.css' export const v = (text) => (
diff --git a/src/pages/Well/Telemetry/DashboardNNB/index.jsx b/src/pages/Well/Telemetry/DashboardNNB/index.jsx index a37f60c..22ff620 100644 --- a/src/pages/Well/Telemetry/DashboardNNB/index.jsx +++ b/src/pages/Well/Telemetry/DashboardNNB/index.jsx @@ -22,7 +22,7 @@ import { import AddGroupWindow from './AddGroupWindow' import AddWidgetWindow, { makeWidgetFromWits } from './AddWidgetWindow' -import '@styles/dashboard_nnb.less' +import '@styles/pages/dashboard_nnb.less' const getWitsInfo = async () => { // TODO: Добавить expire с принудительным обновлением diff --git a/src/pages/Well/Telemetry/Messages.jsx b/src/pages/Well/Telemetry/Messages.jsx index 185c838..a5aed6d 100644 --- a/src/pages/Well/Telemetry/Messages.jsx +++ b/src/pages/Well/Telemetry/Messages.jsx @@ -11,8 +11,8 @@ import { makeColumn, makeDateColumn, makeNumericColumn, makeNumericSorter, makeT import { withPermissions } from '@utils' import { MessageService } from '@api' -import '@styles/filter.less' -import '@styles/message.less' +import '@styles/components/filter.less' +import '@styles/pages/message.less' const pageSize = 26 const { Search } = Input diff --git a/src/pages/Well/Telemetry/OperationTime/index.jsx b/src/pages/Well/Telemetry/OperationTime/index.jsx index 11f7597..4733f56 100644 --- a/src/pages/Well/Telemetry/OperationTime/index.jsx +++ b/src/pages/Well/Telemetry/OperationTime/index.jsx @@ -10,8 +10,8 @@ import { DateRangeWrapper, makeColumn, makeNumericColumn, makeNumericRender, mak import { arrayOrDefault, range, withPermissions } from '@utils' import { SubsystemOperationTimeService } from '@api' -import '@styles/filter.less' -import '@styles/operation_time.less' +import '@styles/components/filter.less' +import '@styles/pages/operation_time.less' const subsystemColors = [ '#1abc9c', '#16a085', '#2ecc71', '#27ae60', diff --git a/src/pages/Well/Telemetry/Operations/OperationsChart.jsx b/src/pages/Well/Telemetry/Operations/OperationsChart.jsx index 3a20be2..8f15fbd 100644 --- a/src/pages/Well/Telemetry/Operations/OperationsChart.jsx +++ b/src/pages/Well/Telemetry/Operations/OperationsChart.jsx @@ -4,7 +4,7 @@ import { D3Chart } from '@components/d3' import { Grid, GridItem } from '@components/Grid' import { formatDate, makeDisplayValue } from '@utils' -import '@styles/detected_operations.less' +import '@styles/pages/detected_operations.less' const displayNumber = makeDisplayValue({ fixed: 2 }) diff --git a/src/pages/Well/Telemetry/Operations/OperationsTable.jsx b/src/pages/Well/Telemetry/Operations/OperationsTable.jsx index 3f7a2b5..a4e3e9d 100644 --- a/src/pages/Well/Telemetry/Operations/OperationsTable.jsx +++ b/src/pages/Well/Telemetry/Operations/OperationsTable.jsx @@ -2,7 +2,7 @@ import { memo } from 'react' import { Table, makeTextColumn, makeNumericColumn, makeNumericRender } from '@components/Table' -import '@styles/detected_operations.less' +import '@styles/pages/detected_operations.less' const numericRender = makeNumericRender(2) diff --git a/src/pages/Well/Telemetry/Operations/index.jsx b/src/pages/Well/Telemetry/Operations/index.jsx index fc1129d..4589e8a 100644 --- a/src/pages/Well/Telemetry/Operations/index.jsx +++ b/src/pages/Well/Telemetry/Operations/index.jsx @@ -16,7 +16,7 @@ import DrillerSchedule from './DrillerSchedule' import OperationsChart from './OperationsChart' import OperationsTable from './OperationsTable' -import '@styles/detected_operations.less' +import '@styles/pages/detected_operations.less' const Operations = memo(() => { const [isLoading, setIsLoading] = useState(false) diff --git a/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx b/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx index dbe5af1..d2a5c0b 100644 --- a/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/ActiveMessagesOnline.jsx @@ -9,7 +9,7 @@ import { MessageService } from '@api' import { makeMessageColumns } from '../Messages' -import '@styles/message.less' +import '@styles/pages/message.less' export const ActiveMessagesOnline = memo(({ well: givenWell }) => { const [messages, setMessages] = useState([]) diff --git a/src/pages/Well/Telemetry/TelemetryView/index.jsx b/src/pages/Well/Telemetry/TelemetryView/index.jsx index ee21a85..ba288a7 100644 --- a/src/pages/Well/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Well/Telemetry/TelemetryView/index.jsx @@ -32,8 +32,8 @@ import MomentStabPicDisabled from '@images/DempherOff.png' import SpinPicEnabled from '@images/SpinEnabled.png' import SpinPicDisabled from '@images/SpinDisabled.png' -import '@styles/telemetry_view.less' -import '@styles/message.less' +import '@styles/pages/telemetry_view.less' +import '@styles/pages/message.less' const { Option } = Select diff --git a/src/pages/Well/WellCase/HistoryTable.jsx b/src/pages/Well/WellCase/HistoryTable.jsx index 8bbb56a..6b7d41e 100644 --- a/src/pages/Well/WellCase/HistoryTable.jsx +++ b/src/pages/Well/WellCase/HistoryTable.jsx @@ -10,7 +10,7 @@ import { invokeWebApiWrapperAsync } from '@components/factory' import { WellFinalDocumentsService } from '@api' import { formatDate } from '@utils' -import '@styles/well_case.less' +import '@styles/pages/well_case.less' export const HistoryTable = memo(({ category }) => { const [isLoading, setIsLoading] = useState(false) diff --git a/src/pages/Well/WellCase/WellCaseEditor.jsx b/src/pages/Well/WellCase/WellCaseEditor.jsx index dcc287c..1a61acc 100644 --- a/src/pages/Well/WellCase/WellCaseEditor.jsx +++ b/src/pages/Well/WellCase/WellCaseEditor.jsx @@ -8,7 +8,7 @@ import { invokeWebApiWrapperAsync } from '@components/factory' import { WellFinalDocumentsService } from '@api' import { arrayOrDefault } from '@utils' -import '@styles/well_case.less' +import '@styles/pages/well_case.less' const filterCategoriesByText = (text) => (cat) => !text || !!cat?.nameCategory?.toLowerCase().includes(text.toLowerCase()) diff --git a/src/pages/Well/WellCase/index.jsx b/src/pages/Well/WellCase/index.jsx index db08348..44d2a20 100644 --- a/src/pages/Well/WellCase/index.jsx +++ b/src/pages/Well/WellCase/index.jsx @@ -15,7 +15,7 @@ import { withPermissions } from '@utils' import WellCaseEditor from './WellCaseEditor' import { HistoryTable } from './HistoryTable' -import '@styles/well_case.less' +import '@styles/pages/well_case.less' const expandable = { expandedRowRender: (category) => , diff --git a/src/pages/Well/WellOperations/Tvd/AdditionalTables.jsx b/src/pages/Well/WellOperations/Tvd/AdditionalTables.jsx index 35fb51c..2492921 100644 --- a/src/pages/Well/WellOperations/Tvd/AdditionalTables.jsx +++ b/src/pages/Well/WellOperations/Tvd/AdditionalTables.jsx @@ -5,7 +5,7 @@ import { makeNumericRender } from '@components/Table' import { invokeWebApiWrapperAsync } from '@components/factory' import { formatDate, fractionalSum } from '@utils' -import '@styles/tvd.less' +import '@styles/pages/tvd.less' const { Item } = Descriptions diff --git a/src/pages/Well/WellOperations/Tvd/NptTable.jsx b/src/pages/Well/WellOperations/Tvd/NptTable.jsx index db71331..b85610f 100644 --- a/src/pages/Well/WellOperations/Tvd/NptTable.jsx +++ b/src/pages/Well/WellOperations/Tvd/NptTable.jsx @@ -6,7 +6,7 @@ import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' import { makeDateColumn, makeNumericColumn, makeNumericRender, makeTextColumn, Table } from '@components/Table' -import '@styles/tvd.less' +import '@styles/pages/tvd.less' export const columns = [ makeTextColumn('Конструкция секции', 'wellSectionTypeName', null, null, null, { width: 140 }), diff --git a/src/pages/Well/WellOperations/Tvd/TLChart.jsx b/src/pages/Well/WellOperations/Tvd/TLChart.jsx index c2f3a7b..c4e6bba 100644 --- a/src/pages/Well/WellOperations/Tvd/TLChart.jsx +++ b/src/pages/Well/WellOperations/Tvd/TLChart.jsx @@ -13,9 +13,9 @@ import { formatDate } from '@utils' import { makeTooltipRender } from '../../Telemetry/Operations/OperationsChart' import { makeGetColor } from '.' -import '@styles/d3.less' -import '@styles/tvd.less' -import '@styles/detected_operations.less' +import '@styles/components/d3.less' +import '@styles/pages/tvd.less' +import '@styles/pages/detected_operations.less' const defaultOffset = { left: 40, right: 20, top: 20, bottom: 20 } const zeroDate = moment('2000-01-01 00:00:00') diff --git a/src/pages/Well/WellOperations/Tvd/TLPie.jsx b/src/pages/Well/WellOperations/Tvd/TLPie.jsx index b86382f..35a9000 100644 --- a/src/pages/Well/WellOperations/Tvd/TLPie.jsx +++ b/src/pages/Well/WellOperations/Tvd/TLPie.jsx @@ -11,7 +11,7 @@ import { unique } from '@utils/filters' import { makeGetColor } from '.' -import '@styles/tvd.less' +import '@styles/pages/tvd.less' const tableColumns = [ makeColumn('Цвет', 'color', { width: 50, render: (d) => ( diff --git a/src/pages/Well/WellOperations/Tvd/index.jsx b/src/pages/Well/WellOperations/Tvd/index.jsx index ba4f036..f3357f6 100644 --- a/src/pages/Well/WellOperations/Tvd/index.jsx +++ b/src/pages/Well/WellOperations/Tvd/index.jsx @@ -18,29 +18,13 @@ import NetGraphExport from './NetGraphExport' import AdditionalTables from './AdditionalTables' import '@styles/index.css' -import '@styles/tvd.less' +import '@styles/pages/tvd.less' const operationsColors = [ - '#1abc9c', - '#16a085', - '#2ecc71', - '#27ae60', - '#3498db', - '#2980b9', - '#9b59b6', - '#8e44ad', - '#34495e', - '#2c3e50', - '#f1c40f', - '#f39c12', - '#e67e22', - '#d35400', - '#e74c3c', - '#c0392b', - '#ecf0f1', - '#bdc3c7', - '#95a5a6', - '#7f8c8d', + '#1abc9c', '#16a085', '#2ecc71', '#27ae60', '#3498db', + '#2980b9', '#9b59b6', '#8e44ad', '#34495e', '#2c3e50', + '#f1c40f', '#f39c12', '#e67e22', '#d35400', '#e74c3c', + '#c0392b', '#ecf0f1', '#bdc3c7', '#95a5a6', '#7f8c8d', ] export const makeGetColor = (types) => (type) => { diff --git a/src/pages/public/Login.jsx b/src/pages/public/Login.jsx index d322a11..125ad09 100644 --- a/src/pages/public/Login.jsx +++ b/src/pages/public/Login.jsx @@ -9,9 +9,10 @@ import { loginRules, passwordRules } from '@utils/validationRules' import { setUser, withPermissions } from '@utils' import { AuthService } from '@api' -import '@styles/index.css' import Logo from '@images/Logo' +import '@styles/index.css' + const Login = memo(() => { const [showLoader, setShowLoader] = useState(false) diff --git a/src/styles/charts.less b/src/styles/components/charts.less old mode 100755 new mode 100644 similarity index 100% rename from src/styles/charts.less rename to src/styles/components/charts.less diff --git a/src/styles/d3.less b/src/styles/components/d3.less similarity index 100% rename from src/styles/d3.less rename to src/styles/components/d3.less diff --git a/src/styles/display.less b/src/styles/components/display.less similarity index 100% rename from src/styles/display.less rename to src/styles/components/display.less diff --git a/src/styles/fast_run_menu.less b/src/styles/components/fast_run_menu.less similarity index 100% rename from src/styles/fast_run_menu.less rename to src/styles/components/fast_run_menu.less diff --git a/src/styles/filter.less b/src/styles/components/filter.less similarity index 100% rename from src/styles/filter.less rename to src/styles/components/filter.less diff --git a/src/styles/layout.less b/src/styles/components/layout.less similarity index 100% rename from src/styles/layout.less rename to src/styles/components/layout.less diff --git a/src/styles/loader.css b/src/styles/components/loader.css old mode 100755 new mode 100644 similarity index 100% rename from src/styles/loader.css rename to src/styles/components/loader.css diff --git a/src/styles/user_menu.less b/src/styles/components/user_menu.less similarity index 100% rename from src/styles/user_menu.less rename to src/styles/components/user_menu.less diff --git a/src/styles/wellTreeSelect.css b/src/styles/components/well_tree_select.css old mode 100755 new mode 100644 similarity index 100% rename from src/styles/wellTreeSelect.css rename to src/styles/components/well_tree_select.css diff --git a/src/styles/App.less b/src/styles/pages/App.less old mode 100755 new mode 100644 similarity index 97% rename from src/styles/App.less rename to src/styles/pages/App.less index 90db107..4fc5892 --- a/src/styles/App.less +++ b/src/styles/pages/App.less @@ -1,4 +1,4 @@ -@import './loader.css'; +@import '../components/loader.css'; #root, .app{ min-height:100%; diff --git a/src/styles/dashboard_nnb.less b/src/styles/pages/dashboard_nnb.less similarity index 100% rename from src/styles/dashboard_nnb.less rename to src/styles/pages/dashboard_nnb.less diff --git a/src/styles/detected_operations.less b/src/styles/pages/detected_operations.less similarity index 100% rename from src/styles/detected_operations.less rename to src/styles/pages/detected_operations.less diff --git a/src/styles/drilling_program.less b/src/styles/pages/drilling_program.less old mode 100755 new mode 100644 similarity index 100% rename from src/styles/drilling_program.less rename to src/styles/pages/drilling_program.less diff --git a/src/styles/equipment_details.css b/src/styles/pages/equipment_details.css old mode 100755 new mode 100644 similarity index 100% rename from src/styles/equipment_details.css rename to src/styles/pages/equipment_details.css diff --git a/src/styles/measure.css b/src/styles/pages/measure.css old mode 100755 new mode 100644 similarity index 100% rename from src/styles/measure.css rename to src/styles/pages/measure.css diff --git a/src/styles/message.less b/src/styles/pages/message.less similarity index 100% rename from src/styles/message.less rename to src/styles/pages/message.less diff --git a/src/styles/message_telemetry.css b/src/styles/pages/message_telemetry.css old mode 100755 new mode 100644 similarity index 100% rename from src/styles/message_telemetry.css rename to src/styles/pages/message_telemetry.css diff --git a/src/styles/operation_time.less b/src/styles/pages/operation_time.less similarity index 100% rename from src/styles/operation_time.less rename to src/styles/pages/operation_time.less diff --git a/src/styles/smbo.css b/src/styles/pages/smbo.css old mode 100755 new mode 100644 similarity index 100% rename from src/styles/smbo.css rename to src/styles/pages/smbo.css diff --git a/src/styles/statistics.less b/src/styles/pages/statistics.less old mode 100755 new mode 100644 similarity index 100% rename from src/styles/statistics.less rename to src/styles/pages/statistics.less diff --git a/src/styles/statistics_adw.less b/src/styles/pages/statistics_adw.less similarity index 100% rename from src/styles/statistics_adw.less rename to src/styles/pages/statistics_adw.less diff --git a/src/styles/telemetry_view.less b/src/styles/pages/telemetry_view.less similarity index 100% rename from src/styles/telemetry_view.less rename to src/styles/pages/telemetry_view.less diff --git a/src/styles/tvd.less b/src/styles/pages/tvd.less old mode 100755 new mode 100644 similarity index 100% rename from src/styles/tvd.less rename to src/styles/pages/tvd.less diff --git a/src/styles/well_case.less b/src/styles/pages/well_case.less similarity index 100% rename from src/styles/well_case.less rename to src/styles/pages/well_case.less diff --git a/src/styles/well_composite.less b/src/styles/pages/well_composite.less similarity index 100% rename from src/styles/well_composite.less rename to src/styles/pages/well_composite.less From 7af33d702d37ea1ee9f8774084c125567f0e4263 Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 13 Dec 2022 15:29:19 +0500 Subject: [PATCH 16/32] =?UTF-8?q?=D0=94=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=87?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D1=8C=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2=20=D0=B8=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 +- src/components/Table/Columns/date.tsx | 16 ++++ src/components/Table/DatePickerWrapper.tsx | 3 + src/components/Table/DateRangeWrapper.tsx | 46 +++++++----- src/components/Table/Table.tsx | 61 +++++++++++----- src/components/Table/TimePickerWrapper.tsx | 3 + src/components/Table/index.tsx | 7 ++ src/components/views/CompanyView.tsx | 1 + src/components/views/PermissionView.tsx | 1 + src/components/views/RoleView.tsx | 1 + src/components/views/TelemetryView.tsx | 7 ++ src/components/views/UserView.tsx | 1 + src/components/views/WellView.tsx | 6 ++ src/components/views/WirelineView.tsx | 1 + src/pages/Well/Telemetry/Operations/index.jsx | 4 +- src/pages/Well/WellOperations/Tvd/index.jsx | 4 +- src/utils/filters/columnFilters.ts | 28 +++++-- src/utils/filters/index.ts | 8 ++ src/utils/functions/arrayOrDefault.ts | 8 +- src/utils/functions/chart.tsx | 29 ++++++++ src/utils/functions/datetime.ts | 73 +++++++++++++++++++ src/utils/functions/numbers.tsx | 21 +++++- src/utils/functions/objects.ts | 20 ++--- src/utils/functions/permissions.tsx | 48 +++++++++++- src/utils/functions/saubOperations.tsx | 7 +- src/utils/functions/storage.ts | 62 +++++++++++++++- src/utils/functions/string.ts | 18 +++-- src/utils/functions/svg.ts | 6 +- src/utils/functions/table_settings.ts | 37 +++++++++- src/utils/hooks/functionalValue.ts | 6 +- src/utils/queue.ts | 6 ++ src/utils/types/index.ts | 4 +- 32 files changed, 463 insertions(+), 83 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a8ab85f..c5c4663 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ - "день" + "день", + "Saub" ], "liveServer.settings.port": 5501 } \ No newline at end of file diff --git a/src/components/Table/Columns/date.tsx b/src/components/Table/Columns/date.tsx index a401a04..ed3428a 100755 --- a/src/components/Table/Columns/date.tsx +++ b/src/components/Table/Columns/date.tsx @@ -5,6 +5,11 @@ import { DatePickerWrapper, getObjectByDeepKey } from '..' import { DatePickerWrapperProps } from '../DatePickerWrapper' import { formatDate, isRawDate } from '@utils' +/** + * Фабрика методов сортировки столбцов для данных типа **Дата** + * @param key Ключ столбца + * @returns Метод сортировки + */ export const makeDateSorter = (key: Key): SorterMethod => (a, b) => { const vA = a ? getObjectByDeepKey(key, a) : null const vB = b ? getObjectByDeepKey(key, b) : null @@ -16,6 +21,17 @@ export const makeDateSorter = (key: Key): SorterMethod => return (new Date(vA)).getTime() - (new Date(vB)).getTime() } +/** + * Фабрика объектов-столбцов для компонента `Table` для работы с данными типа **Дата** + * + * @param title Название столбца + * @param key Ключ столбца + * @param utc Конвертировать ли дату в UTC + * @param format Формат отображения даты + * @param other Дополнительные опции столбца + * @param pickerOther Опции компонента селектора даты + * @returns Объект-столбец для работы с данными типа **Дата** + */ export const makeDateColumn = ( title: ReactNode, key: string, diff --git a/src/components/Table/DatePickerWrapper.tsx b/src/components/Table/DatePickerWrapper.tsx index 8aaa9ba..188d985 100755 --- a/src/components/Table/DatePickerWrapper.tsx +++ b/src/components/Table/DatePickerWrapper.tsx @@ -6,8 +6,11 @@ import moment, { Moment } from 'moment' import { defaultFormat } from '@utils' export type DatePickerWrapperProps = PickerDateProps & { + /** Значение селектора */ value?: Moment, + /** Метод вызывается при изменений даты */ onChange?: (date: Moment | null) => any + /** Конвертировать ли значение в UTC */ isUTC?: boolean } diff --git a/src/components/Table/DateRangeWrapper.tsx b/src/components/Table/DateRangeWrapper.tsx index 4ad140f..c9246c5 100755 --- a/src/components/Table/DateRangeWrapper.tsx +++ b/src/components/Table/DateRangeWrapper.tsx @@ -9,31 +9,41 @@ import { defaultFormat } from '@utils' const { RangePicker } = DatePicker export type DateRangeWrapperProps = RangePickerSharedProps & { - value?: RangeValue, + /** Значение селектора в виде массива из 2 элементов (от, до) */ + value?: RangeValue + /** Конвертировать ли значения в UTC */ isUTC?: boolean + /** Разрешить сброс значения селектора */ allowClear?: boolean } +/** + * Подготавливает значения к передаче в селектор + * + * @param value Массиз из 2 дат + * @param isUTC Конвертировать ли значения в UTC + * @returns Подготовленные даты + */ const normalizeDates = (value?: RangeValue, isUTC?: boolean): RangeValue => { - if (!value) return [null, null] - return [ - value[0] ? (isUTC ? moment.utc(value[0]).local() : moment(value[0])) : null, - value[1] ? (isUTC ? moment.utc(value[1]).local() : moment(value[1])) : null, - ] + if (!value) return [null, null] + return [ + value[0] ? (isUTC ? moment.utc(value[0]).local() : moment(value[0])) : null, + value[1] ? (isUTC ? moment.utc(value[1]).local() : moment(value[1])) : null, + ] } -export const DateRangeWrapper = memo(({ value, isUTC, allowClear = false, ...other }) => ( - +export const DateRangeWrapper = memo(({ value, isUTC, allowClear, ...other }) => ( + )) export default DateRangeWrapper diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index c9ecbbe..e1b7f8c 100755 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -1,6 +1,6 @@ import { Key, memo, useCallback, useEffect, useState } from 'react' import { ColumnGroupType, ColumnType } from 'antd/lib/table' -import { Table as RawTable, TableProps } from 'antd' +import { Table as RawTable, TableProps as RawTableProps } from 'antd' import { RenderMethod } from './Columns' import { tryAddKeys } from './EditableTable' @@ -14,16 +14,28 @@ export type BaseTableColumn = ColumnGroupType | ColumnType export type TableColumn = OmitExtends, TableColumnSettings> export type TableColumns = TableColumn[] -export type TableContainer = TableProps & { +export type TableProps = RawTableProps & { + /** Массив колонок таблицы с настройками (описаны в `TableColumnSettings`) */ columns: TableColumn[] + /** Название таблицы для сохранения настроек */ tableName?: string + /** Отображать ли кнопку настроек */ showSettingsChanger?: boolean } export interface DataSet { - [k: Key]: DataSet | T | D + [k: Key]: DataSet | T | D } +/** + * Получить значение из объекта по составному ключу + * + * Составной ключ имеет вид: `<поле 1>[.<поле 2>...]` + * + * @param key Составной ключ + * @param data Объект из которого будет полученно значение + * @returns Значение, найденное по ключу, либо `undefined` + */ export const getObjectByDeepKey = (key: Key | undefined, data: DataSet): T | undefined => { if (!key) return undefined const parts = String(key).split('.') @@ -36,36 +48,44 @@ export const getObjectByDeepKey = (key: Key | undefined, data: DataSet): return out as T } +/** + * Фабрика обёрток render-функций ячеек с поддержкой составных ключей + * @param key Составной ключ + * @param render Стандартная render-функция + * @returns Обёрнутая render-функция + */ 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), - } - } +/** + * Применяет необходимые обёртки ко всем столбцам таблицы + * @param columns Исходные столбцы + * @returns Обёрнутые столбцы + */ +const applyColumnWrappers = >(columns: TableColumns): TableColumns => columns.map((column) => { + if ('children' in column) { return { ...column, - render: makeColumnRenderWrapper(column.key, column.render), + children: applyColumnWrappers(column.children), } - }) -} + } + return { + ...column, + render: makeColumnRenderWrapper(column.key, column.render), + } +}) -function _Table>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableContainer) { +function _Table>({ columns, dataSource, tableName, showSettingsChanger, ...other }: TableProps) { const [newColumns, setNewColumns] = useState[]>([]) const [settings, setSettings] = useState({}) const onSettingsChanged = useCallback((settings?: TableSettings | null) => { if (tableName) setTableSettings(tableName, settings) - setSettings(settings ?? {}) + setSettings(settings || {}) }, [tableName]) useEffect(() => setSettings(tableName ? getTableSettings(tableName) : {}), [tableName]) @@ -92,6 +112,13 @@ function _Table>({ columns, dataSource, tableName, showSe ) } +/** + * Обёртка над компонентом таблицы AntD + * + * Особенности: + * * Поддержка составных ключей столбцов + * * Работа с настройками столбцов таблицы + */ export const Table = memo(_Table) as typeof _Table export default Table diff --git a/src/components/Table/TimePickerWrapper.tsx b/src/components/Table/TimePickerWrapper.tsx index de748d8..e47848a 100644 --- a/src/components/Table/TimePickerWrapper.tsx +++ b/src/components/Table/TimePickerWrapper.tsx @@ -6,8 +6,11 @@ import { defaultTimeFormat, momentToTime, timeToMoment } from '@utils' import { TimeDto } from '@api' export type TimePickerWrapperProps = Omit, 'onChange'> & { + /** Текущее значение */ value?: TimeDto, + /** Метод вызывается при изменений времени */ onChange?: (date: TimeDto | null) => any + /** Конвертировать ли время в UTC */ isUTC?: boolean } diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx index 3f4eca8..bb9a292 100755 --- a/src/components/Table/index.tsx +++ b/src/components/Table/index.tsx @@ -17,6 +17,13 @@ export type PaginationContainer = { items?: T[] | null } +/** + * Генерирует объект пагинации для компонента `Table` из данных от сервисов + * + * @param сontainer данные от сервиса + * @param other Дополнительные поля (передаются в объект напрямую в приоритете) + * @returns Объект пагинации + */ export const makePaginationObject = (сontainer: PaginationContainer, other: M) => ({ ...other, pageSize: сontainer.take, diff --git a/src/components/views/CompanyView.tsx b/src/components/views/CompanyView.tsx index db012a5..647100a 100755 --- a/src/components/views/CompanyView.tsx +++ b/src/components/views/CompanyView.tsx @@ -9,6 +9,7 @@ export type CompanyViewProps = { company?: CompanyDto } +/** Компонент для отображения информации о компании */ export const CompanyView = memo(({ company }) => company ? ( diff --git a/src/components/views/PermissionView.tsx b/src/components/views/PermissionView.tsx index 7708be5..9c185a7 100755 --- a/src/components/views/PermissionView.tsx +++ b/src/components/views/PermissionView.tsx @@ -8,6 +8,7 @@ export type PermissionViewProps = { info?: PermissionDto } +/** Компонент для отображения информации о разрешении */ export const PermissionView = memo(({ info }) => info ? ( diff --git a/src/components/views/RoleView.tsx b/src/components/views/RoleView.tsx index 8974419..66c0ad3 100755 --- a/src/components/views/RoleView.tsx +++ b/src/components/views/RoleView.tsx @@ -9,6 +9,7 @@ export type RoleViewProps = { role?: UserRoleDto } +/** Компонент для отображения информации о роли */ export const RoleView = memo(({ role }) => { if (!role) return ( - ) diff --git a/src/components/views/TelemetryView.tsx b/src/components/views/TelemetryView.tsx index 8758353..cec6093 100755 --- a/src/components/views/TelemetryView.tsx +++ b/src/components/views/TelemetryView.tsx @@ -19,6 +19,12 @@ export const lables: Record = { spinPlcVersion: 'Версия Спин Мастер', } +/** + * Строит название для телеметрии + * + * @param telemetry Объект телеметрии + * @returns Название + */ export const getTelemetryLabel = (telemetry?: TelemetryDto) => `${telemetry?.id ?? '-'} / ${telemetry?.info?.deposit ?? '-'} / ${telemetry?.info?.cluster ?? '-'} / ${telemetry?.info?.well ?? '-'}` @@ -26,6 +32,7 @@ export type TelemetryViewProps = { telemetry?: TelemetryDto } +/** Компонент для отображения информации о телеметрии */ export const TelemetryView = memo(({ telemetry }) => telemetry?.info ? ( & { user?: UserDto } +/** Компонент для отображения информации о пользователе */ export const UserView = memo(({ user, ...other }) => user ? ( , HTMLSpanElement> } +/** + * Получить название скважины + * @param well Объект с данными скважины + * @returns Название скважины + */ export const getWellTitle = (well: WellDto) => `${well.deposit || '-'} / ${well.cluster || '-'} / ${well.caption || '-'}` +/** Компонент для отображения информации о скважине */ export const WellView = memo(({ well, iconProps, labelProps, ...other }) => well ? ( diff --git a/src/components/views/WirelineView.tsx b/src/components/views/WirelineView.tsx index 5ea9bd0..be0b3f9 100644 --- a/src/components/views/WirelineView.tsx +++ b/src/components/views/WirelineView.tsx @@ -21,6 +21,7 @@ export type WirelineViewProps = TooltipProps & { buttonProps?: ButtonProps } +/** Компонент для отображения информации о талевом канате */ export const WirelineView = memo(({ wireline, buttonProps, ...other }) => ( { const maxTarget = Math.max(...data.operations?.map((op) => op.operationValue?.targetValue || 0)) const uniqueOps = data.operations?.map((op) => op.value || 0).filter(unique) const value = uniqueOps.reduce((out, op) => out + op, 0) / uniqueOps.length * 3 / 2 - setYDomain(pretify(Math.max(maxTarget, value))) + setYDomain(prettify(Math.max(maxTarget, value))) }, [data]) useEffect(() => { diff --git a/src/pages/Well/WellOperations/Tvd/index.jsx b/src/pages/Well/WellOperations/Tvd/index.jsx index f3357f6..ba0a19b 100644 --- a/src/pages/Well/WellOperations/Tvd/index.jsx +++ b/src/pages/Well/WellOperations/Tvd/index.jsx @@ -8,7 +8,7 @@ import { useTopRightBlock, useWell } from '@asb/context' import { D3Chart } from '@components/d3' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' -import { formatDate, fractionalSum, withPermissions, getOperations, pretify } from '@utils' +import { formatDate, fractionalSum, withPermissions, getOperations, prettify } from '@utils' import TLPie from './TLPie' import TLChart from './TLChart' @@ -180,7 +180,7 @@ const Tvd = memo(({ well: givenWell, title, ...other }) => { .map(([_, ops]) => Math.max(...ops.map((op) => op.depth).filter(Boolean))) .filter(Boolean) ) - const minValue = pretify(maxValue) + const minValue = prettify(maxValue) return { date: { diff --git a/src/utils/filters/columnFilters.ts b/src/utils/filters/columnFilters.ts index dcf0e2d..6965521 100644 --- a/src/utils/filters/columnFilters.ts +++ b/src/utils/filters/columnFilters.ts @@ -1,12 +1,30 @@ +import { getObjectByDeepKey } from "@asb/components/Table" + +/** + * Фабрика методов фильтрации строк таблицы по столбцу с текстом + * @param key Составной ключ столбца + * @returns Метод фильтрации + */ export const makeTextOnFilter = (key: string) => - (value: string, record?: Record) => String(record?.[key]).startsWith(value) + (value: string, record?: Record) => record && String(getObjectByDeepKey(key, record)).startsWith(value) +/** + * Фабрика методов фильтрации строк таблицы по столбцу с массивами + * @param key Составной ключ столбца + * @returns Метод фильтрации + */ export const makeArrayOnFilter = (key: string) => - (value: string, record?: Record) => (!value && (record?.[key]?.length ?? 0) <= 0) || record?.[key]?.includes(value) - -export const makeObjectOnFilter = (field: string, key: string) => - (value: string, record?: Record>) => String(record?.[field]?.[key]).startsWith(value) + (value: string, record?: Record) => record && ( + (!value && (getObjectByDeepKey(key, record)?.length ?? 0) <= 0) || + getObjectByDeepKey(key, record)?.includes(value) + ) +/** + * Создаёт список значений для фильтрации текстовых столбцов + * @param array Массив значений + * @param keys Массив ключей + * @returns Список значений + */ export const makeTextFilters = (array: Record[], keys: string[]) => { const filters: string[][] = Array(keys.length) diff --git a/src/utils/filters/index.ts b/src/utils/filters/index.ts index cb2982b..26e6062 100644 --- a/src/utils/filters/index.ts +++ b/src/utils/filters/index.ts @@ -1,3 +1,11 @@ export * from './columnFilters' +/** + * Проверяет значение на уникальность в массиве + * + * @param value Проверяемое значение + * @param index Индекс проверяемого значения в массиве + * @param self Массив, содержащий значение + * @returns Является ли значение уникальным или первым + */ export const unique = (value: T, index: number, self: T[]) => self.indexOf(value) === index diff --git a/src/utils/functions/arrayOrDefault.ts b/src/utils/functions/arrayOrDefault.ts index eed23e8..636faf2 100644 --- a/src/utils/functions/arrayOrDefault.ts +++ b/src/utils/functions/arrayOrDefault.ts @@ -1,10 +1,10 @@ /** - * Возвращает - * + * Гарантированно возвращает массив нужного типа + * * @param arr Входящие данные * @param def Значение по-умолчанию - * - * @returns Если `arr` - массив будет возвращено оно, иначе `def` + * + * @returns Если входящие данные - массив будут возвращены они, иначе значение из `def` */ export const arrayOrDefault = (arr: unknown, def: T[] = []): T[] => arr instanceof Array ? arr : def diff --git a/src/utils/functions/chart.tsx b/src/utils/functions/chart.tsx index 93c677c..42eb574 100644 --- a/src/utils/functions/chart.tsx +++ b/src/utils/functions/chart.tsx @@ -3,6 +3,12 @@ import { AntdIconProps } from '@ant-design/icons/lib/components/AntdIcon' import { BaseDataType, ChartDataset } from '@components/d3' +/** + * Фабрика методов-оптимизаторов для массивов точек перед выводом на график + * + * @param isEquals Метод сравнения элементов на равенство + * @returns Метод вырезки идентичных элементов (по результатам работы переданного метода) + */ export const makePointsOptimizator = (isEquals: (a: DataType, b: DataType) => boolean) => (points: DataType[]) => { if (!Array.isArray(points) || points.length < 3) return points @@ -16,6 +22,22 @@ export const makePointsOptimizator = (isEquals: ( export type TouchType = 'all' | 'x' | 'y' +/** + * Получает расстояние между точками в зависимости от типа касания + * + * Если тип касания 'x', то вернёт модуль разницы абсцисс + * + * Если тип касания 'y', то вернёт модуль разницы ординат + * + * Иначе вернёт корень суммы квадратов разностей координат точек + * + * @param x1 Абсцисса первой точки + * @param y1 Ордината первой точки + * @param x2 Абсцисса второй точки + * @param y2 Ордината второй точки + * @param type Тип касания + * @returns Расстояние между точками + */ export const getDistance = (x1: number, y1: number, x2: number, y2: number, type: TouchType = 'all') => { if (type === 'x') return Math.abs(x1 - x2) if (type === 'y') return Math.abs(y1 - y2) @@ -23,6 +45,13 @@ export const getDistance = (x1: number, y1: number, x2: number, y2: number, type return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) } +/** + * Возвращает иконку графика в зависимости от типа + * + * @param chart График, у которого будет проверен тип + * @param options Дополнительные опции иконки + * @returns Элемент иконки + */ export const getChartIcon = (chart: ChartDataset, options?: Omit) => { let Icon switch (chart.type) { diff --git a/src/utils/functions/datetime.ts b/src/utils/functions/datetime.ts index f4edc4e..f409d9e 100644 --- a/src/utils/functions/datetime.ts +++ b/src/utils/functions/datetime.ts @@ -16,10 +16,22 @@ export enum timeInS { week = day * 7, } +/** + * Проверка значения на возможность преобразования к дате + * + * @param value Проверяемое значение + * @returns Является ли значение потенциальной датой + */ export function isRawDate(value: unknown): value is RawDate { return !isNaN(Date.parse(String(value))) } +/** + * Проверка значения на возможность преобразования к `TimeDto` + * + * @param value Проверяемое значение + * @returns Является ли значение потенциальным объектом `TimeDto` + */ export function isTime(value: unknown): value is TimeDto { if (!value || typeof value !== 'object') return false @@ -27,18 +39,40 @@ export function isTime(value: unknown): value is TimeDto { return ['hour', 'minute', 'second'].every((key) => keys.includes(key)) } +/** + * Форматировать значение как дату в строку + * + * @param date Форматируемое значение + * @param utc Преобразовывать ли к UTC + * @param format Формат вывода + * @returns Форматированная дата в строке + */ export const formatDate = (date: unknown, utc: boolean = false, format: string = defaultFormat) => { if (!isRawDate(date)) return null const out = utc ? moment.utc(date).local() : moment(date) return out.format(format) } +/** + * Форматировать значение как время в строку + * + * @param time Форматируемое значение + * @param utc Преобразовывать ли к UTC + * @param format Формат вывода + * @returns Форматированное время в строке + */ export const formatTime = (time: unknown, utc: boolean = false, format: string = defaultTimeFormat) => { if(!isTime(time)) return const out = timeToMoment(time, utc, format) return out.format(format) } +/** + * Привести секунды к строке периоду + * + * @param time Указанное кол-во секунд + * @returns Форматированная строка период + */ export const periodToString = (time?: number) => { if (!time || time <= 0) return '00:00:00' const days = Math.floor(time / timeInS.day) @@ -54,11 +88,26 @@ export const periodToString = (time?: number) => { return `${days > 0 ? days : ''} ${toFixed(hours)}:${toFixed(minutes)}:${toFixed(seconds)}` } +/** + * Вычислить кол-во дней между двумя датами + * + * @param start Левая дата + * @param end Правая дата + * @returns если оба аргумента потенциальны даты, то кол-во дней между ними, иначе `undefined` + */ export const calcDuration = (start: unknown, end: unknown): number | undefined => { if (!isRawDate(start) || !isRawDate(end)) return undefined return (+new Date(end) - +new Date(start)) * timeInS.millisecond / timeInS.day } +/** + * Сдвинуть дату на указанное время + * + * @param date Исходная дата + * @param value Коэффициент сдвига + * @param type Тип сдвига (день, час, минут и т.д.) + * @returns Смещённая дата + */ export const fractionalSum = (date: unknown, value: number, type: keyof typeof timeInS): RawDate | null => { if (!isRawDate(date) || !timeInS[type] || isNaN(value ?? NaN)) return null const d = new Date(date) @@ -86,17 +135,41 @@ export const rawTimezones = Object.freeze({ export type TimezoneId = keyof typeof rawTimezones +/** + * Проверяет, является ли переданное значение корректным ID часовой зоны + * + * @param value Проверяемое значение + * @returns Является ли переданное значение корректным ID часовой зоны + */ export const isTimezoneId = (value: unknown): value is TimezoneId => !!value && String(value) in rawTimezones +/** + * Ищет часовую зону для переданной телеметрии + * @param value Данные телеметрии + * @returns Название часовой зоны + */ export const findTimezoneId = (value: SimpleTimezoneDto): TimezoneId => (isTimezoneId(value.timezoneId) && value.timezoneId) || (Object.keys(rawTimezones) as TimezoneId[]).find(id => rawTimezones[id] === value.hours) as TimezoneId +/** + * Приводит `TimeDto`-объект к `Moment`-объекту + * + * @param time `TimeDto`-объект + * @param isUtc Приводить ли к UTC + * @param format Формат для обработки + * @returns `Moment`-объект + */ export const timeToMoment = (time?: TimeDto | null, isUtc?: boolean, format: string = defaultTimeFormat): Moment => { const input = `${time?.hour ?? 0}:${time?.minute ?? 0}:${time?.second ?? 0}` return isUtc ? moment.utc(input, format).local() : moment(input, format) } +/** + * Приводит `Moment`-объект к `TimeDto`-объекту + * @param time `Moment`-объект + * @returns `TimeDto`-объекту + */ export const momentToTime = (time?: Moment | null): TimeDto => ({ hour: time?.hour() ?? 0, minute: time?.minute() ?? 0, diff --git a/src/utils/functions/numbers.tsx b/src/utils/functions/numbers.tsx index 6efb69c..949e44e 100644 --- a/src/utils/functions/numbers.tsx +++ b/src/utils/functions/numbers.tsx @@ -1,10 +1,18 @@ import { ReactNode } from 'react' +/** + * Форматирует число по заданным параметрам + * + * @param number Форматируемое число + * @param def Вывод по-умолчанию + * @param fixed Длина числа после форматирования + * @returns Строка - форматированное число + */ export const getPrecision = (number: number, def: string = '-', fixed: number = 2): string => Number.isFinite(number) ? number.toFixed(fixed) : def /** * Генерирует функцию ограничения значения в заданном диапазоне - * + * * @param min Минимальное значение * @param max Максимальное значение * @returns Функция, ограничивающая значение в диапазоне [`min`; `max`] @@ -17,15 +25,20 @@ export const limitValue = (min: T, max: T) => (value: T) => { /** * Генерирует массив чисел в заданном диапазоне - * + * * @param end Конечное значение * @param start Начальное значение - * + * * @returns Массив чисел в диапазоне от `start` до `end` */ export const range = (end: number, start: number = 0) => Array(end - start).fill(undefined).map((_, i) => start + i) -export const pretify = (n: number): number | null => { +/** + * Округляет число до ближайшего красивого + * @param n Округляемое число + * @returns Округлённое число + */ +export const prettify = (n: number): number | null => { if (!Number.isFinite(n)) return null let i = 0 for (; Math.abs(n) >= 100; i++) n /= 10 diff --git a/src/utils/functions/objects.ts b/src/utils/functions/objects.ts index 3435196..350d090 100644 --- a/src/utils/functions/objects.ts +++ b/src/utils/functions/objects.ts @@ -1,21 +1,21 @@ /** - * Копирует данные в глубину. - * + * Копирует данные с максимальной глубиной + * * @remarks - * При копированиий объектов, содержащих функций может возникнуть исключение. + * При копирований объектов, содержащих функций может возникнуть исключение. * Не предназначено для копирования функций. - * + * * @param data Копируемые данные - * @returns Полная копия `data` + * @returns Полная копия исходных данных */ export const deepCopy = (data: T): T => JSON.parse(JSON.stringify(data ?? null)) /** - * Маппинг полей объекта - * - * @param data Входящие данные - * @param handler Обработчик - * + * Аналог функции `Array.prototype.map`, но для работы с объектами + * + * @param data Исходный объект + * @param handler Метод-обработчик + * * @returns Объект с обработанными полями */ export const wrapValues = (data: Record, handler: (data: T, key: string, object: Record) => R): Record => diff --git a/src/utils/functions/permissions.tsx b/src/utils/functions/permissions.tsx index 347ad53..44883d1 100644 --- a/src/utils/functions/permissions.tsx +++ b/src/utils/functions/permissions.tsx @@ -12,10 +12,22 @@ export type ServiceName = string export type ServiceRequestType = 'get' | 'edit' | 'delete' export type PermissionRequest = `${ServiceName}.${ServiceRequestType}` +/** + * Проверка соответствует ли значение типу `ServiceRequestType` + * + * @param value Проверяемое значение + * @returns Является ли значение объектом типа `ServiceRequestType` + */ export function isRequestType(value: string): value is ServiceRequestType { return ['get', 'edit', 'delete'].includes(value) } +/** + * Генерация объекта, содержащего информацию о наличии или отсутствия перечисленных разрешений + * + * @param values Список разрешений + * @returns Объект с информацией о разрешениях + */ export const getPermissions = (...values: PermissionRequest[]) => { const permissions: Record>> = {} values.forEach((key) => { @@ -27,6 +39,13 @@ export const getPermissions = (...values: PermissionRequest[]) => { return permissions } +/** + * Проверка наличия у пользователя разрешения или списка разрешений + * + * @param permission Разрешение или список разрешений + * @param userPermissions Список разрешений пользователя (если не указано, будут получены из локального хранилища) + * @returns `true` если все разрешения присутствуют, иначе `false` + */ export const hasPermission = (permission?: Permission | Permission[], userPermissions?: Permission[]): boolean => { if (!Array.isArray(permission) && typeof permission !== 'string') return true @@ -36,6 +55,13 @@ export const hasPermission = (permission?: Permission | Permission[], userPermis return permission.every((perm) => userPerms.includes(perm)) } +/** + * Проверка доступности секции сайта для посещения пользователем + * + * @param section Секция сайта + * @param userPermission Разрешения пользователя + * @returns `true` если все разрешения присутствуют, иначе `false` + */ const sectionAvailable = (section: PermissionRecord, userPermission: Permission[]) => { for (const child of Object.values(section)) { if (!child) continue @@ -48,6 +74,12 @@ const sectionAvailable = (section: PermissionRecord, userPermission: Permission[ return false } +/** + * Проверка доступности URL для посещения пользователем + * @param path URL + * @param userPermissions Разрешения пользователя (если не заданы, будут получены из локального хранилища) + * @returns `true` если все разрешения присутствуют, иначе `false` + */ export const isURLAvailable = (path: string, userPermissions?: Permission[]) => { if (publicPages.includes(path)) return true @@ -95,14 +127,26 @@ export const NoAccessComponent = memo(() => getUser().login ? ( )) +/** + * HOC добавляющий проверку на наличие разрешений для отображения компонента + * @param Component Исходных компонент + * @param requirements Необходимые разрешения + * @param elseNode Компонент по-умолчанию + * @returns Обёрнутый компонент с проверкой разрешений + */ export const withPermissions =

( Component: NamedExoticComponent

| ((props: P) => ReactElement), requirements: Permission[] = [], elseNode: JSX.Element = -): PrivateComponent

=> Object.assign(memo

(function PrivateWrapper(props) { +): PrivateComponent

=> memo

(function PrivateWrapper(props) { return hasPermission(requirements) ? : elseNode -})) +}) +/** + * Получить url текущей выбранной вкладки + * + * @returns url текущей выбранной вкладки + */ export const getTabname = () => { const params = useParams() const attr = useMemo(() => params['*']?.split('/').filter(s => s)[0] ?? null, [params]) diff --git a/src/utils/functions/saubOperations.tsx b/src/utils/functions/saubOperations.tsx index 85e105c..d81a09e 100644 --- a/src/utils/functions/saubOperations.tsx +++ b/src/utils/functions/saubOperations.tsx @@ -11,10 +11,15 @@ export type SaubData = WellOperationDto & { depth?: number /** Дата */ date?: string - /** Колличество часов НПВ с начала бурения до текущего момента */ + /** Количество часов НПВ с начала бурения до текущего момента */ nptHours?: number } +/** + * Получить списки операций для конкретной скважины + * @param idWell ID скважины + * @returns Списки операций + */ export const getOperations = async (idWell: number): Promise<{ operations: WellOperationDtoPlanFactPredictBase[], plan: SaubData[] diff --git a/src/utils/functions/storage.ts b/src/utils/functions/storage.ts index 85772b7..ecc2cb9 100644 --- a/src/utils/functions/storage.ts +++ b/src/utils/functions/storage.ts @@ -16,12 +16,25 @@ export enum StorageNames { witsInfo = 'witsInfo' } +/** + * Получить массив значений из локального хранилища + * + * @param name Имя массива + * @param sep Разделитель элементов + * @returns Массив значений или `null` + */ export const getArrayFromLocalStorage = (name: string, sep: string | RegExp = ','): T[] | null => { const raw = localStorage.getItem(name) if (!raw) return null - return raw.split(sep).map(elm => elm as T) + return raw.split(sep) as T[] } +/** + * Получить объект из JSON строки в локальном хранилище + * + * @param name Имя строки + * @returns Прочитанный объект или `null` + */ export const getJSON = (name: StorageNames): T | null => { try { const raw = localStorage.getItem(name) @@ -32,6 +45,13 @@ export const getJSON = (name: StorageNames): T | null => { return null } +/** + * Записать объект в локальное хранилище, как JSON строка + * + * @param name Имя строки + * @param data Сохраняемый объект + * @returns `true` если сохранение успешно, иначе `false` + */ export const setJSON = (name: StorageNames, data: T | null): boolean => { try { localStorage.setItem(name, JSON.stringify(data)) @@ -42,8 +62,17 @@ export const setJSON = (name: StorageNames, data: T | null): boolean => { return false } +/** + * Получить информацию о пользователе из локального хранилища + * + * @returns Объект данных о пользователе + */ export const getUser = (): UserTokenDto => getJSON(StorageNames.user) || {} +/** + * Получить разрешения пользователя из локального хранилища + * @returns Список разрешений или `null` + */ export const getUserPermissions = (): Permission[] | null => { let permissions = getUser()?.permissions?.map((perm) => perm.name as string) if (!permissions) // TODO: Удалить в следующем релизе, вставлено для совместимости @@ -51,11 +80,20 @@ export const getUserPermissions = (): Permission[] | null => { return permissions || null } +/** + * Сохранить данные пользователя в локальное хранилище + * + * @param user Данные пользователя + * @returns `true` если сохранение успешно, иначе `false` + */ export const setUser = (user: UserTokenDto) => { OpenAPI.TOKEN = user.token ?? undefined - localStorage.setItem(StorageNames.user, JSON.stringify(user)) + return setJSON(StorageNames.user, user) } +/** + * Очистить данные о пользователе в локальном хранилище + */ export const removeUser = () => { localStorage.removeItem(StorageNames.userId) localStorage.removeItem(StorageNames.login) @@ -65,13 +103,26 @@ export const removeUser = () => { localStorage.removeItem(StorageNames.user) } +/** + * Получить объект настроек таблицы + * + * @param tableName Имя таблицы + * @returns Объект настроек таблицы + */ export const getTableSettings = (tableName: string): TableSettings => { const tables = getJSON(StorageNames.tableSettings) ?? {} if (!(tableName in tables)) return {} return wrapValues(tables[tableName] ?? {}, normalizeTableColumn) } -export const setTableSettings = (tableName: string, settings?: TableSettings | null): boolean => { +/** + * Сохранить объект настроек таблицы + * + * @param tableName Имя таблицы + * @param settings Объект настроек + * @returns `true` если сохранение успешно, иначе `false` + */ +export const setTableSettings = (tableName: string, settings?: TableSettings | null) => { const currentStore = getJSON(StorageNames.tableSettings) ?? {} currentStore[tableName] = wrapValues(settings ?? {}, optimizeTableColumn) return setJSON(StorageNames.tableSettings, currentStore) @@ -81,4 +132,9 @@ export type DataDashboardNNB = { } +/** + * Получить настройки панели ННБ + * + * @returns Объект настроек панели ННБ + */ export const getDashboardNNB = () => getJSON(StorageNames.dashboardNNB) diff --git a/src/utils/functions/string.ts b/src/utils/functions/string.ts index 50829da..1dbf82d 100644 --- a/src/utils/functions/string.ts +++ b/src/utils/functions/string.ts @@ -1,10 +1,18 @@ +/** + * Фабрика методов обрезки строк по словам с добавлением суффикса + * + * @param maxLength Максимальная длинна строки + * @param separator Разделитель слов в строке + * @param suffix Суффикс строки, отображаемый в случае обрезки + * @returns Метод обрезки строки + */ export const makeStringCutter = (maxLength: number = 100, separator: string = ' ', suffix: string = '...') => (comment: T): T | string => { if (!comment || typeof comment !== 'string' || comment.length <= maxLength) - return comment - if (maxLength <= suffix.length) + return comment // Обрабатываются только строки с длинной выше максимальной + if (maxLength <= suffix.length) // Если максимальная длина меньше длины суффикса вывести начало суффикса длинной `maxLength` return suffix.substring(0, maxLength) - const lastSep = comment.lastIndexOf(separator, maxLength - suffix.length) - if (lastSep < 0) + const lastSep = comment.lastIndexOf(separator, maxLength - suffix.length) // Ищем последнее разделение слов перед максимальной длинной с вычетом длины суффикса + if (lastSep < 0) // Если разделитель не найден обрезаем само слово и добавляем суффикс return comment.substring(0, maxLength - suffix.length) + suffix - return comment.substring(0, lastSep) + suffix + return comment.substring(0, lastSep) + suffix // Иначе обрезаем до разделителя и добавляем суффикс } diff --git a/src/utils/functions/svg.ts b/src/utils/functions/svg.ts index 7ec6902..c6ccd7f 100644 --- a/src/utils/functions/svg.ts +++ b/src/utils/functions/svg.ts @@ -1,4 +1,8 @@ - +/** + * Превращает SVG-элемент в `BLOB` + * @param svg SVG-элемент + * @returns `BLOB` строка + */ export const svgToDataURL = (svg: SVGSVGElement) => { const serializer = new XMLSerializer() let source = serializer.serializeToString(svg) diff --git a/src/utils/functions/table_settings.ts b/src/utils/functions/table_settings.ts index c035a4c..769db0f 100644 --- a/src/utils/functions/table_settings.ts +++ b/src/utils/functions/table_settings.ts @@ -9,20 +9,32 @@ export type TableColumnSettings = { export type TableSettings = Record export type TableSettingsStore = Record +/** + * Создаёт объект настроек таблицы исходя из массива столбцов + * @param columns Массив столбцов таблицы + * @returns Объект настроек таблицы + */ export const makeTableSettings = (columns: TableColumns): TableSettings => { const settings: TableSettings = {} columns.forEach((column) => { - if (!column.key) return + if (!column.key) return // Столбцы без ключей игнорируются const key = String(column.key) settings[key] = { columnName: key, - title: typeof column.title === 'string' ? column.title : key, - visible: column.visible ?? true, + title: typeof column.title === 'string' ? column.title : key, // В качестве заголовка невозможно использовать `ReactNode` + visible: column.visible ?? true, // По-умолчанию все столбцы видимые } }) return settings } +/** + * Объединяет несколько объектов настроек таблицы в один + * + * Приоритет объектом снижается с последнего влево + * @param settings Список объектов настроек + * @returns Совмещённый объект настроек + */ export const mergeTableSettings = (...settings: TableSettings[]): TableSettings => { const newSettings: TableSettings = {} for (const setting of settings) { @@ -36,17 +48,36 @@ export const mergeTableSettings = (...settings: TableSettings[]): TableSettings return newSettings } +/** + * Расширяет настройки столбца, полученные из хранилища + * @param column Настройки столбца + * @param name Имя столбца в случае его отсутствия в настройках + * @returns Расширенные настройки столбца + */ export const normalizeTableColumn = (column: TableColumnSettings, name?: string): TableColumnSettings => ({ ...column, columnName: column.columnName ?? name, visible: column.visible ?? true, }) +/** + * Подготавливает настройки столбца к записи в хранилище + * + * @param column Настройки столбца + * @returns Подготовленные настройки столбца + */ export const optimizeTableColumn = (column: TableColumnSettings): TableColumnSettings => ({ ...column, visible: column.visible ?? true, }) +/** + * Применяет настройки таблицы к списку столбцов + * + * @param columns Список столбцов таблицы + * @param settings Объект настройки таблицы + * @returns Список столбцов с настройками + */ export const applyTableSettings = (columns: TableColumns, settings: TableSettings): TableColumns => { let newColumns: TableColumns = columns.map((column) => ({ ...column })) newColumns = newColumns.filter((column) => { diff --git a/src/utils/hooks/functionalValue.ts b/src/utils/hooks/functionalValue.ts index 95fb552..cb4cdbb 100644 --- a/src/utils/hooks/functionalValue.ts +++ b/src/utils/hooks/functionalValue.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react' import { ArgumentTypes } from '@utils/types' /** - * Значение типа может быть представлено непосредственно значением либо функцией его возвращаюшей + * Значение типа может быть представлено непосредственно значением либо функцией его возвращающей */ export type ReturnType = T extends (...args: any) => infer R ? R : any export type FunctionalValue = ReturnType | F @@ -11,8 +11,8 @@ export type FunctionalValue = ReturnType | F export const getFunctionalValue = (value: FunctionalValue) => value instanceof Function ? value : ((...args: ArgumentTypes) => value) as unknown as F /** - * Облегчает работу со значениями, которые могут быть представлены функциям. - * + * Облегчает работу со значениями, которые могут быть представлены функциям + * * @param value Значение или функция его возвращающая * @returns Функция, вызов которой вернёт искомое значение */ diff --git a/src/utils/queue.ts b/src/utils/queue.ts index cebc32e..e387511 100644 --- a/src/utils/queue.ts +++ b/src/utils/queue.ts @@ -1,10 +1,16 @@ export type TaskHandler = () => T | PromiseLike export type Queue = { + /** Метод добавления задачи в очередь */ push: (task: TaskHandler) => Promise + /** Длина очереди */ readonly length: number } +/** + * Фабрика очередей задач + * @returns Очередь задач + */ export const makeTaskQueue = (): Queue => { let pending: Promise = Promise.resolve() let count: number = 0 diff --git a/src/utils/types/index.ts b/src/utils/types/index.ts index c619b26..5431989 100644 --- a/src/utils/types/index.ts +++ b/src/utils/types/index.ts @@ -1,9 +1,9 @@ /** * Объединить типы, исключив совпадающие поля справа. - * + * * @typeParam T - Тип, передаваемый полностью * @typeParam R - Аддитивный тип - * + * * @returns Общий тип с полным `T` и несовпадающими полями из `R` */ export type OmitExtends = T & Omit From f4adb528ca9991845c4475a259cee5e0aa96098a Mon Sep 17 00:00:00 2001 From: goodmice Date: Tue, 13 Dec 2022 18:28:30 +0500 Subject: [PATCH 17/32] =?UTF-8?q?*=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=B8=D0=B3=D0=BD=D0=B0=D1=82=D1=83=D1=80?= =?UTF-8?q?=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B9=20makeNumer?= =?UTF-8?q?icColumn=20=D0=B8=20=D0=B5=D1=91=20=D0=BE=D0=B1=D1=91=D1=80?= =?UTF-8?q?=D1=82=D0=BE=D0=BA=20*=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D1=8B=D1=85=20=D1=8D=D0=BB=D0=B5=D0=BC=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 10 +- src/components/Table/Columns/date.tsx | 1 + src/components/Table/Columns/index.ts | 2 +- src/components/Table/Columns/numeric.tsx | 68 +++++++---- src/components/Table/Columns/plan_fact.tsx | 22 ---- src/components/Table/Columns/select.tsx | 15 ++- src/components/Table/Columns/tag.tsx | 1 + src/components/Table/Columns/text.tsx | 18 ++- src/components/Table/Columns/time.tsx | 1 + src/pages/AdminPanel/ClusterController.jsx | 17 +-- src/pages/AdminPanel/CompanyController.jsx | 17 +-- .../AdminPanel/CompanyTypeController.jsx | 9 +- src/pages/AdminPanel/DepositController.jsx | 8 +- src/pages/AdminPanel/PermissionController.jsx | 14 +-- src/pages/AdminPanel/RoleController.jsx | 4 +- .../AdminPanel/Telemetry/TelemetryViewer.jsx | 2 +- src/pages/AdminPanel/UserController/index.jsx | 10 +- src/pages/AdminPanel/WellController/index.jsx | 26 ++-- src/pages/Cluster/ClusterWells.jsx | 15 ++- src/pages/Cluster/WellOperationsTable.jsx | 8 +- src/pages/Deposit/StatisticsADW.jsx | 2 +- src/pages/Well/Analytics/Statistics.jsx | 8 +- .../WellCompositeSections.jsx | 28 ++--- .../Well/Documents/DocumentsTemplate.jsx | 2 +- src/pages/Well/Telemetry/Messages.jsx | 4 +- .../Well/Telemetry/OperationTime/index.jsx | 8 +- .../Well/Telemetry/Operations/DrillerList.jsx | 3 +- .../Telemetry/Operations/DrillerSchedule.jsx | 1 - .../Telemetry/Operations/OperationsTable.jsx | 10 +- .../Telemetry/Operations/TargetEditor.jsx | 9 +- .../Setpoints/SetpointSender.jsx | 24 +--- .../Telemetry/TelemetryView/cursorRender.jsx | 10 +- .../OperationEditor/WellOperationsEditor.jsx | 22 ++-- .../Well/WellOperations/Tvd/NptTable.jsx | 6 +- src/pages/Well/WellOperations/Tvd/TLPie.jsx | 6 +- .../Well/WellOperations/WellSectionsStat.jsx | 111 +++++++++--------- src/styles/components/loader.css | 8 +- 37 files changed, 247 insertions(+), 283 deletions(-) delete mode 100755 src/components/Table/Columns/plan_fact.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index a8ab85f..6dcba4d 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,14 @@ { "cSpell.words": [ - "день" + "день", + "КНБК", + "САУБ", + "antd", + "Poprompt", + "saub", + "setpoint", + "Setpoints", + "usehooks" ], "liveServer.settings.port": 5501 } \ No newline at end of file diff --git a/src/components/Table/Columns/date.tsx b/src/components/Table/Columns/date.tsx index a401a04..dda6340 100755 --- a/src/components/Table/Columns/date.tsx +++ b/src/components/Table/Columns/date.tsx @@ -24,6 +24,7 @@ export const makeDateColumn = ( other?: ColumnProps, pickerOther?: DatePickerWrapperProps, ) => makeColumn(title, key, { + editable: true, ...other, render: (date) => (

diff --git a/src/components/Table/Columns/index.ts b/src/components/Table/Columns/index.ts index 778bffd..e21c93c 100755 --- a/src/components/Table/Columns/index.ts +++ b/src/components/Table/Columns/index.ts @@ -9,7 +9,6 @@ import { OmitExtends } from '@utils/types' export * from './date' export * from './time' export * from './numeric' -export * from './plan_fact' export * from './select' export * from './tag' export * from './text' @@ -45,6 +44,7 @@ export const makeColumn = (title: ReactNode, key: Key, other?: ColumnPr title: title, key: key, dataIndex: key, + render: (value: T) => value, ...other, }) diff --git a/src/components/Table/Columns/numeric.tsx b/src/components/Table/Columns/numeric.tsx index 36f89b2..c15ee52 100755 --- a/src/components/Table/Columns/numeric.tsx +++ b/src/components/Table/Columns/numeric.tsx @@ -1,4 +1,3 @@ -import { ColumnFilterItem } from 'antd/lib/table/interface' import { InputNumber } from 'antd' import { Key, ReactNode } from 'react' @@ -46,18 +45,17 @@ export const makeNumericColumnOptions = (fixed?: number, sorte export const makeNumericColumn = ( title: ReactNode, key: Key, - filters?: ColumnFilterItem[], - filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, + filterDelegate?: FilterGenerator, width?: string | number, other?: ColumnProps, ) => makeColumn(title, key, { - filters, + editable: true, onFilter: filterDelegate ? filterDelegate(key) : undefined, sorter: makeNumericSorter(key), width, - input: , - render: renderDelegate ?? makeNumericRender(2), + input: , + render: renderDelegate || makeNumericRender(2), align: 'right', ...other }) @@ -65,54 +63,78 @@ export const makeNumericColumn = ( export const makeNumericColumnPlanFact = ( title: ReactNode, key: Key, - filters?: ColumnFilterItem[], - filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, + filterDelegate?: FilterGenerator, + width?: string | number, + other?: ColumnProps, +) => { + return { + title, + children: [ + makeNumericColumn('План', `${key}.plan`, renderDelegate, filterDelegate, width, other), + makeNumericColumn('Факт', `${key}.fact`, renderDelegate, filterDelegate, width, other), + ] + } +} + +/** + * @deprecated Для значений типа план/факт появилась модель `PlanFactDto`, использование 2 полей с суффиксами неактуально + * @param title Заголовок столбца + * @param key Ключ столбца + * @param filters Список значений для фильтрации + * @param filterDelegate Метод фильтрации + * @param renderDelegate Render-метод отображения ячейки + * @param width Ширина столбца + * @param other Дополнительные опции + * @returns Объект-столбец для таблицы + */ +export const makeNumericColumnPlanFactOld = ( + title: ReactNode, + key: Key, + renderDelegate?: RenderMethod, + filterDelegate?: FilterGenerator, width?: string | number, other?: ColumnProps, ) => makeGroupColumn(title, [ - makeNumericColumn('п', key + 'Plan', filters, filterDelegate, renderDelegate, width, other), - makeNumericColumn('ф', key + 'Fact', filters, filterDelegate, renderDelegate, width, other), + makeNumericColumn('План', key + 'Plan', renderDelegate, filterDelegate, width, other), + makeNumericColumn('Факт', key + 'Fact', renderDelegate, filterDelegate, width, other), ]) export const makeNumericStartEnd = ( title: ReactNode, key: Key, fixed: number, - filters?: ColumnFilterItem[], - filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, + filterDelegate?: FilterGenerator, width?: string | number, ) => makeGroupColumn(title, [ - makeNumericColumn('старт', key + 'Start', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, key + 'Start')), - makeNumericColumn('конец', key + 'End', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, key + 'End')) + makeNumericColumn('старт', key + 'Start', renderDelegate, filterDelegate, width, makeNumericColumnOptions(fixed, key + 'Start')), + makeNumericColumn('конец', key + 'End', renderDelegate, filterDelegate, width, makeNumericColumnOptions(fixed, key + 'End')) ]) export const makeNumericMinMax = ( title: ReactNode, key: Key, fixed: number, - filters?: ColumnFilterItem[], - filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, + filterDelegate?: FilterGenerator, width?: string | number, ) => makeGroupColumn(title, [ - makeNumericColumn('мин', key + 'Min', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, key + 'Min')), - makeNumericColumn('макс', key + 'Max', filters, filterDelegate, renderDelegate, width, makeNumericColumnOptions(fixed, key + 'Max')), + makeNumericColumn('мин', key + 'Min', renderDelegate, filterDelegate, width, makeNumericColumnOptions(fixed, key + 'Min')), + makeNumericColumn('макс', key + 'Max', renderDelegate, filterDelegate, width, makeNumericColumnOptions(fixed, key + 'Max')), ]) export const makeNumericAvgRange = ( title: ReactNode, key: Key, fixed: number, - filters?: ColumnFilterItem[], - filterDelegate?: FilterGenerator, renderDelegate?: RenderMethod, + filterDelegate?: FilterGenerator, 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`)), + makeNumericColumn('мин', `${key}.min`, renderDelegate, filterDelegate, width, makeNumericColumnOptions(fixed, `${key}.min`)), + makeNumericColumn('сред', `${key}.avg`, renderDelegate, filterDelegate, width, makeNumericColumnOptions(fixed, `${key}.avg`)), + makeNumericColumn('макс', `${key}.max`, renderDelegate, filterDelegate, 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 deleted file mode 100755 index 852b9e4..0000000 --- a/src/components/Table/Columns/plan_fact.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Key, ReactNode } from 'react' - -import { ColumnProps, makeColumn } from '.' - -export const makeColumnsPlanFact = ( - title: string | ReactNode, - key: Key | [Key, Key], - columsOther?: ColumnProps | [ColumnProps, ColumnProps], -) => { - const keys = Array.isArray(key) ? key : [`${key}Plan`, `${key}Fact`] - const others = Array.isArray(columsOther) ? columsOther : [columsOther, columsOther] - - return { - title, - children: [ - makeColumn('план', keys[0], others[0]), - makeColumn('факт', keys[1], others[1]), - ] - } -} - -export default makeColumnsPlanFact diff --git a/src/components/Table/Columns/select.tsx b/src/components/Table/Columns/select.tsx index 098d983..54e948b 100755 --- a/src/components/Table/Columns/select.tsx +++ b/src/components/Table/Columns/select.tsx @@ -1,9 +1,17 @@ import { Select, SelectProps } from 'antd' import { DefaultOptionType, SelectValue } from 'antd/lib/select' -import { Key, ReactNode } from 'react' +import { Key, ReactNode, useMemo } from 'react' import { ColumnProps, makeColumn } from '.' +const findOption = (value: any, options: T[] | undefined) => + options?.find((option) => String(option?.value) === String(value)) + +const SelectWrapper = ({ value, options, ...other }: SelectProps) => { + const selectValue = useMemo(() => findOption(value, options)?.label, [value, options]) + return , + input: , render: (value, dataset, index) => { - const item = options?.find(option => String(option?.value) === String(value)) + const item = findOption(value, options) return other?.render?.(item, dataset, index) ?? item?.label ?? defaultValue?.label ?? value?.label ?? '--' } }) diff --git a/src/components/Table/Columns/tag.tsx b/src/components/Table/Columns/tag.tsx index e683f19..c83329b 100755 --- a/src/components/Table/Columns/tag.tsx +++ b/src/components/Table/Columns/tag.tsx @@ -65,6 +65,7 @@ export const makeTagColumn = ( const InputComponent = makeTagInput(value_key, label_key) return makeColumn(title, dataIndex, { + editable: true, ...other, render: (item: T[] | undefined, dataset, index) => item?.map((elm: T) => {other?.render?.(elm, dataset, index) ?? elm[label_key]}) ?? '-', input: , diff --git a/src/components/Table/Columns/text.tsx b/src/components/Table/Columns/text.tsx index 91431ad..69db9a3 100755 --- a/src/components/Table/Columns/text.tsx +++ b/src/components/Table/Columns/text.tsx @@ -1,3 +1,4 @@ +import { Tooltip } from 'antd' import { ColumnFilterItem } from 'antd/lib/table/interface' import { Key, ReactNode } from 'react' @@ -15,6 +16,18 @@ export const makeStringSorter = (key: Key): SorterMethod => return String(vA).localeCompare(String(vB)) } +export const makeTextRender = (def = '---', stringCutter?: (text: string) => string) => (value: T) => { + if (!value) return def + if (stringCutter) { + return ( + + {stringCutter(value)} + + ) + } + return value +} + export const makeFilterTextMatch = (key: keyof DataType) => (filterValue: T, dataItem: DataType) => dataItem[key] === filterValue @@ -26,10 +39,11 @@ export const makeTextColumn = ( render?: RenderMethod, other?: ColumnProps ) => makeColumn(title, key, { + editable: true, filters, onFilter: filters ? makeFilterTextMatch(key) : undefined, - sorter: sorter ?? makeStringSorter(key), - render: render, + sorter: sorter || makeStringSorter(key), + render: render || makeTextRender(), ...other }) diff --git a/src/components/Table/Columns/time.tsx b/src/components/Table/Columns/time.tsx index 7547814..116840c 100644 --- a/src/components/Table/Columns/time.tsx +++ b/src/components/Table/Columns/time.tsx @@ -24,6 +24,7 @@ export const makeTimeColumn = ( other?: ColumnProps, pickerOther?: TimePickerWrapperProps, ) => makeColumn(title, key, { + editable: true, ...other, render: (time) => (
diff --git a/src/pages/AdminPanel/ClusterController.jsx b/src/pages/AdminPanel/ClusterController.jsx index 35c9b9b..16259d3 100755 --- a/src/pages/AdminPanel/ClusterController.jsx +++ b/src/pages/AdminPanel/ClusterController.jsx @@ -3,11 +3,12 @@ import { Input } from 'antd' import { EditableTable, - makeColumn, makeSelectColumn, makeStringSorter, defaultPagination, - makeTimezoneColumn + makeTimezoneColumn, + makeNumericColumn, + makeTextColumn } from '@components/Table' import { invokeWebApiWrapperAsync } from '@components/factory' import { AdminClusterService, AdminDepositService } from '@api' @@ -30,17 +31,11 @@ const ClusterController = memo(() => { const clusterColumns = useMemo(() => [ makeSelectColumn('Месторождение', 'idDeposit', deposits, '--', { width: 200, - editable: true, sorter: makeStringSorter('idDeposit') }), - makeColumn('Название', 'caption', { - width: 200, - editable: true, - sorter: makeStringSorter('caption'), - formItemRules: min1, - }), - makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFormat }), - makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFormat }), + makeTextColumn('Название', 'caption', undefined, undefined, undefined, { width: 200, formItemRules: min1 }), + makeNumericColumn('Широта', 'latitude', coordsFormat, undefined, 150), + makeNumericColumn('Долгота', 'longitude', coordsFormat, undefined, 150), makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }), ], [deposits]) diff --git a/src/pages/AdminPanel/CompanyController.jsx b/src/pages/AdminPanel/CompanyController.jsx index 454b16c..5577e1f 100755 --- a/src/pages/AdminPanel/CompanyController.jsx +++ b/src/pages/AdminPanel/CompanyController.jsx @@ -3,10 +3,9 @@ import { Input } from 'antd' import { EditableTable, - makeColumn, - makeStringSorter, makeSelectColumn, - defaultPagination + defaultPagination, + makeTextColumn } from '@components/Table' import { invokeWebApiWrapperAsync } from '@components/factory' import { AdminCompanyService, AdminCompanyTypeService } from '@api' @@ -37,16 +36,8 @@ const CompanyController = memo(() => { })) setColumns([ - makeColumn('Название', 'caption', { - width: 200, - editable: true, - sorter: makeStringSorter('caption'), - formItemRules: min1, - }), - makeSelectColumn('Тип компании', 'idCompanyType', companyTypes, null, { - width: 200, - editable: true - }), + makeTextColumn('Название', 'caption', undefined, undefined, undefined, { width: 200, formItemRules: min1 }), + makeSelectColumn('Тип компании', 'idCompanyType', companyTypes, null, { width: 200 }), ]) await updateTable() diff --git a/src/pages/AdminPanel/CompanyTypeController.jsx b/src/pages/AdminPanel/CompanyTypeController.jsx index 3debe2c..601d1aa 100755 --- a/src/pages/AdminPanel/CompanyTypeController.jsx +++ b/src/pages/AdminPanel/CompanyTypeController.jsx @@ -1,19 +1,14 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { Input } from 'antd' -import { EditableTable, makeColumn, makeStringSorter, defaultPagination } from '@components/Table' +import { EditableTable, defaultPagination, makeTextColumn } from '@components/Table' import { invokeWebApiWrapperAsync } from '@components/factory' import { arrayOrDefault, withPermissions } from '@utils' import { min1 } from '@utils/validationRules' import { AdminCompanyTypeService } from '@api' const columns = [ - makeColumn('Название', 'caption', { - width: 200, - editable: true, - sorter: makeStringSorter('caption'), - formItemRules: min1, - }), + makeTextColumn('Название', 'caption', undefined, undefined, undefined, { width: 200, formItemRules: min1 }), ] const CompanyTypeController = memo(() => { diff --git a/src/pages/AdminPanel/DepositController.jsx b/src/pages/AdminPanel/DepositController.jsx index 748490b..974a420 100755 --- a/src/pages/AdminPanel/DepositController.jsx +++ b/src/pages/AdminPanel/DepositController.jsx @@ -2,15 +2,15 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { Input } from 'antd' import { invokeWebApiWrapperAsync } from '@components/factory' -import { EditableTable, makeColumn, defaultPagination, makeTimezoneColumn } from '@components/Table' +import { EditableTable, defaultPagination, makeTimezoneColumn, makeTextColumn, makeNumericColumn } from '@components/Table' import { arrayOrDefault, coordsFormat, withPermissions } from '@utils' import { min1 } from '@utils/validationRules' import { AdminDepositService } from '@api' const depositColumns = [ - makeColumn('Название', 'caption', { width: 200, editable: true, formItemRules: min1 }), - makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFormat }), - makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFormat }), + makeTextColumn('Название', 'caption', undefined, undefined, undefined, { width: 200, formItemRules: min1 }), + makeNumericColumn('Широта', 'latitude', coordsFormat, undefined, 150), + makeNumericColumn('Долгота', 'longitude', coordsFormat, undefined, 150), makeTimezoneColumn('Зона', 'timezone', null, true, { width: 150 }), ] diff --git a/src/pages/AdminPanel/PermissionController.jsx b/src/pages/AdminPanel/PermissionController.jsx index 73db56a..0ee736e 100755 --- a/src/pages/AdminPanel/PermissionController.jsx +++ b/src/pages/AdminPanel/PermissionController.jsx @@ -1,23 +1,15 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { Input } from 'antd' -import { EditableTable, makeColumn, makeStringSorter } from '@components/Table' +import { EditableTable, makeTextColumn } from '@components/Table' import { invokeWebApiWrapperAsync } from '@components/factory' import { arrayOrDefault, withPermissions } from '@utils' import { min1 } from '@utils/validationRules' import { AdminPermissionService } from '@api' const columns = [ - makeColumn('Название', 'name', { - editable: true, - sorter: makeStringSorter('name'), - isRequired: true, - formItemRules: min1, - }), - makeColumn('Описание', 'description', { - editable: true, - sorter: makeStringSorter('description'), - }), + makeTextColumn('Название', 'name', undefined, undefined, undefined, { isRequired: true, formItemRules: min1 }), + makeTextColumn('Описание', 'description'), ] const PermissionController = memo(() => { diff --git a/src/pages/AdminPanel/RoleController.jsx b/src/pages/AdminPanel/RoleController.jsx index b995fde..6276ac5 100755 --- a/src/pages/AdminPanel/RoleController.jsx +++ b/src/pages/AdminPanel/RoleController.jsx @@ -19,15 +19,13 @@ const RoleController = memo(() => { )), [roles, searchValue]) const columns = useMemo(() => [ - makeTextColumn('Название', 'caption', null, null, null, { width: 100, editable: true, formItemRules: min1 }), + makeTextColumn('Название', 'caption', null, null, null, { width: 100, formItemRules: min1 }), makeTagColumn('Включённые роли', 'roles', roles, 'id', 'caption', { width: 400, - editable: true, render: (role) => }, { allowClear: true }), makeTagColumn('Разрешения', 'permissions', permissions, 'id', 'name', { width: 600, - editable: true, render: (permission) => , }), ], [roles, permissions]) diff --git a/src/pages/AdminPanel/Telemetry/TelemetryViewer.jsx b/src/pages/AdminPanel/Telemetry/TelemetryViewer.jsx index f90ff30..3836aff 100755 --- a/src/pages/AdminPanel/Telemetry/TelemetryViewer.jsx +++ b/src/pages/AdminPanel/Telemetry/TelemetryViewer.jsx @@ -50,7 +50,7 @@ const TelemetryController = memo(() => { const columns = useMemo(() => [ makeColumn('', 'hasParent', { render: mergeRender }), - makeNumericColumn('ID', 'id', null, null, makeNumericRender(0)), + makeNumericColumn('ID', 'id', makeNumericRender(0)), makeTextColumn('UID', 'remoteUid'), makeTextColumn('Назначена на скважину', 'realWell'), makeDateColumn('Дата начала бурения', 'drillingStartDate'), diff --git a/src/pages/AdminPanel/UserController/index.jsx b/src/pages/AdminPanel/UserController/index.jsx index 2f6b301..5bb3ef2 100755 --- a/src/pages/AdminPanel/UserController/index.jsx +++ b/src/pages/AdminPanel/UserController/index.jsx @@ -115,7 +115,6 @@ const UserController = memo(() => { setColumns([ makeTextColumn('Логин', 'login', null, null, null, { - editable: true, formItemRules: [ { required: true }, ...createLoginRules, @@ -130,41 +129,34 @@ const UserController = memo(() => { ], }), makeTextColumn('Фамилия', 'surname', filters.surname, null, null, { - editable: true, formItemRules: [{ required: true }, ...nameRules], filterSearch: true, onFilter: makeTextOnFilter('surname'), }), makeTextColumn('Имя', 'name', filters.name, null, null, { - editable: true, formItemRules: nameRules, filterSearch: true, onFilter: makeTextOnFilter('name'), }), makeTextColumn('Отчество', 'patronymic', filters.partonymic, null, null, { - editable: true, formItemRules: nameRules, filterSearch: true, onFilter: makeTextOnFilter('patronymic'), }), makeTextColumn('E-mail', 'email', filters.email, null, null, { - editable: true, formItemRules: [{ required: true }, ...emailRules], filterSearch: true, onFilter: makeTextOnFilter('email'), }), makeTextColumn('Номер телефона', 'phone', null, null, null, { - editable: true, formItemRules: phoneRules, }), - makeTextColumn('Должность', 'position', null, null, null, { editable: true }), + makeTextColumn('Должность', 'position', null, null, null), makeTextColumn('Роли', 'roleNames', roleFilters, null, rolesRender, { - editable: true, input: , onFilter: makeArrayOnFilter('roleNames'), }), makeSelectColumn('Компания', 'idCompany', companies, '--', { - editable: true, sorter: makeNumericSorter('idCompany'), }) ]) diff --git a/src/pages/AdminPanel/WellController/index.jsx b/src/pages/AdminPanel/WellController/index.jsx index 6fe163e..ed74ca6 100755 --- a/src/pages/AdminPanel/WellController/index.jsx +++ b/src/pages/AdminPanel/WellController/index.jsx @@ -12,11 +12,12 @@ import { EditableTable, makeColumn, makeSelectColumn, - makeStringSorter, makeNumericSorter, makeTagColumn, defaultPagination, makeTimezoneColumn, + makeTextColumn, + makeNumericColumn, } from '@components/Table' import { invokeWebApiWrapperAsync } from '@components/factory' import { TelemetryView, CompanyView } from '@components/views' @@ -81,23 +82,11 @@ const WellController = memo(() => { })) setColumns([ - makeSelectColumn('Куст', 'idCluster', clusters, '--', { - width: '5rem', - editable: true, - sorter: makeNumericSorter('idCluster'), - }), - makeColumn('Название', 'caption', { - width: '5rem', - editable: true, - sorter: makeStringSorter('caption'), - }), - makeSelectColumn('Тип', 'idWellType', wellTypes, '--', { - width: 150, - editable: true, - sorter: makeNumericSorter('idWellType'), - }), - makeColumn('Широта', 'latitude', { width: 150, editable: true, render: coordsFormat }), - makeColumn('Долгота', 'longitude', { width: 150, editable: true, render: coordsFormat }), + makeSelectColumn('Куст', 'idCluster', clusters, '--', { width: '5rem', sorter: makeNumericSorter('idCluster') }), + makeTextColumn('Название', 'caption', undefined, undefined, undefined, { width: '5rem' }), + makeSelectColumn('Тип', 'idWellType', wellTypes, '--', { width: 150, sorter: makeNumericSorter('idWellType') }), + makeNumericColumn('Широта', 'latitude', coordsFormat, undefined, 150), + makeNumericColumn('Долгота', 'longitude', coordsFormat, undefined, 150), makeColumn('Телеметрия', 'telemetry', { editable: true, render: (telemetry) => , @@ -105,7 +94,6 @@ const WellController = memo(() => { }, ), makeTimezoneColumn('Зона', 'timezone', null, true, { width: 170 }), makeTagColumn('Компании', 'companies', companies, 'id', 'caption', { - editable: true, render: (company) => , }), ]) diff --git a/src/pages/Cluster/ClusterWells.jsx b/src/pages/Cluster/ClusterWells.jsx index a84e9cb..81410ec 100755 --- a/src/pages/Cluster/ClusterWells.jsx +++ b/src/pages/Cluster/ClusterWells.jsx @@ -7,7 +7,7 @@ import { makeTextColumn, makeGroupColumn, makeColumn, - makeNumericColumnPlanFact, + makeNumericColumnPlanFactOld, Table, makeNumericRender, makeNumericColumn, @@ -117,7 +117,10 @@ const ClusterWells = memo(({ statsWells }) => { const columns = useMemo(() => [ makeTextColumn('скв №', 'caption', null, null, (_, item) => ( - + { makeDateColumn('начало', 'factStart'), makeDateColumn('окончание', 'factEnd'), ]), - makeNumericColumnPlanFact('Продолжительность, сут', 'period', filtersMinMax, makeFilterMinMaxFunction, numericRender), - makeNumericColumnPlanFact('МСП, м/ч', 'rateOfPenetration', filtersMinMax, makeFilterMinMaxFunction, numericRender), - makeNumericColumnPlanFact('Рейсовая скорость, м/ч', 'routeSpeed', filtersMinMax, makeFilterMinMaxFunction, numericRender), - makeNumericColumn('НПВ, ч', 'notProductiveTimeFact', filtersMinMax, makeFilterMinMaxFunction, numericRender), + makeNumericColumnPlanFactOld('Продолжительность, сут', 'period', numericRender, makeFilterMinMaxFunction, { filters: filtersMinMax }), + makeNumericColumnPlanFactOld('МСП, м/ч', 'rateOfPenetration', numericRender, makeFilterMinMaxFunction, { filters: filtersMinMax }), + makeNumericColumnPlanFactOld('Рейсовая скорость, м/ч', 'routeSpeed', numericRender, makeFilterMinMaxFunction, { filters: filtersMinMax }), + makeNumericColumn('НПВ, ч', 'notProductiveTimeFact', numericRender, makeFilterMinMaxFunction, { filters: filtersMinMax }), makeColumn('TVD', 'tvd', { align: 'center', render: (_, value) => (