Добавлена панель быстрого перехода между страницами

This commit is contained in:
Александр Сироткин 2022-10-31 01:18:16 +05:00
parent 11cb245cf5
commit e773943b61
6 changed files with 198 additions and 33 deletions

View 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

View File

@ -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 />} />

View File

@ -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>
</>
) )
}) })

View File

@ -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>
</>
) )
}) })

View File

@ -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 />} />

View 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;
}