From 6ab9cd1c75aef4b7f422fa1ed9123c91310edbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Mon, 22 Jun 2026 03:19:56 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=B0=D0=BA=D1=82=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8E?= =?UTF-8?q?=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/cloud.ts | 2 +- src/entities/history/types.ts | 23 ++++--------- src/features/edge-detail/EdgeHistoryPage.tsx | 2 +- .../edge-detail/components/ArchiveView.tsx | 1 + src/features/history-chart/HistoryChart.tsx | 14 ++++---- .../history-chart/historyChartOptions.ts | 9 ++---- .../history-chart/historyChartSeries.ts | 32 +++++++++---------- 7 files changed, 35 insertions(+), 48 deletions(-) diff --git a/src/api/cloud.ts b/src/api/cloud.ts index 5f042e1..ab891eb 100644 --- a/src/api/cloud.ts +++ b/src/api/cloud.ts @@ -5,6 +5,6 @@ export type { EdgeItem, EdgeResponse } from '../entities/edge/types'; export { getHealth } from '../entities/health/api'; export type { HealthResponse } from '../entities/health/types'; export { getHistory } from '../entities/history/api'; -export type { HistoryPoint, HistoryRequest, HistoryResponse, HistorySeries } from '../entities/history/types'; +export type { HistoryPoint, HistoryRequest, HistoryResponse } from '../entities/history/types'; export { getTags } from '../entities/tag/api'; export type { TagItem, TagResponse } from '../entities/tag/types'; diff --git a/src/entities/history/types.ts b/src/entities/history/types.ts index bd4f0e8..77b1af1 100644 --- a/src/entities/history/types.ts +++ b/src/entities/history/types.ts @@ -1,24 +1,13 @@ export type HistoryPoint = { - t: number; - min: number; - avg: number; - max: number; - count: number; -}; - -export type HistorySeries = { - edge: string; - tag: string; - points: HistoryPoint[]; + time: string; + min_value: number; + avg_value: number; + max_value: number; + point_count: number; }; export type HistoryResponse = { - edge: string; - tag: string; - granulate: string; - series: HistorySeries[]; - from: string; - to: string; + rows: HistoryPoint[]; }; export type HistoryRequest = { diff --git a/src/features/edge-detail/EdgeHistoryPage.tsx b/src/features/edge-detail/EdgeHistoryPage.tsx index 4c79391..498f77c 100644 --- a/src/features/edge-detail/EdgeHistoryPage.tsx +++ b/src/features/edge-detail/EdgeHistoryPage.tsx @@ -78,7 +78,7 @@ export function EdgeHistoryPage() { selectedTags={selectedTags} history={history.data} historyLoading={history.isPending && selectedTags.length > 0} - historyGranulate={history.data?.granulate ?? historyGranularity.granulate} + historyGranulate={historyGranularity.granulate} historyAxis={historyGranularity} range={range} onSearchChange={setSearch} diff --git a/src/features/edge-detail/components/ArchiveView.tsx b/src/features/edge-detail/components/ArchiveView.tsx index b080c81..338fc1a 100644 --- a/src/features/edge-detail/components/ArchiveView.tsx +++ b/src/features/edge-detail/components/ArchiveView.tsx @@ -253,6 +253,7 @@ export function ArchiveView({ to={toIsoFromInput(range.to)} tickIntervalMs={historyAxis.tickIntervalMs} labelFormat={historyAxis.labelFormat} + tag={selectedTags[0]} tagLabels={tagLabels} /> ) : ( diff --git a/src/features/history-chart/HistoryChart.tsx b/src/features/history-chart/HistoryChart.tsx index 418f004..6568cb5 100644 --- a/src/features/history-chart/HistoryChart.tsx +++ b/src/features/history-chart/HistoryChart.tsx @@ -31,6 +31,7 @@ type HistoryChartProps = { to?: string; tickIntervalMs?: number; labelFormat?: HistoryAxisLabelFormat; + tag?: string; tagLabels?: Record; }; @@ -42,13 +43,12 @@ export function HistoryChart({ to, tickIntervalMs, labelFormat, + tag, tagLabels = {}, }: HistoryChartProps) { const dataZoomRef = useRef(null); - const legendData = useMemo( - () => data?.series.map((series) => tagLabels[series.tag] ?? series.tag) ?? [], - [data?.series, tagLabels], - ); + const seriesLabel = tag ? tagLabels[tag] ?? tag : ''; + const legendData = useMemo(() => (data?.rows.length && seriesLabel ? [seriesLabel] : []), [data?.rows.length, seriesLabel]); const option = useMemo( () => @@ -59,10 +59,10 @@ export function HistoryChart({ to, labelFormat, legendData, - tagLabels, + seriesLabel, tickIntervalMs, }), - [data, from, labelFormat, legendData, tagLabels, tickIntervalMs, to], + [data, from, labelFormat, legendData, seriesLabel, tickIntervalMs, to], ); const onChartEvents = useMemo( @@ -84,7 +84,7 @@ export function HistoryChart({ return
Загрузка графика...
; } - if (!data?.series.length) { + if (!data?.rows.length) { return
Нет данных для выбранного диапазона
; } diff --git a/src/features/history-chart/historyChartOptions.ts b/src/features/history-chart/historyChartOptions.ts index 3b5cf2e..46c4c9d 100644 --- a/src/features/history-chart/historyChartOptions.ts +++ b/src/features/history-chart/historyChartOptions.ts @@ -12,7 +12,7 @@ export type HistoryChartOptionsInput = { to?: string; labelFormat?: HistoryAxisLabelFormat; legendData: string[]; - tagLabels: Record; + seriesLabel: string; tickIntervalMs?: number; }; @@ -23,7 +23,7 @@ export function createHistoryChartOptions({ to, labelFormat, legendData, - tagLabels, + seriesLabel, tickIntervalMs, }: HistoryChartOptionsInput): EChartsOption { const xMin = from ? new Date(from).getTime() : undefined; @@ -102,9 +102,6 @@ export function createHistoryChartOptions({ endValue: dataZoomState.endValue, }, ], - series: - data?.series.flatMap((series, index) => - createSeriesOptions(series, index, tagLabels[series.tag] ?? series.tag), - ) ?? [], + series: data?.rows.length ? createSeriesOptions(data.rows, 0, seriesLabel) : [], }; } diff --git a/src/features/history-chart/historyChartSeries.ts b/src/features/history-chart/historyChartSeries.ts index 2efed73..03b8c1f 100644 --- a/src/features/history-chart/historyChartSeries.ts +++ b/src/features/history-chart/historyChartSeries.ts @@ -1,5 +1,5 @@ import type { SeriesOption } from 'echarts'; -import type { HistorySeries } from '../../entities/history/types'; +import type { HistoryPoint } from '../../entities/history/types'; import type { AvgPointValue } from './chartTypes'; export const SERIES_COLORS = [ @@ -14,20 +14,20 @@ export const SERIES_COLORS = [ ]; /** Создает avg-линию, вертикальные min/max-отрезки и точки экстремумов для одной серии. */ -export function createSeriesOptions(series: HistorySeries, index: number, label: string): SeriesOption[] { +export function createSeriesOptions(points: HistoryPoint[], index: number, label: string): SeriesOption[] { const color = SERIES_COLORS[index % SERIES_COLORS.length]; - const avgData: AvgPointValue[] = series.points.map((point) => [ - point.t, - point.avg, - point.min, - point.max, - point.count, - ]); - const spreadData = series.points.flatMap((point) => [ - [point.t, point.min], - [point.t, point.max], - [point.t, null], - ]); + const avgData: AvgPointValue[] = points.map((point) => { + const time = new Date(point.time).getTime(); + return [time, point.avg_value, point.min_value, point.max_value, point.point_count]; + }); + const spreadData = points.flatMap((point) => { + const time = new Date(point.time).getTime(); + return [ + [time, point.min_value], + [time, point.max_value], + [time, null], + ]; + }); return [ { @@ -52,7 +52,7 @@ export function createSeriesOptions(series: HistorySeries, index: number, label: { name: `${label} min`, type: 'scatter', - data: series.points.map((point) => [point.t, point.min]), + data: points.map((point) => [new Date(point.time).getTime(), point.min_value]), symbolSize: 4, silent: true, tooltip: { show: false }, @@ -68,7 +68,7 @@ export function createSeriesOptions(series: HistorySeries, index: number, label: { name: `${label} max`, type: 'scatter', - data: series.points.map((point) => [point.t, point.max]), + data: points.map((point) => [new Date(point.time).getTime(), point.max_value]), symbolSize: 4, silent: true, tooltip: { show: false }, -- 2.49.1 From 9fc66624dcde3aad6bef68413ffe1c4158cdcf3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Mon, 22 Jun 2026 21:49:52 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D0=BC=D0=BD?= =?UTF-8?q?=D0=BE=D0=B6=D0=B5=D1=81=D1=82=D0=B2=D0=B0=20=D1=82=D0=B5=D0=B3?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=82=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/edge-detail/EdgeHistoryPage.tsx | 28 ++--- .../edge-detail/components/ArchiveView.tsx | 27 ++--- src/features/history-chart/HistoryChart.tsx | 102 +++++++----------- .../history-chart/HistoryChartArea.tsx | 43 ++++++++ .../history-chart/historyChartEcharts.ts | 21 ++++ .../history-chart/historyChartOptions.ts | 12 +-- .../history-chart/historyChartSeries.ts | 6 +- .../history-chart/useHistoryChartQueries.ts | 52 +++++++++ .../history-chart/useHistoryChartSeries.ts | 14 +++ src/styles/detail/archive.css | 33 +++++- 10 files changed, 229 insertions(+), 109 deletions(-) create mode 100644 src/features/history-chart/HistoryChartArea.tsx create mode 100644 src/features/history-chart/historyChartEcharts.ts create mode 100644 src/features/history-chart/useHistoryChartQueries.ts create mode 100644 src/features/history-chart/useHistoryChartSeries.ts diff --git a/src/features/edge-detail/EdgeHistoryPage.tsx b/src/features/edge-detail/EdgeHistoryPage.tsx index 498f77c..f061304 100644 --- a/src/features/edge-detail/EdgeHistoryPage.tsx +++ b/src/features/edge-detail/EdgeHistoryPage.tsx @@ -1,8 +1,7 @@ import { useMemo, useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useParams } from 'react-router-dom'; import { getCurrent } from '../../entities/current/api'; -import { getHistory } from '../../entities/history/api'; import { toIsoFromInput } from '../../utils/format'; import { getHistoryGranularity } from '../../utils/historyGranularity'; import { createCurrentTagLabels, filterCurrentItems } from '../current/model'; @@ -13,6 +12,7 @@ import { EdgePageLayout } from './components/EdgePageLayout'; export function EdgeHistoryPage() { const { edgeId = '' } = useParams(); + const queryClient = useQueryClient(); const [search, setSearch] = useState(''); const [range, setRange] = useState(() => createRange(24)); const [selectedTags, setSelectedTags] = useState([]); @@ -30,23 +30,8 @@ export function EdgeHistoryPage() { const from = toIsoFromInput(range.from) as string; const to = toIsoFromInput(range.to) as string; const historyGranularity = useMemo(() => getHistoryGranularity(from, to), [from, to]); - const selectedTag = selectedTags[0]; - const history = useQuery({ - queryKey: ['history', edgeId, selectedTag, from, to, historyGranularity.granulate], - queryFn: () => - getHistory({ - edge: edgeId, - tag: selectedTag as string, - from, - to, - granulate: historyGranularity.granulate, - }), - enabled: Boolean(edgeId && selectedTag), - refetchInterval: false, - }); - const toggleTag = (tag: string) => { - setSelectedTags((prev) => (prev.includes(tag) ? [] : [tag])); + setSelectedTags((prev) => (prev.includes(tag) ? prev.filter((selected) => selected !== tag) : [...prev, tag])); }; const selectFirstTags = () => { @@ -54,7 +39,7 @@ export function EdgeHistoryPage() { }; const selectVisibleTags = () => { - setSelectedTags(visibleItems[0] ? [visibleItems[0].tag] : []); + setSelectedTags((prev) => Array.from(new Set([...prev, ...visibleItems.map((item) => item.tag)]))); }; const clearVisibleTags = () => { @@ -69,15 +54,14 @@ export function EdgeHistoryPage() { view="archive" onRefresh={() => { void current.refetch(); - void history.refetch(); + void queryClient.invalidateQueries({ queryKey: ['history', edgeId] }); }} > 0} historyGranulate={historyGranularity.granulate} historyAxis={historyGranularity} range={range} diff --git a/src/features/edge-detail/components/ArchiveView.tsx b/src/features/edge-detail/components/ArchiveView.tsx index 338fc1a..0d6e449 100644 --- a/src/features/edge-detail/components/ArchiveView.tsx +++ b/src/features/edge-detail/components/ArchiveView.tsx @@ -1,20 +1,18 @@ import { useState } from 'react'; import type { RefObject } from 'react'; import { useRef } from 'react'; -import { CalendarDays, CalendarClock, ChevronDown, Clock3, DatabaseZap, Search } from 'lucide-react'; +import { CalendarDays, CalendarClock, Check, ChevronDown, Clock3, DatabaseZap, Search } from 'lucide-react'; import type { CurrentItem } from '../../../entities/current/types'; -import type { HistoryResponse } from '../../../entities/history/types'; import { HistoryChart } from '../../../features/history-chart/HistoryChart'; import { RANGE_PRESETS, createRange, type DateRangeState } from '../../../features/history/dateRange'; import { toIsoFromInput } from '../../../utils/format'; import type { HistoryGranularity } from '../../../utils/historyGranularity'; type ArchiveViewProps = { + edgeId: string; getTagLabel: (tag: string) => string; - history?: HistoryResponse; historyAxis: HistoryGranularity; historyGranulate?: string; - historyLoading: boolean; items: CurrentItem[]; range: DateRangeState; search: string; @@ -78,11 +76,10 @@ function DateRangeField({ icon, inputRef, type, value, onChange }: DateRangeFiel } export function ArchiveView({ + edgeId, getTagLabel, - history, historyAxis, historyGranulate, - historyLoading, items, range, search, @@ -103,6 +100,8 @@ export function ArchiveView({ const fromTimeRef = useRef(null); const toDateRef = useRef(null); const toTimeRef = useRef(null); + const fromIso = toIsoFromInput(range.from) as string; + const toIso = toIsoFromInput(range.to) as string; const updateRangePart = (rangePart: RangePart, inputPart: RangeInputPart, value: string) => { onRangeChange({ ...range, @@ -185,12 +184,16 @@ export function ArchiveView({ className={`tag-select-item ${selected ? 'tag-select-item--selected' : ''}`} title={item.tag} onClick={() => onToggleTag(item.tag)} + aria-pressed={selected} > {label} {label !== item.tag ? {item.tag} : null} - {item.value.toLocaleString('ru-RU', { maximumFractionDigits: item.precision ?? 3 })} + + {item.value.toLocaleString('ru-RU', { maximumFractionDigits: item.precision ?? 3 })} + {selected ? : null} + ); })} @@ -247,13 +250,13 @@ export function ArchiveView({ {selectedTags.length ? ( ) : ( diff --git a/src/features/history-chart/HistoryChart.tsx b/src/features/history-chart/HistoryChart.tsx index 6568cb5..28a12de 100644 --- a/src/features/history-chart/HistoryChart.tsx +++ b/src/features/history-chart/HistoryChart.tsx @@ -1,92 +1,70 @@ -import { LineChart, ScatterChart } from 'echarts/charts'; -import { - DataZoomComponent, - GridComponent, - LegendComponent, - TooltipComponent, -} from 'echarts/components'; -import * as echarts from 'echarts/core'; -import { CanvasRenderer } from 'echarts/renderers'; -import ReactEChartsCore from 'echarts-for-react/esm/core.js'; -import { useMemo, useRef } from 'react'; -import type { HistoryResponse } from '../../entities/history/types'; +import { useCallback, useMemo, useRef } from 'react'; import type { HistoryAxisLabelFormat } from '../../utils/historyGranularity'; import type { DataZoomEventBatch, DataZoomState } from './chartTypes'; +import { HistoryChartArea } from './HistoryChartArea'; import { createHistoryChartOptions } from './historyChartOptions'; - -echarts.use([ - LineChart, - ScatterChart, - GridComponent, - LegendComponent, - TooltipComponent, - DataZoomComponent, - CanvasRenderer, -]); +import { useHistoryChartQueries } from './useHistoryChartQueries'; +import { useHistoryChartSeries } from './useHistoryChartSeries'; type HistoryChartProps = { - data?: HistoryResponse; - loading: boolean; - from?: string; - to?: string; - tickIntervalMs?: number; + edge: string; + from: string; + granulate: string; labelFormat?: HistoryAxisLabelFormat; - tag?: string; + tags: string[]; + tickIntervalMs?: number; + to: string; tagLabels?: Record; }; -/** Рисует avg-линию, min/max-точки и вертикальный диапазон для агрегированной истории. */ +/** Контейнер истории: загружает выбранные теги и передает готовые серии в область ECharts. */ export function HistoryChart({ - data, - loading, + edge, from, - to, - tickIntervalMs, + granulate, labelFormat, - tag, + tags, + tickIntervalMs, + to, tagLabels = {}, }: HistoryChartProps) { const dataZoomRef = useRef(null); - const seriesLabel = tag ? tagLabels[tag] ?? tag : ''; - const legendData = useMemo(() => (data?.rows.length && seriesLabel ? [seriesLabel] : []), [data?.rows.length, seriesLabel]); + const lines = useHistoryChartQueries({ edge, from, granulate, tags, to, tagLabels }); + const series = useHistoryChartSeries(lines); + const legendData = useMemo(() => lines.map((line) => line.label), [lines]); + const loading = lines.some((line) => line.loading); const option = useMemo( () => createHistoryChartOptions({ - data, dataZoomState: dataZoomRef.current ?? {}, from, to, labelFormat, legendData, - seriesLabel, + series, tickIntervalMs, }), - [data, from, labelFormat, legendData, seriesLabel, tickIntervalMs, to], + [from, labelFormat, legendData, series, tickIntervalMs, to], ); - const onChartEvents = useMemo( - () => ({ - datazoom: (event: DataZoomEventBatch) => { - const state = event.batch?.[0] ?? event; - dataZoomRef.current = { - start: state.start, - end: state.end, - startValue: state.startValue, - endValue: state.endValue, - }; - }, - }), - [], + const handleDataZoom = useCallback((event: DataZoomEventBatch) => { + const state = event.batch?.[0] ?? event; + dataZoomRef.current = { + start: state.start, + end: state.end, + startValue: state.startValue, + endValue: state.endValue, + }; + }, []); + + return ( + 0} + hasSelection={tags.length > 0} + loading={loading} + option={option} + onDataZoom={handleDataZoom} + /> ); - - if (loading) { - return
Загрузка графика...
; - } - - if (!data?.rows.length) { - return
Нет данных для выбранного диапазона
; - } - - return ; } diff --git a/src/features/history-chart/HistoryChartArea.tsx b/src/features/history-chart/HistoryChartArea.tsx new file mode 100644 index 0000000..9b23950 --- /dev/null +++ b/src/features/history-chart/HistoryChartArea.tsx @@ -0,0 +1,43 @@ +import type { EChartsOption } from 'echarts'; +import ReactEChartsCore from 'echarts-for-react/esm/core.js'; +import { useMemo } from 'react'; +import type { DataZoomEventBatch } from './chartTypes'; +import { echarts } from './historyChartEcharts'; + +type HistoryChartAreaProps = { + hasData: boolean; + hasSelection: boolean; + loading: boolean; + option: EChartsOption; + onDataZoom: (event: DataZoomEventBatch) => void; +}; + +/** Отвечает только за визуальную область графика: placeholder или ECharts canvas. */ +export function HistoryChartArea({ + hasData, + hasSelection, + loading, + option, + onDataZoom, +}: HistoryChartAreaProps) { + const onChartEvents = useMemo( + () => ({ + datazoom: onDataZoom, + }), + [onDataZoom], + ); + + if (!hasSelection) { + return
Разверните список показателей и выберите серию для графика
; + } + + if (loading) { + return
Загрузка графика...
; + } + + if (!hasData) { + return
Нет данных для выбранного диапазона
; + } + + return ; +} diff --git a/src/features/history-chart/historyChartEcharts.ts b/src/features/history-chart/historyChartEcharts.ts new file mode 100644 index 0000000..dba3d06 --- /dev/null +++ b/src/features/history-chart/historyChartEcharts.ts @@ -0,0 +1,21 @@ +import { LineChart, ScatterChart } from 'echarts/charts'; +import { + DataZoomComponent, + GridComponent, + LegendComponent, + TooltipComponent, +} from 'echarts/components'; +import * as echarts from 'echarts/core'; +import { CanvasRenderer } from 'echarts/renderers'; + +echarts.use([ + LineChart, + ScatterChart, + GridComponent, + LegendComponent, + TooltipComponent, + DataZoomComponent, + CanvasRenderer, +]); + +export { echarts }; diff --git a/src/features/history-chart/historyChartOptions.ts b/src/features/history-chart/historyChartOptions.ts index 46c4c9d..0ff826c 100644 --- a/src/features/history-chart/historyChartOptions.ts +++ b/src/features/history-chart/historyChartOptions.ts @@ -1,29 +1,27 @@ import type { EChartsOption } from 'echarts'; -import type { HistoryResponse } from '../../entities/history/types'; +import type { SeriesOption } from 'echarts'; import type { HistoryAxisLabelFormat } from '../../utils/historyGranularity'; import type { DataZoomState } from './chartTypes'; import { formatAxisDate, formatTooltip } from './historyChartFormat'; -import { createSeriesOptions, SERIES_COLORS } from './historyChartSeries'; +import { SERIES_COLORS } from './historyChartSeries'; export type HistoryChartOptionsInput = { - data?: HistoryResponse; dataZoomState: DataZoomState; from?: string; to?: string; labelFormat?: HistoryAxisLabelFormat; legendData: string[]; - seriesLabel: string; + series: SeriesOption[]; tickIntervalMs?: number; }; export function createHistoryChartOptions({ - data, dataZoomState, from, to, labelFormat, legendData, - seriesLabel, + series, tickIntervalMs, }: HistoryChartOptionsInput): EChartsOption { const xMin = from ? new Date(from).getTime() : undefined; @@ -102,6 +100,6 @@ export function createHistoryChartOptions({ endValue: dataZoomState.endValue, }, ], - series: data?.rows.length ? createSeriesOptions(data.rows, 0, seriesLabel) : [], + series, }; } diff --git a/src/features/history-chart/historyChartSeries.ts b/src/features/history-chart/historyChartSeries.ts index 03b8c1f..9637aab 100644 --- a/src/features/history-chart/historyChartSeries.ts +++ b/src/features/history-chart/historyChartSeries.ts @@ -31,7 +31,7 @@ export function createSeriesOptions(points: HistoryPoint[], index: number, label return [ { - name: `${label} min/max`, + name: label, type: 'line', data: spreadData, showSymbol: false, @@ -50,7 +50,7 @@ export function createSeriesOptions(points: HistoryPoint[], index: number, label z: 1, }, { - name: `${label} min`, + name: label, type: 'scatter', data: points.map((point) => [new Date(point.time).getTime(), point.min_value]), symbolSize: 4, @@ -66,7 +66,7 @@ export function createSeriesOptions(points: HistoryPoint[], index: number, label z: 2, }, { - name: `${label} max`, + name: label, type: 'scatter', data: points.map((point) => [new Date(point.time).getTime(), point.max_value]), symbolSize: 4, diff --git a/src/features/history-chart/useHistoryChartQueries.ts b/src/features/history-chart/useHistoryChartQueries.ts new file mode 100644 index 0000000..efbb3ad --- /dev/null +++ b/src/features/history-chart/useHistoryChartQueries.ts @@ -0,0 +1,52 @@ +import { useMemo } from 'react'; +import { useQueries } from '@tanstack/react-query'; +import { getHistory } from '../../entities/history/api'; +import type { HistoryPoint } from '../../entities/history/types'; + +type UseHistoryChartQueriesParams = { + edge: string; + from: string; + granulate: string; + tags: string[]; + to: string; + tagLabels: Record; +}; + +export type HistoryChartLineData = { + index: number; + label: string; + loading: boolean; + rows: HistoryPoint[]; + tag: string; +}; + +export function useHistoryChartQueries({ + edge, + from, + granulate, + tags, + to, + tagLabels, +}: UseHistoryChartQueriesParams): HistoryChartLineData[] { + const enabled = Boolean(edge && from && granulate && to); + + const queries = useQueries({ + queries: tags.map((tag) => ({ + queryKey: ['history', edge, tag, from, to, granulate], + queryFn: () => getHistory({ edge, tag, from, to, granulate }), + enabled, + })), + }); + + return useMemo( + () => + tags.map((tag, index) => ({ + index, + label: tagLabels[tag] ?? tag, + loading: enabled && queries[index].isPending, + rows: queries[index].data?.rows ?? [], + tag, + })), + [enabled, queries, tagLabels, tags], + ); +} diff --git a/src/features/history-chart/useHistoryChartSeries.ts b/src/features/history-chart/useHistoryChartSeries.ts new file mode 100644 index 0000000..cd5bac2 --- /dev/null +++ b/src/features/history-chart/useHistoryChartSeries.ts @@ -0,0 +1,14 @@ +import { useMemo } from 'react'; +import type { SeriesOption } from 'echarts'; +import { createSeriesOptions } from './historyChartSeries'; +import type { HistoryChartLineData } from './useHistoryChartQueries'; + +export function useHistoryChartSeries(lines: HistoryChartLineData[]): SeriesOption[] { + return useMemo( + () => + lines.flatMap((line) => + line.rows.length ? createSeriesOptions(line.rows, line.index, line.label) : [], + ), + [lines], + ); +} diff --git a/src/styles/detail/archive.css b/src/styles/detail/archive.css index e7ec582..48afed0 100644 --- a/src/styles/detail/archive.css +++ b/src/styles/detail/archive.css @@ -91,12 +91,21 @@ } .tag-selector__tools button:hover, -.tag-select-item:hover, -.tag-select-item--selected { +.tag-select-item:hover { border-color: var(--line-strong); background: rgba(201, 122, 61, 0.12); } +.tag-select-item--selected { + border-color: rgba(232, 201, 160, 0.72); + background: + linear-gradient(135deg, rgba(201, 122, 61, 0.22), rgba(212, 165, 116, 0.08)), + rgba(15, 23, 42, 0.62); + box-shadow: + inset 3px 0 0 var(--accent-soft), + 0 0 0 1px rgba(201, 122, 61, 0.12); +} + .selected-tags { min-height: 38px; display: flex; @@ -165,10 +174,28 @@ font-size: 0.76rem; } -.tag-select-item--selected span { +.tag-select-item__side { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 7px; + min-width: 0; +} + +.tag-select-item__side svg { + flex: 0 0 auto; + color: var(--accent-soft); +} + +.tag-select-item--selected .tag-select-item__name span, +.tag-select-item--selected .tag-select-item__side strong { color: #f8fafc; } +.tag-select-item--selected .tag-select-item__name small { + color: #e8c9a0; +} + .archive-chart { min-width: 0; min-height: 0; -- 2.49.1