В телеметрию админ-панели добавлена страница объединения, добавлены кнопки на странице просмотра для перехода к объединению

This commit is contained in:
Александр Сироткин 2022-03-23 14:30:48 +05:00
parent c2eac25470
commit befac57c30
6 changed files with 247 additions and 35 deletions

View File

@ -4,7 +4,7 @@ import { Tooltip } from 'antd'
import { TelemetryDto, TelemetryInfoDto } from '@api' import { TelemetryDto, TelemetryInfoDto } from '@api'
import { Grid, GridItem } from '@components/Grid' import { Grid, GridItem } from '@components/Grid'
const lables: Record<string, string> = { export const lables: Record<string, string> = {
timeZoneId: 'Временная зона', timeZoneId: 'Временная зона',
timeZoneOffsetTotalHours: 'Сдвиг временной зоны', timeZoneOffsetTotalHours: 'Сдвиг временной зоны',
drillingStartDate: 'Начало бурения', drillingStartDate: 'Начало бурения',

View File

@ -0,0 +1,136 @@
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { PullRequestOutlined } from '@ant-design/icons'
import { Button, Descriptions, Popconfirm } from 'antd'
import { Grid, GridItem } from '@components/Grid'
import LoaderPortal from '@components/LoaderPortal'
import { lables } from '@components/views/TelemetryView'
import { invokeWebApiWrapperAsync } from '@components/factory'
import TelemetrySelect from '@components/selectors/TelemetrySelect'
import { AdminTelemetryService } from '@api'
import { arrayOrDefault } from '@utils'
const { Item } = Descriptions
export const TelemetryInfo = memo(({ info, danger, ...other }) => (
<Descriptions
bordered
column={1}
size={'small'}
style={{ background: 'white' }}
className={'telemetry_merger_info'}
{...other}
>
{Object.keys({ ...lables, ...info }).map(key => (
<Item
key={key}
label={lables[key] ?? key}
style={{ color: danger === true || danger?.includes(key) ? 'red' : 'black' }}
>{info?.[key] ?? '-'}</Item>
))}
</Descriptions>
))
export const TelemetryMerger = memo(() => {
const [primary, setPrimary] = useState(null)
const [secondary, setSecondary] = useState(null)
const [telemetry, setTelemetry] = useState([])
const [isLoading, setIsLoading] = useState(false)
const [isMerging, setIsMerging] = useState(false)
const location = useLocation()
const danger = useMemo(() => [
primary?.info?.well !== secondary?.info?.well && 'well',
primary?.info?.cluster !== secondary?.info?.cluster && 'cluster',
primary?.info?.deposit !== secondary?.info?.deposit && 'deposit',
], [primary, secondary])
const updateTelemetry = useCallback(async () => await invokeWebApiWrapperAsync(
async () => {
const telemetry = arrayOrDefault(await AdminTelemetryService.getAll())
setTelemetry(telemetry)
},
setIsLoading,
'Не удалось загрузить список телеметрий',
'Получение списка телеметрий',
), [])
const mergeTelemetry = useCallback(() => invokeWebApiWrapperAsync(
async () => {
await new Promise(res => setTimeout(res, 1000))
/// await AdminTelemetryService.mergeTelemetries(secondary.id, primary.id)
await updateTelemetry()
},
setIsMerging,
'Не удалось объединить телеметрии',
'Объединение телеметрий',
), [primary, secondary])
useEffect(() => updateTelemetry(), [])
useEffect(() => {
const query = new URLSearchParams(location.search)
const primaryId = parseInt(query.get('primary') ?? null)
const secondaryId = parseInt(query.get('secondary') ?? null)
const primary = isNaN(primaryId) ? null : telemetry.find((t) => t.id === primaryId)
const secondary = isNaN(secondaryId) ? null : telemetry.find((t) => t.id === secondaryId)
console.log([primary, secondary])
setPrimary(primary)
setSecondary(secondary)
}, [location, telemetry])
return (
<LoaderPortal show={isLoading}>
<div className={'description'} style={{ marginTop: '15px' }}>{
///TODO: Добавить описание
}</div>
<Grid>
<GridItem col={1} row={1}>Результирующая телеметрия</GridItem>
<GridItem col={2} row={1}>Исходная телеметрия</GridItem>
<GridItem col={1} row={2}>
<TelemetrySelect
value={primary}
disabled={isMerging}
telemetry={telemetry}
onChange={setPrimary}
style={{ width: '100%', marginRight: '15px' }}
/>
</GridItem>
<GridItem col={2} row={2}>
<TelemetrySelect
value={secondary}
disabled={isMerging}
telemetry={telemetry}
onChange={setSecondary}
style={{ width: '100%' }}
/>
</GridItem>
<GridItem col={3} row={2}>
<Popconfirm
disabled={isMerging || !primary || !secondary}
title={'Исходная телеметрия будет удалена после объединения. Вы уверены?'}
okText={'Объединить'}
onConfirm={mergeTelemetry}
>
<Button
type={'primary'}
icon={<PullRequestOutlined />}
disabled={!primary || !secondary}
loading={isMerging}
>Объединить</Button>
</Popconfirm>
</GridItem>
<GridItem col={1} row={3}>
<TelemetryInfo info={primary?.info} danger={danger} />
</GridItem>
<GridItem col={2} row={3}>
<TelemetryInfo info={secondary?.info} danger={danger} />
</GridItem>
</Grid>
</LoaderPortal>
)
})
export default TelemetryMerger

View File

@ -1,39 +1,70 @@
import { memo, useEffect, useMemo, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { Input } from 'antd' import { PullRequestOutlined } from '@ant-design/icons'
import { Button, Input } from 'antd'
import { import {
defaultPagination, defaultPagination,
makeColumn,
makeDateSorter, makeDateSorter,
makeNumericColumn, makeNumericColumn,
makeNumericRender, makeNumericRender,
makeTextColumn, makeTextColumn,
Table Table
} from '@components/Table' } from '@components/Table'
import Poprompt from '@components/selectors/Poprompt'
import { invokeWebApiWrapperAsync } from '@components/factory' import { invokeWebApiWrapperAsync } from '@components/factory'
import { AdminTelemetryService } from '@api' import { AdminTelemetryService } from '@api'
import { arrayOrDefault } from '@utils' import { arrayOrDefault } from '@utils'
import { useHistory } from 'react-router-dom'
const columns = [
makeNumericColumn('ID', 'id', null, null, makeNumericRender(0)),
makeTextColumn('UID', 'remoteUid'),
makeTextColumn('Назначена на скважину', 'realWell'),
makeTextColumn('Дата начала бурения', 'drillingStartDate', null, makeDateSorter('drillingStartDate')),
makeTextColumn('Часовой пояс', 'timeZoneId'),
makeTextColumn('Скважина', 'well'),
makeTextColumn('Куст', 'cluster'),
makeTextColumn('Месторождение', 'deposit'),
makeTextColumn('Заказчик', 'customer'),
makeTextColumn('Комментарий', 'comment'),
makeTextColumn('Версия HMI', 'hmiVersion'),
makeTextColumn('Версия САУБ', 'saubPlcVersion'),
makeTextColumn('Версия Спин Мастер', 'spinPlcVersion'),
]
export const TelemetryController = memo(() => { export const TelemetryController = memo(() => {
const [telemetryData, setTelemetryData] = useState([]) const [telemetryData, setTelemetryData] = useState([])
const [showLoader, setShowLoader] = useState(false) const [showLoader, setShowLoader] = useState(false)
const [searchValue, setSearchValue] = useState('') const [searchValue, setSearchValue] = useState('')
const history = useHistory()
const toMerger = useCallback((type, id) => () => history.push(`/admin/telemetry/merger/?${type}=${id}`), [history])
const mergeRender = useCallback((value, record) => (
<Poprompt
placement={'topLeft'}
buttonProps={{
icon: <PullRequestOutlined />,
size: 'small',
danger: !!value,
}}
footer={(
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button onClick={toMerger('primary', record.id)}>Основная</Button>
<Button onClick={toMerger('secondary', record.id)} style={{ marginLeft: '8px' }}>Сливаемая</Button>
</div>
)}
>
<p>Вы собираетесь использовать данную телеметрию для слияния</p>
{record.well && (
<p style={{ color: 'red' }}>Внимание! Телеметрии назначена скважина!</p>
)}
</Poprompt>
), [toMerger])
const columns = useMemo(() => [
makeColumn('', 'hasParent', { render: mergeRender }),
makeNumericColumn('ID', 'id', null, null, makeNumericRender(0)),
makeTextColumn('UID', 'remoteUid'),
makeTextColumn('Назначена на скважину', 'realWell'),
makeTextColumn('Дата начала бурения', 'drillingStartDate', null, makeDateSorter('drillingStartDate')),
makeTextColumn('Часовой пояс', 'timeZoneId'),
makeTextColumn('Скважина', 'well'),
makeTextColumn('Куст', 'cluster'),
makeTextColumn('Месторождение', 'deposit'),
makeTextColumn('Заказчик', 'customer'),
makeTextColumn('Комментарий', 'comment'),
makeTextColumn('Версия HMI', 'hmiVersion'),
makeTextColumn('Версия САУБ', 'saubPlcVersion'),
makeTextColumn('Версия Спин Мастер', 'spinPlcVersion'),
], [mergeRender])
const filteredTelemetryData = useMemo(() => telemetryData.filter((telemetry) => telemetry && (!searchValue || [ const filteredTelemetryData = useMemo(() => telemetryData.filter((telemetry) => telemetry && (!searchValue || [
telemetry.id?.toString() ?? '', telemetry.id?.toString() ?? '',
telemetry.remoteUid ?? '', telemetry.remoteUid ?? '',

View File

@ -0,0 +1,42 @@
import { Layout, Menu } from 'antd'
import { lazy, memo, Suspense } from 'react'
import { Switch, useParams } from 'react-router-dom'
import { PrivateMenuItem, PrivateRoute, PrivateDefaultRoute } from '@components/Private'
import { SuspenseFallback } from '@pages/SuspenseFallback'
const TelemetryViewer = lazy(() => import('./TelemetryViewer'))
const TelemetryMerger = lazy(() => import('./TelemetryMerger'))
const rootPath = '/admin/telemetry'
export const Telemetry = memo(() => {
const { tab } = useParams()
return (
<Layout>
<Menu mode={'horizontal'} selectable={true} selectedKeys={[tab]}>
<PrivateMenuItem.Link root={rootPath} key={'viewer'} path={'viewer'} title={'Просмотр'} />
<PrivateMenuItem.Link root={rootPath} key={'merger'} path={'merger'} title={'Объединение'} />
</Menu>
<Layout>
<Layout.Content className={'site-layout-background'}>
<Suspense fallback={<SuspenseFallback />}>
<Switch>
<PrivateRoute path={`${rootPath}/viewer`} component={TelemetryViewer} />
<PrivateRoute path={`${rootPath}/merger`} component={TelemetryMerger} />
<PrivateDefaultRoute urls={[
`${rootPath}/viewer`,
`${rootPath}/merger`,
]}/>
</Switch>
</Suspense>
</Layout.Content>
</Layout>
</Layout>
)
})
export default Telemetry

View File

@ -1,5 +1,5 @@
import { Layout, Menu } from 'antd' import { Layout, Menu } from 'antd'
import { lazy, Suspense } from 'react' import { lazy, memo, Suspense } from 'react'
import { Switch, useParams } from 'react-router-dom' import { Switch, useParams } from 'react-router-dom'
import { PrivateMenuItem, PrivateRoute, PrivateDefaultRoute } from '@components/Private' import { PrivateMenuItem, PrivateRoute, PrivateDefaultRoute } from '@components/Private'
@ -14,12 +14,12 @@ const WellController = lazy(() => import( './WellController'))
const RoleController = lazy(() => import( './RoleController')) const RoleController = lazy(() => import( './RoleController'))
const CompanyTypeController = lazy(() => import('./CompanyTypeController')) const CompanyTypeController = lazy(() => import('./CompanyTypeController'))
const PermissionController = lazy(() => import( './PermissionController')) const PermissionController = lazy(() => import( './PermissionController'))
const TelemetryController = lazy(() => import( './TelemetryController')) const TelemetrySection = lazy(() => import( './Telemetry'))
const VisitLog = lazy(() => import( './VisitLog')) const VisitLog = lazy(() => import( './VisitLog'))
const rootPath = '/admin' const rootPath = '/admin'
export const AdminPanel = () => { export const AdminPanel = memo(() => {
const { tab } = useParams() const { tab } = useParams()
return ( return (
@ -33,7 +33,7 @@ export const AdminPanel = () => {
<PrivateMenuItem.Link root={rootPath} key={'company_type'} path={'company_type'} title={'Типы компаний' } /> <PrivateMenuItem.Link root={rootPath} key={'company_type'} path={'company_type'} title={'Типы компаний' } />
<PrivateMenuItem.Link root={rootPath} key={'role' } path={'role' } title={'Роли' } /> <PrivateMenuItem.Link root={rootPath} key={'role' } path={'role' } title={'Роли' } />
<PrivateMenuItem.Link root={rootPath} key={'permission' } path={'permission' } title={'Разрешения' } /> <PrivateMenuItem.Link root={rootPath} key={'permission' } path={'permission' } title={'Разрешения' } />
<PrivateMenuItem.Link root={rootPath} key={'telemetry' } path={'telemetry' } title={'Телеметрии' } /> <PrivateMenuItem.Link root={rootPath} key={'telemetry' } path={'telemetry' } title={'Телеметрия' } />
<PrivateMenuItem.Link root={rootPath} key={'visit_log' } path={'visit_log' } title={'Журнал посещений'} /> <PrivateMenuItem.Link root={rootPath} key={'visit_log' } path={'visit_log' } title={'Журнал посещений'} />
</Menu> </Menu>
@ -41,16 +41,16 @@ export const AdminPanel = () => {
<Layout.Content className={'site-layout-background'}> <Layout.Content className={'site-layout-background'}>
<Suspense fallback={<SuspenseFallback />}> <Suspense fallback={<SuspenseFallback />}>
<Switch> <Switch>
<PrivateRoute path={`${rootPath}/deposit` } component={ DepositController} /> <PrivateRoute path={`${rootPath}/deposit` } component={ DepositController} />
<PrivateRoute path={`${rootPath}/cluster` } component={ ClusterController} /> <PrivateRoute path={`${rootPath}/cluster` } component={ ClusterController} />
<PrivateRoute path={`${rootPath}/well` } component={ WellController} /> <PrivateRoute path={`${rootPath}/well` } component={ WellController} />
<PrivateRoute path={`${rootPath}/user` } component={ UserController} /> <PrivateRoute path={`${rootPath}/user` } component={ UserController} />
<PrivateRoute path={`${rootPath}/company` } component={ CompanyController} /> <PrivateRoute path={`${rootPath}/company` } component={ CompanyController} />
<PrivateRoute path={`${rootPath}/company_type`} component={CompanyTypeController} /> <PrivateRoute path={`${rootPath}/company_type` } component={CompanyTypeController} />
<PrivateRoute path={`${rootPath}/role` } component={ RoleController} /> <PrivateRoute path={`${rootPath}/role` } component={ RoleController} />
<PrivateRoute path={`${rootPath}/permission` } component={ PermissionController} /> <PrivateRoute path={`${rootPath}/permission` } component={ PermissionController} />
<PrivateRoute path={`${rootPath}/telemetry` } component={ TelemetryController} /> <PrivateRoute path={`${rootPath}/telemetry/:tab?`} component={TelemetrySection} />
<PrivateRoute path={`${rootPath}/visit_log` } component={VisitLog} /> <PrivateRoute path={`${rootPath}/visit_log` } component={VisitLog} />
<PrivateDefaultRoute urls={[ <PrivateDefaultRoute urls={[
`${rootPath}/deposit`, `${rootPath}/deposit`,
`${rootPath}/cluster`, `${rootPath}/cluster`,
@ -69,6 +69,6 @@ export const AdminPanel = () => {
</Layout> </Layout>
</Layout> </Layout>
) )
} })
export default AdminPanel export default AdminPanel

View File

@ -78,7 +78,10 @@ export const requirements: PermissionRecord = {
permission: ['AdminPermission.get'], permission: ['AdminPermission.get'],
role: ['AdminUserRole.get', 'AdminPermission.get'], role: ['AdminUserRole.get', 'AdminPermission.get'],
visit_log: ['RequerstTracker.get'], visit_log: ['RequerstTracker.get'],
telemetry: ['AdminTelemetry.get'], telemetry: {
merger: ['AdminTelemetry.get'],
viewer: ['AdminTelemetry.get'],
},
}, },
deposit: ['Deposit.get', 'Cluster.get'], deposit: ['Deposit.get', 'Cluster.get'],
cluster: { cluster: {