forked from ddrilling/asb_cloud_front
Мемоизированно больше страниц и функций, добавлена переадресация со старницы входа на предыдущую, косметика
This commit is contained in:
parent
8f451035f7
commit
dca17e31e8
@ -15,12 +15,13 @@ import Login from '@pages/Login'
|
||||
import Register from '@pages/Register'
|
||||
|
||||
import '@styles/App.less'
|
||||
import { memo } from 'react'
|
||||
|
||||
//OpenAPI.BASE = 'http://localhost:3000'
|
||||
OpenAPI.TOKEN = async () => getUserToken()
|
||||
OpenAPI.HEADERS = {'Content-Type': 'application/json'}
|
||||
|
||||
export const App = () => (
|
||||
export const App = memo(() => (
|
||||
<ConfigProvider locale={locale}>
|
||||
<Router>
|
||||
<Switch>
|
||||
@ -36,6 +37,6 @@ export const App = () => (
|
||||
</Switch>
|
||||
</Router>
|
||||
</ConfigProvider>
|
||||
)
|
||||
))
|
||||
|
||||
export default App
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { memo, ReactNode } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Button, Layout, LayoutProps } from 'antd'
|
||||
|
||||
import PageHeader from '@components/PageHeader'
|
||||
@ -8,17 +8,21 @@ export type AdminLayoutPortalProps = LayoutProps & {
|
||||
title?: ReactNode
|
||||
}
|
||||
|
||||
export const AdminLayoutPortal = memo<AdminLayoutPortalProps>(({ title, ...props }) => (
|
||||
<Layout.Content>
|
||||
<PageHeader isAdmin title={title} style={{ backgroundColor: '#900' }}>
|
||||
<Button size={'large'}>
|
||||
<Link to={'/'}>Вернуться на сайт</Link>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Layout>
|
||||
<Layout.Content className={'site-layout-background sheet'} {...props}/>
|
||||
</Layout>
|
||||
</Layout.Content>
|
||||
))
|
||||
export const AdminLayoutPortal = memo<AdminLayoutPortalProps>(({ title, ...props }) => {
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Layout.Content>
|
||||
<PageHeader isAdmin title={title} style={{ backgroundColor: '#900' }}>
|
||||
<Button size={'large'}>
|
||||
<Link to={{ pathname: '/', state: { from: location.pathname }}}>Вернуться на сайт</Link>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<Layout>
|
||||
<Layout.Content className={'site-layout-background sheet'} {...props}/>
|
||||
</Layout>
|
||||
</Layout.Content>
|
||||
)
|
||||
})
|
||||
|
||||
export default AdminLayoutPortal
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { HTMLAttributes } from 'react'
|
||||
|
||||
import Loader from './Loader'
|
||||
import { Loader } from '@components/icons'
|
||||
|
||||
type LoaderPortalProps = HTMLAttributes<HTMLDivElement> & {
|
||||
show?: boolean,
|
||||
fade?: boolean,
|
||||
spinnerProps?: HTMLAttributes<HTMLDivElement>,
|
||||
}
|
||||
|
||||
export const LoaderPortal: React.FC<LoaderPortalProps> = ({ className, show, fade = true, children, ...other }) => (
|
||||
export const LoaderPortal: React.FC<LoaderPortalProps> = ({ className, show, fade = true, children, spinnerProps, ...other }) => (
|
||||
<div className={`loader-container ${className}`} {...other}>
|
||||
<div className={'loader-content'}>{children}</div>
|
||||
{show && fade && <div className={'loader-fade'}/>}
|
||||
{show && <div className={'loader-overlay'}><Loader/></div>}
|
||||
{show && <div className={'loader-overlay'}><Loader {...spinnerProps} /></div>}
|
||||
</div>
|
||||
)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { memo } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Layout } from 'antd'
|
||||
import { BasicProps } from 'antd/lib/layout/layout'
|
||||
|
||||
@ -14,17 +14,21 @@ export type PageHeaderProps = BasicProps & {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const PageHeader: React.FC<PageHeaderProps> = memo(({ title = 'Мониторинг', isAdmin, children, ...other }) => (
|
||||
<Layout>
|
||||
<Layout.Header className={'header'} {...other}>
|
||||
<Link to={'/'} style={{ height: headerHeight }}>
|
||||
<Logo />
|
||||
</Link>
|
||||
{children}
|
||||
<h1 className={'title'}>{title}</h1>
|
||||
<UserMenu isAdmin={isAdmin} />
|
||||
</Layout.Header>
|
||||
</Layout>
|
||||
))
|
||||
export const PageHeader: React.FC<PageHeaderProps> = memo(({ title = 'Мониторинг', isAdmin, children, ...other }) => {
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Header className={'header'} {...other}>
|
||||
<Link to={{ pathname: '/', state: { from: location.pathname }}} style={{ height: headerHeight }}>
|
||||
<Logo />
|
||||
</Link>
|
||||
{children}
|
||||
<h1 className={'title'}>{title}</h1>
|
||||
<UserMenu isAdmin={isAdmin} />
|
||||
</Layout.Header>
|
||||
</Layout>
|
||||
)
|
||||
})
|
||||
|
||||
export default PageHeader
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { memo, ReactNode, useState } from 'react'
|
||||
import { memo, ReactNode, useCallback, useState } from 'react'
|
||||
import { Button, ButtonProps, Form, FormProps, Popover, PopoverProps } from 'antd'
|
||||
|
||||
export type PopromptProps = PopoverProps & {
|
||||
@ -13,10 +13,10 @@ export type PopromptProps = PopoverProps & {
|
||||
export const Poprompt = memo<PopromptProps>(({ formProps, buttonProps, footer, children, onDone, text, ...other }) => {
|
||||
const [visible, setVisible] = useState<boolean>(false)
|
||||
|
||||
const onFormFinish = (values: any) => {
|
||||
const onFormFinish = useCallback((values: any) => {
|
||||
setVisible(false)
|
||||
onDone?.(values)
|
||||
}
|
||||
}, [onDone])
|
||||
|
||||
return (
|
||||
<Popover
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React from 'react'
|
||||
import { memo, ReactElement } from 'react'
|
||||
|
||||
import { isURLAvailable } from '@utils/permissions'
|
||||
|
||||
export type PrivateContentProps = {
|
||||
absolutePath: string
|
||||
children?: React.ReactElement<any, any>
|
||||
children?: ReactElement<any, any>
|
||||
}
|
||||
|
||||
export const PrivateContent: React.FC<PrivateContentProps> = ({ absolutePath, children = null }) =>
|
||||
export const PrivateContent = memo<PrivateContentProps>(({ absolutePath, children = null }) =>
|
||||
isURLAvailable(absolutePath) ? children : null
|
||||
)
|
||||
|
||||
export default PrivateContent
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { memo } from 'react'
|
||||
import { Redirect, Route, RouteProps } from 'react-router-dom'
|
||||
import { Redirect, Route, RouteProps, useLocation } from 'react-router-dom'
|
||||
|
||||
import { getUserId } from '@utils/storage'
|
||||
import { isURLAvailable } from '@utils/permissions'
|
||||
@ -9,10 +9,17 @@ export type PrivateDefaultRouteProps = RouteProps & {
|
||||
elseRedirect?: string
|
||||
}
|
||||
|
||||
export const PrivateDefaultRoute = memo<PrivateDefaultRouteProps>(({ elseRedirect, urls, ...other }) => (
|
||||
<Route {...other} path={'/'}>
|
||||
<Redirect to={{ pathname: urls.find((url) => isURLAvailable(url)) ?? elseRedirect ?? (getUserId() ? '/access_denied' : '/login') }} />
|
||||
</Route>
|
||||
))
|
||||
export const PrivateDefaultRoute = memo<PrivateDefaultRouteProps>(({ elseRedirect, urls, ...other }) => {
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Route {...other} path={'/'}>
|
||||
<Redirect to={{
|
||||
pathname: urls.find((url) => isURLAvailable(url)) ?? elseRedirect ?? (getUserId() ? '/access_denied' : '/login'),
|
||||
state: { from: location.pathname },
|
||||
}} />
|
||||
</Route>
|
||||
)
|
||||
})
|
||||
|
||||
export default PrivateDefaultRoute
|
||||
|
@ -3,7 +3,7 @@ import { Menu, MenuItemProps } from 'antd'
|
||||
import { memo, NamedExoticComponent } from 'react'
|
||||
|
||||
import { isURLAvailable } from '@utils/permissions'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
export type PrivateMenuItemProps = MenuItemProps & {
|
||||
root: string
|
||||
@ -16,11 +16,14 @@ export type PrivateMenuLinkProps = MenuItemProps & {
|
||||
title: string
|
||||
}
|
||||
|
||||
export const PrivateMenuItemLink = memo<PrivateMenuLinkProps>(({ root = '', path, title, ...other }) => (
|
||||
<PrivateMenuItem key={path} root={root} path={path} {...other}>
|
||||
<Link to={join(root, path)}>{title}</Link>
|
||||
</PrivateMenuItem>
|
||||
))
|
||||
export const PrivateMenuItemLink = memo<PrivateMenuLinkProps>(({ root = '', path, title, ...other }) => {
|
||||
const location = useLocation()
|
||||
return (
|
||||
<PrivateMenuItem key={path} root={root} path={path} {...other}>
|
||||
<Link to={{ pathname: join(root, path), state: { from: location.pathname }}}>{title}</Link>
|
||||
</PrivateMenuItem>
|
||||
)
|
||||
})
|
||||
|
||||
export const PrivateMenuItem: NamedExoticComponent<PrivateMenuItemProps> & {
|
||||
Link: NamedExoticComponent<PrivateMenuLinkProps>
|
||||
|
@ -14,7 +14,7 @@ export type PrivateRouteProps = RouteProps & {
|
||||
}
|
||||
|
||||
export const defaultRedirect = (location?: Location<unknown>) => (
|
||||
<Redirect to={{ pathname: getUserId() ? '/access_denied' : '/login', state: { from: location } }} />
|
||||
<Redirect to={{ pathname: getUserId() ? '/access_denied' : '/login', state: { from: location?.pathname } }} />
|
||||
)
|
||||
|
||||
export const PrivateRoute = memo<PrivateRouteProps>(({ root = '', path, component, children, redirect = defaultRedirect, ...other }) => {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { memo } from 'react'
|
||||
import { Form, Input } from 'antd'
|
||||
import { NamePath, Rule } from 'rc-field-form/lib/interface'
|
||||
|
||||
type EditableCellProps = {
|
||||
type EditableCellProps = React.DetailedHTMLProps<React.TdHTMLAttributes<HTMLTableDataCellElement>, HTMLTableDataCellElement> & {
|
||||
editing?: boolean
|
||||
dataIndex?: NamePath
|
||||
input?: React.Component
|
||||
@ -13,7 +14,7 @@ type EditableCellProps = {
|
||||
initialValue: any
|
||||
}
|
||||
|
||||
export const EditableCell = ({
|
||||
export const EditableCell = memo<EditableCellProps>(({
|
||||
editing,
|
||||
dataIndex,
|
||||
input,
|
||||
@ -22,8 +23,9 @@ export const EditableCell = ({
|
||||
formItemRules,
|
||||
children,
|
||||
initialValue,
|
||||
}: EditableCellProps) => (
|
||||
<td style={editing ? { padding: 0 } : undefined}>
|
||||
...other
|
||||
}) => (
|
||||
<td style={editing ? { padding: 0 } : undefined} {...other}>
|
||||
{!editing ? children : (
|
||||
<Form.Item
|
||||
name={dataIndex}
|
||||
@ -39,4 +41,4 @@ export const EditableCell = ({
|
||||
</Form.Item>
|
||||
)}
|
||||
</td>
|
||||
)
|
||||
))
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Form, Table, Button, Popconfirm } from 'antd'
|
||||
import { EditOutlined, SaveOutlined, PlusOutlined, CloseCircleOutlined, DeleteOutlined } from '@ant-design/icons'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, memo, useCallback } from 'react'
|
||||
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
|
||||
@ -42,7 +42,7 @@ export const tryAddKeys = (items) => {
|
||||
return items.map((item, index) => ({...item, key: item.key ?? item.id ?? index }))
|
||||
}
|
||||
|
||||
export const EditableTable = ({
|
||||
export const EditableTable = memo(({
|
||||
columns,
|
||||
dataSource,
|
||||
onChange, // Метод вызывается со всем dataSource с измененными элементами после любого действия
|
||||
@ -62,14 +62,14 @@ export const EditableTable = ({
|
||||
setData(tryAddKeys(dataSource))
|
||||
}, [dataSource])
|
||||
|
||||
const isEditing = (record) => record?.key === editingKey
|
||||
const isEditing = useCallback((record) => record?.key === editingKey, [editingKey])
|
||||
|
||||
const edit = (record) => {
|
||||
const edit = useCallback((record) => {
|
||||
form.setFieldsValue({...record})
|
||||
setEditingKey(record.key)
|
||||
}
|
||||
}, [form])
|
||||
|
||||
const cancel = () => {
|
||||
const cancel = useCallback(() => {
|
||||
if (editingKey === newRowKeyValue) {
|
||||
const newData = [...data]
|
||||
const index = newData.findIndex((item) => newRowKeyValue === item.key)
|
||||
@ -77,9 +77,9 @@ export const EditableTable = ({
|
||||
setData(newData)
|
||||
}
|
||||
setEditingKey('')
|
||||
}
|
||||
}, [data, editingKey])
|
||||
|
||||
const addNewRow = async () => {
|
||||
const addNewRow = useCallback(async () => {
|
||||
let newRow = {
|
||||
...form.initialValues,
|
||||
key:newRowKeyValue
|
||||
@ -88,9 +88,9 @@ export const EditableTable = ({
|
||||
const newData = [newRow, ...data]
|
||||
setData(newData)
|
||||
edit(newRow)
|
||||
}
|
||||
}, [data, edit, form.initialValues])
|
||||
|
||||
const save = async (record) => {
|
||||
const save = useCallback(async (record) => {
|
||||
try {
|
||||
const row = await form.validateFields()
|
||||
const newData = [...data]
|
||||
@ -121,8 +121,7 @@ export const EditableTable = ({
|
||||
}
|
||||
|
||||
try {
|
||||
if (onChange)
|
||||
onChange(newData)
|
||||
onChange?.(newData)
|
||||
} catch (err) {
|
||||
console.log('callback onChange fault:', err)
|
||||
}
|
||||
@ -130,9 +129,9 @@ export const EditableTable = ({
|
||||
} catch (errInfo) {
|
||||
console.log('Validate Failed:', errInfo)
|
||||
}
|
||||
}
|
||||
}, [data, editingKey, form, onChange, onRowAdd, onRowEdit])
|
||||
|
||||
const deleteRow = (record) =>{
|
||||
const deleteRow = useCallback((record) => {
|
||||
const newData = [...data]
|
||||
const index = newData.findIndex((item) => record.key === item.key)
|
||||
|
||||
@ -140,10 +139,8 @@ export const EditableTable = ({
|
||||
setData(newData)
|
||||
|
||||
onRowDelete(record)
|
||||
|
||||
if (onChange)
|
||||
onChange(newData)
|
||||
}
|
||||
onChange?.(newData)
|
||||
}, [data, onChange, onRowDelete])
|
||||
|
||||
const operationColumn = {
|
||||
width: buttonsWidth ?? 82,
|
||||
@ -180,7 +177,7 @@ export const EditableTable = ({
|
||||
),
|
||||
}
|
||||
|
||||
const handleColumn = (col) => {
|
||||
const handleColumn = useCallback((col) => {
|
||||
if (col.children)
|
||||
col.children = col.children.map(handleColumn)
|
||||
|
||||
@ -197,13 +194,13 @@ export const EditableTable = ({
|
||||
input: col.input,
|
||||
isRequired: col.isRequired,
|
||||
title: col.title,
|
||||
dataType: col.dataType,
|
||||
datatype: col.datatype,
|
||||
formItemClass: col.formItemClass,
|
||||
formItemRules: col.formItemRules,
|
||||
initialValue: col.initialValue,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}, [isEditing])
|
||||
|
||||
const mergedColumns = [...columns.map(handleColumn), operationColumn]
|
||||
|
||||
@ -221,4 +218,4 @@ export const EditableTable = ({
|
||||
/>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { memo, useEffect, useState, ReactNode } from 'react'
|
||||
import { InputNumber, Select, Table as RawTable, Tag, SelectProps, TableProps } from 'antd'
|
||||
import { SelectValue } from 'antd/lib/select'
|
||||
import { OptionsType } from 'rc-select/lib/interface'
|
||||
import { DefaultOptionType, SelectValue } from 'antd/lib/select'
|
||||
import { Rule } from 'rc-field-form/lib/interface'
|
||||
|
||||
import { tryAddKeys } from './EditableTable'
|
||||
@ -210,7 +209,7 @@ export const makeNumericAvgRange = (
|
||||
export const makeSelectColumn = <T extends unknown = string>(
|
||||
title: string,
|
||||
dataIndex: string,
|
||||
options: OptionsType,
|
||||
options: DefaultOptionType[],
|
||||
defaultValue?: T,
|
||||
other?: columnPropsOther,
|
||||
selectOther?: SelectProps<SelectValue>
|
||||
@ -229,7 +228,7 @@ const makeTagInput = <T extends Record<string, any>>(value_key: string, label_ke
|
||||
onChange?: (values: T[]) => void,
|
||||
other?: SelectProps<SelectValue>,
|
||||
}>(({ options, value, onChange, other }) => {
|
||||
const [selectOptions, setSelectOptions] = useState<OptionsType>([])
|
||||
const [selectOptions, setSelectOptions] = useState<DefaultOptionType[]>([])
|
||||
const [selectedValue, setSelectedValue] = useState<SelectValue>([])
|
||||
|
||||
useEffect(() => {
|
||||
@ -307,6 +306,6 @@ export type TableContainer = TableProps<any> & {
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export const Table = ({dataSource, children, ...other}: TableContainer) => (
|
||||
export const Table = memo<TableContainer>(({dataSource, children, ...other}) => (
|
||||
<RawTable dataSource={tryAddKeys(dataSource)} {...other}>{children}</RawTable>
|
||||
)
|
||||
))
|
||||
|
@ -1,6 +1,8 @@
|
||||
export const makeNumericSorter = (key: string) => (a: any, b: any) => Number(a[key]) - Number(b[key])
|
||||
import { RawDate } from "@asb/utils"
|
||||
|
||||
export const makeStringSorter = (key: string) => (a: any, b: any) => {
|
||||
export const makeNumericSorter = (key: string) => (a: Record<string, number>, b: Record<string, number>) => Number(a[key]) - Number(b[key])
|
||||
|
||||
export const makeStringSorter = (key: string) => (a: Record<string, string> | null | undefined, b: Record<string, string> | null | undefined) => {
|
||||
if (!a && !b) return 0
|
||||
if (!a) return 1
|
||||
if (!b) return -1
|
||||
@ -8,7 +10,7 @@ export const makeStringSorter = (key: string) => (a: any, b: any) => {
|
||||
return ('' + a[key]).localeCompare(b[key])
|
||||
}
|
||||
|
||||
export const makeDateSorter = (key: string) => (a: any, b: any) => {
|
||||
export const makeDateSorter = (key: string) => (a: Record<string, RawDate>, b: Record<string, RawDate>) => {
|
||||
const date = new Date(a[key])
|
||||
|
||||
if (Number.isNaN(date.getTime()))
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { memo, useState } from 'react'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Upload, Button } from 'antd'
|
||||
import { UploadOutlined } from '@ant-design/icons'
|
||||
|
||||
@ -8,7 +8,7 @@ import { ErrorFetch } from './ErrorFetch'
|
||||
export const UploadForm = memo(({ url, disabled, accept, style, formData, onUploadStart, onUploadSuccess, onUploadComplete, onUploadError }) => {
|
||||
const [fileList, setfileList] = useState([])
|
||||
|
||||
const handleFileSend = async () => {
|
||||
const handleFileSend = useCallback(async () => {
|
||||
onUploadStart?.()
|
||||
try {
|
||||
const formDataLocal = new FormData()
|
||||
@ -34,7 +34,7 @@ export const UploadForm = memo(({ url, disabled, accept, style, formData, onUplo
|
||||
setfileList([])
|
||||
onUploadComplete?.()
|
||||
}
|
||||
}
|
||||
}, [fileList, formData, onUploadComplete, onUploadError, onUploadStart, onUploadSuccess, url])
|
||||
|
||||
const isSendButtonEnabled = fileList.length > 0
|
||||
return(
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { memo, MouseEventHandler, useState } from 'react'
|
||||
import { Link, useHistory } from 'react-router-dom'
|
||||
import { memo, MouseEventHandler, useCallback, useState } from 'react'
|
||||
import { Link, useHistory, useLocation } from 'react-router-dom'
|
||||
import { Button, Dropdown, DropDownProps, Menu } from 'antd'
|
||||
import { UserOutlined } from '@ant-design/icons'
|
||||
|
||||
@ -14,16 +14,17 @@ export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
||||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false)
|
||||
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
|
||||
const onChangePasswordClick: MouseEventHandler = (e) => {
|
||||
const onChangePasswordClick: MouseEventHandler = useCallback((e) => {
|
||||
setIsModalVisible(true)
|
||||
e.preventDefault()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const onChangePasswordOk = () => {
|
||||
const onChangePasswordOk = useCallback(() => {
|
||||
setIsModalVisible(false)
|
||||
history.push('/login')
|
||||
}
|
||||
history.push({ pathname: '/login', state: { from: location.pathname }})
|
||||
}, [history, location])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -33,15 +34,15 @@ export const UserMenu = memo<UserMenuProps>(({ isAdmin, ...other }) => {
|
||||
overlay={(
|
||||
<Menu style={{ textAlign: 'right' }}>
|
||||
{isAdmin ? (
|
||||
<PrivateMenuItemLink path={'/'} title={'Вернуться на сайт'}/>
|
||||
<PrivateMenuItemLink key={''} path={'/'} title={'Вернуться на сайт'}/>
|
||||
) : (
|
||||
<PrivateMenuItemLink path={'/admin'} title={'Панель администратора'}/>
|
||||
<PrivateMenuItemLink key={'admin'} path={'/admin'} title={'Панель администратора'}/>
|
||||
)}
|
||||
<Menu.Item>
|
||||
<Link to={'/'} onClick={onChangePasswordClick}>Сменить пароль</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Link to={'/login'} onClick={removeUser}>Выход</Link>
|
||||
<Link to={{ pathname: '/login', state: { from: location.pathname }}} onClick={removeUser}>Выход</Link>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
)}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { TreeSelect } from 'antd'
|
||||
import { DefaultValueType } from 'rc-tree-select/lib/interface'
|
||||
import { useState, useEffect, ReactNode } from 'react'
|
||||
import { useHistory, useRouteMatch } from 'react-router-dom'
|
||||
import { useState, useEffect, ReactNode, useCallback, memo } from 'react'
|
||||
import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'
|
||||
|
||||
import { RawValueType } from 'rc-tree-select/lib/TreeSelect'
|
||||
import { LabelInValueType } from 'rc-select/lib/Select'
|
||||
|
||||
import { isRawDate } from '@utils'
|
||||
import LoaderPortal from './LoaderPortal'
|
||||
@ -54,11 +57,12 @@ const getLabel = (wellsTree: TreeNodeData[], value?: string): string | undefined
|
||||
return value
|
||||
}
|
||||
|
||||
export const WellTreeSelector = () => {
|
||||
const [wellsTree, setWellsTree] = useState<any[]>([]) // TODO: Исправить тип (необходимо разобраться с типом value rc-select)
|
||||
export const WellTreeSelector = memo(({ ...other }) => {
|
||||
const [wellsTree, setWellsTree] = useState<TreeNodeData[]>([])
|
||||
const [showLoader, setShowLoader] = useState<boolean>(false)
|
||||
const [value, setValue] = useState<string>()
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
const routeMatch = useRouteMatch('/:route/:id')
|
||||
|
||||
useEffect(() => {
|
||||
@ -100,14 +104,15 @@ export const WellTreeSelector = () => {
|
||||
setValue(getLabel(wellsTree, routeMatch?.url))
|
||||
}, [wellsTree, routeMatch])
|
||||
|
||||
const onChange = (value: string): void => {
|
||||
const onChange = useCallback((value: string): void => {
|
||||
if (wellsTree)
|
||||
setValue(getLabel(wellsTree, value))
|
||||
}
|
||||
}, [wellsTree])
|
||||
|
||||
const onSelect = (value: string): void => {
|
||||
if (value) history.push(value)
|
||||
}
|
||||
const onSelect = useCallback((value: RawValueType | LabelInValueType): void => {
|
||||
if (['number', 'string'].includes(typeof value))
|
||||
history.push({ pathname: String(value), state: { from: location.pathname }})
|
||||
}, [history, location])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader}>
|
||||
@ -123,9 +128,10 @@ export const WellTreeSelector = () => {
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
style={{ width: '350px' }}
|
||||
{...other}
|
||||
/>
|
||||
</LoaderPortal>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default WellTreeSelector
|
||||
|
@ -3,3 +3,4 @@ export type { WellIconColors, WellIconProps, WellIconState } from './WellIcon'
|
||||
|
||||
export { PointerIcon } from './PointerIcon'
|
||||
export { WellIcon } from './WellIcon'
|
||||
export { Loader } from './Loader'
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { Tooltip, Tag, Typography, Popconfirm, Button } from 'antd'
|
||||
import { memo } from 'react'
|
||||
|
||||
import { FileMarkDto } from '@api'
|
||||
import { UserView } from './UserView'
|
||||
|
||||
const markTypes: { [id: number]: {color: string, text: string} } = {
|
||||
0: {color: 'orange', text: 'неизвестно'},
|
||||
1: {color: 'green', text: 'согласовано'},
|
||||
}
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
export type MarkViewProps = {
|
||||
mark: FileMarkDto
|
||||
onDelete?: (e?: React.MouseEvent<HTMLElement, MouseEvent>) => void
|
||||
}
|
||||
|
||||
export const MarkView = memo<MarkViewProps>(({ mark, onDelete }) => {
|
||||
const markType = markTypes[mark.idMarkType ?? 0] ?? markTypes[0]
|
||||
return <Tooltip title={<UserView user={mark.user}/>}>
|
||||
<Tag color={markType.color}>
|
||||
<Text delete={mark?.isDeleted}>
|
||||
{`${markType.text} ${new Date(mark.dateCreated ?? 0).toLocaleString()}`}
|
||||
</Text>
|
||||
{!mark?.isDeleted && (
|
||||
<Popconfirm title='Отозвать согласование?' onConfirm={onDelete}>
|
||||
<Button type='link'>x</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
})
|
@ -10,10 +10,12 @@ export type RoleViewProps = {
|
||||
}
|
||||
|
||||
export const RoleView = memo<RoleViewProps>(({ role }) => {
|
||||
if (!role) return ( <Tooltip title={'нет данных'}>-</Tooltip> )
|
||||
|
||||
const hasIncludedRoles = (role?.roles?.length && role.roles.length > 0) ? 1 : 0
|
||||
const hasPermissions = (role?.permissions?.length && role.permissions.length > 0) ? 1 : 0
|
||||
|
||||
return role ? (
|
||||
return (
|
||||
<Tooltip
|
||||
overlayInnerStyle={{ width: '400px' }}
|
||||
title={
|
||||
@ -54,7 +56,5 @@ export const RoleView = memo<RoleViewProps>(({ role }) => {
|
||||
>
|
||||
{role.caption}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title={'нет данных'}>-</Tooltip>
|
||||
)
|
||||
})
|
||||
|
@ -1,13 +1,11 @@
|
||||
export type { PermissionViewProps } from './PermissionView'
|
||||
export type { TelemetryViewProps } from './TelemetryView'
|
||||
export type { CompanyViewProps } from './CompanyView'
|
||||
export type { MarkViewProps } from './MarkView'
|
||||
export type { RoleViewProps } from './RoleView'
|
||||
export type { UserViewProps } from './UserView'
|
||||
|
||||
export { PermissionView } from './PermissionView'
|
||||
export { TelemetryView, getTelemetryLabel } from './TelemetryView'
|
||||
export { CompanyView } from './CompanyView'
|
||||
export { MarkView } from './MarkView'
|
||||
export { RoleView } from './RoleView'
|
||||
export { UserView } from './UserView'
|
20
src/index.js
20
src/index.js
@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './styles/index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
import reportWebVitals from './reportWebVitals'
|
||||
|
||||
ReactDOM.render(
|
||||
import '@styles/index.css'
|
||||
|
||||
ReactDOM.render((
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
</React.StrictMode>
|
||||
), document.getElementById('root'))
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
reportWebVitals()
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useState, useEffect, memo } from 'react'
|
||||
import { Tag, Button, Modal } from 'antd'
|
||||
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
||||
|
||||
@ -35,7 +35,9 @@ const filtersWellsType = []
|
||||
const DAY_IN_MS = 86_400_000
|
||||
const ONLINE_DEADTIME = 600_000
|
||||
|
||||
export const ClusterWells = ({ statsWells }) => {
|
||||
const getDate = (str) => isRawDate(str) ? new Date(str).toLocaleString() : '-'
|
||||
|
||||
export const ClusterWells = memo(({ statsWells }) => {
|
||||
const [selectedWellId, setSelectedWellId] = useState(0)
|
||||
const [isTVDModalVisible, setIsTVDModalVisible] = useState(false)
|
||||
const [isOpsModalVisible, setIsOpsModalVisible] = useState(false)
|
||||
@ -43,6 +45,8 @@ export const ClusterWells = ({ statsWells }) => {
|
||||
const [tableData, setTableData] = useState([])
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpsModalVisible || selectedWellId <= 0) {
|
||||
setWellOperations([])
|
||||
@ -104,12 +108,10 @@ export const ClusterWells = ({ statsWells }) => {
|
||||
setTableData(data)
|
||||
}, [statsWells])
|
||||
|
||||
const getDate = (str) => isRawDate(str) ? new Date(str).toLocaleString() : '-'
|
||||
|
||||
const columns = [
|
||||
makeTextColumn('скв №', 'caption', null, null,
|
||||
(_, item) => (
|
||||
<Link to={`/well/${item.id}`} style={{display: 'flex', alignItems: 'center'}}>
|
||||
<Link to={{ pathname: `/well/${item.id}`, state: { from: location.pathname }}} style={{display: 'flex', alignItems: 'center'}}>
|
||||
<PointerIcon
|
||||
state={item.idState === 1 ? 'active' : 'unknown'}
|
||||
width={32}
|
||||
@ -195,6 +197,6 @@ export const ClusterWells = ({ statsWells }) => {
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default ClusterWells
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Map, Overlay } from 'pigeon-maps'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useState, useEffect, memo } from 'react'
|
||||
|
||||
import { ClusterService } from '@api'
|
||||
@ -41,6 +41,8 @@ export const Deposit = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const [viewParams, setViewParams] = useState(defaultViewParams)
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const data = await ClusterService.getClusters()
|
||||
@ -62,7 +64,7 @@ export const Deposit = memo(() => {
|
||||
anchor={[cluster.latitude, cluster.longitude]}
|
||||
key={`${cluster.latitude} ${cluster.longitude}`}
|
||||
>
|
||||
<Link to={`/cluster/${cluster.id}`}>
|
||||
<Link to={{ pathname: `/cluster/${cluster.id}`, state: { from: location.pathname }}}>
|
||||
<PointerIcon state={'active'} width={48} height={59} />
|
||||
<span>{cluster.caption}</span>
|
||||
</Link>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Button, Layout, Menu } from 'antd'
|
||||
import { Button, Layout } from 'antd'
|
||||
import {
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { memo, useState } from 'react'
|
||||
import { Link, useHistory } from 'react-router-dom'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Link, useHistory, useLocation } from 'react-router-dom'
|
||||
import { Card, Form, Input, Button } from 'antd'
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons'
|
||||
|
||||
import { AuthService } from '@api'
|
||||
import { setUser } from '@utils/storage'
|
||||
import LoaderPortal from '@components/LoaderPortal'
|
||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||
import { loginRules, passwordRules } from '@utils/validationRules'
|
||||
import { setUser } from '@utils/storage'
|
||||
import { AuthService } from '@api'
|
||||
|
||||
import '@styles/index.css'
|
||||
import Logo from '@images/Logo'
|
||||
@ -16,19 +16,21 @@ const logoIcon = <Logo width={130} />
|
||||
|
||||
export const Login = memo(() => {
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
|
||||
const handleLogin = (formData) => invokeWebApiWrapperAsync(
|
||||
const handleLogin = useCallback((formData) => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
const user = await AuthService.login(formData)
|
||||
if (!user) throw Error('Неправильный логин или пароль')
|
||||
setUser(user)
|
||||
history.push('well')
|
||||
console.log(location.state?.from)
|
||||
history.push(location.state?.from ?? 'well')
|
||||
},
|
||||
setShowLoader,
|
||||
(ex) => ex?.message ?? 'Ошибка входа',
|
||||
'Вход в систему'
|
||||
)
|
||||
), [history, location])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader} className={'loader-container login_page shadow'}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { memo, useState } from 'react'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Link, useHistory } from 'react-router-dom'
|
||||
import { Card, Form, Input, Button } from 'antd'
|
||||
import {
|
||||
@ -54,7 +54,7 @@ export const Register = memo(() => {
|
||||
const [showLoader, setShowLoader] = useState(false)
|
||||
const history = useHistory()
|
||||
|
||||
const handleRegister = (formData) => invokeWebApiWrapperAsync(
|
||||
const handleRegister = useCallback((formData) => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
await AuthService.register(formData)
|
||||
history.push('/login')
|
||||
@ -62,7 +62,7 @@ export const Register = memo(() => {
|
||||
setShowLoader,
|
||||
`Ошибка отправки заявки на регистрацию`,
|
||||
'Отправка заявки на регистрацию'
|
||||
)
|
||||
), [history])
|
||||
|
||||
return (
|
||||
<LoaderPortal show={showLoader} className={'loader-container login_page shadow'}>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useState, useEffect, memo } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useState, useEffect, memo, useCallback } from 'react'
|
||||
import { LineChartOutlined, ProfileOutlined } from '@ant-design/icons'
|
||||
import { Table, Tag, Button, Badge, Divider, Modal, Row, Col, Popconfirm } from 'antd'
|
||||
|
||||
@ -42,6 +42,8 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
||||
const [isOpsModalVisible, setIsOpsModalVisible] = useState(false)
|
||||
const [isParamsModalVisible, setIsParamsModalVisible] = useState(false)
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => (async() => setParamsColumns(await getColumns(idWell)))(), [idWell])
|
||||
|
||||
useEffect(() => {
|
||||
@ -125,7 +127,7 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
||||
|
||||
const columns = [
|
||||
makeTextColumn('скв №', 'caption', null, null,
|
||||
(text, item) => <Link to={`/well/${item?.id}`}>{text ?? '-'}</Link>
|
||||
(text, item) => <Link to={{ pathname: `/well/${item?.id}`, state: { from: location.pathname }}}>{text ?? '-'}</Link>
|
||||
),
|
||||
makeTextColumn('Секция', 'sectionType', filtersSectionsType, null, (text) => text ?? '-'),
|
||||
makeNumericColumnPlanFact('Глубина, м', 'sectionWellDepth', filtersMinMax, makeFilterMinMaxFunction),
|
||||
@ -187,7 +189,7 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
||||
)
|
||||
}
|
||||
|
||||
const onParamButtonClick = () => invokeWebApiWrapperAsync(
|
||||
const onParamButtonClick = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
setIsParamsModalVisible(true)
|
||||
const params = await DrillParamsService.getCompositeAll(idWell)
|
||||
@ -196,9 +198,9 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
||||
setShowParamsLoader,
|
||||
`Не удалось загрузить список режимов для скважины "${idWell}"`,
|
||||
'Получение списка режимов скважины'
|
||||
)
|
||||
), [idWell])
|
||||
|
||||
const onParamsAddClick = () => invokeWebApiWrapperAsync(
|
||||
const onParamsAddClick = useCallback(() => invokeWebApiWrapperAsync(
|
||||
async () => {
|
||||
await DrillParamsService.save(idWell, params)
|
||||
setIsParamsModalVisible(false)
|
||||
@ -206,7 +208,7 @@ export const WellCompositeSections = memo(({ idWell, statsWells, selectedSection
|
||||
setShowLoader,
|
||||
`Не удалось добавить режимы в список скважины "${idWell}"`,
|
||||
'Добавление режима скважины'
|
||||
)
|
||||
), [idWell, params])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { memo } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { Layout, Menu } from 'antd'
|
||||
import { Switch, useParams, useHistory } from 'react-router-dom'
|
||||
import { Switch, useParams, useHistory, useLocation } from 'react-router-dom'
|
||||
import {
|
||||
BarChartOutlined,
|
||||
BuildOutlined,
|
||||
@ -23,9 +23,12 @@ const { Content } = Layout
|
||||
export const WellOperations = memo(({ idWell }) => {
|
||||
const { tab } = useParams()
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
const rootPath = `/well/${idWell}/operations`
|
||||
|
||||
const onImported = () => history.push(`${rootPath}`)
|
||||
const onImported = useCallback(() =>
|
||||
history.push({ pathname: `${rootPath}`, state: { from: location.pathname }})
|
||||
, [history, location, rootPath])
|
||||
|
||||
const isIEBarDisabled = !['plan', 'fact'].includes(tab)
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
export const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
getCLS(onPerfEntry)
|
||||
getFID(onPerfEntry)
|
||||
getFCP(onPerfEntry)
|
||||
getLCP(onPerfEntry)
|
||||
getTTFB(onPerfEntry)
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default reportWebVitals;
|
||||
export default reportWebVitals
|
||||
|
Loading…
Reference in New Issue
Block a user