@dexteel/mesf-core 7.20.1 → 7.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "7.20.1"
2
+ ".": "7.22.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.22.0](https://github.com/dexteel/mesf-core-frontend/compare/@dexteel/mesf-core-v7.21.0...@dexteel/mesf-core-v7.22.0) (2026-05-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * **trendings-v2:** simplify chart layout ([114ea8f](https://github.com/dexteel/mesf-core-frontend/commit/114ea8fd2f5495f8ccc2d52bcff0bdb6d5202999))
9
+
10
+ ## [7.21.0](https://github.com/dexteel/mesf-core-frontend/compare/@dexteel/mesf-core-v7.20.1...@dexteel/mesf-core-v7.21.0) (2026-04-24)
11
+
12
+
13
+ ### Features
14
+
15
+ * **trendings-v2:** add stepped mode and URL state sync ([d87f8b6](https://github.com/dexteel/mesf-core-frontend/commit/d87f8b6f1e22b46d2a32b7e59017a4f62a9b0323))
16
+
3
17
  ## [7.20.1](https://github.com/dexteel/mesf-core-frontend/compare/@dexteel/mesf-core-v7.20.0...@dexteel/mesf-core-v7.20.1) (2026-04-20)
4
18
 
5
19
 
package/dist/index.esm.js CHANGED
@@ -25,7 +25,7 @@ import DeleteIcon from '@mui/icons-material/Delete';
25
25
  import EditIcon from '@mui/icons-material/Edit';
26
26
  import FindInPageIcon from '@mui/icons-material/FindInPage';
27
27
  import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
28
- import { ArrowRight, ArrowBackRounded, ArrowForwardRounded, SkipNext, ChevronLeft, ChevronRight, Cloud, ExpandLess, ExpandMore, Square as Square$1, Timeline, Send, Menu as Menu$1, People, Storage, Group as Group$1, Assignment, Chat, ViewList, Build, Settings as Settings$2, Code as Code$1, FastRewind, FastForward, ZoomIn, Restore, Lock, Create, Delete, Folder, InsertChart, Search, PlaylistAdd, DragIndicator, Save, AttachFile, CloudUpload, GetApp } from '@mui/icons-material';
28
+ import { ArrowRight, ArrowBackRounded, ArrowForwardRounded, SkipNext, ChevronLeft, ChevronRight, Cloud, ExpandLess, ExpandMore, Square as Square$1, Timeline, Send, Menu as Menu$1, People, Storage, Group as Group$1, Assignment, Chat, ViewList, Build, Settings as Settings$2, Code as Code$1, KeyboardDoubleArrowLeft, KeyboardArrowLeft, KeyboardArrowRight, KeyboardDoubleArrowRight, Update, ZoomIn, Restore, Lock, Create, Delete, Folder, InsertChart, Search, PlaylistAdd, DragIndicator, Save, AttachFile, CloudUpload, GetApp } from '@mui/icons-material';
29
29
  import ContentCopyIcon from '@mui/icons-material/ContentCopy';
30
30
  import FormatListBulletedSharpIcon from '@mui/icons-material/FormatListBulletedSharp';
31
31
  import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
@@ -11851,19 +11851,21 @@ const dateNavigator = (startDate, endDate, scope, operator, current = false) =>
11851
11851
  return { newStartDate, newEndDate };
11852
11852
  };
11853
11853
 
11854
- const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setChartOptions, chartInstance }) => {
11854
+ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setChartOptions, setAggregationMode, chartInstance, }) => {
11855
11855
  const queryClient = useQueryClient();
11856
11856
  const { state: { timeScopeStart, timeScopeEnd, scope, viewSelected, views }, actions: { setTotalScope, setViewSelected }, } = useTrendingContextV2();
11857
11857
  const [customOptions, setCustomOptions] = useState({
11858
11858
  showGridLines: true,
11859
11859
  showSymbols: false,
11860
- smoothLines: true,
11860
+ smoothLines: false,
11861
+ steppedLines: true,
11861
11862
  showLegend: false,
11862
11863
  showTooltip: true,
11863
- combinedView: false,
11864
+ combinedView: true,
11864
11865
  showToolbox: true,
11865
- showYAxisNames: true,
11866
+ showYAxisNames: false,
11866
11867
  });
11868
+ const [aggregationMode, setAggregationModeState] = useState("avg");
11867
11869
  // View management state
11868
11870
  const [viewModalMode, setViewModalMode] = useState(null);
11869
11871
  const [viewForModal, setViewForModal] = useState(null);
@@ -11980,6 +11982,11 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
11980
11982
  setChartOptions(customOptions);
11981
11983
  }
11982
11984
  }, [customOptions, setChartOptions]);
11985
+ useEffect(() => {
11986
+ if (setAggregationMode) {
11987
+ setAggregationMode(aggregationMode);
11988
+ }
11989
+ }, [aggregationMode, setAggregationMode]);
11983
11990
  return (React__default.createElement(React__default.Fragment, null,
11984
11991
  React__default.createElement(Grid2, { size: 12, container: true, spacing: 1, justifyContent: "space-between", alignItems: "center", style: { height: "auto" } },
11985
11992
  React__default.createElement(Grid2, { size: { md: 5, sm: 12, xs: 12 }, container: true, justifyContent: "flex-start", alignItems: "center", wrap: "wrap" },
@@ -11992,12 +11999,13 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
11992
11999
  width: "100%",
11993
12000
  } },
11994
12001
  React__default.createElement(Grid2, null,
11995
- React__default.createElement(IconButton$1, { onClick: () => handleDateNavigator("subtract"), size: "small" },
11996
- React__default.createElement(ChevronLeft, { fontSize: "medium", sx: { color: "black" } }))),
12002
+ React__default.createElement(Tooltip, { title: "Previous period", arrow: true },
12003
+ React__default.createElement(IconButton$1, { onClick: () => handleDateNavigator("subtract"), size: "small" },
12004
+ React__default.createElement(KeyboardDoubleArrowLeft, { fontSize: "medium", sx: { color: "black" } })))),
11997
12005
  React__default.createElement(Grid2, null,
11998
- React__default.createElement(Tooltip, { title: "Navigate backward 20%", arrow: true },
12006
+ React__default.createElement(Tooltip, { title: "Pan backward 20%", arrow: true },
11999
12007
  React__default.createElement(IconButton$1, { onClick: () => handlePartialDateNavigator("subtract"), size: "small" },
12000
- React__default.createElement(FastRewind, { fontSize: "medium", sx: { color: "black" } })))),
12008
+ React__default.createElement(KeyboardArrowLeft, { fontSize: "medium", sx: { color: "black" } })))),
12001
12009
  React__default.createElement(Grid2, { size: { md: 3.5 } },
12002
12010
  React__default.createElement(LocalizationProvider$1, { dateAdapter: AdapterMoment },
12003
12011
  React__default.createElement(DateTimePicker, { label: "Start", format: "MM/DD/YYYY HH:mm:ss", value: moment$g(timeScopeStart), onChange: (newValue) => {
@@ -12035,15 +12043,17 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
12035
12043
  },
12036
12044
  }, disabled: autoRefresh }))),
12037
12045
  React__default.createElement(Grid2, null,
12038
- React__default.createElement(Tooltip, { title: "Navigate forward 20%", arrow: true },
12046
+ React__default.createElement(Tooltip, { title: "Pan forward 20%", arrow: true },
12039
12047
  React__default.createElement(IconButton$1, { onClick: () => handlePartialDateNavigator("add"), size: "small" },
12040
- React__default.createElement(FastForward, { fontSize: "medium", sx: { color: "black" } })))),
12048
+ React__default.createElement(KeyboardArrowRight, { fontSize: "medium", sx: { color: "black" } })))),
12041
12049
  React__default.createElement(Grid2, null,
12042
- React__default.createElement(IconButton$1, { onClick: () => handleDateNavigator("add"), size: "small" },
12043
- React__default.createElement(ChevronRight, { fontSize: "medium", sx: { color: "black" } }))),
12050
+ React__default.createElement(Tooltip, { title: "Next period", arrow: true },
12051
+ React__default.createElement(IconButton$1, { onClick: () => handleDateNavigator("add"), size: "small" },
12052
+ React__default.createElement(KeyboardDoubleArrowRight, { fontSize: "medium", sx: { color: "black" } })))),
12044
12053
  React__default.createElement(Grid2, null,
12045
- React__default.createElement(IconButton$1, { onClick: () => handleDateNavigator("subtract", true), size: "small" },
12046
- React__default.createElement(SkipNext, { fontSize: "medium", sx: { color: "black" } }))))),
12054
+ React__default.createElement(Tooltip, { title: "Jump to current time", arrow: true },
12055
+ React__default.createElement(IconButton$1, { onClick: () => handleDateNavigator("subtract", true), size: "small" },
12056
+ React__default.createElement(Update, { fontSize: "medium", sx: { color: "black" } })))))),
12047
12057
  React__default.createElement(Grid2, { size: { md: 5, sm: 6, xs: 12 } },
12048
12058
  React__default.createElement(Paper, { elevation: 0, sx: {
12049
12059
  p: 1,
@@ -12055,31 +12065,24 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
12055
12065
  alignItems: "stretch",
12056
12066
  justifyContent: "space-between",
12057
12067
  } },
12058
- React__default.createElement(Box, { sx: { width: 230 } },
12068
+ React__default.createElement(Box, { sx: { width: 155 } },
12059
12069
  React__default.createElement(FormControl, { fullWidth: true, size: "small" },
12060
12070
  React__default.createElement(Select, { multiple: true, value: [
12061
12071
  ...(customOptions.showGridLines ? ["grid"] : []),
12062
12072
  ...(customOptions.showSymbols ? ["symbols"] : []),
12063
12073
  ...(customOptions.smoothLines ? ["smooth"] : []),
12074
+ ...(customOptions.steppedLines ? ["stepped"] : []),
12064
12075
  ...(customOptions.showTooltip ? ["tooltip"] : []),
12065
12076
  ...(customOptions.showToolbox ? ["toolbox"] : []),
12066
12077
  ...(customOptions.showYAxisNames ? ["yAxisNames"] : []),
12067
12078
  ], onChange: (e) => {
12068
12079
  const selected = e.target.value;
12069
- setCustomOptions((prevOptions) => (Object.assign(Object.assign({}, prevOptions), { showGridLines: selected.includes("grid"), showSymbols: selected.includes("symbols"), smoothLines: selected.includes("smooth"), showTooltip: selected.includes("tooltip"), showToolbox: selected.includes("toolbox"), showYAxisNames: selected.includes("yAxisNames") })));
12070
- }, renderValue: (selected) => {
12071
- const labels = {
12072
- grid: "Grid",
12073
- symbols: "Symbols",
12074
- smooth: "Smooth",
12075
- tooltip: "Tooltip",
12076
- toolbox: "Toolbox",
12077
- yAxisNames: "Y-Axis Names",
12078
- };
12079
- return (React__default.createElement("span", { style: { fontSize: "0.75rem" } }, selected
12080
- .map((val) => labels[val])
12081
- .join(", ")));
12082
- } },
12080
+ setCustomOptions((prevOptions) => (Object.assign(Object.assign({}, prevOptions), { showGridLines: selected.includes("grid"), showSymbols: selected.includes("symbols"), smoothLines: selected.includes("smooth") &&
12081
+ !selected.includes("stepped"), steppedLines: selected.includes("stepped"), showTooltip: selected.includes("tooltip"), showToolbox: selected.includes("toolbox"), showYAxisNames: selected.includes("yAxisNames") })));
12082
+ }, renderValue: (selected) => (React__default.createElement("span", { style: { fontSize: "0.75rem" } },
12083
+ "Chart: ",
12084
+ selected.length,
12085
+ " options")) },
12083
12086
  React__default.createElement(MenuItem, { value: "grid", style: { padding: 0 } },
12084
12087
  React__default.createElement(Checkbox, { checked: customOptions.showGridLines, size: "small" }),
12085
12088
  React__default.createElement(ListItemText, null, "Grid")),
@@ -12089,6 +12092,9 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
12089
12092
  React__default.createElement(MenuItem, { value: "smooth", style: { padding: 0 } },
12090
12093
  React__default.createElement(Checkbox, { checked: customOptions.smoothLines, size: "small" }),
12091
12094
  React__default.createElement(ListItemText, null, "Smooth")),
12095
+ React__default.createElement(MenuItem, { value: "stepped", style: { padding: 0 } },
12096
+ React__default.createElement(Checkbox, { checked: customOptions.steppedLines, size: "small" }),
12097
+ React__default.createElement(ListItemText, null, "Stepped")),
12092
12098
  React__default.createElement(MenuItem, { value: "tooltip", style: { padding: 0 } },
12093
12099
  React__default.createElement(Checkbox, { checked: customOptions.showTooltip, size: "small" }),
12094
12100
  React__default.createElement(ListItemText, null, "Tooltip")),
@@ -12098,11 +12104,18 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
12098
12104
  React__default.createElement(MenuItem, { value: "yAxisNames", style: { padding: 0 } },
12099
12105
  React__default.createElement(Checkbox, { checked: customOptions.showYAxisNames, size: "small" }),
12100
12106
  React__default.createElement(ListItemText, null, "Y-Axis Names"))))),
12107
+ React__default.createElement(Box, { sx: { minWidth: 150 } },
12108
+ React__default.createElement(FormControl, { fullWidth: true, size: "small" },
12109
+ React__default.createElement(Select, { value: aggregationMode, onChange: (e) => {
12110
+ setAggregationModeState(e.target.value);
12111
+ }, renderValue: (selected) => selected === "last" ? "Agg: Last" : "Agg: Avg" },
12112
+ React__default.createElement(MenuItem, { value: "avg" }, "Average"),
12113
+ React__default.createElement(MenuItem, { value: "last" }, "Last value")))),
12101
12114
  React__default.createElement(Divider, { orientation: "vertical", flexItem: true }),
12102
12115
  React__default.createElement(FormControlLabel, { checked: customOptions.combinedView, control: React__default.createElement(Switch, { color: "primary", size: "small" }), label: React__default.createElement("span", { style: { fontSize: "0.75rem", fontWeight: 600 } }, "Combined"), style: { margin: "0" }, onChange: (e, checked) => {
12103
12116
  setCustomOptions((prevOptions) => (Object.assign(Object.assign({}, prevOptions), { combinedView: checked })));
12104
12117
  } }),
12105
- React__default.createElement(FormControlLabel, { checked: autoRefresh, control: React__default.createElement(Switch, { color: "primary", size: "small" }), label: React__default.createElement("span", { style: { fontSize: "0.75rem", fontWeight: 600 } }, "Auto Refresh"), style: { margin: "0" }, onChange: (e, checked) => {
12118
+ React__default.createElement(FormControlLabel, { checked: autoRefresh, control: React__default.createElement(Switch, { color: "primary", size: "small" }), label: React__default.createElement("span", { style: { fontSize: "0.75rem", fontWeight: 600 } }, "Auto"), style: { margin: "0" }, onChange: (e, checked) => {
12106
12119
  if (checked) {
12107
12120
  // When enabling auto-refresh, set end to now
12108
12121
  // and keep the current period to calculate start
@@ -12121,8 +12134,12 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
12121
12134
  setAutoRefresh(checked);
12122
12135
  } }),
12123
12136
  React__default.createElement(Divider, { orientation: "vertical", flexItem: true }),
12124
- React__default.createElement(Button, { variant: "outlined", size: "small", color: "primary", startIcon: React__default.createElement(ZoomIn, null), onClick: handleZoomIn, style: { fontSize: "0.7rem" } }, "Zoom"),
12125
- React__default.createElement(Button, { variant: "outlined", size: "small", color: "primary", startIcon: React__default.createElement(Restore, null), onClick: handleRestoreZoom, style: { fontSize: "0.7rem" } }, "Restore")))),
12137
+ React__default.createElement(Tooltip, { title: "Area zoom", arrow: true },
12138
+ React__default.createElement(IconButton$1, { color: "primary", onClick: handleZoomIn, size: "small" },
12139
+ React__default.createElement(ZoomIn, { fontSize: "small" }))),
12140
+ React__default.createElement(Tooltip, { title: "Restore zoom", arrow: true },
12141
+ React__default.createElement(IconButton$1, { color: "primary", onClick: handleRestoreZoom, size: "small" },
12142
+ React__default.createElement(Restore, { fontSize: "small" })))))),
12126
12143
  React__default.createElement(Grid2, { size: { md: 2, sm: 6, xs: 12 }, container: true, justifyContent: "flex-end", alignItems: "center" },
12127
12144
  React__default.createElement(Paper, { elevation: 0, sx: {
12128
12145
  p: 1,
@@ -14513,13 +14530,25 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
14513
14530
  const opts = customOptions || {
14514
14531
  showGridLines: true,
14515
14532
  showSymbols: false,
14516
- smoothLines: true,
14533
+ smoothLines: false,
14534
+ steppedLines: true,
14517
14535
  showLegend: true,
14518
14536
  showTooltip: true,
14519
- combinedView: false,
14537
+ combinedView: true,
14520
14538
  showToolbox: true,
14521
- showYAxisNames: true,
14539
+ showYAxisNames: false,
14522
14540
  };
14541
+ const getTagName = useCallback((tag) => tag.Alias || tag.TagName, []);
14542
+ const getActiveAxisTag = useCallback((tags) => {
14543
+ if (tags.length === 0)
14544
+ return null;
14545
+ if (highlightedSeries) {
14546
+ const highlightedTag = tags.find((tag) => tag.IsVisible && getTagName(tag) === highlightedSeries);
14547
+ if (highlightedTag)
14548
+ return highlightedTag;
14549
+ }
14550
+ return tags.find((tag) => tag.IsVisible) || null;
14551
+ }, [highlightedSeries, getTagName]);
14523
14552
  // Clear zoom state when time scope changes or combined view toggles
14524
14553
  useEffect(() => {
14525
14554
  const currentTimeScope = `${timeScopeStart.getTime()}-${timeScopeEnd.getTime()}`;
@@ -14538,13 +14567,14 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
14538
14567
  prevCombinedViewRef.current = opts.combinedView;
14539
14568
  }, [timeScopeStart, timeScopeEnd, opts.combinedView]);
14540
14569
  // Helper to build yAxis config with scale settings
14541
- const buildYAxisConfig = useCallback((tag, gridIndex, position, offset = 0) => {
14570
+ const buildYAxisConfig = useCallback((tag, gridIndex, position, offset = 0, isAxisVisible = true) => {
14542
14571
  const baseConfig = {
14543
14572
  gridIndex,
14544
14573
  type: "value",
14545
- name: opts.showYAxisNames ? tag.Alias || tag.TagName : "",
14574
+ show: isAxisVisible,
14575
+ name: isAxisVisible && opts.showYAxisNames ? getTagName(tag) : "",
14546
14576
  nameLocation: "middle",
14547
- nameGap: 45 + offset,
14577
+ nameGap: 45,
14548
14578
  position,
14549
14579
  offset,
14550
14580
  splitNumber: 8, // Suggest 8 divisions, ECharts will adjust for readability
@@ -14554,21 +14584,24 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
14554
14584
  fontWeight: "bold",
14555
14585
  },
14556
14586
  axisLine: {
14557
- show: true,
14587
+ show: isAxisVisible,
14558
14588
  lineStyle: {
14559
14589
  color: tag.Color,
14590
+ width: 2,
14560
14591
  },
14561
14592
  },
14562
14593
  axisTick: {
14563
- show: true,
14594
+ show: isAxisVisible,
14564
14595
  },
14565
14596
  axisLabel: {
14566
- show: true,
14597
+ show: isAxisVisible,
14567
14598
  color: tag.Color,
14568
14599
  fontSize: 10,
14569
14600
  },
14570
14601
  splitLine: {
14571
- show: position === "left" && offset === 0 ? opts.showGridLines : false,
14602
+ show: isAxisVisible && position === "left" && offset === 0
14603
+ ? opts.showGridLines
14604
+ : false,
14572
14605
  lineStyle: {
14573
14606
  color: "#e0e0e0",
14574
14607
  type: "solid",
@@ -14600,7 +14633,7 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
14600
14633
  delete baseConfig.scale;
14601
14634
  }
14602
14635
  return baseConfig;
14603
- }, [opts.showGridLines, true, opts.showYAxisNames]);
14636
+ }, [opts.showGridLines, opts.showYAxisNames, getTagName]);
14604
14637
  // Determine if we should use separate grids (analog/digital)
14605
14638
  const useSeparateGrids = !opts.combinedView &&
14606
14639
  processedData.analogTags.length > 0 &&
@@ -14731,21 +14764,8 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
14731
14764
  isUserDraggingRef.current = false;
14732
14765
  }, [handleCursor2Change]);
14733
14766
  const chartOptions = useMemo(() => {
14734
- // Calculate dynamic margins based on number of tags to ensure all Y-axes are visible
14735
- // Formula: Each pair of tags (left + right) needs 60px offset
14736
- // Base margin is 60px, then add 60px for each additional pair
14737
- const calculateMargin = (tagCount) => {
14738
- if (tagCount === 0)
14739
- return 60;
14740
- // Number of axes on each side (alternating left/right)
14741
- const axesPerSide = Math.ceil(tagCount / 2);
14742
- // Last axis offset = (axesPerSide - 1) * 60
14743
- // Total margin = last offset + base margin (60px)
14744
- return (axesPerSide - 1) * 60 + 60;
14745
- };
14746
- const analogMargin = calculateMargin(processedData.analogTags.length);
14747
- const digitalMargin = calculateMargin(processedData.digitalTags.length);
14748
- const combinedMargin = calculateMargin(processedData.analogTags.length + processedData.digitalTags.length);
14767
+ const chartLeftMargin = opts.showYAxisNames ? 96 : 72;
14768
+ const chartRightMargin = 32;
14749
14769
  return {
14750
14770
  animation: false,
14751
14771
  backgroundColor: "#fff",
@@ -14754,16 +14774,16 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
14754
14774
  ? [
14755
14775
  {
14756
14776
  id: "analog",
14757
- left: `${analogMargin}px`,
14758
- right: `${analogMargin}px`,
14777
+ left: `${chartLeftMargin}px`,
14778
+ right: `${chartRightMargin}px`,
14759
14779
  top: opts.showLegend ? "70px" : "50px", // Increased to prevent cursor circle clipping
14760
14780
  bottom: "55%",
14761
14781
  containLabel: false,
14762
14782
  },
14763
14783
  {
14764
14784
  id: "digital",
14765
- left: `${digitalMargin}px`,
14766
- right: `${digitalMargin}px`,
14785
+ left: `${chartLeftMargin}px`,
14786
+ right: `${chartRightMargin}px`,
14767
14787
  top: "55%",
14768
14788
  bottom: 60,
14769
14789
  containLabel: false,
@@ -14772,8 +14792,8 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
14772
14792
  : [
14773
14793
  {
14774
14794
  id: "combined",
14775
- left: `${combinedMargin}px`,
14776
- right: `${combinedMargin}px`,
14795
+ left: `${chartLeftMargin}px`,
14796
+ right: `${chartRightMargin}px`,
14777
14797
  top: opts.showLegend ? "70px" : "50px", // Increased to prevent cursor circle clipping
14778
14798
  bottom: 60,
14779
14799
  containLabel: false,
@@ -15000,34 +15020,25 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
15000
15020
  yAxis: (() => {
15001
15021
  const yAxisArray = [];
15002
15022
  if (useSeparateGrids) {
15003
- // Separate view with both analog and digital: analog in grid 0, digital in grid 1
15004
- // Add analog axes (grid 0)
15023
+ const activeAnalogTag = getActiveAxisTag(processedData.analogTags);
15024
+ const activeDigitalTag = getActiveAxisTag(processedData.digitalTags);
15005
15025
  processedData.analogTags.forEach((tag, idx) => {
15006
- const position = idx % 2 === 0 ? "left" : "right";
15007
- const offset = Math.floor(idx / 2) * 60;
15008
- yAxisArray.push(buildYAxisConfig(tag, 0, position, offset));
15026
+ yAxisArray.push(buildYAxisConfig(tag, 0, "left", 0, tag === activeAnalogTag));
15009
15027
  });
15010
- // Add digital axes (grid 1)
15011
- processedData.digitalTags.forEach((tag, idx) => {
15012
- const position = idx % 2 === 0 ? "left" : "right";
15013
- const offset = Math.floor(idx / 2) * 60;
15014
- yAxisArray.push(buildYAxisConfig(tag, 1, position, offset));
15028
+ processedData.digitalTags.forEach((tag) => {
15029
+ yAxisArray.push(buildYAxisConfig(tag, 1, "left", 0, tag === activeDigitalTag));
15015
15030
  });
15016
15031
  }
15017
15032
  else {
15018
- // Combined view or only one type: all axes in grid 0
15019
- // Add analog axes
15020
- processedData.analogTags.forEach((tag, idx) => {
15021
- const position = idx % 2 === 0 ? "left" : "right";
15022
- const offset = Math.floor(idx / 2) * 60;
15023
- yAxisArray.push(buildYAxisConfig(tag, 0, position, offset));
15033
+ const activeCombinedTag = getActiveAxisTag([
15034
+ ...processedData.analogTags,
15035
+ ...processedData.digitalTags,
15036
+ ]);
15037
+ processedData.analogTags.forEach((tag) => {
15038
+ yAxisArray.push(buildYAxisConfig(tag, 0, "left", 0, tag === activeCombinedTag));
15024
15039
  });
15025
- // Add digital axes
15026
- processedData.digitalTags.forEach((tag, idx) => {
15027
- const position = idx % 2 === 0 ? "left" : "right";
15028
- const baseOffset = Math.ceil(processedData.analogTags.length / 2) * 60;
15029
- const offset = baseOffset + Math.floor(idx / 2) * 60;
15030
- yAxisArray.push(buildYAxisConfig(tag, 0, position, offset));
15040
+ processedData.digitalTags.forEach((tag) => {
15041
+ yAxisArray.push(buildYAxisConfig(tag, 0, "left", 0, tag === activeCombinedTag));
15031
15042
  });
15032
15043
  }
15033
15044
  // If no axes were created at all, add a default one to prevent errors
@@ -15057,7 +15068,8 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
15057
15068
  xAxisIndex: 0,
15058
15069
  yAxisIndex: idx, // Map to corresponding analog yAxis
15059
15070
  data: s.data,
15060
- smooth: opts.smoothLines,
15071
+ smooth: opts.steppedLines ? false : opts.smoothLines,
15072
+ step: opts.steppedLines ? "end" : undefined,
15061
15073
  symbol: opts.showSymbols ? "circle" : "none",
15062
15074
  sampling: "lttb",
15063
15075
  show: s.visible,
@@ -15126,10 +15138,25 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
15126
15138
  if (!params || params.length === 0)
15127
15139
  return "";
15128
15140
  const date = new Date(params[0].value[0]);
15141
+ const formatTooltipValue = (value) => {
15142
+ if (value === null || value === undefined || value === "") {
15143
+ return "-";
15144
+ }
15145
+ const numericValue = Number(value);
15146
+ if (!Number.isFinite(numericValue)) {
15147
+ return String(value);
15148
+ }
15149
+ if (Number.isInteger(numericValue)) {
15150
+ return numericValue.toString();
15151
+ }
15152
+ return numericValue.toLocaleString(undefined, {
15153
+ maximumFractionDigits: 5,
15154
+ });
15155
+ };
15129
15156
  let content = `<div style="font-weight: bold; margin-bottom: 5px;">${date.toLocaleString()}</div>`;
15130
15157
  params.forEach((param) => {
15131
15158
  const marker = `<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background-color:${param.color};margin-right:5px;"></span>`;
15132
- content += `<div>${marker}${param.seriesName}: ${param.value[1]}</div>`;
15159
+ content += `<div>${marker}${param.seriesName}: ${formatTooltipValue(param.value[1])}</div>`;
15133
15160
  });
15134
15161
  return content;
15135
15162
  },
@@ -15200,6 +15227,7 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
15200
15227
  timeScopeStart,
15201
15228
  timeScopeEnd,
15202
15229
  buildYAxisConfig,
15230
+ getActiveAxisTag,
15203
15231
  zoomState,
15204
15232
  useSeparateGrids,
15205
15233
  ]);
@@ -15499,9 +15527,9 @@ const useSearchViewTags = ({ viewId }) => {
15499
15527
  enabled: viewId !== null,
15500
15528
  });
15501
15529
  };
15502
- const useSearchSeries = ({ start, end, tagIds, autoRefresh = false, }) => {
15530
+ const useSearchSeries = ({ start, end, tagIds, aggregationMode, autoRefresh = false, }) => {
15503
15531
  return useQuery({
15504
- queryKey: ["series-v2", start, end, tagIds],
15532
+ queryKey: ["series-v2", start, end, tagIds, aggregationMode],
15505
15533
  queryFn: (_a) => __awaiter(void 0, [_a], void 0, function* ({ signal }) {
15506
15534
  try {
15507
15535
  if (!tagIds || tagIds.length === 0) {
@@ -15512,6 +15540,7 @@ const useSearchSeries = ({ start, end, tagIds, autoRefresh = false, }) => {
15512
15540
  end: end || new Date().getTime(),
15513
15541
  tagIds,
15514
15542
  sampleCount: 1000,
15543
+ aggregationMode,
15515
15544
  }, {
15516
15545
  signal,
15517
15546
  });
@@ -15528,14 +15557,60 @@ const useSearchSeries = ({ start, end, tagIds, autoRefresh = false, }) => {
15528
15557
  enabled: tagIds.length > 0,
15529
15558
  });
15530
15559
  };
15560
+ const VALID_SCOPE_VALUES = [
15561
+ "10 min",
15562
+ "1 hour",
15563
+ "4 hours",
15564
+ "12 hours",
15565
+ "1 day",
15566
+ "10 days",
15567
+ "custom",
15568
+ ];
15569
+ const getInitialUrlParams = () => {
15570
+ if (typeof window === "undefined") {
15571
+ return {
15572
+ viewId: null,
15573
+ start: null,
15574
+ end: null,
15575
+ period: null,
15576
+ };
15577
+ }
15578
+ const searchParams = new URLSearchParams(window.location.search);
15579
+ const viewParam = Number(searchParams.get("view"));
15580
+ const startParam = Number(searchParams.get("start"));
15581
+ const endParam = Number(searchParams.get("end"));
15582
+ const periodParam = searchParams.get("period");
15583
+ return {
15584
+ viewId: Number.isInteger(viewParam) ? viewParam : null,
15585
+ start: Number.isFinite(startParam) ? startParam : null,
15586
+ end: Number.isFinite(endParam) ? endParam : null,
15587
+ period: VALID_SCOPE_VALUES.includes(periodParam)
15588
+ ? periodParam
15589
+ : null,
15590
+ };
15591
+ };
15531
15592
  const TrendingsPageV2 = () => {
15532
15593
  const [autoRefresh, setAutoRefresh] = useState(false);
15533
- const [chartOptions, setChartOptions] = useState(null);
15594
+ const [chartOptions, setChartOptions] = useState({
15595
+ showGridLines: true,
15596
+ showSymbols: false,
15597
+ smoothLines: false,
15598
+ steppedLines: true,
15599
+ showLegend: false,
15600
+ showTooltip: true,
15601
+ combinedView: true,
15602
+ showToolbox: true,
15603
+ showYAxisNames: false,
15604
+ });
15605
+ const [aggregationMode, setAggregationMode] = useState("avg");
15534
15606
  const [error, setError] = useState("");
15535
15607
  const [viewId, setViewId] = useState(null);
15536
15608
  const [chartInstance, setChartInstance] = useState(null);
15537
15609
  const [dataLoadedTrigger, setDataLoadedTrigger] = useState(0);
15538
- const { state: { viewSelected, viewTags, timeScopeStart, timeScopeEnd }, actions: { setViews, setViewTagsAndRefetch, setViewSelected, setSeriesMinMax, resetCursors, }, } = useTrendingContextV2();
15610
+ const { state: { viewSelected, viewTags, timeScopeStart, timeScopeEnd, scope }, actions: { setViews, setViewTagsAndRefetch, setViewSelected, setTotalScope, setSeriesMinMax, resetCursors, }, } = useTrendingContextV2();
15611
+ const initialUrlParamsRef = useRef(getInitialUrlParams());
15612
+ const initialTimeScopeAppliedRef = useRef(false);
15613
+ const initialViewSelectionResolvedRef = useRef(false);
15539
15614
  // Fetch views
15540
15615
  const { data: views, isLoading: viewsLoading, isError: viewsIsError, error: viewsError, isSuccess: viewSuccess, } = useSearchViews({ autoRefresh });
15541
15616
  // Fetch view tags
@@ -15580,6 +15655,7 @@ const TrendingsPageV2 = () => {
15580
15655
  start: timeScopeStart.getTime(),
15581
15656
  end: timeScopeEnd.getTime(),
15582
15657
  tagIds: queryTagIds,
15658
+ aggregationMode,
15583
15659
  autoRefresh,
15584
15660
  });
15585
15661
  // Filter and reorder series to match current viewTags order
@@ -15634,6 +15710,21 @@ const TrendingsPageV2 = () => {
15634
15710
  }, [series, tagIds, queryTagIds, viewTags]);
15635
15711
  // Calculate overall min/max values from filtered series data
15636
15712
  const seriesMinMaxData = useSeriesMinMax(filteredSeries, tagIds);
15713
+ // Apply explicit time range from URL once on initial load
15714
+ useEffect(() => {
15715
+ if (initialTimeScopeAppliedRef.current)
15716
+ return;
15717
+ const { start, end, period } = initialUrlParamsRef.current;
15718
+ initialTimeScopeAppliedRef.current = true;
15719
+ if (start === null || end === null || period === null || start >= end) {
15720
+ return;
15721
+ }
15722
+ setTotalScope({
15723
+ start: new Date(start),
15724
+ end: new Date(end),
15725
+ scope: period,
15726
+ });
15727
+ }, [setTotalScope]);
15637
15728
  // Load initial view when views are fetched
15638
15729
  useEffect(() => {
15639
15730
  if (!viewSuccess || !views || !Array.isArray(views))
@@ -15642,12 +15733,26 @@ const TrendingsPageV2 = () => {
15642
15733
  setViews(views);
15643
15734
  // Only set initial view if none is selected
15644
15735
  if (!viewSelected) {
15736
+ const initialViewId = initialUrlParamsRef.current.viewId;
15737
+ if (!initialViewSelectionResolvedRef.current && initialViewId !== null) {
15738
+ initialViewSelectionResolvedRef.current = true;
15739
+ const urlView = views.find((view) => view.ViewId === initialViewId);
15740
+ if (urlView) {
15741
+ setViewId(urlView.ViewId);
15742
+ setViewSelected(urlView);
15743
+ return;
15744
+ }
15745
+ }
15645
15746
  const lastCreated = views.reduce((prev, current) => prev && prev.ViewId > current.ViewId ? prev : current, null);
15646
15747
  if (lastCreated) {
15748
+ initialViewSelectionResolvedRef.current = true;
15647
15749
  setViewId(lastCreated.ViewId);
15648
15750
  setViewSelected(lastCreated);
15649
15751
  }
15650
15752
  }
15753
+ else {
15754
+ initialViewSelectionResolvedRef.current = true;
15755
+ }
15651
15756
  }, [viewSuccess, views, viewSelected, setViews, setViewSelected]);
15652
15757
  // Update viewId when viewSelected changes (e.g., from LoadViewModal)
15653
15758
  useEffect(() => {
@@ -15655,6 +15760,26 @@ const TrendingsPageV2 = () => {
15655
15760
  setViewId(viewSelected.ViewId);
15656
15761
  }
15657
15762
  }, [viewSelected]);
15763
+ useEffect(() => {
15764
+ if (typeof window === "undefined" ||
15765
+ !initialTimeScopeAppliedRef.current ||
15766
+ !initialViewSelectionResolvedRef.current) {
15767
+ return;
15768
+ }
15769
+ const searchParams = new URLSearchParams(window.location.search);
15770
+ if (viewSelected) {
15771
+ searchParams.set("view", String(viewSelected.ViewId));
15772
+ }
15773
+ else {
15774
+ searchParams.delete("view");
15775
+ }
15776
+ searchParams.set("start", String(timeScopeStart.getTime()));
15777
+ searchParams.set("end", String(timeScopeEnd.getTime()));
15778
+ searchParams.set("period", scope);
15779
+ const newSearch = searchParams.toString();
15780
+ const newUrl = `${window.location.pathname}${newSearch ? `?${newSearch}` : ""}${window.location.hash}`;
15781
+ window.history.replaceState(null, "", newUrl);
15782
+ }, [viewSelected, timeScopeStart, timeScopeEnd, scope]);
15658
15783
  // Load view tags when fetched - ONLY when viewId changes, not on every data refetch
15659
15784
  // This preserves local order changes (from drag-and-drop) until a new view is loaded
15660
15785
  const loadedViewIdRef = useRef(null);
@@ -15806,7 +15931,7 @@ const TrendingsPageV2 = () => {
15806
15931
  } },
15807
15932
  React__default.createElement(CircularProgress, { size: "3rem" }))),
15808
15933
  React__default.createElement("div", { style: { flexShrink: 0 } },
15809
- React__default.createElement(HeaderSectionV2, { autoRefresh: autoRefresh, setAutoRefresh: setAutoRefresh, setChartOptions: setChartOptions, chartInstance: chartInstance })),
15934
+ React__default.createElement(HeaderSectionV2, { autoRefresh: autoRefresh, setAutoRefresh: setAutoRefresh, setChartOptions: setChartOptions, setAggregationMode: setAggregationMode, chartInstance: chartInstance })),
15810
15935
  React__default.createElement(Divider, { sx: { my: 2 } }),
15811
15936
  React__default.createElement("div", { style: {
15812
15937
  flexGrow: 1,