Мемоизированно больше страниц и функций, добавлена переадресация со старницы входа на предыдущую, косметика

This commit is contained in:
Александр Сироткин 2022-02-25 16:57:08 +05:00
parent 8f451035f7
commit dca17e31e8
30 changed files with 201 additions and 196 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 }) => {

View File

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

View File

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

View File

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

View File

@ -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()))

View File

@ -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(

View File

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

View File

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

View File

@ -3,3 +3,4 @@ export type { WellIconColors, WellIconProps, WellIconState } from './WellIcon'
export { PointerIcon } from './PointerIcon'
export { WellIcon } from './WellIcon'
export { Loader } from './Loader'

View File

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

View File

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

View File

@ -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'

View File

@ -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()

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { Button, Layout, Menu } from 'antd'
import { Button, Layout } from 'antd'
import {
CheckOutlined,
CloseOutlined,

View File

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

View File

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

View File

@ -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 (
<>

View File

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

View File

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