diff --git a/.vscode/settings.json b/.vscode/settings.json index 24a6479..a8ab85f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ "день" - ] + ], + "liveServer.settings.port": 5501 } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8fc82f..cd7af1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1329,15 +1329,6 @@ "@hapi/hoek": "^8.3.0" } }, - "@hypnosphi/create-react-context": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", - "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", - "requires": { - "gud": "^1.0.0", - "warning": "^4.0.3" - } - }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1895,9 +1886,9 @@ }, "dependencies": { "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "requires": { "async-limiter": "~1.0.0" } @@ -2379,14 +2370,6 @@ "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" }, - "@types/http-proxy": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", - "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", - "requires": { - "@types/node": "*" - } - }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -4256,6 +4239,14 @@ "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.0.0-rc.1.tgz", "integrity": "sha512-p3JwpvIspZKHB7cRas3xoxa5BYeCOMCuCTp/AS/44RyvO8OuZ/Pz0DWl3WK2L+BMnoK72UUm0XnGLKiMEekaYA==" }, + "chartjs-plugin-zoom": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-1.1.1.tgz", + "integrity": "sha512-1q54WOzK7FtAjkbemQeqvmFUV0btNYIQny2HbQ6Awq9wUtCz7Zmj6vIgp3C1DYMQwN0nqgpC3vnApqiwI7cSdQ==", + "requires": { + "hammerjs": "^2.0.8" + } + }, "check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -7353,11 +7344,6 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "optional": true }, - "gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, "gzip-size": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", @@ -7367,6 +7353,11 @@ "pify": "^4.0.1" } }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -7751,25 +7742,6 @@ "requires-port": "^1.0.0" } }, - "http-proxy-middleware": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz", - "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==", - "requires": { - "@types/http-proxy": "^1.17.5", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "dependencies": { - "is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" - } - } - }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -10392,8 +10364,7 @@ "dependencies": { "hosted-git-info": { "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "resolved": "", "dev": true }, "normalize-package-data": { @@ -11690,11 +11661,6 @@ "ts-pnp": "^1.1.6" } }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -13401,18 +13367,6 @@ "whatwg-fetch": "^3.4.1" } }, - "react-datepicker": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.8.0.tgz", - "integrity": "sha512-iFVNEp8DJoX5yEvEiciM7sJKmLGrvE70U38KhpG13XrulNSijeHw1RZkhd/0UmuXR71dcZB/kdfjiidifstZjw==", - "requires": { - "classnames": "^2.2.6", - "date-fns": "^2.0.1", - "prop-types": "^15.7.2", - "react-onclickoutside": "^6.10.0", - "react-popper": "^1.3.8" - } - }, "react-dev-utils": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", @@ -13539,25 +13493,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "react-onclickoutside": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.10.0.tgz", - "integrity": "sha512-7i2L3ef+0ILXpL6P+Hg304eCQswh4jl3ynwR71BSlMU49PE2uk31k8B2GkP6yE9s2D4jTGKnzuSpzWxu4YxfQQ==" - }, - "react-popper": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", - "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", - "requires": { - "@babel/runtime": "^7.1.2", - "@hypnosphi/create-react-context": "^0.3.1", - "deep-equal": "^1.1.1", - "popper.js": "^1.14.4", - "prop-types": "^15.6.1", - "typed-styles": "^0.0.7", - "warning": "^4.0.2" - } - }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -15910,11 +15845,6 @@ "mime-types": "~2.1.24" } }, - "typed-styles": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", - "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" - }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -16768,8 +16698,7 @@ }, "ssri": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "resolved": "", "requires": { "figgy-pudding": "^3.5.1" } @@ -17263,9 +17192,9 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "requires": { "async-limiter": "~1.0.0" } diff --git a/package.json b/package.json index c042595..576e207 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,11 @@ "chart.js": "^3.3.0", "chartjs-adapter-moment": "^1.0.0", "chartjs-plugin-datalabels": "^2.0.0-rc.1", + "chartjs-plugin-zoom": "^1.1.1", "craco-less": "^1.17.1", - "date-fns": "^2.20.0", - "http-proxy-middleware": "^2.0.1", "moment": "^2.29.1", "pigeon-maps": "^0.19.7", "react": "^17.0.2", - "react-datepicker": "^3.8.0", "react-dom": "^17.0.2", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", @@ -34,7 +32,7 @@ "react_test": "react-scripts test", "eject": "react-scripts eject" }, - "proxy":"http://192.168.1.70:5000", + "proxy": "http://192.168.1.70:5000", "eslintConfig": { "extends": [ "react-app", diff --git a/public/manifest.json b/public/manifest.json index 080d6c7..1f2f141 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -6,16 +6,6 @@ "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" } ], "start_url": ".", diff --git a/src/___setupProxy_js b/src/___setupProxy_js deleted file mode 100644 index 18b7b0e..0000000 --- a/src/___setupProxy_js +++ /dev/null @@ -1,20 +0,0 @@ -// const { createProxyMiddleware } = require('http-proxy-middleware'); - -// module.exports = function(app) { -// app.use( -// '/api', -// createProxyMiddleware({ -// target: 'http://192.168.1.70:5000', -// changeOrigin: true, -// ws:true, -// }) -// ); -// app.use( -// '/hubs', -// createProxyMiddleware({ -// target: 'http://192.168.1.70:5000', -// changeOrigin: true, -// ws:true, -// }) -// ); -// }; \ No newline at end of file diff --git a/src/components/AnalysisDepthToDay.jsx b/src/components/AnalysisDepthToDay.jsx new file mode 100644 index 0000000..f7f5517 --- /dev/null +++ b/src/components/AnalysisDepthToDay.jsx @@ -0,0 +1,41 @@ +import { ChartDepthToDay } from './charts/ChartDepthToDay' +import { useParams } from "react-router-dom" +import notify from "../components/notify" +import { useState, useEffect } from 'react' +import { AnalyticsService } from "../services/api" + +const lines = [ + { label: "Глубина забоя", yAccessorName: "wellDepth", color: '#f00' }, + { label: "Положение инструмента", yAccessorName: "bitDepth", color: '#ff0' } +] + +export function AnalysisDepthToDay() { + let { id } = useParams() + const [depthToDayData, setDepthToDayData] = useState([]) + const [loader, setLoader] = useState(false) + + const handleReceiveDepthToDayData = (data) => { + setDepthToDayData(data) + } + + useEffect(() => { + setLoader(true) + AnalyticsService.getWellDepthToDay(id) + .then(handleReceiveDepthToDayData) + .catch(error => { + notify(`Не удалось получить данные для Анализа Глубина-День по скважине "${id}"`, + 'warning') + console.log(error) + }) + .finally(setLoader(false)) + }, [id]) + + return ( + <> + + + ) +} \ No newline at end of file diff --git a/src/components/AnalysisDepthToInterval.jsx b/src/components/AnalysisDepthToInterval.jsx new file mode 100644 index 0000000..840206c --- /dev/null +++ b/src/components/AnalysisDepthToInterval.jsx @@ -0,0 +1,61 @@ +import { useParams } from "react-router-dom" +import { DatePicker } from 'antd' +import notify from "../components/notify" +import { useState, useEffect } from 'react' +import { AnalyticsService } from '../services/api' +import { ChartDepthToInterval } from './charts/ChartDepthToInterval' +import { Select } from 'antd' + +const { Option } = Select + +const timePeriodCollection = [ + { value: '60', label: '1 минута' }, + { value: '300', label: '5 минут' }, + { value: '600', label: '10 минут' }, + { value: '1800', label: '30 минут' }, + { value: '3600', label: '1 час' }, + { value: '21600', label: '6 часов' }, + { value: '43200', label: '12 часов' }, + { value: '86400', label: '24 часа' } +] + +const { RangePicker } = DatePicker + +const lines = [{ label: 'График скорость проходки-интервал', yAccessorName: "intervalDepthProgress", xAccessorName: "intervalStartDate", color: '#00f' }] + +export function AnalysisDepthToInterval() { + let { id } = useParams() + const [depthToIntervalData, setDepthToIntervalData] = useState([]) + const [loader, setLoader] = useState(false) + const [chartInterval, setChartInterval] = useState(600) + + const children = timePeriodCollection.map((line) => ) + + const handleReceiveDepthToIntervalData = (data) => { + setDepthToIntervalData(data) + } + + useEffect(() => { + setLoader(true) + AnalyticsService.getWellDepthToInterval(id, chartInterval) + .then(handleReceiveDepthToIntervalData) + .catch(error => { + notify(`Не удалось получить данные для Анализа скорость проходки-интервал "${id}"`, + 'warning') + console.log(error) + }) + .finally(setLoader(false)) + }, [id, chartInterval]) + + return ( + <> + + + + ) +} \ No newline at end of file diff --git a/src/components/AnalysisOperationTime.jsx b/src/components/AnalysisOperationTime.jsx new file mode 100644 index 0000000..1bcd261 --- /dev/null +++ b/src/components/AnalysisOperationTime.jsx @@ -0,0 +1,60 @@ +import { useParams } from "react-router-dom" +import { DatePicker, ConfigProvider } from 'antd'; +import notify from "../components/notify" +import { useState, useEffect } from 'react' +import { AnalyticsService } from '../services/api' +import { ChartOperationTime } from './charts/ChartOperationTime' +import locale from "antd/lib/locale/ru_RU"; + +const { RangePicker } = DatePicker + +const lines = [{ labelAccessorName: "processName", pieceAccessorName: "duration" }] + +export function AnalysisOperationTime() { + let { id } = useParams() + const [operationTimeData, setOperationTimeData] = useState([]) + const [loader, setLoader] = useState(false) + const [range, setRange] = useState([]) + + let begin = null + let end = null + + const onChangeRange = (range) => { + setRange(range) + } + + const handleReceiveOperationTimeData = (data) => { + setOperationTimeData(data) + } + + useEffect(() => { + setLoader(true) + if (range?.length > 1) { + begin = range[0].toISOString() + end = range[1].toISOString() + } + AnalyticsService.getOperationsSummary(id, begin, end) + .then(handleReceiveOperationTimeData) + .catch(error => { + notify(`Не удалось получить данные для Анализа Операция-Время по скважине "${id}" за период с ${begin} по ${end}`, + 'warning') + console.log(error) + }) + .finally(setLoader(false)) + }, [id, range]) + + return ( + <> + + + + + + ) +} \ No newline at end of file diff --git a/src/components/charts/ChartDepthToDay.jsx b/src/components/charts/ChartDepthToDay.jsx new file mode 100644 index 0000000..761dc14 --- /dev/null +++ b/src/components/charts/ChartDepthToDay.jsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from 'react' +import { ChartDepthToDayBase } from './ChartDepthToDayBase' +import { CreateDataset } from './ChartTimeArchive' + +export const ChartDepthToDay = ({data, lines}) => { + const [depthToDayDataParams, setDepthToDayDataParams] = useState({data: {datasets: []}}) + + useEffect(() => { + if ((!lines) + || (!data)) + return + + let newDatasets = lines.map(lineCfg => { + let datasets = CreateDataset(lineCfg) + if(data.length !== 0) + datasets.data = data.map(dataItem => { + return { + x: new Date(dataItem[lineCfg.xAccessorName??'date']), + y: dataItem[lineCfg.yAccessorName], + label: dataItem[lineCfg.label] + } + }) + return datasets + }) + + let newParams = { + displayLabels: true, + data: { + datasets: newDatasets + } + } + setDepthToDayDataParams(newParams) + }, [data, lines]) + + return ( + + ) +} \ No newline at end of file diff --git a/src/components/charts/ChartDepthToDayBase.tsx b/src/components/charts/ChartDepthToDayBase.tsx new file mode 100644 index 0000000..e1e6159 --- /dev/null +++ b/src/components/charts/ChartDepthToDayBase.tsx @@ -0,0 +1,224 @@ +import {useEffect, useRef, useState} from 'react'; +import { + Chart, + TimeScale, + LinearScale, + Legend, + LineController, + PointElement, + LineElement, + ChartData, + ChartTypeRegistry, + ChartOptions +} from 'chart.js' +import 'chartjs-adapter-moment'; +import ChartDataLabels from 'chartjs-plugin-datalabels'; +import zoomPlugin from 'chartjs-plugin-zoom'; + +Chart.register(TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin); + +const defaultOptions = { + responsive: true, + aspectRatio: 6, + animation: false, + tooltips: { + enabled: true, + callbacks: { + label(tooltipItem:any) { + return tooltipItem.yLabel; + } + } + }, + events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], + scales: { + x:{ + type: 'time', + reverse: false, + time: { + stepSize: 20, + displayFormats: { + millisecond: 'HH:mm:ss.SSS', + second: 'HH:mm:ss', + minute: 'HH:mm:ss', + hour: 'DD HH:mm:ss', + day: 'MM.DD HH:mm', + week: 'yy.MM.DD HH:mm', + month: 'yyyy.MM.DD', + quarter: 'yyyy.MM.DD', + year: 'yyyy.MM', + }, + }, + grid:{ + drawTicks: false, + }, + ticks: { + z: 1, + display : true, + textStrokeColor : "#fff", + textStrokeWidth : 2, + color:"#000", + } + }, + + y:{ + type:'linear', + position:'top' + } + }, + elements:{ + point:{ + radius:0, + hoverRadius:5, + }, + }, + plugins:{ + legend:{ + display: true, + }, + datalabels: { + display: false, + }, + } +} + +export type ChartTimeData = ChartData + +export type ChartTimeDataParams = { + data: ChartTimeData, + xStart?: Date, + xInterval?: number, + displayLabels?: Boolean, +} + +export type ChartTimeBaseProps = { + dataParams: ChartTimeDataParams, + // TODO: Create good type for options + options?: ChartOptions | any, +} + +export type TimeParams = { + unit: String + stepSize: number +} + +const linesPerInterval = 32 + +export const timeUnitByInterval = (intervalSec:number):String => { + if(intervalSec <= 60) + return 'millisecond' + + if(intervalSec <= 32*60) + return 'second' + + if(intervalSec <= 32*60*60) + return 'minute' + + if(intervalSec <= 32*12*60*60) + return 'hour' + + if(intervalSec <= 32*24*60*60) + return 'day' + + if(intervalSec <= 32*7*24*60*60) + return 'week' + + if(intervalSec <= 32*30.4375*24*60*60) + return 'month' + + if(intervalSec <= 32*121.75*24*60*60) + return 'quarter' + else + return 'year' +} + +export const timeParamsByInterval = (intervalSec:number) :TimeParams => { + let stepSize = intervalSec + let unit = timeUnitByInterval(intervalSec) + + switch(unit){ + case "millisecond": + stepSize *= 1000 + break; + case "second": + //stepSize *= 1 + break; + case "minute": + stepSize /= 60 + break; + case "hour": + stepSize /= 60*60 + break; + case "day": + stepSize /= 24*60*60 + break; + case "week": + stepSize /= 7*24*60*60 + break; + case "month": + stepSize /= 30*24*60*60 + break; + case "quarter": + stepSize /= 91*24*60*60 + break; + case "year": + stepSize /= 365.25*24*60*60 + break; + } + + stepSize = Math.round(stepSize/linesPerInterval) + stepSize = stepSize > 0 ? stepSize : 1; + return {unit, stepSize} +} + +export const ChartDepthToDayBase: React.FC = ({options, dataParams}) => { + const chartRef = useRef(null) + const [chart, setChart] = useState() + + useEffect(() => { + if((chartRef.current)&&(!chart)) { + let thisOptions = {} + Object.assign(thisOptions, defaultOptions, options) + + let newChart = new Chart(chartRef.current, { + type: 'line', + plugins: [ChartDataLabels], + options: thisOptions, + data: dataParams.data + }) + setChart(newChart) + + return () => chart?.destroy() + } + }, [chart, options, dataParams]) + + useEffect(() => { + if (!chart) + return + + chart.data = dataParams.data + chart.options.aspectRatio = options?.aspectRatio + if (dataParams.xStart) { + let interval = Number(dataParams.xInterval ?? 600) + let start = new Date(dataParams.xStart) + let end = new Date(dataParams.xStart) + end.setSeconds(end.getSeconds() + interval) + let {unit, stepSize} = timeParamsByInterval(interval) + + if(chart.options.scales?.x){ + chart.options.scales.x.max = end.getTime() + chart.options.scales.x.min = start.getTime() + chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true + chart.options.scales.x.time.unit = unit + chart.options.scales.x.time.stepSize = stepSize + } + } + + chart.update() + }, [chart, dataParams, options]) + + return() +} \ No newline at end of file diff --git a/src/components/charts/ChartDepthToInterval.jsx b/src/components/charts/ChartDepthToInterval.jsx new file mode 100644 index 0000000..f3cb669 --- /dev/null +++ b/src/components/charts/ChartDepthToInterval.jsx @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react' +import { ChartDepthToIntervalBase } from './ChartDepthToIntervalBase' +import { CreateDataset } from './ChartTimeArchive' + +export const ChartDepthToInterval = ({ lines, data }) => { + const [depthToIntervalDataParams, setDepthToIntervalDataParams] = useState({ data: { datasets: [] } }) + + useEffect(() => { + if ((!lines) + || (!data)) + return + + let newDatasets = lines.map(lineCfg => { + let datasets = CreateDataset(lineCfg) + if (data.length !== 0) + datasets.data = data.map(dataItem => { + return { + x: new Date(dataItem[lineCfg.xAccessorName ?? 'intervalStartDate']), + y: dataItem[lineCfg.yAccessorName ?? 'intervalDepthProgress'], + } + }) + return datasets + }) + + let newParams = { + displayLabels: true, + data: { + datasets: newDatasets + } + } + setDepthToIntervalDataParams(newParams) + + }, [data, lines]) + + return (<> + + + ) +} \ No newline at end of file diff --git a/src/components/charts/ChartDepthToIntervalBase.tsx b/src/components/charts/ChartDepthToIntervalBase.tsx new file mode 100644 index 0000000..9760d0b --- /dev/null +++ b/src/components/charts/ChartDepthToIntervalBase.tsx @@ -0,0 +1,224 @@ +import { useEffect, useRef, useState } from 'react' +import { + Chart, + TimeScale, + Legend, + PointElement, + ChartData, + ChartTypeRegistry, + ChartOptions, + BarController, + BarElement, + TimeSeriesScale, + LinearScale, + LineController, +} from 'chart.js' +import 'chartjs-adapter-moment' +import ChartDataLabels from 'chartjs-plugin-datalabels' + +Chart.register(TimeScale, BarController, BarElement, PointElement, TimeSeriesScale, LineController, LinearScale, Legend, ChartDataLabels) + +const defaultOptions = { + responsive: true, + aspectRatio: 4, + animation: false, + tooltips: { + enabled: true, + callbacks: { + label(tooltipItem: any) { + return tooltipItem.yLabel; + } + } + }, + events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], + scales: { + x: { + position: 'bottom', + type: 'time', + reverse: false, + time: { + stepSize: 20, + displayFormats: { + millisecond: 'HH:mm:ss.SSS', + second: 'HH:mm:ss', + minute: 'HH:mm:ss', + hour: 'DD HH:mm:ss', + day: 'MM.DD HH:mm', + week: 'yy.MM.DD HH:mm', + month: 'yyyy.MM.DD', + quarter: 'yyyy.MM.DD', + year: 'yyyy.MM', + }, + }, + grid: { + drawTicks: false, + }, + ticks: { + z: 1, + display: true, + textStrokeColor: "#ffff", + textStrokeWidth: 2, + color: "#000", + } + }, + y: { + beginAtZero: true, + } + }, + elements: { + point: { + radius: 0, + hoverRadius: 5, + }, + }, + plugins: { + legend: { + display: true, + }, + datalabels: { + display: false, + }, + } +} + +export type ChartTimeData = ChartData + +export type ChartTimeDataParams = { + data: ChartTimeData, + xStart?: Date, + xInterval?: number, + displayLabels?: Boolean, +} + +export type ChartTimeBaseProps = { + dataParams: ChartTimeDataParams, + // TODO: Create good type for options + options?: ChartOptions | any, +} + +export type TimeParams = { + unit: String + stepSize: number +} + +const linesPerInterval = 32 + +export const timeUnitByInterval = (intervalSec: number): String => { + if (intervalSec <= 60) + return 'millisecond' + + if (intervalSec <= 32 * 60) + return 'second' + + if (intervalSec <= 32 * 60 * 60) + return 'minute' + + if (intervalSec <= 32 * 12 * 60 * 60) + return 'hour' + + if (intervalSec <= 32 * 24 * 60 * 60) + return 'day' + + if (intervalSec <= 32 * 7 * 24 * 60 * 60) + return 'week' + + if (intervalSec <= 32 * 30.4375 * 24 * 60 * 60) + return 'month' + + if (intervalSec <= 32 * 121.75 * 24 * 60 * 60) + return 'quarter' + else + return 'year' +} + +export const timeParamsByInterval = (intervalSec: number): TimeParams => { + let stepSize = intervalSec + let unit = timeUnitByInterval(intervalSec) + + switch (unit) { + case "millisecond": + stepSize *= 1000 + break; + case "second": + //stepSize *= 1 + break; + case "minute": + stepSize /= 60 + break; + case "hour": + stepSize /= 60 * 60 + break; + case "day": + stepSize /= 24 * 60 * 60 + break; + case "week": + stepSize /= 7 * 24 * 60 * 60 + break; + case "month": + stepSize /= 30 * 24 * 60 * 60 + break; + case "quarter": + stepSize /= 91 * 24 * 60 * 60 + break; + case "year": + stepSize /= 365.25 * 24 * 60 * 60 + break; + } + + stepSize = Math.round(stepSize / linesPerInterval) + stepSize = stepSize > 0 ? stepSize : 1; + return { unit, stepSize } +} + +export const ChartDepthToIntervalBase: React.FC = ({ options, dataParams }) => { + const chartRef = useRef(null) + const [chart, setChart] = useState() + + useEffect(() => { + if ((chartRef.current) && (!chart)) { + let thisOptions = {} + Object.assign(thisOptions, defaultOptions, options) + + let newChart = new Chart(chartRef.current, { + type: 'bar', + plugins: [ChartDataLabels], + options: thisOptions, + data: dataParams.data + }) + setChart(newChart) + + return () => chart?.destroy() + } + }, [chart, options, dataParams]) + + useEffect(() => { + if (!chart) + return + + chart.data = dataParams.data + chart.options.aspectRatio = options?.aspectRatio + if (dataParams.xStart) { + let interval = Number(dataParams.xInterval ?? 600) + let start = new Date(dataParams.xStart) + let end = new Date(dataParams.xStart) + end.setSeconds(end.getSeconds() + interval) + let { unit, stepSize } = timeParamsByInterval(interval) + + if (chart.options.scales?.x) { + chart.options.scales.x.max = end.getTime() + chart.options.scales.x.min = start.getTime() + chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true + chart.options.scales.x.time.unit = unit + chart.options.scales.x.time.stepSize = stepSize + } + } + + chart.update() + }, [chart, dataParams, options]) + + return () +} \ No newline at end of file diff --git a/src/components/charts/ChartOperationTime.jsx b/src/components/charts/ChartOperationTime.jsx new file mode 100644 index 0000000..e3f4829 --- /dev/null +++ b/src/components/charts/ChartOperationTime.jsx @@ -0,0 +1,67 @@ +import { useEffect, useState } from 'react' +import { ChartOpertationTimeBase } from './ChartOperationTimeBase' +import moment from 'moment' + +export const CreateLabels = () => { + let labels = [] + return labels +} + +export const CreateData = (lineConfig) => { + let datasets = { + label: lineConfig.label, + data: [], + backgroundColor: [ + '#f00', '#ff0', '#f0f', '#0ff', '#00f', '#0f0' + ], + } + return datasets +} + +export const ChartOperationTime = ({ lines, data, rangeDate }) => { + const [opertationTimeDataParams, setOpertationTimeDataParams] = useState({ data: { labels: [], datasets: [] } }) + + useEffect(() => { + if ((!lines) + || (!data)) + return + + let newLabels = lines.map(lineCfg => { + let labels = CreateLabels(lineCfg) + if (data.length !== 0) + labels = data.map(dataItem => { + return dataItem[lineCfg.labelAccessorName] + }) + return labels + }) + + let newDatasets = lines.map(lineCfg => { + let datasets = CreateData(lineCfg) + if (data.length !== 0) + datasets.data = data.map(dataItem => { + return dataItem[lineCfg.pieceAccessorName] + }) + return datasets + }) + + let interval = rangeDate ? (rangeDate[1] - rangeDate[0]) / 1000 : null + let startDate = rangeDate ? rangeDate[0] : moment() + let newParams = { + xInterval: interval, + xStart: startDate, + data: { + labels: newLabels, + datasets: newDatasets + } + } + setOpertationTimeDataParams(newParams) + + console.log(newParams) + + }, [data, lines, rangeDate]) + + return (<> + + + ) +} \ No newline at end of file diff --git a/src/components/charts/ChartOperationTimeBase.tsx b/src/components/charts/ChartOperationTimeBase.tsx new file mode 100644 index 0000000..5d9e96c --- /dev/null +++ b/src/components/charts/ChartOperationTimeBase.tsx @@ -0,0 +1,177 @@ +import { useEffect, useRef, useState } from 'react' +import { + Chart, + ArcElement, + TimeScale, + Legend, + PointElement, + ChartData, + ChartTypeRegistry, + ChartOptions, + DoughnutController, +} from 'chart.js' +import 'chartjs-adapter-moment' +import ChartDataLabels from 'chartjs-plugin-datalabels' + +Chart.register(TimeScale, DoughnutController, PointElement, ArcElement, Legend, ChartDataLabels) + +const defaultOptions = { + responsive: true, + title: { + display: true, + position: "top", + text: "Doughnut Chart", + fontSize: 18, + fontColor: "#111" + }, + legend: { + display: true, + position: "bottom", + labels: { + fontColor: "#333", + fontSize: 16 + } + } +} + +export type ChartTimeData = ChartData + +export type ChartTimeDataParams = { + data: ChartTimeData, + xStart?: Date, + xInterval?: number, + displayLabels?: Boolean, +} + +export type ChartTimeBaseProps = { + dataParams: ChartTimeDataParams, + // TODO: Create good type for options + options?: ChartOptions | any, +} + +export type TimeParams = { + unit: String + stepSize: number +} + +const linesPerInterval = 32 + +export const timeUnitByInterval = (intervalSec: number): String => { + if (intervalSec <= 60) + return 'millisecond' + + if (intervalSec <= 32 * 60) + return 'second' + + if (intervalSec <= 32 * 60 * 60) + return 'minute' + + if (intervalSec <= 32 * 12 * 60 * 60) + return 'hour' + + if (intervalSec <= 32 * 24 * 60 * 60) + return 'day' + + if (intervalSec <= 32 * 7 * 24 * 60 * 60) + return 'week' + + if (intervalSec <= 32 * 30.4375 * 24 * 60 * 60) + return 'month' + + if (intervalSec <= 32 * 121.75 * 24 * 60 * 60) + return 'quarter' + else + return 'year' +} + +export const timeParamsByInterval = (intervalSec: number): TimeParams => { + let stepSize = intervalSec + let unit = timeUnitByInterval(intervalSec) + + switch (unit) { + case "millisecond": + stepSize *= 1000 + break; + case "second": + //stepSize *= 1 + break; + case "minute": + stepSize /= 60 + break; + case "hour": + stepSize /= 60 * 60 + break; + case "day": + stepSize /= 24 * 60 * 60 + break; + case "week": + stepSize /= 7 * 24 * 60 * 60 + break; + case "month": + stepSize /= 30 * 24 * 60 * 60 + break; + case "quarter": + stepSize /= 91 * 24 * 60 * 60 + break; + case "year": + stepSize /= 365.25 * 24 * 60 * 60 + break; + } + + stepSize = Math.round(stepSize / linesPerInterval) + stepSize = stepSize > 0 ? stepSize : 1; + return { unit, stepSize } +} + +export const ChartOpertationTimeBase: React.FC = ({ options, dataParams }) => { + const chartRef = useRef(null) + const [chart, setChart] = useState() + + useEffect(() => { + if ((chartRef.current) && (!chart)) { + let thisOptions = {} + Object.assign(thisOptions, defaultOptions, options) + + let newChart = new Chart(chartRef.current, { + type: 'doughnut', + plugins: [ChartDataLabels], + options: thisOptions, + data: dataParams.data + }) + setChart(newChart) + + return () => chart?.destroy() + } + }, [chart, options, dataParams]) + + useEffect(() => { + if (!chart) + return + + chart.data = dataParams.data + chart.options.aspectRatio = options?.aspectRatio + if (dataParams.xStart) { + let interval = Number(dataParams.xInterval ?? 600) + let start = new Date(dataParams.xStart) + let end = new Date(dataParams.xStart) + end.setSeconds(end.getSeconds() + interval) + let { unit, stepSize } = timeParamsByInterval(interval) + + if (chart.options.scales?.x) { + chart.options.scales.x.max = end.getTime() + chart.options.scales.x.min = start.getTime() + chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true + chart.options.scales.x.time.unit = unit + chart.options.scales.x.time.stepSize = stepSize + } + } + + chart.update() + }, [chart, dataParams, options]) + + return () +} \ No newline at end of file diff --git a/src/components/charts/ChartTimeArchive.jsx b/src/components/charts/ChartTimeArchive.jsx index d392104..e2bffa7 100644 --- a/src/components/charts/ChartTimeArchive.jsx +++ b/src/components/charts/ChartTimeArchive.jsx @@ -2,15 +2,15 @@ import moment from 'moment'; import { useEffect, useState} from 'react'; import {ChartTimeBase} from './ChartTimeBase' -const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16) +export const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16) -const CreateDataset = (lineConfig) => { +export const CreateDataset = (lineConfig) => { let color = lineConfig.borderColor ?? lineConfig.backgroundColor ?? lineConfig.color ?? GetRandomColor() - let dataset = { + let datasets = { label: lineConfig.label, data: [], backgroundColor: lineConfig.backgroundColor ?? color, @@ -18,7 +18,7 @@ const CreateDataset = (lineConfig) => { borderWidth: lineConfig.borderWidth ?? 1, borderDash: lineConfig.dash ?? [], } - return dataset + return datasets } const ChartOptions = { @@ -28,14 +28,14 @@ const ChartOptions = { // display: false, // maxHeight: 64, // fullSize: true, - // posision:'chartArea', + // position: 'chartArea', // align: 'start', // } // } } export const ChartTimeArchive = ({lines, data, yDisplay, rangeDate, chartRatio}) => { - const [dataParams, setDataParams] = useState({data:{datasets:[]}}) + const [dataParams, setDataParams] = useState({data:{datasets: []}}) useEffect(() => { if ((!lines) @@ -70,4 +70,4 @@ export const ChartTimeArchive = ({lines, data, yDisplay, rangeDate, chartRatio}) opt.aspectRatio = chartRatio return () -} +} \ No newline at end of file diff --git a/src/components/charts/ChartTimeBase.tsx b/src/components/charts/ChartTimeBase.tsx index e56104e..eae15c3 100644 --- a/src/components/charts/ChartTimeBase.tsx +++ b/src/components/charts/ChartTimeBase.tsx @@ -12,13 +12,22 @@ import { ChartOptions} from 'chart.js' import 'chartjs-adapter-moment'; import ChartDataLabels from 'chartjs-plugin-datalabels'; +import zoomPlugin from 'chartjs-plugin-zoom'; -Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels ); +Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels, zoomPlugin ); const defaultOptions = { responsive: true, aspectRatio: 0.45, - animation: false, + animation: false, + tooltips: { + enabled: true, + callbacks: { + label(tooltipItem:any) { + return tooltipItem.yLabel; + } + } + }, events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], scales: { y:{ @@ -68,6 +77,18 @@ const defaultOptions = { datalabels: { display: false, }, + zoom: { + zoom: { + wheel: { + enabled: true, + modifierKey: 'alt', + }, + pinch: { + enabled: true + }, + mode: 'x', + } + }, } } @@ -97,7 +118,7 @@ export type TimeParams = { const linesPerInterval = 32 -const timeUnitByInterval = (intervalSec:number):String => { +export const timeUnitByInterval = (intervalSec:number):String => { if(intervalSec <= 60) return 'millisecond' @@ -125,7 +146,7 @@ const timeUnitByInterval = (intervalSec:number):String => { return 'year' } -const timeParamsByInterval = (intervalSec:number) :TimeParams => { +export const timeParamsByInterval = (intervalSec:number) :TimeParams => { let stepSize = intervalSec let unit = timeUnitByInterval(intervalSec) diff --git a/src/components/charts/ChartTimeOnline.tsx b/src/components/charts/ChartTimeOnline.tsx index 99ebfff..4e9eb26 100644 --- a/src/components/charts/ChartTimeOnline.tsx +++ b/src/components/charts/ChartTimeOnline.tsx @@ -2,7 +2,6 @@ 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) @@ -110,6 +109,9 @@ export const ChartTimeOnline: React.FC = (props) => { align: 'left', anchor: 'center', clip: true + }, + tooltip: { + enable: true } } } diff --git a/src/concept/AnalisysOperationsToInterval.jsx b/src/concept/AnalisysOperationsToInterval.jsx new file mode 100644 index 0000000..2080547 --- /dev/null +++ b/src/concept/AnalisysOperationsToInterval.jsx @@ -0,0 +1,44 @@ +import { ChartOperationsToInterval } from './charts/ChartOperationsToInterval' +import { useParams } from "react-router-dom" +import notify from "../components/notify" +import { useState, useEffect } from 'react' +import { AnalyticsService } from "../services/api" + +const lines = [ + { yAccessorName: "operationName", xAccessorName: "duration" }, +] + +export function AnalysisOperationsToInterval() { + let { id } = useParams() + const [operationsToInterval, setOperationsToInterval] = useState([]) + const [loader, setLoader] = useState(false) + + const handleReceiveOperationsToIntervalData = (data) => { + setOperationsToInterval(data[0].operations) + console.log(data) + } + + useEffect(() => { + setLoader(true) + let intervalHoursTimestamp = 600 + let workBeginTimestamp = 1 + + AnalyticsService.getOperationsToInterval(id, intervalHoursTimestamp, workBeginTimestamp) + .then(handleReceiveOperationsToIntervalData) + .catch(error => { + notify(`Не удалось получить данные для Анализа Глубина-День по скважине "${id}"`, + 'warning') + console.log(error) + }) + .finally(setLoader(false)) + }, [id]) + + return ( + <> + + + ) +} \ No newline at end of file diff --git a/src/concept/ChartOperationsToInterval.jsx b/src/concept/ChartOperationsToInterval.jsx new file mode 100644 index 0000000..e3264a5 --- /dev/null +++ b/src/concept/ChartOperationsToInterval.jsx @@ -0,0 +1,83 @@ +import { ChartDepthToIntervalBase } from './ChartOperationsToIntervalBase' +import { useEffect, useState } from 'react' +import { GetRandomColor, CreateDataset } from './ChartTimeArchive' + +const CreateLabels = () => { + let labels = [] + return labels +} + +const CreateData = (lineConfig) => { + + let datasets = { + label: lineConfig.label, + data: [], + backgroundColor: GetRandomColor() + } + return datasets +} + +const labels = ['Первый'] +const datasets = [ + { + label: 'Dataset 1', + data: [123], + backgroundColor: "#00f", + }, + { + label: 'Dataset 2', + data: [170], + backgroundColor: "#f0f", + }, + { + label: 'Dataset 3', + data: [150], + backgroundColor: "#0f0", + } +] + + +export const ChartOperationsToInterval = ({ data, lines }) => { + const [operationsToIntervalDataParams, setOperationsToIntervalDataParams] = useState({data: {labels: labels, datasets: datasets}}) + + useEffect(() => { + // if ((!lines) + // || (!data)) + // return + + let newDatasets = lines.map(lineCfg => { + let datasets = CreateData(lineCfg) + if (data.length !== 0) + datasets = data.map(dataItem => { + return { + label: 'Dataset 1', + data: [dataItem[lineCfg.xAccessorName]], + backgroundColor: "#00f", + } + }) + return datasets + }) + + // let newLabels = lines.map(lineCfg => { + // let labels = CreateLabels(lineCfg) + // if (data.length !== 0) + // labels = data.map(dataItem => { + // return dataItem[lineCfg.yAccessorName] + // }) + // return labels + // }) + + let newParams = { + data: { + datasets: newDatasets + } + } + setOperationsToIntervalDataParams(newParams) + + }, [data, lines]) + + return (<> + + + ) +} \ No newline at end of file diff --git a/src/concept/ChartOperationsToIntervalBase.tsx b/src/concept/ChartOperationsToIntervalBase.tsx new file mode 100644 index 0000000..3f715e4 --- /dev/null +++ b/src/concept/ChartOperationsToIntervalBase.tsx @@ -0,0 +1,180 @@ +import { useEffect, useRef, useState } from 'react' +import { + Chart, + TimeScale, + Legend, + PointElement, + ChartData, + ChartTypeRegistry, + ChartOptions, + BarController, + BarElement, + TimeSeriesScale, + LinearScale, + LineController, + CategoryScale +} from 'chart.js' +import 'chartjs-adapter-moment' +import ChartDataLabels from 'chartjs-plugin-datalabels' + +Chart.register(TimeScale, BarController, BarElement, PointElement, TimeSeriesScale, LineController, LinearScale, CategoryScale, Legend, ChartDataLabels) + +const defaultOptions = { + plugins: { + title: { + display: true, + text: 'Chart.js Bar Chart - Stacked' + }, + }, + responsive: true, + scales: { + x: { + stacked: true, + }, + y: { + stacked: true + } + } +} + +export type ChartTimeData = ChartData + +export type ChartTimeDataParams = { + data: ChartTimeData, + xStart?: 0, + xInterval?: number, + displayLabels?: Boolean, +} + +export type ChartTimeBaseProps = { + dataParams: ChartTimeDataParams, + // TODO: Create good type for options + options?: ChartOptions | any, +} + +export type TimeParams = { + unit: String + stepSize: number +} + +const linesPerInterval = 32 + +export const timeUnitByInterval = (intervalSec: number): String => { + if (intervalSec <= 60) + return 'millisecond' + + if (intervalSec <= 32 * 60) + return 'second' + + if (intervalSec <= 32 * 60 * 60) + return 'minute' + + if (intervalSec <= 32 * 12 * 60 * 60) + return 'hour' + + if (intervalSec <= 32 * 24 * 60 * 60) + return 'day' + + if (intervalSec <= 32 * 7 * 24 * 60 * 60) + return 'week' + + if (intervalSec <= 32 * 30.4375 * 24 * 60 * 60) + return 'month' + + if (intervalSec <= 32 * 121.75 * 24 * 60 * 60) + return 'quarter' + else + return 'year' +} + +export const timeParamsByInterval = (intervalSec: number): TimeParams => { + let stepSize = intervalSec + let unit = timeUnitByInterval(intervalSec) + + switch (unit) { + case "millisecond": + stepSize *= 1000 + break; + case "second": + //stepSize *= 1 + break; + case "minute": + stepSize /= 60 + break; + case "hour": + stepSize /= 60 * 60 + break; + case "day": + stepSize /= 24 * 60 * 60 + break; + case "week": + stepSize /= 7 * 24 * 60 * 60 + break; + case "month": + stepSize /= 30 * 24 * 60 * 60 + break; + case "quarter": + stepSize /= 91 * 24 * 60 * 60 + break; + case "year": + stepSize /= 365.25 * 24 * 60 * 60 + break; + } + + stepSize = Math.round(stepSize / linesPerInterval) + stepSize = stepSize > 0 ? stepSize : 1; + return { unit, stepSize } +} + +export const ChartDepthToIntervalBase: React.FC = ({ options, dataParams }) => { + const chartRef = useRef(null) + const [chart, setChart] = useState() + + useEffect(() => { + if ((chartRef.current) && (!chart)) { + let thisOptions = {} + Object.assign(thisOptions, defaultOptions, options) + + let newChart = new Chart(chartRef.current, { + type: 'bar', + plugins: [ChartDataLabels], + options: thisOptions, + data: dataParams.data + }) + setChart(newChart) + + return () => chart?.destroy() + } + }, [chart, options, dataParams]) + + useEffect(() => { + if (!chart) + return + + chart.data = dataParams.data + chart.options.aspectRatio = options?.aspectRatio + if (dataParams.xStart) { + let interval = Number(dataParams.xInterval ?? 600) + let start = new Date(dataParams.xStart) + let end = new Date(dataParams.xStart) + end.setSeconds(end.getSeconds() + interval) + let { unit, stepSize } = timeParamsByInterval(interval) + + if (chart.options.scales?.x) { + chart.options.scales.x.max = end.getTime() + chart.options.scales.x.min = start.getTime() + chart.options.scales.x.ticks.display = dataParams.displayLabels ?? true + chart.options.scales.x.time.unit = unit + chart.options.scales.x.time.stepSize = stepSize + } + } + + chart.update() + }, [chart, dataParams, options]) + + return () +} \ No newline at end of file diff --git a/src/pages/Analysis.jsx b/src/pages/Analysis.jsx index 17186de..dacb12c 100644 --- a/src/pages/Analysis.jsx +++ b/src/pages/Analysis.jsx @@ -1,11 +1,30 @@ -// import {UserOfWells} from "../components/UserOfWells"; +import { AnalysisDepthToDay } from '../components/AnalysisDepthToDay' +import { AnalysisDepthToInterval } from '../components/AnalysisDepthToInterval' +import { AnalysisOperationTime } from '../components/AnalysisOperationTime' +import { Row, Col } from 'antd' + +export default function Analysis() { -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 6c5d4f0..16af5f2 100644 --- a/src/pages/Archive.jsx +++ b/src/pages/Archive.jsx @@ -15,7 +15,7 @@ import moment from 'moment' import notify from '../components/notify' import LoaderPortal from '../components/LoaderPortal' -const { RangePicker } = DatePicker; +const { RangePicker } = DatePicker const SaveObject = (key, obj) => { let json = JSON.stringify(obj) diff --git a/src/pages/Deposit.jsx b/src/pages/Deposit.jsx index 9988fc6..26818fb 100644 --- a/src/pages/Deposit.jsx +++ b/src/pages/Deposit.jsx @@ -7,7 +7,7 @@ import {ClusterService} from '../services/api' import notify from '../components/notify' const calcViewParams = (clusters) => { - if (clusters || clusters.length === 0) + if ((!clusters) || clusters.length === 0) return {center:[60.81226, 70.0562], zoom: 5} const center = clusters.reduce((sum, cluster) => { diff --git a/src/pages/Messages.jsx b/src/pages/Messages.jsx index 2f9e92e..9a37f56 100644 --- a/src/pages/Messages.jsx +++ b/src/pages/Messages.jsx @@ -47,7 +47,7 @@ const columns = [ ]; const filterOptions = [ - {label: 'Авария', value: 1}, + {label: 'Важное', value: 1}, {label: 'Предупреждение', value: 2}, {label: 'Информация', value: 3}, ] @@ -72,6 +72,7 @@ export default function Messages() { useEffect(() => { const GetMessages = async () => { setLoader(true) + try { let begin = null let end = null @@ -85,6 +86,12 @@ export default function Messages() { categories, begin, end) + if (paginatedMessages === null){ + notify(`Данных по скважине "${id}" нет`, 'warning') + setLoader(false) + return + } + setMessages(paginatedMessages.items.map(m => { return { key: m.id, @@ -97,9 +104,10 @@ export default function Messages() { total: paginatedMessages.count, current: Math.floor(paginatedMessages.skip / pageSize), }) + } catch (ex) { - notify(`Не удалось загрузить сообщения по скважине "${id}"`, 'error') console.log(ex) + notify(`Не удалось загрузить сообщения по скважине "${id}"`, 'error') } setLoader(false) } @@ -108,7 +116,6 @@ export default function Messages() { return ( <> -

Фильтр сообщений

setStep(value)} + ) + const options = timePeriodCollection.map((line) => ) const handleReceiveDataSaub = (data) => { if (data) { @@ -268,8 +270,8 @@ export default function TelemetryView(props) {   Интервал:  - + {options}   diff --git a/src/services/signalr/index.ts b/src/services/signalr/index.ts index 449cd65..72ed2fb 100644 --- a/src/services/signalr/index.ts +++ b/src/services/signalr/index.ts @@ -48,6 +48,21 @@ type handlerFunction = (...args: any[]) => void; type cleanFunction = (...args: any[]) => void; +const MakeUnsubscribeFunction = ( + connection: HubConnection, + methodName: string, + groupName: (string|null)):cleanFunction => { + return async() => { + if(connection.state === HubConnectionState.Connected) + { + if(groupName) + await connection.send('RemoveFromGroup', groupName) + connection.off(methodName) + } + connection.stop() + } +} + /** Subscribe on some SignalR method (topic). * @example useEffect(() => Subscribe('methodName', `${id}`, handleNewData), [id]); * @param {string} methodName name of the method @@ -66,12 +81,7 @@ const Subscribe = ( connection.on(methodName, handler) }) - if(groupName) - return () => { - Connections[hubUrl].send('RemoveFromGroup', groupName) - .finally(()=>Connections[hubUrl].off(methodName)) - } - return () => Connections[hubUrl].off(methodName) + return MakeUnsubscribeFunction(Connections[hubUrl],methodName,groupName) } /** Invokes some SignalR method. diff --git a/src/styles/App.less b/src/styles/App.less index 8f7e2cc..25c6ad0 100644 --- a/src/styles/App.less +++ b/src/styles/App.less @@ -41,7 +41,8 @@ html { .header { display: flex; align-items: center; - //padding: 4px 24px; + justify-content: space-around; + gap: 50px; } .header .logo { @@ -55,9 +56,7 @@ html { .header .title{ flex-grow: 1; color: #fff; - text-align: start; - justify-content: start; - margin-left: 100px; + padding-left: 100px; } .header button{ @@ -154,9 +153,13 @@ tr.table_row_size { margin-right: 5px; } +.header-tree-select { + width: 300px +} + .header-tree-select *{ color: #fff; - font-size: '1.5rem'; + font-size: 1rem; } .header-tree-select{