forked from ddrilling/asb_cloud_front
Добавлена панель быстрого перехода между страницами
This commit is contained in:
parent
11cb245cf5
commit
e773943b61
144
src/components/FastRunMenu.tsx
Normal file
144
src/components/FastRunMenu.tsx
Normal file
@ -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, any>): 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<string, any>): 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<string | null>()
|
||||||
|
const ref = useRef<BaseSelectRef | null>(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 ? (
|
||||||
|
<div className={'fast-run-menu'}>
|
||||||
|
<AutoComplete
|
||||||
|
ref={ref}
|
||||||
|
autoFocus
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
options={options}
|
||||||
|
onBlur={onClose}
|
||||||
|
onChange={setValue}
|
||||||
|
placeholder={'Введите название страницы...'}
|
||||||
|
onSelect={onTextChanged}
|
||||||
|
value={value}
|
||||||
|
filterOption={onFilter}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : <></>
|
||||||
|
})
|
||||||
|
|
||||||
|
export default FastRunMenu
|
@ -2,6 +2,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'
|
|||||||
import { lazy, memo, useMemo } from 'react'
|
import { lazy, memo, useMemo } from 'react'
|
||||||
|
|
||||||
import { RootPathContext, useLayoutProps, useRootPath } from '@asb/context'
|
import { RootPathContext, useLayoutProps, useRootPath } from '@asb/context'
|
||||||
|
import { FastRunMenu } from '@components/FastRunMenu'
|
||||||
import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb'
|
import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb'
|
||||||
import { NoAccessComponent, withPermissions } from '@utils'
|
import { NoAccessComponent, withPermissions } from '@utils'
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ const AdminPanel = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RootPathContext.Provider value={rootPath}>
|
<RootPathContext.Provider value={rootPath}>
|
||||||
|
<FastRunMenu />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<Navigate to={'visit_log'} replace />} />
|
<Route index element={<Navigate to={'visit_log'} replace />} />
|
||||||
<Route path={'*'} element={<NoAccessComponent />} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
|
@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom'
|
|||||||
|
|
||||||
import { useLayoutProps } from '@asb/context'
|
import { useLayoutProps } from '@asb/context'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import { FastRunMenu } from '@components/FastRunMenu'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { arrayOrDefault, withPermissions } from '@utils'
|
import { arrayOrDefault, withPermissions } from '@utils'
|
||||||
import { OperationStatService } from '@api'
|
import { OperationStatService } from '@api'
|
||||||
@ -34,9 +35,12 @@ const Cluster = memo(() => {
|
|||||||
}, [idCluster])
|
}, [idCluster])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
<ClusterWells statsWells={data} />
|
<FastRunMenu />
|
||||||
</LoaderPortal>
|
<LoaderPortal show={showLoader}>
|
||||||
|
<ClusterWells statsWells={data} />
|
||||||
|
</LoaderPortal>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Map, Overlay } from 'pigeon-maps'
|
|
||||||
import { useState, useEffect, memo, useMemo } from 'react'
|
import { useState, useEffect, memo, useMemo } from 'react'
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
|
import { Map, Overlay } from 'pigeon-maps'
|
||||||
import { Popover, Badge } from 'antd'
|
import { Popover, Badge } from 'antd'
|
||||||
|
|
||||||
import { useLayoutProps } from '@asb/context'
|
import { useLayoutProps } from '@asb/context'
|
||||||
import { PointerIcon } from '@components/icons'
|
import { PointerIcon } from '@components/icons'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
|
import { FastRunMenu } from '@components/FastRunMenu'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { arrayOrDefault, limitValue, withPermissions } from '@utils'
|
import { arrayOrDefault, limitValue, withPermissions } from '@utils'
|
||||||
import { DepositService } from '@api'
|
import { DepositService } from '@api'
|
||||||
@ -81,35 +82,38 @@ const Deposit = memo(() => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoaderPortal show={showLoader}>
|
<>
|
||||||
<div className={'h-100vh'} style={{ overflow: 'hidden' }}>
|
<FastRunMenu />
|
||||||
<Map {...viewParams}>
|
<LoaderPortal show={showLoader}>
|
||||||
{depositsData.map(deposit => (
|
<div className={'h-100vh'} style={{ overflow: 'hidden' }}>
|
||||||
<Overlay
|
<Map {...viewParams}>
|
||||||
width={32}
|
{depositsData.map(deposit => (
|
||||||
anchor={[deposit.latitude, deposit.longitude]}
|
<Overlay
|
||||||
key={`${deposit.latitude} ${deposit.longitude}`}
|
width={32}
|
||||||
>
|
anchor={[deposit.latitude, deposit.longitude]}
|
||||||
<Popover content={
|
key={`${deposit.latitude} ${deposit.longitude}`}
|
||||||
<div>
|
>
|
||||||
{deposit.clusters.map(cluster => (
|
<Popover content={
|
||||||
<Link key={cluster.id} to={{ pathname: `/cluster/${cluster.id}`, state: { from: location.pathname }}}>
|
<div>
|
||||||
<div>{cluster.caption}</div>
|
{deposit.clusters.map(cluster => (
|
||||||
</Link>
|
<Link key={cluster.id} to={{ pathname: `/cluster/${cluster.id}`, state: { from: location.pathname }}}>
|
||||||
))}
|
<div>{cluster.caption}</div>
|
||||||
</div>
|
</Link>
|
||||||
} trigger={['click']} title={deposit.caption}>
|
))}
|
||||||
<div style={{cursor: 'pointer'}}>
|
</div>
|
||||||
<Badge count={deposit.clusters.length}>
|
} trigger={['click']} title={deposit.caption}>
|
||||||
<PointerIcon state={'active'} width={48} height={59} />
|
<div style={{cursor: 'pointer'}}>
|
||||||
</Badge>
|
<Badge count={deposit.clusters.length}>
|
||||||
</div>
|
<PointerIcon state={'active'} width={48} height={59} />
|
||||||
</Popover>
|
</Badge>
|
||||||
</Overlay>
|
</div>
|
||||||
))}
|
</Popover>
|
||||||
</Map>
|
</Overlay>
|
||||||
</div>
|
))}
|
||||||
</LoaderPortal>
|
</Map>
|
||||||
|
</div>
|
||||||
|
</LoaderPortal>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { lazy, memo, useCallback, useEffect, useMemo, useState } from 'react'
|
|||||||
import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom'
|
import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { WellContext, RootPathContext, useRootPath, useLayoutProps, TopRightBlockContext } from '@asb/context'
|
import { WellContext, RootPathContext, useRootPath, useLayoutProps, TopRightBlockContext } from '@asb/context'
|
||||||
|
import { FastRunMenu } from '@components/FastRunMenu'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb'
|
import { MenuBreadcrumbItems } from '@components/MenuBreadcrumb'
|
||||||
import { NoAccessComponent, withPermissions } from '@utils'
|
import { NoAccessComponent, withPermissions } from '@utils'
|
||||||
@ -86,6 +87,7 @@ const Well = memo(() => {
|
|||||||
return (
|
return (
|
||||||
<RootPathContext.Provider value={rootPath}>
|
<RootPathContext.Provider value={rootPath}>
|
||||||
<WellContext.Provider value={[well, updateWell]}>
|
<WellContext.Provider value={[well, updateWell]}>
|
||||||
|
<FastRunMenu />
|
||||||
<TopRightBlockContext.Provider value={setTopRightBlock}>
|
<TopRightBlockContext.Provider value={setTopRightBlock}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<Navigate to={'telemetry'} replace />} />
|
<Route index element={<Navigate to={'telemetry'} replace />} />
|
||||||
|
9
src/styles/fast_run_menu.less
Normal file
9
src/styles/fast_run_menu.less
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user