@dexteel/mesf-core 7.20.1 → 7.21.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.21.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [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)
4
+
5
+
6
+ ### Features
7
+
8
+ * **trendings-v2:** add stepped mode and URL state sync ([d87f8b6](https://github.com/dexteel/mesf-core-frontend/commit/d87f8b6f1e22b46d2a32b7e59017a4f62a9b0323))
9
+
3
10
  ## [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
11
 
5
12
 
package/dist/index.esm.js CHANGED
@@ -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
11864
  combinedView: false,
11864
11865
  showToolbox: true,
11865
11866
  showYAxisNames: true,
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" },
@@ -12061,17 +12068,20 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
12061
12068
  ...(customOptions.showGridLines ? ["grid"] : []),
12062
12069
  ...(customOptions.showSymbols ? ["symbols"] : []),
12063
12070
  ...(customOptions.smoothLines ? ["smooth"] : []),
12071
+ ...(customOptions.steppedLines ? ["stepped"] : []),
12064
12072
  ...(customOptions.showTooltip ? ["tooltip"] : []),
12065
12073
  ...(customOptions.showToolbox ? ["toolbox"] : []),
12066
12074
  ...(customOptions.showYAxisNames ? ["yAxisNames"] : []),
12067
12075
  ], onChange: (e) => {
12068
12076
  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") })));
12077
+ setCustomOptions((prevOptions) => (Object.assign(Object.assign({}, prevOptions), { showGridLines: selected.includes("grid"), showSymbols: selected.includes("symbols"), smoothLines: selected.includes("smooth") &&
12078
+ !selected.includes("stepped"), steppedLines: selected.includes("stepped"), showTooltip: selected.includes("tooltip"), showToolbox: selected.includes("toolbox"), showYAxisNames: selected.includes("yAxisNames") })));
12070
12079
  }, renderValue: (selected) => {
12071
12080
  const labels = {
12072
12081
  grid: "Grid",
12073
12082
  symbols: "Symbols",
12074
12083
  smooth: "Smooth",
12084
+ stepped: "Stepped",
12075
12085
  tooltip: "Tooltip",
12076
12086
  toolbox: "Toolbox",
12077
12087
  yAxisNames: "Y-Axis Names",
@@ -12089,6 +12099,9 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
12089
12099
  React__default.createElement(MenuItem, { value: "smooth", style: { padding: 0 } },
12090
12100
  React__default.createElement(Checkbox, { checked: customOptions.smoothLines, size: "small" }),
12091
12101
  React__default.createElement(ListItemText, null, "Smooth")),
12102
+ React__default.createElement(MenuItem, { value: "stepped", style: { padding: 0 } },
12103
+ React__default.createElement(Checkbox, { checked: customOptions.steppedLines, size: "small" }),
12104
+ React__default.createElement(ListItemText, null, "Stepped")),
12092
12105
  React__default.createElement(MenuItem, { value: "tooltip", style: { padding: 0 } },
12093
12106
  React__default.createElement(Checkbox, { checked: customOptions.showTooltip, size: "small" }),
12094
12107
  React__default.createElement(ListItemText, null, "Tooltip")),
@@ -12098,6 +12111,15 @@ const HeaderSectionV2 = React__default.memo(({ autoRefresh, setAutoRefresh, setC
12098
12111
  React__default.createElement(MenuItem, { value: "yAxisNames", style: { padding: 0 } },
12099
12112
  React__default.createElement(Checkbox, { checked: customOptions.showYAxisNames, size: "small" }),
12100
12113
  React__default.createElement(ListItemText, null, "Y-Axis Names"))))),
12114
+ React__default.createElement(Box, { sx: { minWidth: 150 } },
12115
+ React__default.createElement(FormControl, { fullWidth: true, size: "small" },
12116
+ React__default.createElement(Select, { value: aggregationMode, onChange: (e) => {
12117
+ setAggregationModeState(e.target.value);
12118
+ }, renderValue: (selected) => selected === "last"
12119
+ ? "Aggregation: Last value"
12120
+ : "Aggregation: Average" },
12121
+ React__default.createElement(MenuItem, { value: "avg" }, "Average"),
12122
+ React__default.createElement(MenuItem, { value: "last" }, "Last value")))),
12101
12123
  React__default.createElement(Divider, { orientation: "vertical", flexItem: true }),
12102
12124
  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
12125
  setCustomOptions((prevOptions) => (Object.assign(Object.assign({}, prevOptions), { combinedView: checked })));
@@ -14513,7 +14535,8 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
14513
14535
  const opts = customOptions || {
14514
14536
  showGridLines: true,
14515
14537
  showSymbols: false,
14516
- smoothLines: true,
14538
+ smoothLines: false,
14539
+ steppedLines: true,
14517
14540
  showLegend: true,
14518
14541
  showTooltip: true,
14519
14542
  combinedView: false,
@@ -15057,7 +15080,8 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
15057
15080
  xAxisIndex: 0,
15058
15081
  yAxisIndex: idx, // Map to corresponding analog yAxis
15059
15082
  data: s.data,
15060
- smooth: opts.smoothLines,
15083
+ smooth: opts.steppedLines ? false : opts.smoothLines,
15084
+ step: opts.steppedLines ? "end" : undefined,
15061
15085
  symbol: opts.showSymbols ? "circle" : "none",
15062
15086
  sampling: "lttb",
15063
15087
  show: s.visible,
@@ -15126,10 +15150,25 @@ const TrendingChartV2 = ({ series = [], customOptions, isLoading = false, onChar
15126
15150
  if (!params || params.length === 0)
15127
15151
  return "";
15128
15152
  const date = new Date(params[0].value[0]);
15153
+ const formatTooltipValue = (value) => {
15154
+ if (value === null || value === undefined || value === "") {
15155
+ return "-";
15156
+ }
15157
+ const numericValue = Number(value);
15158
+ if (!Number.isFinite(numericValue)) {
15159
+ return String(value);
15160
+ }
15161
+ if (Number.isInteger(numericValue)) {
15162
+ return numericValue.toString();
15163
+ }
15164
+ return numericValue.toLocaleString(undefined, {
15165
+ maximumFractionDigits: 5,
15166
+ });
15167
+ };
15129
15168
  let content = `<div style="font-weight: bold; margin-bottom: 5px;">${date.toLocaleString()}</div>`;
15130
15169
  params.forEach((param) => {
15131
15170
  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>`;
15171
+ content += `<div>${marker}${param.seriesName}: ${formatTooltipValue(param.value[1])}</div>`;
15133
15172
  });
15134
15173
  return content;
15135
15174
  },
@@ -15499,9 +15538,9 @@ const useSearchViewTags = ({ viewId }) => {
15499
15538
  enabled: viewId !== null,
15500
15539
  });
15501
15540
  };
15502
- const useSearchSeries = ({ start, end, tagIds, autoRefresh = false, }) => {
15541
+ const useSearchSeries = ({ start, end, tagIds, aggregationMode, autoRefresh = false, }) => {
15503
15542
  return useQuery({
15504
- queryKey: ["series-v2", start, end, tagIds],
15543
+ queryKey: ["series-v2", start, end, tagIds, aggregationMode],
15505
15544
  queryFn: (_a) => __awaiter(void 0, [_a], void 0, function* ({ signal }) {
15506
15545
  try {
15507
15546
  if (!tagIds || tagIds.length === 0) {
@@ -15512,6 +15551,7 @@ const useSearchSeries = ({ start, end, tagIds, autoRefresh = false, }) => {
15512
15551
  end: end || new Date().getTime(),
15513
15552
  tagIds,
15514
15553
  sampleCount: 1000,
15554
+ aggregationMode,
15515
15555
  }, {
15516
15556
  signal,
15517
15557
  });
@@ -15528,14 +15568,60 @@ const useSearchSeries = ({ start, end, tagIds, autoRefresh = false, }) => {
15528
15568
  enabled: tagIds.length > 0,
15529
15569
  });
15530
15570
  };
15571
+ const VALID_SCOPE_VALUES = [
15572
+ "10 min",
15573
+ "1 hour",
15574
+ "4 hours",
15575
+ "12 hours",
15576
+ "1 day",
15577
+ "10 days",
15578
+ "custom",
15579
+ ];
15580
+ const getInitialUrlParams = () => {
15581
+ if (typeof window === "undefined") {
15582
+ return {
15583
+ viewId: null,
15584
+ start: null,
15585
+ end: null,
15586
+ period: null,
15587
+ };
15588
+ }
15589
+ const searchParams = new URLSearchParams(window.location.search);
15590
+ const viewParam = Number(searchParams.get("view"));
15591
+ const startParam = Number(searchParams.get("start"));
15592
+ const endParam = Number(searchParams.get("end"));
15593
+ const periodParam = searchParams.get("period");
15594
+ return {
15595
+ viewId: Number.isInteger(viewParam) ? viewParam : null,
15596
+ start: Number.isFinite(startParam) ? startParam : null,
15597
+ end: Number.isFinite(endParam) ? endParam : null,
15598
+ period: VALID_SCOPE_VALUES.includes(periodParam)
15599
+ ? periodParam
15600
+ : null,
15601
+ };
15602
+ };
15531
15603
  const TrendingsPageV2 = () => {
15532
15604
  const [autoRefresh, setAutoRefresh] = useState(false);
15533
- const [chartOptions, setChartOptions] = useState(null);
15605
+ const [chartOptions, setChartOptions] = useState({
15606
+ showGridLines: true,
15607
+ showSymbols: false,
15608
+ smoothLines: false,
15609
+ steppedLines: true,
15610
+ showLegend: false,
15611
+ showTooltip: true,
15612
+ combinedView: false,
15613
+ showToolbox: true,
15614
+ showYAxisNames: true,
15615
+ });
15616
+ const [aggregationMode, setAggregationMode] = useState("avg");
15534
15617
  const [error, setError] = useState("");
15535
15618
  const [viewId, setViewId] = useState(null);
15536
15619
  const [chartInstance, setChartInstance] = useState(null);
15537
15620
  const [dataLoadedTrigger, setDataLoadedTrigger] = useState(0);
15538
- const { state: { viewSelected, viewTags, timeScopeStart, timeScopeEnd }, actions: { setViews, setViewTagsAndRefetch, setViewSelected, setSeriesMinMax, resetCursors, }, } = useTrendingContextV2();
15621
+ const { state: { viewSelected, viewTags, timeScopeStart, timeScopeEnd, scope }, actions: { setViews, setViewTagsAndRefetch, setViewSelected, setTotalScope, setSeriesMinMax, resetCursors, }, } = useTrendingContextV2();
15622
+ const initialUrlParamsRef = useRef(getInitialUrlParams());
15623
+ const initialTimeScopeAppliedRef = useRef(false);
15624
+ const initialViewSelectionResolvedRef = useRef(false);
15539
15625
  // Fetch views
15540
15626
  const { data: views, isLoading: viewsLoading, isError: viewsIsError, error: viewsError, isSuccess: viewSuccess, } = useSearchViews({ autoRefresh });
15541
15627
  // Fetch view tags
@@ -15580,6 +15666,7 @@ const TrendingsPageV2 = () => {
15580
15666
  start: timeScopeStart.getTime(),
15581
15667
  end: timeScopeEnd.getTime(),
15582
15668
  tagIds: queryTagIds,
15669
+ aggregationMode,
15583
15670
  autoRefresh,
15584
15671
  });
15585
15672
  // Filter and reorder series to match current viewTags order
@@ -15634,6 +15721,21 @@ const TrendingsPageV2 = () => {
15634
15721
  }, [series, tagIds, queryTagIds, viewTags]);
15635
15722
  // Calculate overall min/max values from filtered series data
15636
15723
  const seriesMinMaxData = useSeriesMinMax(filteredSeries, tagIds);
15724
+ // Apply explicit time range from URL once on initial load
15725
+ useEffect(() => {
15726
+ if (initialTimeScopeAppliedRef.current)
15727
+ return;
15728
+ const { start, end, period } = initialUrlParamsRef.current;
15729
+ initialTimeScopeAppliedRef.current = true;
15730
+ if (start === null || end === null || period === null || start >= end) {
15731
+ return;
15732
+ }
15733
+ setTotalScope({
15734
+ start: new Date(start),
15735
+ end: new Date(end),
15736
+ scope: period,
15737
+ });
15738
+ }, [setTotalScope]);
15637
15739
  // Load initial view when views are fetched
15638
15740
  useEffect(() => {
15639
15741
  if (!viewSuccess || !views || !Array.isArray(views))
@@ -15642,12 +15744,26 @@ const TrendingsPageV2 = () => {
15642
15744
  setViews(views);
15643
15745
  // Only set initial view if none is selected
15644
15746
  if (!viewSelected) {
15747
+ const initialViewId = initialUrlParamsRef.current.viewId;
15748
+ if (!initialViewSelectionResolvedRef.current && initialViewId !== null) {
15749
+ initialViewSelectionResolvedRef.current = true;
15750
+ const urlView = views.find((view) => view.ViewId === initialViewId);
15751
+ if (urlView) {
15752
+ setViewId(urlView.ViewId);
15753
+ setViewSelected(urlView);
15754
+ return;
15755
+ }
15756
+ }
15645
15757
  const lastCreated = views.reduce((prev, current) => prev && prev.ViewId > current.ViewId ? prev : current, null);
15646
15758
  if (lastCreated) {
15759
+ initialViewSelectionResolvedRef.current = true;
15647
15760
  setViewId(lastCreated.ViewId);
15648
15761
  setViewSelected(lastCreated);
15649
15762
  }
15650
15763
  }
15764
+ else {
15765
+ initialViewSelectionResolvedRef.current = true;
15766
+ }
15651
15767
  }, [viewSuccess, views, viewSelected, setViews, setViewSelected]);
15652
15768
  // Update viewId when viewSelected changes (e.g., from LoadViewModal)
15653
15769
  useEffect(() => {
@@ -15655,6 +15771,26 @@ const TrendingsPageV2 = () => {
15655
15771
  setViewId(viewSelected.ViewId);
15656
15772
  }
15657
15773
  }, [viewSelected]);
15774
+ useEffect(() => {
15775
+ if (typeof window === "undefined" ||
15776
+ !initialTimeScopeAppliedRef.current ||
15777
+ !initialViewSelectionResolvedRef.current) {
15778
+ return;
15779
+ }
15780
+ const searchParams = new URLSearchParams(window.location.search);
15781
+ if (viewSelected) {
15782
+ searchParams.set("view", String(viewSelected.ViewId));
15783
+ }
15784
+ else {
15785
+ searchParams.delete("view");
15786
+ }
15787
+ searchParams.set("start", String(timeScopeStart.getTime()));
15788
+ searchParams.set("end", String(timeScopeEnd.getTime()));
15789
+ searchParams.set("period", scope);
15790
+ const newSearch = searchParams.toString();
15791
+ const newUrl = `${window.location.pathname}${newSearch ? `?${newSearch}` : ""}${window.location.hash}`;
15792
+ window.history.replaceState(null, "", newUrl);
15793
+ }, [viewSelected, timeScopeStart, timeScopeEnd, scope]);
15658
15794
  // Load view tags when fetched - ONLY when viewId changes, not on every data refetch
15659
15795
  // This preserves local order changes (from drag-and-drop) until a new view is loaded
15660
15796
  const loadedViewIdRef = useRef(null);
@@ -15806,7 +15942,7 @@ const TrendingsPageV2 = () => {
15806
15942
  } },
15807
15943
  React__default.createElement(CircularProgress, { size: "3rem" }))),
15808
15944
  React__default.createElement("div", { style: { flexShrink: 0 } },
15809
- React__default.createElement(HeaderSectionV2, { autoRefresh: autoRefresh, setAutoRefresh: setAutoRefresh, setChartOptions: setChartOptions, chartInstance: chartInstance })),
15945
+ React__default.createElement(HeaderSectionV2, { autoRefresh: autoRefresh, setAutoRefresh: setAutoRefresh, setChartOptions: setChartOptions, setAggregationMode: setAggregationMode, chartInstance: chartInstance })),
15810
15946
  React__default.createElement(Divider, { sx: { my: 2 } }),
15811
15947
  React__default.createElement("div", { style: {
15812
15948
  flexGrow: 1,