diff --git a/src/App.tsx b/src/App.tsx index 0ac3eb0..28ed1b2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,8 +4,6 @@ import locale from 'antd/lib/locale/ru_RU' import { ConfigProvider } from 'antd' import { RootPathContext } from '@asb/context' -import { UserOutlet } from '@components/outlets' -import LayoutPortal from '@components/LayoutPortal' import SuspenseFallback from '@components/SuspenseFallback' import { getUser, NoAccessComponent } from '@utils' import { OpenAPI } from '@api' @@ -13,6 +11,10 @@ import { OpenAPI } from '@api' import '@styles/include/antd_theme.less' import '@styles/App.less' +const UserOutlet = lazy(() => import('@components/outlets/UserOutlet')) +const DepositsOutlet = lazy(() => import('@components/outlets/DepositsOutlet')) +const LayoutPortal = lazy(() => import('@components/LayoutPortal')) + const Login = lazy(() => import('@pages/public/Login')) const Register = lazy(() => import('@pages/public/Register')) const FileDownload = lazy(() => import('@pages/FileDownload')) @@ -44,14 +46,16 @@ export const App = memo(() => ( }> } /> - }> - {/* Admin pages */} - } /> + }> + }> + {/* Admin pages */} + } /> - {/* Client pages */} - } /> - } /> - } /> + {/* Client pages */} + } /> + } /> + } /> + diff --git a/src/components/outlets/DepositsOutlet.tsx b/src/components/outlets/DepositsOutlet.tsx new file mode 100644 index 0000000..d191222 --- /dev/null +++ b/src/components/outlets/DepositsOutlet.tsx @@ -0,0 +1,35 @@ +import { memo, useEffect, useState } from 'react' +import { Outlet } from 'react-router-dom' + +import { DepositsContext } from '@asb/context' +import LoaderPortal from '@components/LoaderPortal' +import { invokeWebApiWrapperAsync } from '@components/factory' +import { DepositDto, DepositService } from '@api' +import { arrayOrDefault } from '@utils' + +export const DepositsOutlet = memo(() => { + const [deposits, setDeposits] = useState([]) + const [isLoading, setIsLoading] = useState(false) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const deposits = await DepositService.getDeposits() + setDeposits(arrayOrDefault(deposits)) + }, + setIsLoading, + `Не удалось загрузить список кустов`, + { actionName: 'Получить список кустов' } + ) + }, []) + + return ( + + + + + + ) +}) + +export default DepositsOutlet diff --git a/src/components/outlets/index.ts b/src/components/outlets/index.ts index 77c9a70..3542fbb 100644 --- a/src/components/outlets/index.ts +++ b/src/components/outlets/index.ts @@ -1 +1,2 @@ +export * from './DepositsOutlet' export * from './UserOutlet' diff --git a/src/components/selectors/WellSelector.jsx b/src/components/selectors/WellSelector.jsx index ba448be..da7d95c 100755 --- a/src/components/selectors/WellSelector.jsx +++ b/src/components/selectors/WellSelector.jsx @@ -1,12 +1,11 @@ import { Tag, TreeSelect } from 'antd' import { memo, useEffect, useState } from 'react' +import { useDeposits } from '@asb/context' import { invokeWebApiWrapperAsync } from '@components/factory' import { hasPermission } from '@utils' -import { DepositService } from '@api' -export const getTreeData = async () => { - const deposits = await DepositService.getDeposits() +export const getTreeData = async (deposits) => { const wellsTree = deposits.map((deposit, dIdx) => ({ title: deposit.caption, key: `0-${dIdx}`, @@ -40,10 +39,12 @@ export const WellSelector = memo(({ value, onChange, treeData, treeLabels, ...ot const [wellsTree, setWellsTree] = useState([]) const [wellLabels, setWellLabels] = useState([]) + const deposits = useDeposits() + useEffect(() => { invokeWebApiWrapperAsync( async () => { - const wellsTree = treeData ?? await getTreeData() + const wellsTree = treeData ?? await getTreeData(deposits) const labels = treeLabels ?? getTreeLabels(wellsTree) setWellsTree(wellsTree) setWellLabels(labels) @@ -52,7 +53,7 @@ export const WellSelector = memo(({ value, onChange, treeData, treeLabels, ...ot 'Не удалось загрузить список скважин', { actionName: 'Получение списка скважин' } ) - }, [treeData, treeLabels]) + }, [deposits, treeData, treeLabels]) return ( deposits.map(deposit =>({ + title: deposit.caption, + key: `/deposit/${deposit.id}`, + value: `/deposit/${deposit.id}`, + icon: , + children: deposit.clusters?.map(cluster => { + const wells = cluster.wells ? cluster.wells.slice() : [] + wells.sort(sortWellsByActive) + + return { + title: cluster.caption, + key: `/cluster/${cluster.id}`, + value: `/cluster/${cluster.id}`, + icon: , + children: wells.map(well => ({ + title: well.caption, + key: `/well/${well.id}`, + value: `/well/${well.id}`, + icon: + })), + } + }), +})) + export const WellTreeSelector = memo(({ expand, current, onChange, onClose, open, ...other }) => { - const [wellsTree, setWellsTree] = useState([]) - const [showLoader, setShowLoader] = useState(false) const [expanded, setExpanded] = useState([]) const [selected, setSelected] = useState([]) const navigate = useNavigate() const location = useLocation() + const deposits = useDeposits() - useEffect(() => { - if (current) setSelected([current]) - }, [current]) - - useEffect(() => { - setExpanded((prev) => expand ? getExpandKeys(wellsTree, expand) : prev) - }, [wellsTree, expand]) - - useEffect(() => { - invokeWebApiWrapperAsync( - async () => { - const deposits: Array = await DepositService.getDeposits() - const wellsTree: TreeDataNode[] = deposits.map(deposit =>({ - title: deposit.caption, - key: `/deposit/${deposit.id}`, - value: `/deposit/${deposit.id}`, - icon: , - children: deposit.clusters?.map(cluster => { - const wells = cluster.wells ? cluster.wells.slice() : [] - wells.sort(sortWellsByActive) - - return { - title: cluster.caption, - key: `/cluster/${cluster.id}`, - value: `/cluster/${cluster.id}`, - icon: , - children: wells.map(well => ({ - title: well.caption, - key: `/well/${well.id}`, - value: `/well/${well.id}`, - icon: - })), - } - }), - })) - setWellsTree(wellsTree) - }, - setShowLoader, - `Не удалось загрузить список скважин`, - { actionName: 'Получить список скважин' } - ) - }, []) + const wellsTree = useMemo(() => makeWellsTreeData(deposits), [deposits]) const onValueChange = useCallback((value?: string): void => { const key = getKeyByUrl(value)[0] @@ -169,21 +151,27 @@ export const WellTreeSelector = memo(({ expand, current, navigate(newPath, { state: { from: location.pathname }}) }, [navigate, location]) - useEffect(() => onValueChange(location.pathname), [onValueChange, location]) + useEffect(() => { + if (current) setSelected([current]) + }, [current]) + + useEffect(() => { + setExpanded((prev) => expand ? getExpandKeys(wellsTree, expand) : prev) + }, [wellsTree, expand]) + + useEffect(() => onValueChange(location.pathname), [onValueChange, location.pathname]) return ( - - - + ) }) diff --git a/src/context.ts b/src/context.ts index bd186a5..d719031 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,7 +1,7 @@ import { createContext, useContext, useEffect } from 'react' import { LayoutPortalProps } from '@components/LayoutPortal' -import { UserTokenDto, WellDto } from '@api' +import { DepositDto, UserTokenDto, WellDto } from '@api' /** Контекст текущей скважины */ export const WellContext = createContext<[WellDto, (well: WellDto) => void]>([{}, () => {}]) @@ -13,6 +13,8 @@ export const UserContext = createContext({}) export const LayoutPropsContext = createContext<(props: LayoutPortalProps) => void>(() => {}) /** Контекст для блока справа от крошек на страницах скважин и админки */ export const TopRightBlockContext = createContext<(block: JSX.Element) => void>(() => {}) +/** Контекст со списком месторождений */ +export const DepositsContext = createContext([]) /** * Получить текущую скважину @@ -29,19 +31,31 @@ export const useWell = () => useContext(WellContext) export const useRootPath = () => useContext(RootPathContext) /** - * Получить текущего пользователя - * - * @returns Текущий пользователь, либо `null` - */ + * Получить текущего пользователя + * + * @returns Текущий пользователь, либо `null` + */ export const useUser = () => useContext(UserContext) +/** + * Получить список скважин + * + * @returns Список скважин + */ +export const useDeposits = () => useContext(DepositsContext) + +/** + * Получить метод задания элементов справа от крошек + * + * @returns Метод задания элементов справа от крошек + */ export const useTopRightBlock = () => useContext(TopRightBlockContext) /** - * Получить метод задания параметров заголовка и меню - * - * @returns Получить метод задания параметров заголовка и меню - */ + * Получить метод задания параметров заголовка и меню + * + * @returns Получить метод задания параметров заголовка и меню + */ export const useLayoutProps = (props?: LayoutPortalProps) => { const setLayoutProps = useContext(LayoutPropsContext) diff --git a/src/pages/Deposit.jsx b/src/pages/Deposit.jsx index 9779e78..f0eb9c3 100755 --- a/src/pages/Deposit.jsx +++ b/src/pages/Deposit.jsx @@ -1,120 +1,106 @@ -import { useState, useEffect, memo, useMemo } from 'react' +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 { useLayoutProps } from '@asb/context' +import { useDeposits, 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' +import { limitValue, withPermissions } from '@utils' import '@styles/index.css' -const defaultViewParams = { center: [60.81226, 70.0562], zoom: 5 } - const zoomLimit = limitValue(5, 15) const calcViewParams = (clusters) => { - if ((clusters?.length ?? 0) <= 0) - return defaultViewParams + if ((clusters?.length ?? 0) <= 0) + return { center: [60.81226, 70.0562], zoom: 5 } - const center = clusters.reduce((sum, cluster) => { - sum[0] += (cluster.latitude / clusters.length) - sum[1] += (cluster.longitude / clusters.length) - return sum - }, [0, 0]) + 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) - const d = dLatitude > dLongitude ? dLatitude : dLongitude - return d > max ? d : max - }, 0) + 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)) + // 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 } + return { center, zoom } } const Deposit = memo(() => { - const [depositsData, setDepositsData] = useState([]) - const [showLoader, setShowLoader] = useState(false) - const [viewParams, setViewParams] = useState(defaultViewParams) + const deposits = useDeposits() + const setLayoutProps = useLayoutProps() + const location = useLocation() - const setLayoutProps = useLayoutProps() - - const location = useLocation() - - const selectorProps = useMemo(() => { - const hasId = location.pathname.length > '/deposit/'.length - - return { - expand: hasId ? [location.pathname] : true, - current: hasId ? location.pathname : undefined, - } - }, [location.pathname]) - - useEffect(() => setLayoutProps({ - sheet: false, - showSelector: true, - selectorProps, - title: 'Месторождение', - }), [setLayoutProps, selectorProps]) - - useEffect(() => { - invokeWebApiWrapperAsync( - async () => { - const deposits = await DepositService.getDeposits() - setDepositsData(arrayOrDefault(deposits)) - setViewParams(calcViewParams(deposits)) - }, - setShowLoader, - `Не удалось загрузить список кустов`, - { actionName: 'Получить список кустов' } - ) - }, []) - - return ( - <> - - -
- - {depositsData.map(deposit => ( - - - {deposit.clusters.map(cluster => ( - -
{cluster.caption}
- - ))} -
- } trigger={['click']} title={deposit.caption}> -
- - - -
- - + 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/styles/index.css b/src/styles/index.css index c2367c8..217ba16 100755 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -135,7 +135,7 @@ code { -moz-user-select: none; /* Old versions of Firefox */ -ms-user-select: none; /* Internet Explorer/Edge */ user-select: none; /* Non-prefixed version, currently - supported by Chrome, Edge, Opera and Firefox */ + supported by Chrome, Edge, Opera and Firefox */ } .download-link { @@ -157,4 +157,13 @@ code { .asb-grid-item { padding: 4px; -} \ No newline at end of file +} + +.pointer { + cursor: pointer; +} + +.deposit-page { + height: 100vh; + overflow: hidden; +}