diff --git a/src/components/LayoutPortal.tsx b/src/components/LayoutPortal.tsx index 052eae4..909172e 100644 --- a/src/components/LayoutPortal.tsx +++ b/src/components/LayoutPortal.tsx @@ -1,4 +1,4 @@ -import { Button, Layout, LayoutProps, Menu, SiderProps } from 'antd' +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' @@ -6,19 +6,18 @@ import { ApartmentOutlined, CodeOutlined, HomeOutlined, - MenuFoldOutlined, - MenuUnfoldOutlined, UserOutlined, } from '@ant-design/icons' import { LayoutPropsContext } from '@asb/context' -import PageHeader from '@components/PageHeader' import { UserMenu, UserMenuProps } from '@components/UserMenu' import { WellTreeSelector, WellTreeSelectorProps } from '@components/selectors/WellTreeSelector' import { isURLAvailable, wrapPrivateComponent } from '@utils' import SuspenseFallback from './SuspenseFallback' +import Logo from '@images/Logo' + import '@styles/layout.less' const { Content, Sider } = Layout @@ -32,6 +31,8 @@ export type LayoutPortalProps = Omit & { siderProps?: SiderProps & { userMenuProps?: UserMenuProps } isAdmin?: boolean fallback?: JSX.Element + breadcrumb?: boolean | JSX.Element + topRightBlock?: JSX.Element } const defaultProps: LayoutPortalProps = { @@ -49,7 +50,7 @@ const _LayoutPortal = memo(() => { const [currentWell, setCurrentWell] = useState('') const [props, setProps] = useState(defaultProps) - const { isAdmin, title, sheet, showSelector, selectorProps, sider, siderProps, fallback, ...other } = useMemo(() => props, [props]) + const { isAdmin, title, sheet, showSelector, selectorProps, sider, siderProps, fallback, breadcrumb, topRightBlock, ...other } = useMemo(() => props, [props]) const setLayoutProps = useCallback((props: LayoutPortalProps) => setProps({ ...defaultProps, ...props}), []) @@ -71,7 +72,7 @@ const _LayoutPortal = memo(() => {
{sider} @@ -91,25 +92,34 @@ const _LayoutPortal = memo(() => {
)} + {!isAdmin && ( + setWellsTreeOpen(false)} + {...selectorProps} + onChange={(well) => setCurrentWell(well ?? 'Выберите месторождение')} + /> + )} - - {isAdmin ? ( - - ) : ( - <> - setWellsTreeOpen(false)} - {...selectorProps} - onChange={(well) => setCurrentWell(well ?? 'Выберите месторождение')} - /> - - - )} - + {(breadcrumb || topRightBlock) && ( +
+ {breadcrumb && ( + + + + + {!isAdmin && ( + + setWellsTreeOpen((prev) => !prev)}>{currentWell} + + )} + {breadcrumb} + + )} + {topRightBlock} +
+ )} }> @@ -121,8 +131,6 @@ const _LayoutPortal = memo(() => { ) }) -export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, { - requirements: ['Deposit.get'], -}) +export const LayoutPortal = wrapPrivateComponent(_LayoutPortal, { requirements: ['Deposit.get'] }) export default LayoutPortal diff --git a/src/components/MenuBreadcrumb.tsx b/src/components/MenuBreadcrumb.tsx new file mode 100644 index 0000000..05cbc74 --- /dev/null +++ b/src/components/MenuBreadcrumb.tsx @@ -0,0 +1,56 @@ +import { Breadcrumb, BreadcrumbItemProps } from 'antd' +import { Link, useLocation } from 'react-router-dom' +import { memo, useMemo } from 'react' +import { join } from 'path' + +import { PrivateWellMenuItem } from '@components/PrivateWellMenu' +import { FunctionalValue, useFunctionalValue } from '@utils' + +export const makeBreadcrumbItems = (items: PrivateWellMenuItem[], pathParts: string[], root: string = '/') => { + const out = [] + const parts = [...pathParts] + let route = root + let arr: PrivateWellMenuItem[] | undefined = items + while (arr && parts.length > 0) { + const child: PrivateWellMenuItem | undefined = arr.find(elm => elm.route.toLowerCase() === parts[0].toLowerCase()) + if (!child) break + route = join(route, child.route) + out.push({ ...child, route }) + parts.splice(0, 1) + arr = child.children + } + return out +} + +export type MenuBreadcrumbItemsProps = { + menuItems: PrivateWellMenuItem[] + pathRoot?: RegExp + itemsProps?: FunctionalValue<(item: PrivateWellMenuItem) => BreadcrumbItemProps> + itemRender?: (item: PrivateWellMenuItem) => JSX.Element +} + +export const MenuBreadcrumbItems = memo(({ menuItems, pathRoot = /^\//, itemsProps, itemRender }) => { + const location = useLocation() + const getItemProps = useFunctionalValue(itemsProps) + + const items = useMemo(() => { + const path = location.pathname + const rootPart = pathRoot.exec(path) + if (!rootPart || rootPart.length <= 0) return [] + const root = rootPart[0] + const parts = path.trim().slice(root.length).split('/') + return makeBreadcrumbItems(menuItems, parts, root) + }, [location, menuItems, pathRoot]) + + return ( + <> + {items.map((item) => ( + + {itemRender ? itemRender(item) : ( + {item.title} + )} + + ))} + + ) +}) diff --git a/src/components/PageHeader.tsx b/src/components/PageHeader.tsx deleted file mode 100755 index 2b3fd4c..0000000 --- a/src/components/PageHeader.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { memo } from 'react' -import { Layout } from 'antd' -import { Link } from 'react-router-dom' -import { BasicProps } from 'antd/lib/layout/layout' - -import { headerHeight } from '@utils' - -import Logo from '@images/Logo' - -import '@styles/layout.less' - -export type PageHeaderProps = BasicProps & { - title?: string - children?: React.ReactNode -} - -export const PageHeader: React.FC = memo(({ title, children, ...other }) => ( - - - - -

{title}

- {children} -
-)) - -export default PageHeader diff --git a/src/styles/index.css b/src/styles/index.css index a4c71d3..53d4e5d 100755 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -72,7 +72,7 @@ body { } .h-100vh { - height: calc(100vh - 64px); + height: 100vh; } .p-10 { diff --git a/src/styles/layout.less b/src/styles/layout.less index 753925a..7cad243 100644 --- a/src/styles/layout.less +++ b/src/styles/layout.less @@ -1,5 +1,7 @@ -@header-height: 64px; -@layout-min-height: calc(100vh - @header-height); +@link-color: #FFFC; +@active-bg: #c32828; +@admin-bg: #900; +@admin-active-bg: #413f3d; .no-select { -webkit-user-select: none; @@ -8,38 +10,6 @@ user-select: none; } -.header { - display: flex; - align-items: center; - justify-content: space-around; - gap: 50px; - height: @header-height; - - & .logo { - padding: 8px 24px; - margin: 0 10px; - margin-bottom: 2px; - } - - & .title { - flex-grow: 1; - color: #fff; - padding-left: calc(100vw / 2 - 400px); - .no-select; - } - - & button { - color: rgb(255, 255, 255); - background-color: rgba(0, 0, 0, 0.1); - border: 1px solid rgba(0, 0, 0, 0.2); - } -} - -@link-color: #FFFC; -@active-bg: #c32828; -@admin-bg: #900; -@admin-active-bg: #413f3d; - .ant-menu-submenu-popup .ant-menu-sub { color: @link-color; @@ -88,12 +58,14 @@ justify-content: center; align-items: center; width: 100%; - height: calc(@header-height - 4px) !important; + // padding: 10px; + height: 100px !important; color: @link-color; border: none; outline: none; background-color: transparent; cursor: pointer; + overflow: hidden; &:hover { color: white; @@ -115,6 +87,19 @@ & .page-content { overflow-y: auto; background: white; + + & .breadcrumb-block { + display: flex; + justify-content: space-between; + align-items: center; + gap: 15px; + padding: 0 15px 15px 15px; + + & .ant-breadcrumb-link, .ant-breadcrumb-separator { + .no-select; + } + } + } & .sheet{