diff --git a/src/components/FastRunMenu.tsx b/src/components/FastRunMenu.tsx new file mode 100644 index 0000000..9e76558 --- /dev/null +++ b/src/components/FastRunMenu.tsx @@ -0,0 +1,144 @@ +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' +import { DefaultOptionType } from 'antd/lib/select' +import { AutoComplete } from 'antd' +import { BaseSelectRef } from 'rc-select' +import { join } from 'path' + +import { useWell } from '@asb/context' +import { makeItem, PrivateWellMenuItem } from './PrivateWellMenu' +import { hasPermission, isURLAvailable } from '@utils' + +import { menuItems as adminMenuItems } from '@pages/AdminPanel/AdminNavigationMenu' +import { menuItems as wellMenuItems } from '@pages/Well/NavigationMenu' + +import '@styles/fast_run_menu.less' + +const transliterationTable = { + 'q': 'й', 'w': 'ц', 'e': 'у', 'r': 'к', 't': 'е', 'y': 'н', 'u': 'г', 'i': 'ш', 'o': 'щ', 'p': 'з', '[': 'х', ']': 'ъ', '{': 'х', '}': 'ъ', + 'a': 'ф', 's': 'ы', 'd': 'в', 'f': 'а', 'g': 'п', 'h': 'р', 'j': 'о', 'k': 'л', 'l': 'д', ';': 'ж', "'": 'э', ':': 'ж', '"': 'э', + 'z': 'я', 'x': 'ч', 'c': 'с', 'v': 'м', 'b': 'и', 'n': 'т', 'm': 'ь', ',': 'б', '.': 'ю', '<': 'б', '>': 'ю', +} + +const transliterateToRu = (text: string): string => { + let out = text.toLowerCase() + Object.entries(transliterationTable).map(([en, ru]) => out = out.replaceAll(en, ru)) + return out +} + +const transliterateToEn = (text: string): string => { + let out = text.toLowerCase() + Object.entries(transliterationTable).map(([en, ru]) => out = out.replaceAll(ru, en)) + return out +} + +const applyVars = (route: string, vars?: Record): string => { + if (!vars) return route + let out = route + Object.entries(vars).forEach(([key, value]) => { + out = out.replaceAll(`{${key}}`, value) + }) + return out +} + +const makeOptions = (items: PrivateWellMenuItem[], vars?: Record): DefaultOptionType[] => { + const out: DefaultOptionType[] = [] + items.forEach((item) => { + if (!hasPermission(item.permissions)) return + out.push({ + label: item.title, + value: applyVars(item.route, vars), + }) + if (item.children) { + const childrenOptions = makeOptions(item.children).map((child) => ({ + label: `${item.title} > ${child.label}`, + value: applyVars(join(item.route, String(child.value)), vars), + })) + out.push(...childrenOptions) + } + }) + return out +} + +export const FastRunMenu = memo(() => { + const [isOpen, setIsOpen] = useState(false) + const [value, setValue] = useState() + const ref = useRef(null) + + const [well] = useWell() + + const navigate = useNavigate() + const location = useLocation() + + const options = useMemo(() => { + const menus = [ + makeItem('Месторождения', '/deposit', []), + ] + + if (isURLAvailable('/admin')) + menus.push(makeItem('Панель администратора', '/admin', [], undefined, adminMenuItems as PrivateWellMenuItem[])) + + if (well.id) + menus.push( + makeItem(`Куст (${well.cluster})`, `/cluster/${well.idCluster}`, []), + makeItem(`Скважина (${well.caption})`, '/well/{idWell}', [], undefined, wellMenuItems), + ) + + return makeOptions(menus, { idWell: well.id }) + }, [well]) + + const onClose = useCallback(() => { + setIsOpen(false) + setValue(null) + }, []) + + const onTextChanged = useCallback((value: any) => { + navigate(value, { state: { from: location.pathname } }) + onClose() + }, [onClose]) + + useEffect(() => { + const listener = (event: KeyboardEvent) => { + if (event.altKey && event.code === 'KeyA') + setIsOpen((prev) => !prev) + } + + document.addEventListener('keyup', listener) + + return () => document.removeEventListener('keyup', listener) + }, [ref.current]) + + useEffect(() => { + if (!isOpen) return + ref.current?.focus() + ref.current?.scrollTo(0) + }, [isOpen]) + + const onFilter = (text: string, option: DefaultOptionType | undefined) => { + if (!option) return false + const search = (String(option.label).replaceAll(' > ', '') + String(option.value).replaceAll('/', '')).toLowerCase() + if (search.includes(text.toLowerCase())) return true + if (search.includes(transliterateToRu(text))) return true + if (search.includes(transliterateToEn(text))) return true + return false + } + + return isOpen ? ( +
+ +
+ ) : <> +}) + +export default FastRunMenu diff --git a/src/pages/AdminPanel/index.jsx b/src/pages/AdminPanel/index.jsx index 442b7df..ad338e2 100755 --- a/src/pages/AdminPanel/index.jsx +++ b/src/pages/AdminPanel/index.jsx @@ -2,6 +2,7 @@ import { Navigate, Route, Routes } from 'react-router-dom' import { lazy, memo, useMemo } from 'react' import { RootPathContext, useLayoutProps, useRootPath } from '@asb/context' +import { FastRunMenu } from '@components/FastRunMenu' import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb' import { NoAccessComponent, withPermissions } from '@utils' @@ -35,6 +36,7 @@ const AdminPanel = memo(() => { return ( + } /> } /> diff --git a/src/pages/Cluster/index.jsx b/src/pages/Cluster/index.jsx index aebf96d..1fab68f 100755 --- a/src/pages/Cluster/index.jsx +++ b/src/pages/Cluster/index.jsx @@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom' import { useLayoutProps } from '@asb/context' import LoaderPortal from '@components/LoaderPortal' +import { FastRunMenu } from '@components/FastRunMenu' import { invokeWebApiWrapperAsync } from '@components/factory' import { arrayOrDefault, withPermissions } from '@utils' import { OperationStatService } from '@api' @@ -34,9 +35,12 @@ const Cluster = memo(() => { }, [idCluster]) return ( - - - + <> + + + + + ) }) diff --git a/src/pages/Deposit.jsx b/src/pages/Deposit.jsx index 3dbdfe0..9779e78 100755 --- a/src/pages/Deposit.jsx +++ b/src/pages/Deposit.jsx @@ -1,11 +1,12 @@ -import { Map, Overlay } from 'pigeon-maps' import { useState, useEffect, memo, useMemo } from 'react' import { Link, useLocation } from 'react-router-dom' +import { Map, Overlay } from 'pigeon-maps' import { Popover, Badge } from 'antd' import { useLayoutProps } from '@asb/context' import { PointerIcon } from '@components/icons' import LoaderPortal from '@components/LoaderPortal' +import { FastRunMenu } from '@components/FastRunMenu' import { invokeWebApiWrapperAsync } from '@components/factory' import { arrayOrDefault, limitValue, withPermissions } from '@utils' import { DepositService } from '@api' @@ -81,35 +82,38 @@ const Deposit = memo(() => { }, []) return ( - -
- - {depositsData.map(deposit => ( - - - {deposit.clusters.map(cluster => ( - -
{cluster.caption}
- - ))} -
- } trigger={['click']} title={deposit.caption}> -
- - - -
- - - ))} - - -
+ <> + + +
+ + {depositsData.map(deposit => ( + + + {deposit.clusters.map(cluster => ( + +
{cluster.caption}
+ + ))} +
+ } trigger={['click']} title={deposit.caption}> +
+ + + +
+ + + ))} + + +
+ ) }) diff --git a/src/pages/Well/index.jsx b/src/pages/Well/index.jsx index 20c2f47..534b313 100644 --- a/src/pages/Well/index.jsx +++ b/src/pages/Well/index.jsx @@ -2,6 +2,7 @@ import { lazy, memo, useCallback, useEffect, useMemo, useState } from 'react' import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom' import { WellContext, RootPathContext, useRootPath, useLayoutProps, TopRightBlockContext } from '@asb/context' +import { FastRunMenu } from '@components/FastRunMenu' import { invokeWebApiWrapperAsync } from '@components/factory' import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb' import { NoAccessComponent, withPermissions } from '@utils' @@ -86,6 +87,7 @@ const Well = memo(() => { return ( + } /> diff --git a/src/styles/fast_run_menu.less b/src/styles/fast_run_menu.less new file mode 100644 index 0000000..b3eee3a --- /dev/null +++ b/src/styles/fast_run_menu.less @@ -0,0 +1,9 @@ +.fast-run-menu { + position: absolute; + z-index: 1000; + top: 100px; + left: calc(25vw - 5px); + border: 5px solid #777; + border-radius: 5px; + width: 50vw; +}