From 0c351640cdd528672e39e67f596e06302d91a996 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 19 May 2021 16:05:01 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=D0=B4=D0=BD?= =?UTF-8?q?=D0=B8=D1=85=203=D1=85=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9,=20=D0=98=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F,=20=D0=BF=D1=80=D0=BE=D1=81=D0=BC=D0=BE=D1=82=D1=80=20?= =?UTF-8?q?=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9,=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=B5=D1=81=D1=91=D0=BD=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B1=D0=BE=D1=80=20=D1=81=D0=BA=D0=B2=D0=B0=D0=B6=D0=B8?= =?UTF-8?q?=D0=BD=20=D0=B2=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 22 +- .vscode/settings.json | 5 + src/components/CustomColumn.jsx | 2 +- src/components/UserOfWells.jsx | 22 ++ src/components/WellTreeSelector.jsx | 131 ++++++------ src/components/charts/ChartTimeArchive.tsx | 82 ++++++++ src/pages/Analise.jsx | 3 - src/pages/Analysis.jsx | 3 + src/pages/Archive.jsx | 184 ++++++++++++++++- src/pages/Header.jsx | 29 --- src/pages/Main.jsx | 12 +- src/pages/Messages.jsx | 113 +++++++++- src/pages/Report.jsx | 36 +++- src/pages/TelemetryView.jsx | 227 ++++++++++++++------- src/pages/Wells.jsx | 13 +- src/services/signalr/index.ts | 61 ++++-- src/styles/App.less | 4 +- src/styles/message.css | 35 ++++ src/styles/message_telemetry.css | 30 +++ 19 files changed, 778 insertions(+), 236 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/components/UserOfWells.jsx create mode 100644 src/components/charts/ChartTimeArchive.tsx delete mode 100644 src/pages/Analise.jsx create mode 100644 src/pages/Analysis.jsx create mode 100644 src/styles/message.css create mode 100644 src/styles/message_telemetry.css diff --git a/.vscode/launch.json b/.vscode/launch.json index 3274ed5..51cf03e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,20 +4,12 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "name": "Launch Edge", - "request": "launch", - "type": "pwa-msedge", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}" - }, - - { - "type": "pwa-chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}" - } + { + "name": "Launch Chrome", + "request": "launch", + "type": "pwa-chrome", + "url": "http://localhost:3000/", + "webRoot": "${workspaceFolder}" + }, ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..24a6479 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "день" + ] +} \ No newline at end of file diff --git a/src/components/CustomColumn.jsx b/src/components/CustomColumn.jsx index 86cc494..c8d5da3 100644 --- a/src/components/CustomColumn.jsx +++ b/src/components/CustomColumn.jsx @@ -18,7 +18,7 @@ export const CustomColumn = ({data}) => { return (<> {lines.map(line => )} diff --git a/src/components/UserOfWells.jsx b/src/components/UserOfWells.jsx new file mode 100644 index 0000000..97324d9 --- /dev/null +++ b/src/components/UserOfWells.jsx @@ -0,0 +1,22 @@ +import { Display } from './Display' + +export const UserOfWells = ({ data }) => { + const dataLast = data[data.length - 1] + + const lines = [ + { label: 'Пользователь', accessorName: 'user' }, + ] + + if (dataLast) + lines.forEach(line => line.value = dataLast[line.accessorName] ?? '-') + else + lines.forEach(line => line.value = '-') + + return (<> + {lines.map(line => )} + ) +} \ No newline at end of file diff --git a/src/components/WellTreeSelector.jsx b/src/components/WellTreeSelector.jsx index 286f472..23e0f0c 100644 --- a/src/components/WellTreeSelector.jsx +++ b/src/components/WellTreeSelector.jsx @@ -1,76 +1,79 @@ import { useState, useEffect } from 'react' import { WellService } from '../services/api' import Loader from '../components/Loader' -import { TreeSelect } from 'antd' // TreeSelect +import { TreeSelect } from 'antd' import { useHistory } from 'react-router-dom' const groupBy = (table, ...keys) => { - let key = keys[0] - - let groups = table.reduce((rv, item) => { - let keyValue = item[key] - let group = rv.find(o=>o.title === keyValue) - if(!group) - { - group = { - title: keyValue, - value: keys.length === 1 ? item : `${key} ${keyValue} ${item['id']}`, - selectable: keys.length === 1, - children:[]} - rv.push(group) - } - if(keys.length > 1) - group.children.push(item); - return rv; - }, []); - - if(keys.length > 1){ - for(let group of groups){ - group.children = groupBy(group.children, ...keys.slice(1)) + let key = keys[0] + + let groups = table.reduce((rv, item) => { + let keyValue = item[key] + let group = rv.find(o => o.title === keyValue) + if (!group) { + group = { + title: keyValue, + value: keys.length === 1 ? `${item['id']}` : `${key} ${keyValue} ${item['id']}`, + selectable: keys.length === 1, + children: [] } + rv.push(group) } - - return groups - }; + if (keys.length > 1) + group.children.push(item); + return rv; + }, []); -export default function WellTreeSelector(props){ - const [wells, setWells] = useState([]) - const [wellsTree, setWellsTree] = useState([]) // wellsTree, - const [loader, setLoader] = useState(false) - const history = useHistory() + if (keys.length > 1) { + for (let group of groups) { + group.children = groupBy(group.children, ...keys.slice(1)) + } + } - let updateWellsList = async () => { - setLoader(true) - try{ - let newWells = (await WellService.getWells()).map(w =>{return {key:w.id, ...w}}) - let wellsTree = groupBy(newWells, 'deposit', 'cluster', 'caption') - console.log(wellsTree) - setWells( newWells ) - setWellsTree(wellsTree) - } - catch(e){ - console.error(`${e.message}`); - } - setLoader(false) - } - - useEffect(()=>{updateWellsList()}, []) - - const onChange = (value) =>{ - ; - } + return groups +}; - return( - <> - - {loader&&} - - ) +export default function WellTreeSelector(props) { + // const [wells, setWells] = useState([]) + const [wellsTree, setWellsTree] = useState([]) + const [loader, setLoader] = useState(false) + const history = useHistory() + + let updateWellsList = async () => { + setLoader(true) + try { + let newWells = (await WellService.getWells()).map(w => { return { key: w.id, ...w } }) + let wellsTree = groupBy(newWells, 'deposit', 'cluster', 'caption') + // setWells( newWells ) + setWellsTree(wellsTree) + } + catch (e) { + console.error(`${e.message}`); + } + setLoader(false) + } + + useEffect(() => { updateWellsList() }, []) + + const onSelect = (value) => { + if (value) + history.push(`/well/${value}`); + console.log(value) + } + + return ( + <> + + {loader && } + + ) } \ No newline at end of file diff --git a/src/components/charts/ChartTimeArchive.tsx b/src/components/charts/ChartTimeArchive.tsx new file mode 100644 index 0000000..ec4fb6d --- /dev/null +++ b/src/components/charts/ChartTimeArchive.tsx @@ -0,0 +1,82 @@ +import { useEffect, useState} from 'react'; +import {ChartTimeBase, ChartTimeData, ChartTimeDataParams} from './ChartTimeBase' + +const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16) + + +function GetOrCreateDatasetByLineConfig(data: ChartTimeData, lineConfig: LineConfig) { + let dataset = data?.datasets.find(d => d.label === lineConfig.label) + if (!dataset) { + let color = lineConfig.borderColor + ?? lineConfig.backgroundColor + ?? lineConfig.color + ?? GetRandomColor() + + dataset = { + label: lineConfig.label, + data: [], + backgroundColor: lineConfig.backgroundColor ?? color, + borderColor: lineConfig.borderColor ?? color, + borderWidth: lineConfig.borderWidth ?? 1, + borderDash: lineConfig.dash ?? [], + } + data.datasets.push(dataset); + } + return dataset +} + +export type LineConfig = { + type?: string + label: string + units?: string + xAccessorName: string + yAccessorName: string + color?: string + borderColor?: string + backgroundColor?: string + borderWidth?: number + dash?: number[] + labels?: any[] +} + +export type ChartTimeProps = { + label?: string, + yDisplay: Boolean, + lines: LineConfig[], + data: any[], + interval: number, +} + +export const ChartTimeArchive: React.FC = (props) => { + const [dataParams, setDataParams] = useState({ data: { datasets: [] }, yStart: new Date(), }) + + useEffect(() => { + if ((!props?.lines) + || (!props?.data) + || (props.lines.length === 0) + || (props.data.length === 0)) + return + + setDataParams((preDataParams) => { + props.lines.forEach(lineCfg => { + let dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg) + let points = props.data.map(dataItem => { + return { + x: dataItem[lineCfg.xAccessorName], + y: new Date(dataItem[lineCfg.yAccessorName]) + } + }) + dataset.data = points; + }); + + preDataParams.yStart = new Date() + preDataParams.yStart.setSeconds(preDataParams.yStart.getSeconds() - props.interval) + preDataParams.yInterval = props.interval + preDataParams.displayLabels = props.yDisplay + return preDataParams + }) + + }, [props.data, props.lines, props.interval, props.yDisplay]) + + return () +} \ No newline at end of file diff --git a/src/pages/Analise.jsx b/src/pages/Analise.jsx deleted file mode 100644 index a9aea77..0000000 --- a/src/pages/Analise.jsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Analise(props){ - return(

Анализ

) - } \ No newline at end of file diff --git a/src/pages/Analysis.jsx b/src/pages/Analysis.jsx new file mode 100644 index 0000000..57ba47b --- /dev/null +++ b/src/pages/Analysis.jsx @@ -0,0 +1,3 @@ +export default function Analysis(props){ + return(

Анализ

) + } \ No newline at end of file diff --git a/src/pages/Archive.jsx b/src/pages/Archive.jsx index 65ee8e9..0671f1e 100644 --- a/src/pages/Archive.jsx +++ b/src/pages/Archive.jsx @@ -1,5 +1,187 @@ +import { useState, useEffect } from 'react' +import { + Button, + DatePicker, + ConfigProvider, + Select, + Row, + Col, +} from 'antd' +import { useParams } from 'react-router-dom' +import { Subscribe } from '../services/signalr' +import { DataService } from '../services/api' +import 'moment/locale/ru' +import locale from 'antd/lib/locale/ru_RU' +import { ChartTimeArchive } from '../components/charts/ChartTimeArchive' +import { Display } from '../components/Display' +import { ChartTimeOnlineFooter } from '../components/ChartTimeOnlineFooter' +import { UserOfWells } from '../components/UserOfWells' +const { RangePicker } = DatePicker; +const { Option } = Select; + +// Выбор периода времени +const PeriodOfTime = () => { + // const [startDate, setStartDate] = useState(new Date()); + + return ( + + + + ) +} + +// Выбор "перьев" для графиков - перенести в шапку графика +const SelectDataCharts = () => { + const linesCollection = [ + { label: "Глубина забоя", xAccessorName: "wellDepth", color: '#a0a' }, + { label: "Положение инструмента", xAccessorName: "bitDepth", color: '#a0a' }, + { label: "Высота талевого блока", xAccessorName: "blockPosition", color: '#a0a' }, + { label: "Талевый блок. Мин положение", xAccessorName: "blockPositionMin", color: '#a0a' }, + { label: "Талевый блок. Макс положение", xAccessorName: "blockPositionMax", color: '#a0a' }, + { label: "Скорость талевого блока", xAccessorName: "blockSpeed", color: '#a0a' }, + { label: "Скорости талевого блока. Задание", xAccessorName: "blockSpeedSp", color: '#a0a' }, + { label: "Талевый блок. Задание скорости для роторного бурения", xAccessorName: "blockSpeedSpRotor", color: '#a0a' }, + { label: "Талевый блок. Задание скорости для режима слайда", xAccessorName: "blockSpeedSpSlide", color: '#a0a' }, + { label: "Талевый блок. Задание скорости для проработки", xAccessorName: "blockSpeedSpDevelop", color: '#a0a' }, + { label: "Давление", xAccessorName: "pressure", color: '#a0a' }, + { label: "Давление. Холостой ход", xAccessorName: "pressureIdle", color: '#a0a' }, + { label: "Давление. Задание", xAccessorName: "pressureSp", color: '#a0a' }, + { label: "Давление. Задание для роторного бурения", xAccessorName: "pressureSpRotor", color: '#a0a' }, + { label: "Давление. Задание для режима слайда", xAccessorName: "pressureSpSlide", color: '#a0a' }, + { label: "Давление. Задание для проработки", xAccessorName: "pressureSpDevelop", color: '#a0a' }, + { label: "Давление дифф. Аварийное макс.", xAccessorName: "pressureDeltaLimitMax", color: '#a0a' }, + { label: "Осевая нагрузка", xAccessorName: "axialLoad", color: '#a0a' }, + { label: "Осевая нагрузка. Задание", xAccessorName: "axialLoadSp", color: '#a0a' }, + { label: "Осевая нагрузка. Аварийная макс.", xAccessorName: "axialLoadLimitMax", color: '#a0a' }, + { label: "Вес на крюке", xAccessorName: "hookWeight", color: '#a0a' }, + { label: "Вес на крюке. Холостой ход", xAccessorName: "hookWeightIdle", color: '#a0a' }, + { label: "Вес на крюке. Посадка", xAccessorName: "hookWeightLimitMin", color: '#a0a' }, + { label: "Вес на крюке. Затяжка", xAccessorName: "hookWeightLimitMax", color: '#a0a' }, + { label: "Момент на роторе", xAccessorName: "rotorTorque", color: '#a0a' }, + { label: "Момент на роторе. Холостой ход", xAccessorName: "rotorTorqueIdle", color: '#a0a' }, + { label: "Момент на роторе. Задание", xAccessorName: "rotorTorqueSp", color: '#a0a' }, + { label: "Момент на роторе. Аварийный макс.", xAccessorName: "rotorTorqueLimitMax", color: '#a0a' }, + { label: "Обороты ротора", xAccessorName: "rotorSpeed", color: '#a0a' }, + { label: "Расход", xAccessorName: "flow", color: '#a0a' }, + { label: "Расход. Холостой ход", xAccessorName: "flowIdle", color: '#a0a' }, + { label: "Расход. Аварийный макс.", xAccessorName: "flowDeltaLimitMax", color: '#a0a' }, + ] + const children = linesCollection.map((line) => ()) + function handleChange(value) { + console.log(`selected ${value}`); + } + + return ( + + ) +} + +const Column = ({ lineGroup, data, interval }) => { + let lines = [lineGroup.linePv] + + if (lineGroup.lineSp) + lines.push(lineGroup.lineSp) + + let dataLast = null + let pv = null + if (data?.length > 0) { + dataLast = data[data.length - 1]; + if (lineGroup.linePv) + pv = dataLast[lineGroup.linePv?.xAccessorName] + } + return ( + <> + + + + ) +} + +const paramsGroups = [] + export default function Archive(props) { - return (

Архив

) + let { id } = useParams(); + const [saubData, setSaubData] = useState([]) + const [chartInterval, setChartInterval] = useState(600) + + const handleReceiveDataSaub = (data) => { + if (data) { + setSaubData(data) + } + } + + useEffect(() => { + DataService.getData(id) + .then(handleReceiveDataSaub) + .catch(error => console.error(error)) + + let unSubscribeMessages = Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub) + return () => { + unSubscribeMessages() + } + }, [id]); + + const colSpan = 24 / (paramsGroups.length) + + return (<> +
+

Архив

+ + +
+
+ + + + + + Интервал:  + + + + + + + {paramsGroups.map(group => + + + )} + + + + ) } \ No newline at end of file diff --git a/src/pages/Header.jsx b/src/pages/Header.jsx index 6a2a742..3170b0d 100644 --- a/src/pages/Header.jsx +++ b/src/pages/Header.jsx @@ -7,35 +7,6 @@ import WellTreeSelector from '../components/WellTreeSelector' const { Header } = Layout -const groupBy = (table, ...keys) => { - let key = keys[0] - - let groups = table.reduce((rv, item) => { - let keyValue = item[key] - let group = rv.find(o=>o.title === keyValue) - if(!group) - { - group = { - title: keyValue, - value: keys.length === 1 ? item : `${key} ${keyValue} ${item['id']}`, - selectable: keys.length === 1, - children:[]} - rv.push(group) - } - if(keys.length > 1) - group.children.push(item); - return rv; - }, []); - - if(keys.length > 1){ - for(let group of groups){ - group.children = groupBy(group.children, ...keys.slice(1)) - } - } - - return groups - }; - export default function PageHeader(props){ const [sidebarVisible, setSidebarVisible] = useState(true) const login = localStorage['login'] diff --git a/src/pages/Main.jsx b/src/pages/Main.jsx index 6130604..28d711f 100644 --- a/src/pages/Main.jsx +++ b/src/pages/Main.jsx @@ -8,10 +8,10 @@ import Files from './Files' import Archive from './Archive' import Messages from './Messages' import Report from './Report' -import Analise from './Analise' +import Analysis from './Analysis' import PageHeader from './Header' -const { Content, Sider } = Layout // Header +const { Content, Sider } = Layout export default function Main(){ const [sidebarVisible] = useState(true) // setSidebarVisible @@ -47,7 +47,7 @@ export default function Main(){ Рапорт }> - Анализ + Анализ }> Файлы @@ -65,14 +65,14 @@ export default function Main(){ - + - - + + diff --git a/src/pages/Messages.jsx b/src/pages/Messages.jsx index 1c7e101..1589b72 100644 --- a/src/pages/Messages.jsx +++ b/src/pages/Messages.jsx @@ -1,3 +1,110 @@ -export default function Messages(props){ - return(

Сообщения

) - } \ No newline at end of file +import { Table } from 'antd'; +import { MessageService } from '../services/api' +import { useState, useEffect } from 'react' +import { useParams } from 'react-router-dom' +import { Subscribe } from '../services/signalr' +import Loader from '../components/Loader' +import moment from 'moment'; +import '../styles/message.css' + + +// Словарь категорий для строк таблицы +const categoryDictionary = { + 1: {title:'Авария'}, + 2: {title:'Предупреждение'}, + 3: {title:'Информация'}, +} + +// Конфигурация таблицы +const columns = [ + { + title: 'Дата', + key: 'date', + dataIndex: 'date', + render: (item) => moment(item).format('DD MMM YYYY, HH:MM:ss'), + sorter: (a, b) => new Date(b.date) - new Date(a.date), + sortDirections: ['descend', 'ascend'], + }, + { + title: 'Категория', + key: 'categoryId', + dataIndex: 'categoryId', + render: (_, item) => categoryDictionary[item.categoryId].title, + style: (_, item) => categoryDictionary[item.categoryId].style, + sorter: (a, b) => a.categoryId - b.categoryId, + sortDirections: ['descend', 'ascend'], + }, + { + title: 'Сообщение', + key: 'message', + dataIndex: 'message', + onFilter: (value, record) => record.name.indexOf(value) === 0, + }, + { + title: 'Пользователь', + key: 'user', + dataIndex: 'user', + }, +]; + +// Данные для таблицы +export default function Messages(props) { + let { id } = useParams() + const [messages, setMessages] = useState([]) + const [loader] = useState(false) + + // const handleMessages = (messages) => { + // if (messages) { + // messages.map(m => { return { key: m.id, ...m } }) + // } + // } + + const handleReceiveMessages = (messages) => { + if (messages) { + setMessages(messages.items) + } + } + + useEffect(() => { + MessageService.getMessage(id) + .then(handleReceiveMessages) + .catch(error => console.error(error)) + let unSubscribeMessagesHub = Subscribe('ReceiveMessages', `well_${id}`, handleReceiveMessages) + return () => { + unSubscribeMessagesHub() + } + }, [id]); + + // let updateMessagesTable = async () => { + // setLoader(true) + // try { + // let newMessages = await MessageService.getMessage(1, 0, 32, [], '2020-01-01T00:00:00', '2025-01-01T00:00:00') + // setMessages(newMessages.items) + // } catch (e) { + // console.error(`${e.message}`); + // } + // setLoader(false) + // } + + // useEffect(() => { + // updateMessagesTable() + // }, []) + + return ( + <> +

Сообщения

+
+ `event_message_${record.categoryId}`} + size={'small'} + pagination={{ pageSize: 10 }} + rowKey={(record) => record.id} + /> + {loader && } + + ) +} + +// TODO Стили для отсортированных страниц \ No newline at end of file diff --git a/src/pages/Report.jsx b/src/pages/Report.jsx index ed5c046..f437169 100644 --- a/src/pages/Report.jsx +++ b/src/pages/Report.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; // , { useState } import { DatePicker, Radio, @@ -6,18 +6,19 @@ import { Button, Select, Checkbox, - Pagination } from 'antd'; import 'moment/locale/ru'; import locale from 'antd/lib/locale/ru_RU'; - +import moment from "moment"; const { RangePicker } = DatePicker; const { Option } = Select; + + // Расширение файла для составленного рапорта -const ExeptionFileChecker = () => { +const ExceptionFileChecker = () => { const [value, setValue] = React.useState(1); const onChange = e => { console.log('radio checked', e.target.value); @@ -33,12 +34,18 @@ const ExeptionFileChecker = () => { // Выбор периода времени const PeriodOfTime = () => { - const [startDate, setStartDate] = useState(new Date()); + function disabledDate(current) { + return current && current < moment().subtract(3, 'days'); // Наверно, не надо) + } + // const [startDate, setStartDate] = useState(new Date()); + return ( - + ) @@ -64,7 +71,9 @@ const GraphicStep = () => { } return ( - @@ -81,14 +90,19 @@ export default function Report(props) {

Шаг графиков

- {/*

Содержимое отчёта

- */} +

Содержимое отчёта

+
- +

*Предполагаемое колличество страниц


- +
diff --git a/src/pages/TelemetryView.jsx b/src/pages/TelemetryView.jsx index c2123b3..8dcb0aa 100644 --- a/src/pages/TelemetryView.jsx +++ b/src/pages/TelemetryView.jsx @@ -1,144 +1,207 @@ -import { useState, useEffect} from 'react'; +import { useState, useEffect } from 'react'; import { Row, Col, Select, } from 'antd' -import { ChartTimeOnline} from '../components/charts/ChartTimeOnline' -import { ChartTimeOnlineFooter} from '../components/ChartTimeOnlineFooter' -import { CustomColumn} from '../components/CustomColumn' -import { ModeDisplay } from '../components/ModeDisplay'; -import { Display } from '../components/Display'; +import { ChartTimeOnline } from '../components/charts/ChartTimeOnline' +import { ChartTimeOnlineFooter } from '../components/ChartTimeOnlineFooter' +import { CustomColumn } from '../components/CustomColumn' +import { UserOfWells } from '../components/UserOfWells' +import { ModeDisplay } from '../components/ModeDisplay' +import { Display } from '../components/Display' +import moment from 'moment' +import { Table } from 'antd'; +import { MessageService } from '../services/api' +import Loader from '../components/Loader' -import { useParams} from 'react-router-dom' -import { Subscribe} from '../services/signalr' -import { DataService} from '../services/api' +import { useParams } from 'react-router-dom' +import { Subscribe } from '../services/signalr' +import { DataService } from '../services/api' +import '../styles/message_telemetry.css' -const {Option} = Select +const { Option } = Select -const dash = [7,3] +const dash = [7, 3] const blockHeightGroup = { - label:"Высота блока", + label: "Высота блока", yDisplay: false, - linePv: { label:"blockHeight", units:'м', xAccessorName : "blockHeight", yAccessorName : "date", color:'#333' }, + linePv: { label: "blockHeight", units: 'м', xAccessorName: "blockHeight", yAccessorName: "date", color: '#333' }, } const blockSpeedGroup = { - label:"Скорость блока", + label: "Скорость блока", yDisplay: false, - linePv: { label:"blockSpeed", units:'м/ч', xAccessorName : "blockSpeed", yAccessorName : "date", color:'#0a0' }, - lineSp: { label:"blockSpeedSp", units:'м/ч', xAccessorName : "blockSpeedSp", yAccessorName : "date", color:'#0a0' }, + linePv: { label: "blockSpeed", units: 'м/ч', xAccessorName: "blockSpeed", yAccessorName: "date", color: '#0a0' }, + lineSp: { label: "blockSpeedSp", units: 'м/ч', xAccessorName: "blockSpeedSp", yAccessorName: "date", color: '#0a0' }, } const pressureGroup = { - label:"Давтение", - yDisplay: false, - linePv: { label:"pressure", units:'атм', xAccessorName : "pressure", yAccessorName : "date", color:'#c00' }, - lineSp: { label:"pressureSp", units:'атм', xAccessorName : "pressureSp", yAccessorName : "date", color:'#c00' }, - lineIdle: { label:"pressureIdle", units:'атм', xAccessorName : "pressureIdle", yAccessorName : "date", color:'#c00' }, + label: "Давление", + yDisplay: false, + linePv: { label: "pressure", units: 'атм', xAccessorName: "pressure", yAccessorName: "date", color: '#c00' }, + lineSp: { label: "pressureSp", units: 'атм', xAccessorName: "pressureSp", yAccessorName: "date", color: '#c00' }, + lineIdle: { label: "pressureIdle", units: 'атм', xAccessorName: "pressureIdle", yAccessorName: "date", color: '#c00' }, linesOther: [ - { label:"мекс. перепад", units:'атм', xAccessorName : "pressureDeltaLimitMax", yAccessorName : "date", color:'#c00' }, + { label: "мекс. перепад", units: 'атм', xAccessorName: "pressureDeltaLimitMax", yAccessorName: "date", color: '#c00' }, ], } -const axialLoadGroup = { - label:"Осевая нагрузка", +const axialLoadGroup = { + label: "Осевая нагрузка", yDisplay: false, - linePv: { label:"axialLoad", units:'т', xAccessorName : "axialLoad", yAccessorName : "date", color:'#00a' }, - lineSp: { label:"axialLoadSp", units:'т', xAccessorName : "axialLoadSp", yAccessorName : "date", color:'#00a', dash }, + linePv: { label: "axialLoad", units: 'т', xAccessorName: "axialLoad", yAccessorName: "date", color: '#00a' }, + lineSp: { label: "axialLoadSp", units: 'т', xAccessorName: "axialLoadSp", yAccessorName: "date", color: '#00a', dash }, linesOther: [ - { label:"axialLoadLimitMax", units:'т', xAccessorName : "axialLoadLimitMax", yAccessorName : "date", color:'#00a' }, + { label: "axialLoadLimitMax", units: 'т', xAccessorName: "axialLoadLimitMax", yAccessorName: "date", color: '#00a' }, ], } const hookWeightGroup = { - label:"Ввес на крюке", + label: "Ввес на крюке", yDisplay: false, - linePv: { label:"hookWeight", units:'т', xAccessorName : "hookWeight", yAccessorName : "date", color:'#0aa' }, - lineIdle: { label:"hookWeightIdle", units:'т', xAccessorName : "hookWeightIdle", yAccessorName : "date", color:'#0aa', dash }, + linePv: { label: "hookWeight", units: 'т', xAccessorName: "hookWeight", yAccessorName: "date", color: '#0aa' }, + lineIdle: { label: "hookWeightIdle", units: 'т', xAccessorName: "hookWeightIdle", yAccessorName: "date", color: '#0aa', dash }, linesOther: [ - { label:"hookWeightLimitMin", units:'т', xAccessorName : "hookWeightLimitMin", yAccessorName : "date", color:'#0aa' }, - { label:"hookWeightLimitMax", units:'т', xAccessorName : "hookWeightLimitMax", yAccessorName : "date", color:'#0aa' }, + { label: "hookWeightLimitMin", units: 'т', xAccessorName: "hookWeightLimitMin", yAccessorName: "date", color: '#0aa' }, + { label: "hookWeightLimitMax", units: 'т', xAccessorName: "hookWeightLimitMax", yAccessorName: "date", color: '#0aa' }, ], } const rotorTorqueGroup = { - label:"Момент на роторе", + label: "Момент на роторе", yDisplay: false, - linePv: { label:"rotorTorque", units:'кН·м', xAccessorName : "rotorTorque", yAccessorName : "date", color:'#a0a' }, - lineSp: { label:"rotorTorqueSp", units:'кН·м', xAccessorName : "rotorTorqueSp", yAccessorName : "date", color:'#a0a' }, - lineIdle: { label:"rotorTorqueIdle", units:'кН·м', xAccessorName : "rotorTorqueIdle", yAccessorName : "date", color:'#a0a' }, + linePv: { label: "rotorTorque", units: 'кН·м', xAccessorName: "rotorTorque", yAccessorName: "date", color: '#a0a' }, + lineSp: { label: "rotorTorqueSp", units: 'кН·м', xAccessorName: "rotorTorqueSp", yAccessorName: "date", color: '#a0a' }, + lineIdle: { label: "rotorTorqueIdle", units: 'кН·м', xAccessorName: "rotorTorqueIdle", yAccessorName: "date", color: '#a0a' }, linesOther: [ - { label:"rotorTorqueLimitMax", units:'кН·м', xAccessorName : "rotorTorqueLimitMax", yAccessorName : "date", color:'#a0a' }, + { label: "rotorTorqueLimitMax", units: 'кН·м', xAccessorName: "rotorTorqueLimitMax", yAccessorName: "date", color: '#a0a' }, ], } const paramsGroups = [blockHeightGroup, blockSpeedGroup, pressureGroup, axialLoadGroup, hookWeightGroup, rotorTorqueGroup] -export const Column = ({lineGroup, data, interval})=>{ +export const Column = ({ lineGroup, data, interval }) => { let lines = [lineGroup.linePv] - if(lineGroup.lineSp) + if (lineGroup.lineSp) lines.push(lineGroup.lineSp) - let dataLast = null + let dataLast = null let pv = null - if(data?.length > 0 ){ + if (data?.length > 0) { dataLast = data[data.length - 1]; - if(lineGroup.linePv) + if (lineGroup.linePv) pv = dataLast[lineGroup.linePv?.xAccessorName] } - return( + return ( <> - - + - + ) } -export default function TelemetryView(props){ - let { id } = useParams(); - const [saubData, setSaubData] = useState([]) - const [chartInterval, setchartInterval] = useState(600) +// Словарь категорий для строк таблицы +const categoryDictionary = { + 1: { title: 'Авария' }, + 2: { title: 'Предупреждение' }, + 3: { title: 'Информация' }, +} - const handleReceiveDataSaub = (data)=>{ - if(data){ +// Конфигурация таблицы +const columns = [ + { + title: 'Дата', + dataIndex: 'date', + render: (item) => moment(item).format('DD MMM YYYY, HH:MM:ss'), + sorter: (a, b) => new Date(b.date) - new Date(a.date), + sortDirections: ['descend', 'ascend'], + }, + { + title: 'Категория', + dataIndex: 'categoryId', + render: (_, item) => categoryDictionary[item.categoryId].title, + style: (_, item) => categoryDictionary[item.categoryId].style, + sorter: (a, b) => a.categoryId - b.categoryId, + sortDirections: ['descend', 'ascend'], + }, + { + title: 'Сообщение', + dataIndex: 'message', + onFilter: (value, record) => record.name.indexOf(value) === 0, + }, + { + title: 'Пользователь', + dataIndex: 'user', + }, +]; + +export default function TelemetryView(props) { + let { id } = useParams() + const [saubData, setSaubData] = useState([]) + const [chartInterval, setChartInterval] = useState(600) + const [messages, setMessages] = useState([]) + + const [loader] = useState(false) // , setLoader + + const handleReceiveDataSaub = (data) => { + if (data) { setSaubData(data) - } + } } - useEffect( ()=> { + const handleReceiveMessages = (messages) => { + if (messages) { + setMessages(messages.items.splice(0, 3)) + } + } + + useEffect(() => { DataService.getData(id) - .then(handleReceiveDataSaub) - .catch(error=>console.error(error)) + .then(handleReceiveDataSaub) + .catch(error => console.error(error)) + + MessageService.getMessage(id) + .then(handleReceiveMessages) + .catch(error => console.error(error)) - return Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub) - }, [id]); + let unSubscribeDataSaubHub = Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub) + let unSubscribeMessagesHub = Subscribe('ReceiveMessages', `well_${id}`, handleReceiveMessages) + return () => { + unSubscribeDataSaubHub() + unSubscribeMessagesHub() + } + }, [id]); - const colSpan = 24/(paramsGroups.length ) - - return(
+ const colSpan = 24 / (paramsGroups.length) + + return (
{/*
Well id: {id}; points count: {saubData.length}
*/} - +
- + - Интервал:  - + Интервал:  + + + + + @@ -147,12 +210,22 @@ export default function TelemetryView(props){ - {paramsGroups.map(group=> - - - )} + {paramsGroups.map(group => + + + )} +
`event_message_${record.categoryId}`} + size={'small'} + pagination={false} + rowKey={(record) => record.id} + /> + {loader && } ) } \ No newline at end of file diff --git a/src/pages/Wells.jsx b/src/pages/Wells.jsx index 5f89be8..e973dd3 100644 --- a/src/pages/Wells.jsx +++ b/src/pages/Wells.jsx @@ -68,7 +68,7 @@ export default function Wells(props){ try{ let newWells = (await WellService.getWells()).map(w =>{return {key:w.id, ...w}}) let wellsTree = groupBy(newWells, 'deposit', 'cluster', 'caption') - console.log(wellsTree) + console.log(Wells.wellsTree) setWells( newWells ) setWellsTree(wellsTree) } @@ -80,10 +80,6 @@ export default function Wells(props){ useEffect(()=>{updateWellsList()}, []) - const onChange = (value) =>{ - ; - } - return(<>

Скважины

{history.push(`well/${record.id}`)}, }; }}/> - {/* */} {loader&&} ) } \ No newline at end of file diff --git a/src/services/signalr/index.ts b/src/services/signalr/index.ts index 7b9f55b..e25e27c 100644 --- a/src/services/signalr/index.ts +++ b/src/services/signalr/index.ts @@ -9,19 +9,57 @@ const ConnectionOptions = { } const Connection = new HubConnectionBuilder() - .withUrl(`http://localhost:5000/hubs/telemetry`, ConnectionOptions) + .withUrl(`http://192.168.1.70:5000/hubs/telemetry`, ConnectionOptions) .withAutomaticReconnect() .build(); -const GetConnectionAsync = async ()=>{ - if(Connection.state !== HubConnectionState.Connected) - await Connection.start() - - if(Connection.state === HubConnectionState.Connected) +let connectionPromise: Promise + +// const GetConnectionAsync = async () => { +// try { +// await Connection.start(); +// console.log(Connection.state); +// } catch (err) { +// console.log(err); +// } +// } + + +const GetConnectionAsync = async () => { + if (Connection.state === HubConnectionState.Disconnected) + connectionPromise = Connection.start() + + if (Connection.state !== HubConnectionState.Connected) + await connectionPromise + return Connection - else - throw new Error('Can`t connect to websocket') -} + } + + +// const GetConnectionAsync = async () => { +// if (Connection.state === HubConnectionState.Disconnected) +// connectionPromise = Connection.start() + +// if (Connection.state !== HubConnectionState.Connected) +// await connectionPromise + +// return Connection +// } + +// const GetConnectionAsync = async ()=>{ +// if(Connection.state === HubConnectionState.Disconnected) +// await Connection.start() + +// if(Connection.state === HubConnectionState.Connecting) +// await Connection.onreconnected + +// // if(Connection.state === HubConnectionState.Disconnecting) +// // await Connection.onclose + +// // if(Connection.state === HubConnectionState.Connected) +// console.log(Connection.state) +// return Connection +// } type handlerFunction = (...args: any[]) => void; @@ -38,11 +76,10 @@ const Subscribe = ( methodName: string, groupName: string = '', handler: handlerFunction ):cleanFunction=>{ - GetConnectionAsync().then(async connection => { if(groupName) - await connection.send('AddToGroup', groupName) - connection.on(methodName, handler) + await Connection.send('AddToGroup', groupName) + Connection.on(methodName, handler) }) if(groupName) diff --git a/src/styles/App.less b/src/styles/App.less index 4cf4347..8da48d5 100644 --- a/src/styles/App.less +++ b/src/styles/App.less @@ -88,7 +88,7 @@ text-align: right; justify-content: flex-end; align-items:baseline; - margin: 1px 1rem 1px 1rem; + margin: 1px 1rem; flex: auto; } @@ -98,4 +98,4 @@ justify-content: baseline; margin: 1px 1rem 1px 1rem; flex: auto; -} \ No newline at end of file +} diff --git a/src/styles/message.css b/src/styles/message.css new file mode 100644 index 0000000..bcf9f7e --- /dev/null +++ b/src/styles/message.css @@ -0,0 +1,35 @@ +.event_message { + font-size: 14px; +} + +.event_message_off { + color: #505050; + background: #b5b5b5; +} + +.event_message_disabled { + color: #656565; + background: #b5b5b5; +} + +.event_message_1 { + color: white; + background: #ff3737; +} + +.event_message_2 { + color: black; + background: gold; +} + +.event_message_3, +.event_message_3:hover { + color: #c0c0c0; + background: #505060; + height: 1px; +} + +td.ant-table-column-sort { + color: black; + background-color: rgb(221, 247, 221); +} diff --git a/src/styles/message_telemetry.css b/src/styles/message_telemetry.css new file mode 100644 index 0000000..5eed9e1 --- /dev/null +++ b/src/styles/message_telemetry.css @@ -0,0 +1,30 @@ +.event_message { + font-size: 14px; +} + +.event_message_off { + color: #505050; + background: #b5b5b5; +} + +.event_message_disabled { + color: #656565; + background: #b5b5b5; +} + +.event_message_1 { + color: white; + background: #ff3737; +} + +.event_message_2 { + color: black; + background: gold; +} + +.event_message_3, +.event_message_3:hover { + color: #c0c0c0; + background: #505060; + height: 1px; +} \ No newline at end of file