diff --git a/src/components/d3/renders/area.ts b/src/components/d3/renders/area.ts index 9de5819..441b764 100644 --- a/src/components/d3/renders/area.ts +++ b/src/components/d3/renders/area.ts @@ -3,6 +3,8 @@ import * as d3 from 'd3' import { ChartRegistry } from '@components/d3/types' import { makePointsOptimizator } from '@utils' +import { appendTransition } from './base' + export const renderArea = >( xAxis: (value: any) => number, yAxis: (value: any) => number, @@ -47,10 +49,8 @@ export const renderArea = >( if (chart().selectAll('path').empty()) chart().append('path') - chart().selectAll('path') - .transition() - .duration(chart.animDurationMs || 0) - .attr('d', area(data as any)) + appendTransition(chart().selectAll('path'), chart) + .attr('d', area(data)) .attr('stroke-dasharray', chart.dash ? String(chart.dash) : null) .attr('fill', chart.areaColor ?? null) diff --git a/src/components/d3/renders/base.ts b/src/components/d3/renders/base.ts new file mode 100644 index 0000000..93c1de6 --- /dev/null +++ b/src/components/d3/renders/base.ts @@ -0,0 +1,12 @@ +import * as d3 from 'd3' + +import { ChartRegistry } from '../types' + +export const appendTransition = ( + elms: d3.Selection, + chart: ChartRegistry +): d3.Selection => { + if (chart.animDurationMs && chart.animDurationMs > 0) + return elms.transition().duration(chart.animDurationMs) as any + return elms +} diff --git a/src/components/d3/renders/line.ts b/src/components/d3/renders/line.ts index abc9307..f9b0760 100644 --- a/src/components/d3/renders/line.ts +++ b/src/components/d3/renders/line.ts @@ -3,6 +3,8 @@ import * as d3 from 'd3' import { ChartRegistry } from '@components/d3/types' import { makePointsOptimizator } from '@utils' +import { appendTransition } from './base' + export const renderLine = >( xAxis: (value: any) => number, yAxis: (value: any) => number, @@ -35,11 +37,9 @@ export const renderLine = >( if (chart().selectAll('path').empty()) chart().append('path') - chart().selectAll('path') - .transition() - .duration(chart.animDurationMs ?? 0) + appendTransition(chart().selectAll('path'), chart) .attr('d', line(data)) - .attr('stroke-dasharray', String(chart.dash ?? '')) + .attr('stroke-dasharray', chart.dash ? String(chart.dash) : null) return data } diff --git a/src/components/d3/renders/needle.ts b/src/components/d3/renders/needle.ts index b3944bb..eedd5d2 100644 --- a/src/components/d3/renders/needle.ts +++ b/src/components/d3/renders/needle.ts @@ -1,5 +1,7 @@ import { ChartOffset, ChartRegistry } from '@components/d3/types' +import { appendTransition } from './base' + export const renderNeedle = >( xAxis: (value: d3.NumberValue) => number, yAxis: (value: d3.NumberValue) => number, @@ -19,10 +21,7 @@ export const renderNeedle = >( currentNeedles.exit().remove() currentNeedles.enter().append('line') - chart() - .selectAll('line') - .transition() - .duration(chart.animDurationMs ?? 0) + appendTransition(chart().selectAll('line'), chart) .attr('x1', (d) => xAxis(chart.x(d))) .attr('x2', (d) => xAxis(chart.x(d))) .attr('y1', height - offset.bottom - offset.top) diff --git a/src/components/d3/renders/points.ts b/src/components/d3/renders/points.ts index 674ac75..9788556 100644 --- a/src/components/d3/renders/points.ts +++ b/src/components/d3/renders/points.ts @@ -1,5 +1,7 @@ import { ChartRegistry, PointChartDataset } from '@components/d3/types' +import { appendTransition } from './base' + const defaultConfig: Required> = { radius: 3, shape: 'circle', @@ -10,6 +12,14 @@ const defaultConfig: Required> = { fillOpacity: 1, } +const getPointsRoot = (chart: ChartRegistry, embeded?: boolean): d3.Selection => { + const root = chart() + if (!embeded) return root + if (root.select('.points').empty()) + root.append('g').attr('class', 'points') + return root.select('.points') +} + export const renderPoint = >( xAxis: (value: any) => number, yAxis: (value: any) => number, @@ -24,19 +34,7 @@ export const renderPoint = >( config = { ...defaultConfig, ...chart } else return data - const getPointsRoot = (): d3.Selection => { - let root = chart() - if (embeded) { - if (root.select('.points').empty()) - root.append('g').attr('class', 'points') - root = root.select('.points') - } - return root - } - - getPointsRoot() - .transition() - .duration(chart.animDurationMs ?? 0) + appendTransition(getPointsRoot(chart, embeded), chart) .attr('stroke-width', config.strokeWidth) .attr('fill-opacity', config.fillOpacity) .attr('fill', config.fillColor) @@ -45,14 +43,14 @@ export const renderPoint = >( const shape = ['hline', 'vline'].includes(config.shape) ? 'line' : config.shape - const currentPoints = getPointsRoot() + const currentPoints = getPointsRoot(chart, embeded) .selectAll(shape) .data(data.filter(chart.y)) currentPoints.exit().remove() currentPoints.enter().append(shape) - const newPoints = getPointsRoot() + const newPoints = getPointsRoot(chart, embeded) .selectAll(shape) .transition() .duration(chart.animDurationMs ?? 0) diff --git a/src/components/d3/renders/rect_area.ts b/src/components/d3/renders/rect_area.ts index 762b86d..620d668 100644 --- a/src/components/d3/renders/rect_area.ts +++ b/src/components/d3/renders/rect_area.ts @@ -1,6 +1,8 @@ import { getByAccessor } from '@components/d3/functions' import { ChartRegistry } from '@components/d3/types' +import { appendTransition } from './base' + export const renderRectArea = >( xAxis: (value: d3.NumberValue) => number, yAxis: (value: d3.NumberValue) => number, @@ -28,8 +30,7 @@ export const renderRectArea = >( rects.exit().remove() rects.enter().append('rect') - const actualRects = chart() - .selectAll>('rect') + appendTransition(chart().selectAll>('rect'), chart) .attr('x1', (d) => xAxis(xMin(d))) .attr('x2', (d) => xAxis(xMax(d))) .attr('y1', (d) => yAxis(yMin(d))) diff --git a/src/components/selectors/WellTreeSelector.tsx b/src/components/selectors/WellTreeSelector.tsx index f12fa2d..f06b588 100755 --- a/src/components/selectors/WellTreeSelector.tsx +++ b/src/components/selectors/WellTreeSelector.tsx @@ -70,7 +70,6 @@ const getWellSortScore = (well: WellDto) => { let out = [1, 2, 0][well.idState ?? 2] const timeout = Date.now() - +new Date(well.lastTelemetryDate || 0) if (timeout < 600_000) out += 600_000 - timeout - console.log(well, out) return out } diff --git a/src/pages/Analytics/WellCompositeEditor/WellCompositeSections.jsx b/src/pages/Analytics/WellCompositeEditor/WellCompositeSections.jsx old mode 100755 new mode 100644 diff --git a/src/pages/Telemetry/Operations/index.jsx b/src/pages/Telemetry/Operations/index.jsx index 2688873..8306be8 100644 --- a/src/pages/Telemetry/Operations/index.jsx +++ b/src/pages/Telemetry/Operations/index.jsx @@ -69,9 +69,17 @@ const Operations = memo(() => { useEffect(() => { invokeWebApiWrapperAsync( async () => { - const categories = arrayOrDefault(await DetectedOperationService.getCategories(idWell)) + const categories = arrayOrDefault(await DetectedOperationService.getCategories()) setCategories(categories.map(({ id, name }) => ({ value: id, label: name }))) + }, + setIsLoading, + 'Не удалось загрзуить категории операций' + ) + }, []) + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { const dates = await TelemetryDataSaubService.getDataDatesRange(idWell) if (dates) { const dt = [moment(dates.from), moment(dates.to)] @@ -131,11 +139,11 @@ const Operations = memo(() => {
- +
diff --git a/src/pages/Telemetry/TelemetryView/index.jsx b/src/pages/Telemetry/TelemetryView/index.jsx index 67967f5..7d5791c 100755 --- a/src/pages/Telemetry/TelemetryView/index.jsx +++ b/src/pages/Telemetry/TelemetryView/index.jsx @@ -1,5 +1,6 @@ -import { Select } from 'antd' import { useState, useEffect, useCallback, memo, useMemo } from 'react' +import { BehaviorSubject, buffer, throttleTime } from 'rxjs' +import { Select } from 'antd' import { useIdWell } from '@asb/context' import { makeDateSorter } from '@components/Table' @@ -73,7 +74,7 @@ export const makeChartGroups = (flowChart) => { maxYAccessor: accessor + 'Max', bindDomainFrom: accessor, }) - console.log(flowChart) + return [ [ makeDataset('Высота блока', 'Высота ТБ','#303030', 'blockPosition', 'м'), @@ -151,6 +152,9 @@ const TelemetryView = memo(() => { const idWell = useIdWell() + const saubSubject$ = useMemo(() => new BehaviorSubject(), []) + const spinSubject$ = useMemo(() => new BehaviorSubject(), []) + const handleDataSaub = useCallback((data) => { if (data) { const dataSaub = normalizeData(data) @@ -165,11 +169,22 @@ const TelemetryView = memo(() => { const handleDataSpin = useCallback((data) => data && setDataSpin((prev) => [...prev, ...data]), []) useEffect(() => { - const unsubscribe = Subscribe( - 'hubs/telemetry', `well_${idWell}`, - { methodName: 'ReceiveDataSaub', handler: handleDataSaub }, - { methodName: 'ReceiveDataSpin', handler: handleDataSpin } - ) + const subscribtion = saubSubject$.pipe( + buffer(saubSubject$.pipe(throttleTime(700))) + ).subscribe((data) => handleDataSaub(data.flat())) + + return () => subscribtion.unsubscribe() + }, [saubSubject$]) + + useEffect(() => { + const subscribtion = spinSubject$.pipe( + buffer(spinSubject$.pipe(throttleTime(700))) + ).subscribe((data) => handleDataSpin(data.flat())) + + return () => subscribtion.unsubscribe() + }, [spinSubject$]) + + useEffect(() => { invokeWebApiWrapperAsync( async () => { const flowChart = await DrillFlowChartService.getByIdWell(idWell) @@ -183,9 +198,18 @@ const TelemetryView = memo(() => { `Не удалось получить данные по скважине "${idWell}"`, 'Получение данных по скважине' ) - return unsubscribe }, [idWell, chartInterval, handleDataSpin, handleDataSaub]) + useEffect(() => { + const unsubscribe = Subscribe( + 'hubs/telemetry', `well_${idWell}`, + { methodName: 'ReceiveDataSaub', handler: (data) => saubSubject$.next(data) }, + { methodName: 'ReceiveDataSpin', handler: (data) => spinSubject$.next(data) } + ) + + return () => unsubscribe() + }, [idWell, saubSubject$, spinSubject$]) + useEffect(() => { invokeWebApiWrapperAsync( async () => { diff --git a/src/pages/WellOperations/Tvd/NetGraphExport.jsx b/src/pages/WellOperations/Tvd/NetGraphExport.jsx index 72a5d72..048a024 100755 --- a/src/pages/WellOperations/Tvd/NetGraphExport.jsx +++ b/src/pages/WellOperations/Tvd/NetGraphExport.jsx @@ -14,15 +14,16 @@ export const NetGraphExport = memo(({ idWell, ...other }) => { ), [idWell]) return ( - +
+ +
) }) diff --git a/src/pages/WellOperations/Tvd/StatExport.jsx b/src/pages/WellOperations/Tvd/StatExport.jsx new file mode 100644 index 0000000..e425ecc --- /dev/null +++ b/src/pages/WellOperations/Tvd/StatExport.jsx @@ -0,0 +1,38 @@ +import { memo, useCallback, useEffect, useState } from 'react' +import { Button, Input } from 'antd' + +import { useIdWell } from '@asb/context' +import { download, invokeWebApiWrapperAsync } from '@components/factory' +import { WellService } from '@api' + +export const StatExport = memo(() => { + const [isFileExporting, setIsFileExporting] = useState(false) + const [idCluster, setIdCluster] = useState() + const idWell = useIdWell() + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const { idCluster } = await WellService.get(idWell) + setIdCluster(idCluster) + }, + setIsFileExporting, + 'Не удалось загрузить ID куста' + ) + }, [idWell]) + + const onExport = useCallback((well) => invokeWebApiWrapperAsync( + async () => await download(`/api/DetectedOperation/export?${well ? 'idWell' : 'idCluster'}=${well ? idWell : idCluster}`), + setIsFileExporting, + 'Не удалось загрузить файл' + ), [idWell, idCluster]) + + return ( + + + + + ) +}) + +export default StatExport diff --git a/src/pages/WellOperations/Tvd/TLChart.jsx b/src/pages/WellOperations/Tvd/TLChart.jsx new file mode 100644 index 0000000..b2ed74b --- /dev/null +++ b/src/pages/WellOperations/Tvd/TLChart.jsx @@ -0,0 +1,151 @@ +import { memo, useEffect, useMemo, useState } from 'react' +import { useElementSize } from 'usehooks-ts' +import { Empty } from 'antd' +import moment from 'moment' +import * as d3 from 'd3' + +import { useIdWell } from '@asb/context' +import LoaderPortal from '@components/LoaderPortal' +import { invokeWebApiWrapperAsync } from '@components/factory' +import { DetectedOperationService } from '@api' +import { formatDate } from '@utils' + +const defaultOffset = { left: 40, right: 20, top: 20, bottom: 20 } +const zeroDate = moment('2000-01-01 00:00:00') + +const applyTime = (date) => moment(`${zeroDate.format('YYYY-MM-DD')} ${date.format('HH:mm:ss')}`) + +const splitByDate = (startTime, endTime) => { + if (startTime.isSame(endTime, 'day')) + return [{ startTime, endTime }] + + const out = [] + let date = moment(startTime).startOf('day').add(1, 'day') + + out.push({ startTime, endTime: moment(date).subtract(1, 'ms') }) + + while(!date.isSame(endTime, 'day')) { + const newDate = moment(date).add(1, 'day') + out.push({ startTime: date, endTime: moment(newDate).subtract(1, 'ms') }) + date = newDate + } + + out.push({ startTime: date, endTime }) + + return out +} + +export const TLChart = memo(({ + backgroundColor = '#0000', + barHeight = 15, + offset = defaultOffset, + color, +}) => { + const [isLoading, setIsLoading] = useState(false) + const [svgRef, setSvgRef] = useState() + const [data, setData] = useState() + + const [rootRef, { width, height }] = useElementSize() + + const idWell = useIdWell() + + const dates = useMemo(() => { + if (!data || data.length <= 0) return [0, 0] + return [ + d3.min(data, (d) => moment(d.dateStart)).startOf('day'), + d3.max(data, (d) => moment(d.dateEnd)).endOf('day'), + ] + }, [data]) + + const xAxis = useMemo(() => d3.scaleTime() + .range([0, width - offset.left - offset.right]) + .domain([zeroDate, moment(zeroDate).endOf('day')]) + , [width, offset]) + + const yAxis = useMemo(() => d3.scaleTime() + .range([0, height - offset.top - offset.bottom - barHeight]) + .domain(dates) + , [height, offset, barHeight, dates]) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const { operations } = await DetectedOperationService.get(idWell) + setData(operations.map((raw) => { + const startTime = moment(raw.dateStart) + const endTime = moment(raw.dateEnd) + return splitByDate(startTime, endTime).map((dt) => ({ + ...raw, + startTime: dt.startTime, + endTime: dt.endTime, + })) + }).flat()) + }, + setIsLoading, + 'Не удалось загрузить список операций' + ) + }, [idWell]) + + useEffect(() => { // Рисуем ось X + const xAxisArea = d3.select(svgRef).select('.axis.x') + xAxisArea.call(d3.axisTop(xAxis) + .tickSize(offset.top + offset.bottom - height) + .tickFormat((d) => formatDate(d, undefined, 'HH:mm:ss')) + .ticks(d3.timeHour.every(3)) + ) + + xAxisArea.selectAll('.tick line') + .attr('stroke', 'black') + .attr('stroke-dasharray', [5, 3]) + }, [svgRef, xAxis, height, offset]) + + useEffect(() => { // Рисуем ось Y + d3.select(svgRef) + .select('.axis.y') + .call(d3.axisLeft(yAxis) + .tickSize(0) + .ticks(d3.timeDay.every(1)) + .tickFormat((d) => moment(d).format('DD.MM')) + ) + }, [svgRef, yAxis, height, offset, dates]) + + useEffect(() => { + if (!data) return + const elms = d3.select(svgRef).select('.chart-area').selectAll('rect').data(data) + elms.exit().remove() + const newElms = elms.enter().append('rect') + elms.merge(newElms) + .attr('x', (d) => xAxis(applyTime(d.startTime))) + .attr('y', (d) => yAxis(moment(d.startTime).startOf('day')) - barHeight / 2) + .attr('width', (d) => xAxis(d.endTime) - xAxis(d.startTime)) + .attr('height', barHeight) + .attr('fill', (d) => color ? color(d.idCategory) : '#0008') + }, [svgRef, xAxis, yAxis, data, color]) + + return ( +
+ + {data ? ( + + + + + + + ) : ( +
+ +
+ )} +
+
+ ) +}) + +export default TLChart diff --git a/src/pages/WellOperations/Tvd/TLPie.jsx b/src/pages/WellOperations/Tvd/TLPie.jsx new file mode 100644 index 0000000..1305ca5 --- /dev/null +++ b/src/pages/WellOperations/Tvd/TLPie.jsx @@ -0,0 +1,149 @@ +import { memo, useEffect, useMemo, useState } from 'react' +import { useElementSize } from 'usehooks-ts' +import { Empty } from 'antd' +import * as d3 from 'd3' + +import { useIdWell } from '@asb/context' +import { makeColumn, makeNumericColumn, makeTextColumn, Table } from '@components/Table' +import LoaderPortal from '@components/LoaderPortal' +import { invokeWebApiWrapperAsync } from '@components/factory' +import { DetectedOperationService } from '@api' + +const tableColumns = [ + makeColumn('Цвет', 'color', { width: 50, render: (d) => ( +
+ ) }), + makeTextColumn('Название', 'category', undefined, undefined, undefined, { width: 300 }), + makeNumericColumn('Время, мин', 'minutesTotal', undefined, undefined, undefined, 100), + makeNumericColumn('Кол-во', 'count', undefined, undefined, (d) => d ? d.toString() : '---', 100), + makeNumericColumn('Процент, %', 'percent', undefined, undefined, (d) => d ? d.toFixed(2) : '---', 100) +] + +export const TLPie = memo(({ color }) => { + const [isLoading, setIsLoading] = useState(false) + const [svgRef, setSvgRef] = useState() + const [stats, setStats] = useState([]) + + const [rootRef, { width, height }] = useElementSize() + + const idWell = useIdWell() + + const pie = useMemo(() => d3.pie().value((d) => d.minutesTotal), []) + + const tableData = useMemo(() => { + if (!stats) return null + const totalTime = stats.reduce((out, stat) => out + stat.minutesTotal, 0) + return stats.map((stat) => ({ + ...stat, + color: color(stat.idCategory), + percent: stat.minutesTotal / totalTime * 100, + })) + }, [stats, color]) + + const data = useMemo(() => tableData ? pie(tableData) : null, [tableData]) + + const radius = useMemo(() => Math.min(width, height) / 2, [width, height]) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const stats = await DetectedOperationService.getStat(idWell) + setStats(stats) + }, + setIsLoading, + 'Не удалось загрузить статистику автоопределённых операций' + ) + }, [idWell]) + + useEffect(() => { + if (!data) return + const slices = d3.select(svgRef) + .select('.slices') + .selectAll('path') + .data(data) + + slices.exit().remove() + const newSlices = slices.enter().append('path') + + slices.merge(newSlices) + .attr('d', d3.arc().innerRadius(radius * 0.4).outerRadius(radius * 0.8)) + .attr('fill', (d) => color ? color(d.data.idCategory) : '#0008') + }, [svgRef, data, color, radius]) + + useEffect(() => { + if (!data) return + const innerArc = d3.arc().innerRadius(radius * 0.4).outerRadius(radius * 0.8) + const outerArc = d3.arc().innerRadius(radius * 0.9).outerRadius(radius * 0.9) + + const lines = d3.select(svgRef) + .select('.lines') + .selectAll('polyline') + .data(data, (d) => d.data.category) + + lines.exit().remove() + const newLines = lines.enter().append('polyline') + + const abovePi = (d) => (d.startAngle + d.endAngle) / 2 < Math.PI + + lines.merge(newLines) + .attr('points', (d) => { + const pos = outerArc.centroid(d) + pos[0] = radius * 0.95 * (abovePi(d) ? 1 : -1) + return [innerArc.centroid(d), outerArc.centroid(d), pos] + }) + + const lables = d3.select(svgRef) + .select('.labels') + .selectAll('text') + .data(data, (d) => d.data.category) + + lables.exit().remove() + const newLabels = lables.enter() + .append('text') + .attr('dy', '.35em') + + lables.merge(newLabels) + .attr('transform', (d) => { + const pos = outerArc.centroid(d) + pos[0] = radius * 0.95 * (abovePi(d) ? 1 : -1) + return `translate(${pos})` + }) + .style('text-anchor', (d) => abovePi(d) ? 'start' : 'end') + .attr('width', radius * 0.4) + .text((d) => `${d.data.percent.toFixed(2)}% (${d.data.minutesTotal.toFixed(2)} мин)`) + + }, [svgRef, data, radius]) + + return ( +
+ + {data ? ( +
+
+ + + + + + + +
+ + + ) : ( +
+ +
+ )} + + + ) +}) + +export default TLPie diff --git a/src/pages/WellOperations/Tvd/index.jsx b/src/pages/WellOperations/Tvd/index.jsx old mode 100755 new mode 100644 index 08de1b2..8b495c9 --- a/src/pages/WellOperations/Tvd/index.jsx +++ b/src/pages/WellOperations/Tvd/index.jsx @@ -1,22 +1,34 @@ -import { DoubleLeftOutlined, DoubleRightOutlined, LineChartOutlined, LinkOutlined } from '@ant-design/icons' -import { memo, useState, useEffect, useCallback, useMemo } from 'react' +import { LineChartOutlined, LinkOutlined } from '@ant-design/icons' +import { memo, useState, useEffect, useMemo } from 'react' +import { Switch, Segmented, Button } from 'antd' import { Link } from 'react-router-dom' -import { Switch, Button } from 'antd' -import { timeDay } from 'd3' +import * as d3 from 'd3' import { useIdWell } from '@asb/context' import { D3Chart } from '@components/d3' import LoaderPortal from '@components/LoaderPortal' import { invokeWebApiWrapperAsync } from '@components/factory' import { formatDate, fractionalSum, wrapPrivateComponent, getOperations } from '@utils' +import { DetectedOperationService } from '@api' +import TLPie from './TLPie' +import TLChart from './TLChart' import NptTable from './NptTable' +import StatExport from './StatExport' import NetGraphExport from './NetGraphExport' import AdditionalTables from './AdditionalTables' import '@styles/index.css' import '@styles/tvd.less' +const colorArray = [ + '#1abc9c', '#16a085', '#2ecc71', '#27ae60', '#3498db', + '#2980b9', '#9b59b6', '#8e44ad', '#34495e', '#2c3e50', + '#f1c40f', '#f39c12', '#e67e22', '#d35400', '#e74c3c', + '#c0392b', '#ecf0f1', '#bdc3c7', '#95a5a6', '#7f8c8d', +] + +const Item = ({ label, children, ...other }) => (
{label}: {children}
) const numericRender = (d) => d && Number.isFinite(+d) ? (+d).toFixed(2) : '-' @@ -85,7 +97,7 @@ const ticks = { date: { x: { visible: true, - count: timeDay.every(1), + count: d3.timeDay.every(1), format: (d, i) => i % 2 === 0 ? formatDate(d, undefined, 'YYYY-MM-DD') : '', }, y: { visible: true }, @@ -134,17 +146,13 @@ const makeDataset = (key, label, color, width, radius, dash) => ({ const Tvd = memo(({ idWell: wellId, title, ...other }) => { const [xLabel, setXLabel] = useState('day') const [operations, setOperations] = useState({}) - const [tableVisible, setTableVisible] = useState(false) const [isLoading, setIsLoading] = useState(false) const [pointsEnabled, setPointsEnabled] = useState(true) + const [selectedTab, setSelectedTab] = useState('Скрыть') + const [color, setColor] = useState() const idWellContext = useIdWell() const idWell = useMemo(() => wellId ?? idWellContext, [wellId, idWellContext]) - - const toogleTable = useCallback(() => { - setOperations(pre => ({ ...pre })) - setTableVisible(v => !v) - }, []) const chartData = useMemo(() => { const withoutNpt = [] @@ -160,15 +168,6 @@ const Tvd = memo(({ idWell: wellId, title, ...other }) => { return { ...operations, withoutNpt } }, [operations]) - - useEffect(() => { - invokeWebApiWrapperAsync( - async () => setOperations(await getOperations(idWell)), - setIsLoading, - `Не удалось загрузить операции по скважине "${idWell}"`, - 'Получение списка опервций по скважине' - ) - }, [idWell]) const datasets = useMemo(() => { const radius = pointsEnabled ? 6 : 1 @@ -181,29 +180,58 @@ const Tvd = memo(({ idWell: wellId, title, ...other }) => { ] }, [pointsEnabled]) + useEffect(() => { + invokeWebApiWrapperAsync( + async () => setOperations(await getOperations(idWell)), + setIsLoading, + `Не удалось загрузить операции по скважине "${idWell}"`, + 'Получение списка опервций по скважине' + ) + }, [idWell]) + + useEffect(() => { + invokeWebApiWrapperAsync( + async () => { + const cats = await DetectedOperationService.getCategories() + const color = d3.scaleOrdinal() + .domain(cats.map((cat) => cat.id)) + .range(colorArray) + setColor(() => color) + }, + undefined, + 'Не удалось получить список типов операций' + ) + }, []) + return (
-

{title || 'График Глубина-день'}

-
- setPointsEnabled(checked)} - style={{ marginRight: 20 }} - title={'Нажмите для переключения видимости засечек на графиках'} - /> - setXLabel(checked ? 'date' : 'day')} - style={{ marginRight: '20px' }} - title={'Нажмите для переключения горизонтальной оси'} - /> +
+

{title || 'График Глубина-день'}

+ + + + + setPointsEnabled(checked)} + title={'Нажмите для переключения видимости засечек на графиках'} + /> + +
+
+ - +
@@ -221,7 +249,9 @@ const Tvd = memo(({ idWell: wellId, title, ...other }) => { animDurationMs={0} />
- {tableVisible && } + {selectedTab === 'НПВ' && } + {selectedTab === 'ЕСО' && } + {selectedTab === 'Статистика' && }
@@ -229,7 +259,7 @@ const Tvd = memo(({ idWell: wellId, title, ...other }) => { }) export default wrapPrivateComponent(Tvd, { - requirements: [ 'OperationStat.get' ], + requirements: [ 'OperationStat.get', 'DetectedOperation.get' ], title: 'TVD', route: 'tvd', }) diff --git a/src/styles/tvd.less b/src/styles/tvd.less index c5c1356..08137fc 100755 --- a/src/styles/tvd.less +++ b/src/styles/tvd.less @@ -6,9 +6,24 @@ .tvd-top { display: flex; - align-items: baseline; + align-items: center; justify-content: space-between; margin-top: 20px; + + .tvd-inputs { + display: flex; + align-items: center; + + .tvd-input-group { + display: flex; + align-items: center; + margin: 0 15px; + + & > span { + margin-right: 5px; + } + } + } } .tvd-main {