@oneuptime/common 10.0.37 → 10.0.39

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.
Files changed (60) hide show
  1. package/Server/API/TelemetryAPI.ts +10 -0
  2. package/Server/Services/LogAggregationService.ts +24 -1
  3. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +68 -21
  4. package/Server/Utils/Telemetry/Telemetry.ts +38 -19
  5. package/Types/Workflow/Component.ts +1 -0
  6. package/Types/Workflow/Components/Condition.ts +24 -0
  7. package/UI/Components/Charts/Area/AreaChart.tsx +81 -0
  8. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +106 -63
  9. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +986 -0
  10. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +1 -1
  11. package/UI/Components/Charts/ChartLibrary/Utils/ChartColors.ts +18 -1
  12. package/UI/Components/Charts/Utils/XAxis.ts +26 -21
  13. package/UI/Components/ConditionsTable/ConditionsTable.tsx +86 -67
  14. package/UI/Components/Dictionary/DictionaryOfStingsViewer.tsx +48 -28
  15. package/UI/Components/Filters/FiltersForm.tsx +19 -13
  16. package/UI/Components/InfoCard/InfoCard.tsx +3 -1
  17. package/UI/Components/LogsViewer/LogsViewer.tsx +9 -4
  18. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +29 -2
  19. package/UI/Components/LogsViewer/types.ts +1 -0
  20. package/UI/Components/Workflow/Utils.ts +32 -1
  21. package/build/dist/Server/API/TelemetryAPI.js +8 -0
  22. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  23. package/build/dist/Server/Services/LogAggregationService.js +12 -0
  24. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  25. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +49 -19
  26. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  27. package/build/dist/Server/Utils/Telemetry/Telemetry.js +29 -15
  28. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  29. package/build/dist/Types/Workflow/Component.js +1 -0
  30. package/build/dist/Types/Workflow/Component.js.map +1 -1
  31. package/build/dist/Types/Workflow/Components/Condition.js +24 -0
  32. package/build/dist/Types/Workflow/Components/Condition.js.map +1 -1
  33. package/build/dist/UI/Components/Charts/Area/AreaChart.js +39 -0
  34. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -0
  35. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +28 -9
  36. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  37. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +376 -0
  38. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -0
  39. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +1 -1
  40. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  41. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/ChartColors.js +15 -0
  42. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/ChartColors.js.map +1 -1
  43. package/build/dist/UI/Components/Charts/Utils/XAxis.js +25 -21
  44. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  45. package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js +51 -30
  46. package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js.map +1 -1
  47. package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js +23 -11
  48. package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js.map +1 -1
  49. package/build/dist/UI/Components/Filters/FiltersForm.js +10 -6
  50. package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
  51. package/build/dist/UI/Components/InfoCard/InfoCard.js +1 -1
  52. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  53. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +5 -1
  54. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  55. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +17 -2
  56. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  57. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  58. package/build/dist/UI/Components/Workflow/Utils.js +28 -1
  59. package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1,986 @@
1
+ // AreaChart - Based on Tremor Raw LineChart pattern with gradient area fills
2
+
3
+ "use client";
4
+
5
+ import React from "react";
6
+ import { RiArrowLeftSLine, RiArrowRightSLine } from "@remixicon/react";
7
+ import {
8
+ Area,
9
+ AreaChart as RechartsAreaChart,
10
+ CartesianGrid,
11
+ Dot,
12
+ Label,
13
+ Legend as RechartsLegend,
14
+ ResponsiveContainer,
15
+ Tooltip,
16
+ XAxis,
17
+ YAxis,
18
+ } from "recharts";
19
+ import { AxisDomain } from "recharts/types/util/types";
20
+
21
+ import { useOnWindowResize } from "../Utils/UseWindowOnResize";
22
+ import {
23
+ AvailableChartColors,
24
+ AvailableChartColorsKeys,
25
+ constructCategoryColors,
26
+ getColorClassName,
27
+ getColorHex,
28
+ } from "../Utils/ChartColors";
29
+ import { cx } from "../Utils/Cx";
30
+ import { getYAxisDomain } from "../Utils/GetYAxisDomain";
31
+ import { hasOnlyOneValueForKey } from "../Utils/HasOnlyOneValueForKey";
32
+ import ChartCurve from "../../Types/ChartCurve";
33
+
34
+ //#region Legend
35
+
36
+ interface LegendItemProps {
37
+ name: string;
38
+ color: AvailableChartColorsKeys;
39
+ onClick?: (name: string, color: AvailableChartColorsKeys) => void;
40
+ activeLegend?: string;
41
+ }
42
+
43
+ const LegendItem: ({
44
+ name,
45
+ color,
46
+ onClick,
47
+ activeLegend,
48
+ }: LegendItemProps) => React.JSX.Element = ({
49
+ name,
50
+ color,
51
+ onClick,
52
+ activeLegend,
53
+ }: LegendItemProps) => {
54
+ const hasOnValueChange: boolean = Boolean(onClick);
55
+ return (
56
+ <li
57
+ className={cx(
58
+ "group inline-flex flex-nowrap items-center gap-1.5 whitespace-nowrap rounded px-2 py-1 transition",
59
+ hasOnValueChange
60
+ ? "cursor-pointer hover:bg-gray-100"
61
+ : "cursor-default",
62
+ )}
63
+ onClick={(e: React.MouseEvent<HTMLLIElement, MouseEvent>) => {
64
+ e.stopPropagation();
65
+ onClick?.(name, color);
66
+ }}
67
+ >
68
+ <span
69
+ className={cx(
70
+ "h-[3px] w-3.5 shrink-0 rounded-full",
71
+ getColorClassName(color, "bg"),
72
+ activeLegend && activeLegend !== name ? "opacity-40" : "opacity-100",
73
+ )}
74
+ aria-hidden={true}
75
+ />
76
+ <p
77
+ className={cx(
78
+ "truncate whitespace-nowrap text-xs",
79
+ "text-gray-700",
80
+ hasOnValueChange && "group-hover:text-gray-900",
81
+ activeLegend && activeLegend !== name ? "opacity-40" : "opacity-100",
82
+ )}
83
+ >
84
+ {name}
85
+ </p>
86
+ </li>
87
+ );
88
+ };
89
+
90
+ interface ScrollButtonProps {
91
+ icon: React.ElementType;
92
+ onClick?: () => void;
93
+ disabled?: boolean;
94
+ }
95
+
96
+ const ScrollButton: ({
97
+ icon,
98
+ onClick,
99
+ disabled,
100
+ }: ScrollButtonProps) => React.JSX.Element = ({
101
+ icon,
102
+ onClick,
103
+ disabled,
104
+ }: ScrollButtonProps) => {
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ const Icon: React.ElementType<any, keyof React.JSX.IntrinsicElements> = icon;
107
+ const [isPressed, setIsPressed] = React.useState(false);
108
+ const intervalRef: React.MutableRefObject<NodeJS.Timeout | null> =
109
+ React.useRef<NodeJS.Timeout | null>(null);
110
+
111
+ React.useEffect(() => {
112
+ if (isPressed) {
113
+ intervalRef.current = setInterval(() => {
114
+ onClick?.();
115
+ }, 300);
116
+ } else {
117
+ clearInterval(intervalRef.current as NodeJS.Timeout);
118
+ }
119
+ return () => {
120
+ return clearInterval(intervalRef.current as NodeJS.Timeout);
121
+ };
122
+ }, [isPressed, onClick]);
123
+
124
+ React.useEffect(() => {
125
+ if (disabled) {
126
+ clearInterval(intervalRef.current as NodeJS.Timeout);
127
+ setIsPressed(false);
128
+ }
129
+ }, [disabled]);
130
+
131
+ return (
132
+ <button
133
+ type="button"
134
+ className={cx(
135
+ "group inline-flex size-5 items-center truncate rounded transition",
136
+ disabled
137
+ ? "cursor-not-allowed text-gray-400"
138
+ : "cursor-pointer text-gray-700 hover:bg-gray-100 hover:text-gray-900",
139
+ )}
140
+ disabled={disabled}
141
+ onClick={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
142
+ e.stopPropagation();
143
+ onClick?.();
144
+ }}
145
+ onMouseDown={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
146
+ e.stopPropagation();
147
+ setIsPressed(true);
148
+ }}
149
+ onMouseUp={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
150
+ e.stopPropagation();
151
+ setIsPressed(false);
152
+ }}
153
+ >
154
+ <Icon className="size-full" aria-hidden="true" />
155
+ </button>
156
+ );
157
+ };
158
+
159
+ interface LegendProps extends React.OlHTMLAttributes<HTMLOListElement> {
160
+ categories: string[];
161
+ colors?: AvailableChartColorsKeys[];
162
+ onClickLegendItem?: (category: string, color: string) => void;
163
+ activeLegend?: string;
164
+ enableLegendSlider?: boolean;
165
+ }
166
+
167
+ type HasScrollProps = {
168
+ left: boolean;
169
+ right: boolean;
170
+ };
171
+
172
+ const Legend: React.ForwardRefExoticComponent<
173
+ LegendProps & React.RefAttributes<HTMLOListElement>
174
+ > = React.forwardRef<HTMLOListElement, LegendProps>(
175
+ (props: LegendProps, ref: React.ForwardedRef<HTMLOListElement>) => {
176
+ const {
177
+ categories,
178
+ colors = AvailableChartColors,
179
+ className,
180
+ onClickLegendItem,
181
+ activeLegend,
182
+ enableLegendSlider = false,
183
+ ...other
184
+ } = props;
185
+
186
+ const scrollableRef: React.RefObject<HTMLInputElement> =
187
+ React.useRef<HTMLInputElement>(null);
188
+ const scrollButtonsRef: React.RefObject<HTMLDivElement> =
189
+ React.useRef<HTMLDivElement>(null);
190
+ const [hasScroll, setHasScroll] = React.useState<HasScrollProps | null>(
191
+ null,
192
+ );
193
+ const [isKeyDowned, setIsKeyDowned] = React.useState<string | null>(null);
194
+ const intervalRef: React.MutableRefObject<NodeJS.Timeout | null> =
195
+ React.useRef<NodeJS.Timeout | null>(null);
196
+
197
+ const checkScroll: () => void = React.useCallback(() => {
198
+ const scrollable: HTMLInputElement | null = scrollableRef?.current;
199
+ if (!scrollable) {
200
+ return;
201
+ }
202
+
203
+ const hasLeftScroll: boolean = scrollable.scrollLeft > 0;
204
+ const hasRightScroll: boolean =
205
+ scrollable.scrollWidth - scrollable.clientWidth > scrollable.scrollLeft;
206
+
207
+ setHasScroll({ left: hasLeftScroll, right: hasRightScroll });
208
+ }, [setHasScroll]);
209
+
210
+ const scrollToTest: (direction: "left" | "right") => void =
211
+ React.useCallback(
212
+ (direction: "left" | "right") => {
213
+ const element: HTMLInputElement | null = scrollableRef?.current;
214
+ const scrollButtons: HTMLDivElement | null =
215
+ scrollButtonsRef?.current;
216
+ const scrollButtonsWith: number = scrollButtons?.clientWidth ?? 0;
217
+ const width: number = element?.clientWidth ?? 0;
218
+
219
+ if (element && enableLegendSlider) {
220
+ element.scrollTo({
221
+ left:
222
+ direction === "left"
223
+ ? element.scrollLeft - width + scrollButtonsWith
224
+ : element.scrollLeft + width - scrollButtonsWith,
225
+ behavior: "smooth",
226
+ });
227
+ setTimeout(() => {
228
+ checkScroll();
229
+ }, 400);
230
+ }
231
+ },
232
+ [enableLegendSlider, checkScroll],
233
+ );
234
+
235
+ React.useEffect(() => {
236
+ const keyDownHandler: (key: string) => void = (key: string) => {
237
+ if (key === "ArrowLeft") {
238
+ scrollToTest("left");
239
+ } else if (key === "ArrowRight") {
240
+ scrollToTest("right");
241
+ }
242
+ };
243
+ if (isKeyDowned) {
244
+ keyDownHandler(isKeyDowned);
245
+ intervalRef.current = setInterval(() => {
246
+ keyDownHandler(isKeyDowned);
247
+ }, 300);
248
+ } else {
249
+ clearInterval(intervalRef.current!);
250
+ }
251
+ return () => {
252
+ return clearInterval(intervalRef.current as NodeJS.Timeout);
253
+ };
254
+ }, [isKeyDowned, scrollToTest]);
255
+
256
+ const keyDown: (e: KeyboardEvent) => void = (e: KeyboardEvent) => {
257
+ e.stopPropagation();
258
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
259
+ e.preventDefault();
260
+ setIsKeyDowned(e.key);
261
+ }
262
+ };
263
+ const keyUp: (e: KeyboardEvent) => void = (e: KeyboardEvent) => {
264
+ e.stopPropagation();
265
+ setIsKeyDowned(null);
266
+ };
267
+
268
+ React.useEffect(() => {
269
+ const scrollable: HTMLInputElement | null = scrollableRef?.current;
270
+ if (enableLegendSlider) {
271
+ checkScroll();
272
+ scrollable?.addEventListener("keydown", keyDown);
273
+ scrollable?.addEventListener("keyup", keyUp);
274
+ }
275
+
276
+ return () => {
277
+ scrollable?.removeEventListener("keydown", keyDown);
278
+ scrollable?.removeEventListener("keyup", keyUp);
279
+ };
280
+ }, [checkScroll, enableLegendSlider]);
281
+
282
+ return (
283
+ <ol
284
+ ref={ref}
285
+ className={cx("relative overflow-hidden", className)}
286
+ {...other}
287
+ >
288
+ <div
289
+ ref={scrollableRef}
290
+ tabIndex={0}
291
+ className={cx(
292
+ "flex h-full",
293
+ enableLegendSlider
294
+ ? hasScroll?.right || hasScroll?.left
295
+ ? "snap-mandatory items-center overflow-auto pl-4 pr-12 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
296
+ : ""
297
+ : "flex-wrap",
298
+ )}
299
+ >
300
+ {categories.map((category: string, index: number) => {
301
+ return (
302
+ <LegendItem
303
+ key={`item-${index}`}
304
+ name={category}
305
+ color={colors[index] as AvailableChartColorsKeys}
306
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+ onClick={onClickLegendItem as any}
308
+ activeLegend={activeLegend!}
309
+ />
310
+ );
311
+ })}
312
+ </div>
313
+ {enableLegendSlider && (hasScroll?.right || hasScroll?.left) ? (
314
+ <>
315
+ <div
316
+ ref={scrollButtonsRef}
317
+ className={cx(
318
+ "absolute bottom-0 right-0 top-0 flex h-full items-center justify-center pr-1",
319
+ "bg-white",
320
+ )}
321
+ >
322
+ <ScrollButton
323
+ icon={RiArrowLeftSLine}
324
+ onClick={() => {
325
+ setIsKeyDowned(null);
326
+ scrollToTest("left");
327
+ }}
328
+ disabled={!hasScroll?.left}
329
+ />
330
+ <ScrollButton
331
+ icon={RiArrowRightSLine}
332
+ onClick={() => {
333
+ setIsKeyDowned(null);
334
+ scrollToTest("right");
335
+ }}
336
+ disabled={!hasScroll?.right}
337
+ />
338
+ </div>
339
+ </>
340
+ ) : null}
341
+ </ol>
342
+ );
343
+ },
344
+ );
345
+
346
+ Legend.displayName = "Legend";
347
+
348
+ /* eslint-disable react/no-unused-prop-types */
349
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
350
+ type PayloadItem = {
351
+ category: string;
352
+ value: number;
353
+ index: string;
354
+ color: AvailableChartColorsKeys;
355
+ type?: string;
356
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
357
+ payload: any;
358
+ };
359
+ /* eslint-enable react/no-unused-prop-types */
360
+
361
+ const ChartLegend: (
362
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
363
+ { payload }: any,
364
+ categoryColors: Map<string, AvailableChartColorsKeys>,
365
+ setLegendHeight: React.Dispatch<React.SetStateAction<number>>,
366
+ activeLegend: string | undefined,
367
+ onClick?: (category: string, color: string) => void,
368
+ enableLegendSlider?: boolean,
369
+ legendPosition?: "left" | "center" | "right",
370
+ yAxisWidth?: number,
371
+ ) => React.JSX.Element = (
372
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
373
+ { payload }: any,
374
+ categoryColors: Map<string, AvailableChartColorsKeys>,
375
+ setLegendHeight: React.Dispatch<React.SetStateAction<number>>,
376
+ activeLegend: string | undefined,
377
+ onClick?: (category: string, color: string) => void,
378
+ enableLegendSlider?: boolean,
379
+ legendPosition?: "left" | "center" | "right",
380
+ yAxisWidth?: number,
381
+ ): React.JSX.Element => {
382
+ const legendRef: React.RefObject<HTMLDivElement> =
383
+ React.useRef<HTMLDivElement>(null);
384
+
385
+ useOnWindowResize(() => {
386
+ const calculateHeight: (height: number | undefined) => number = (
387
+ height: number | undefined,
388
+ ) => {
389
+ return height ? Number(height) + 15 : 60;
390
+ };
391
+ setLegendHeight(calculateHeight(legendRef.current?.clientHeight));
392
+ });
393
+
394
+ const legendPayload: Array<PayloadItem> = payload.filter(
395
+ (item: PayloadItem) => {
396
+ return item.type !== "none";
397
+ },
398
+ );
399
+
400
+ const paddingLeft: number =
401
+ legendPosition === "left" && yAxisWidth ? yAxisWidth - 8 : 0;
402
+
403
+ return (
404
+ <div
405
+ ref={legendRef}
406
+ style={{ paddingLeft: paddingLeft }}
407
+ className={cx(
408
+ "flex items-center",
409
+ { "justify-center": legendPosition === "center" },
410
+ { "justify-start": legendPosition === "left" },
411
+ { "justify-end": legendPosition === "right" },
412
+ )}
413
+ >
414
+ <Legend
415
+ categories={legendPayload.map((entry: PayloadItem) => {
416
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
417
+ return entry.value as any;
418
+ })}
419
+ colors={legendPayload.map((entry: PayloadItem) => {
420
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
421
+ return categoryColors.get(entry.value! as any)!;
422
+ })}
423
+ onClickLegendItem={onClick!}
424
+ activeLegend={activeLegend!}
425
+ enableLegendSlider={enableLegendSlider!}
426
+ />
427
+ </div>
428
+ );
429
+ };
430
+
431
+ //#region Tooltip
432
+
433
+ type TooltipProps = Pick<ChartTooltipProps, "active" | "payload" | "label">;
434
+
435
+ interface ChartTooltipProps {
436
+ active: boolean | undefined;
437
+ payload: PayloadItem[];
438
+ label: string;
439
+ valueFormatter: (value: number) => string;
440
+ }
441
+
442
+ const ChartTooltip: ({
443
+ active,
444
+ payload,
445
+ label,
446
+ valueFormatter,
447
+ }: ChartTooltipProps) => React.JSX.Element | null = ({
448
+ active,
449
+ payload,
450
+ label,
451
+ valueFormatter,
452
+ }: ChartTooltipProps): React.JSX.Element | null => {
453
+ if (active && payload && payload.length) {
454
+ const legendPayload: PayloadItem[] = payload.filter((item: PayloadItem) => {
455
+ return item.type !== "none";
456
+ });
457
+ return (
458
+ <div
459
+ className={cx(
460
+ "rounded-md border text-sm shadow-md",
461
+ "border-gray-200",
462
+ "bg-white",
463
+ )}
464
+ >
465
+ <div className={cx("border-b border-inherit px-4 py-2")}>
466
+ <p className={cx("font-medium", "text-gray-900")}>{label}</p>
467
+ </div>
468
+ <div className={cx("space-y-1 px-4 py-2")}>
469
+ {legendPayload.map(
470
+ ({ value, category, color }: PayloadItem, index: number) => {
471
+ return (
472
+ <div
473
+ key={`id-${index}`}
474
+ className="flex items-center justify-between space-x-8"
475
+ >
476
+ <div className="flex items-center space-x-2">
477
+ <span
478
+ aria-hidden="true"
479
+ className={cx(
480
+ "h-[3px] w-3.5 shrink-0 rounded-full",
481
+ getColorClassName(color, "bg"),
482
+ )}
483
+ />
484
+ <p
485
+ className={cx(
486
+ "whitespace-nowrap text-right",
487
+ "text-gray-700",
488
+ )}
489
+ >
490
+ {category}
491
+ </p>
492
+ </div>
493
+ <p
494
+ className={cx(
495
+ "whitespace-nowrap text-right font-medium tabular-nums",
496
+ "text-gray-900",
497
+ )}
498
+ >
499
+ {valueFormatter(value)}
500
+ </p>
501
+ </div>
502
+ );
503
+ },
504
+ )}
505
+ </div>
506
+ </div>
507
+ );
508
+ }
509
+ return null;
510
+ };
511
+
512
+ //#region AreaChart
513
+
514
+ interface ActiveDot {
515
+ index?: number;
516
+ dataKey?: string;
517
+ }
518
+
519
+ type BaseEventProps = {
520
+ eventType: "dot" | "category";
521
+ categoryClicked: string;
522
+ [key: string]: number | string;
523
+ };
524
+
525
+ type AreaChartEventProps = BaseEventProps | null | undefined;
526
+
527
+ interface AreaChartProps extends React.HTMLAttributes<HTMLDivElement> {
528
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
529
+ data: Record<string, any>[];
530
+ index: string;
531
+ categories: string[];
532
+ colors?: AvailableChartColorsKeys[];
533
+ valueFormatter?: (value: number) => string;
534
+ startEndOnly?: boolean;
535
+ showXAxis?: boolean;
536
+ showYAxis?: boolean;
537
+ showGridLines?: boolean;
538
+ yAxisWidth?: number;
539
+ intervalType?: "preserveStartEnd" | "equidistantPreserveStart";
540
+ showTooltip?: boolean;
541
+ showLegend?: boolean;
542
+ autoMinValue?: boolean;
543
+ minValue?: number;
544
+ maxValue?: number;
545
+ allowDecimals?: boolean;
546
+ onValueChange?: (value: AreaChartEventProps) => void;
547
+ enableLegendSlider?: boolean;
548
+ tickGap?: number;
549
+ connectNulls?: boolean;
550
+ xAxisLabel?: string;
551
+ yAxisLabel?: string;
552
+ curve?: ChartCurve;
553
+ legendPosition?: "left" | "center" | "right";
554
+ tooltipCallback?: (tooltipCallbackContent: TooltipProps) => void;
555
+ customTooltip?: React.ComponentType<TooltipProps>;
556
+ syncid?: string | undefined;
557
+ }
558
+
559
+ const AreaChart: React.ForwardRefExoticComponent<
560
+ AreaChartProps & React.RefAttributes<HTMLDivElement>
561
+ > = React.forwardRef<HTMLDivElement, AreaChartProps>(
562
+ (props: AreaChartProps, ref: React.ForwardedRef<HTMLDivElement>) => {
563
+ const {
564
+ data = [],
565
+ categories = [],
566
+ index,
567
+ colors = AvailableChartColors,
568
+ valueFormatter = (value: number) => {
569
+ return value.toString();
570
+ },
571
+ startEndOnly = false,
572
+ showXAxis = true,
573
+ showYAxis = true,
574
+ showGridLines = true,
575
+ yAxisWidth = 56,
576
+ intervalType = "equidistantPreserveStart",
577
+ showTooltip = true,
578
+ showLegend = true,
579
+ autoMinValue = false,
580
+ minValue,
581
+ maxValue,
582
+ allowDecimals = true,
583
+ connectNulls = false,
584
+ className,
585
+ onValueChange,
586
+ enableLegendSlider = false,
587
+ tickGap = 5,
588
+ xAxisLabel,
589
+ yAxisLabel,
590
+ legendPosition = "right",
591
+ tooltipCallback,
592
+ customTooltip,
593
+ ...other
594
+ } = props;
595
+ const CustomTooltip: React.ComponentType<TooltipProps> | undefined =
596
+ customTooltip;
597
+ const paddingValue: 0 | 20 =
598
+ (!showXAxis && !showYAxis) || (startEndOnly && !showYAxis) ? 0 : 20;
599
+ const [legendHeight, setLegendHeight] = React.useState(60);
600
+ const [activeDot, setActiveDot] = React.useState<ActiveDot | undefined>(
601
+ undefined,
602
+ );
603
+ const [activeLegend, setActiveLegend] = React.useState<string | undefined>(
604
+ undefined,
605
+ );
606
+ const categoryColors: Map<string, string | number> =
607
+ constructCategoryColors(categories, colors);
608
+
609
+ const yAxisDomain: (number | "auto")[] = getYAxisDomain(
610
+ autoMinValue,
611
+ minValue,
612
+ maxValue,
613
+ );
614
+ const hasOnValueChange: boolean = Boolean(onValueChange);
615
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
616
+ const prevActiveRef: React.MutableRefObject<boolean | undefined> =
617
+ React.useRef<boolean | undefined>(undefined);
618
+ const prevLabelRef: React.MutableRefObject<string | undefined> =
619
+ React.useRef<string | undefined>(undefined);
620
+
621
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
622
+ function onDotClick(itemData: any, event: React.MouseEvent): void {
623
+ event.stopPropagation();
624
+
625
+ if (!hasOnValueChange) {
626
+ return;
627
+ }
628
+ if (
629
+ (itemData.index === activeDot?.index &&
630
+ itemData.dataKey === activeDot?.dataKey) ||
631
+ (hasOnlyOneValueForKey(data, itemData.dataKey) &&
632
+ activeLegend &&
633
+ activeLegend === itemData.dataKey)
634
+ ) {
635
+ setActiveLegend(undefined);
636
+ setActiveDot(undefined);
637
+ onValueChange?.(null);
638
+ } else {
639
+ setActiveLegend(itemData.dataKey);
640
+ setActiveDot({
641
+ index: itemData.index,
642
+ dataKey: itemData.dataKey,
643
+ });
644
+ onValueChange?.({
645
+ eventType: "dot",
646
+ categoryClicked: itemData.dataKey,
647
+ ...itemData.payload,
648
+ });
649
+ }
650
+ }
651
+
652
+ function onCategoryClick(dataKey: string): void {
653
+ if (!hasOnValueChange) {
654
+ return;
655
+ }
656
+ if (
657
+ (dataKey === activeLegend && !activeDot) ||
658
+ (hasOnlyOneValueForKey(data, dataKey) &&
659
+ activeDot &&
660
+ activeDot.dataKey === dataKey)
661
+ ) {
662
+ setActiveLegend(undefined);
663
+ onValueChange?.(null);
664
+ } else {
665
+ setActiveLegend(dataKey);
666
+ onValueChange?.({
667
+ eventType: "category",
668
+ categoryClicked: dataKey,
669
+ });
670
+ }
671
+ setActiveDot(undefined);
672
+ }
673
+
674
+ return (
675
+ <div ref={ref} className={cx("h-80 w-full", className)} {...other}>
676
+ <ResponsiveContainer>
677
+ <RechartsAreaChart
678
+ data={data}
679
+ syncId={props.syncid?.toString() || ""}
680
+ onClick={
681
+ hasOnValueChange && (activeLegend || activeDot)
682
+ ? () => {
683
+ setActiveDot(undefined);
684
+ setActiveLegend(undefined);
685
+ onValueChange?.(null);
686
+ }
687
+ : () => {}
688
+ }
689
+ margin={{
690
+ bottom: (xAxisLabel ? 30 : undefined) as unknown as number,
691
+ left: (yAxisLabel ? 20 : undefined) as unknown as number,
692
+ right: (yAxisLabel ? 5 : undefined) as unknown as number,
693
+ top: 5,
694
+ }}
695
+ >
696
+ <defs>
697
+ {categories.map((category: string, i: number) => {
698
+ const colorKey: AvailableChartColorsKeys =
699
+ (colors[i % colors.length] as AvailableChartColorsKeys) ||
700
+ "blue";
701
+ const hex: string = getColorHex(colorKey);
702
+ return (
703
+ <linearGradient
704
+ key={category}
705
+ id={`gradient-${category.replace(/[^a-zA-Z0-9]/g, "_")}`}
706
+ x1="0"
707
+ y1="0"
708
+ x2="0"
709
+ y2="1"
710
+ >
711
+ <stop offset="0%" stopColor={hex} stopOpacity={0.2} />
712
+ <stop offset="100%" stopColor={hex} stopOpacity={0.01} />
713
+ </linearGradient>
714
+ );
715
+ })}
716
+ </defs>
717
+ {showGridLines ? (
718
+ <CartesianGrid
719
+ className={cx("stroke-gray-200 stroke-1")}
720
+ horizontal={true}
721
+ vertical={false}
722
+ />
723
+ ) : null}
724
+ <XAxis
725
+ padding={{ left: paddingValue, right: paddingValue }}
726
+ hide={!showXAxis}
727
+ dataKey={index}
728
+ interval={startEndOnly ? "preserveStartEnd" : intervalType}
729
+ tick={{ transform: "translate(0, 6)" }}
730
+ ticks={
731
+ startEndOnly
732
+ ? ([
733
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
734
+ (data[0] as any)[index],
735
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
736
+ (data[data.length - 1] as any)[index],
737
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
738
+ ] as any)
739
+ : undefined
740
+ }
741
+ fill=""
742
+ stroke=""
743
+ className={cx("text-xs", "fill-gray-500")}
744
+ tickLine={false}
745
+ axisLine={false}
746
+ minTickGap={tickGap}
747
+ >
748
+ {xAxisLabel && (
749
+ <Label
750
+ position="insideBottom"
751
+ offset={-20}
752
+ className="fill-gray-800 text-sm font-medium"
753
+ >
754
+ {xAxisLabel}
755
+ </Label>
756
+ )}
757
+ </XAxis>
758
+ <YAxis
759
+ width={yAxisWidth}
760
+ hide={!showYAxis}
761
+ axisLine={false}
762
+ tickLine={false}
763
+ type="number"
764
+ domain={yAxisDomain as AxisDomain}
765
+ tick={{ transform: "translate(-3, 0)" }}
766
+ fill=""
767
+ stroke=""
768
+ className={cx("text-xs", "fill-gray-500")}
769
+ tickFormatter={valueFormatter}
770
+ allowDecimals={allowDecimals}
771
+ >
772
+ {yAxisLabel && (
773
+ <Label
774
+ position="insideLeft"
775
+ style={{ textAnchor: "middle" }}
776
+ angle={-90}
777
+ offset={-15}
778
+ className="fill-gray-800 text-sm font-medium"
779
+ >
780
+ {yAxisLabel}
781
+ </Label>
782
+ )}
783
+ </YAxis>
784
+ <Tooltip
785
+ wrapperStyle={{ outline: "none" }}
786
+ isAnimationActive={true}
787
+ animationDuration={100}
788
+ cursor={{ stroke: "#d1d5db", strokeWidth: 1 }}
789
+ offset={20}
790
+ position={{ y: 0 }}
791
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
792
+ content={({ active, payload, label }: any) => {
793
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
794
+ const cleanPayload: TooltipProps["payload"] = payload
795
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
796
+ payload.map((item: any) => {
797
+ return {
798
+ category: item.dataKey,
799
+ value: item.value,
800
+ index: item.payload[index],
801
+ color: categoryColors.get(
802
+ item.dataKey,
803
+ ) as AvailableChartColorsKeys,
804
+ type: item.type,
805
+ payload: item.payload,
806
+ };
807
+ })
808
+ : [];
809
+
810
+ if (
811
+ tooltipCallback &&
812
+ (active !== prevActiveRef.current ||
813
+ label !== prevLabelRef.current)
814
+ ) {
815
+ tooltipCallback({ active, payload: cleanPayload, label });
816
+ prevActiveRef.current = active;
817
+ prevLabelRef.current = label;
818
+ }
819
+
820
+ return showTooltip && active ? (
821
+ CustomTooltip ? (
822
+ <CustomTooltip
823
+ active={active}
824
+ payload={cleanPayload}
825
+ label={label}
826
+ />
827
+ ) : (
828
+ <ChartTooltip
829
+ active={active}
830
+ payload={cleanPayload}
831
+ label={label}
832
+ valueFormatter={valueFormatter}
833
+ />
834
+ )
835
+ ) : null;
836
+ }}
837
+ />
838
+
839
+ {showLegend ? (
840
+ <RechartsLegend
841
+ verticalAlign="top"
842
+ height={legendHeight}
843
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
844
+ content={({ payload }: any) => {
845
+ return ChartLegend(
846
+ { payload },
847
+ categoryColors,
848
+ setLegendHeight,
849
+ activeLegend,
850
+ hasOnValueChange
851
+ ? (clickedLegendItem: string) => {
852
+ return onCategoryClick(clickedLegendItem);
853
+ }
854
+ : undefined,
855
+ enableLegendSlider,
856
+ legendPosition,
857
+ yAxisWidth,
858
+ );
859
+ }}
860
+ />
861
+ ) : null}
862
+ {categories.map((category: string) => {
863
+ const gradientId: string = `gradient-${category.replace(/[^a-zA-Z0-9]/g, "_")}`;
864
+ const colorKey: AvailableChartColorsKeys = categoryColors.get(
865
+ category,
866
+ ) as AvailableChartColorsKeys;
867
+ const hex: string = getColorHex(colorKey);
868
+
869
+ return (
870
+ <Area
871
+ key={category}
872
+ name={category}
873
+ type={props.curve || ChartCurve.MONOTONE}
874
+ dataKey={category}
875
+ stroke={hex}
876
+ strokeWidth={2}
877
+ strokeLinejoin="round"
878
+ strokeLinecap="round"
879
+ fill={`url(#${gradientId})`}
880
+ fillOpacity={1}
881
+ strokeOpacity={
882
+ activeDot || (activeLegend && activeLegend !== category)
883
+ ? 0.3
884
+ : 1
885
+ }
886
+ isAnimationActive={false}
887
+ connectNulls={connectNulls}
888
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
889
+ activeDot={(dotProps: any) => {
890
+ const {
891
+ cx: cxCoord,
892
+ cy: cyCoord,
893
+ stroke,
894
+ strokeLinecap: slc,
895
+ strokeLinejoin: slj,
896
+ strokeWidth,
897
+ dataKey,
898
+ } = dotProps;
899
+ return (
900
+ <Dot
901
+ className={cx(
902
+ "stroke-white",
903
+ onValueChange ? "cursor-pointer" : "",
904
+ getColorClassName(
905
+ categoryColors.get(
906
+ dataKey,
907
+ ) as AvailableChartColorsKeys,
908
+ "fill",
909
+ ),
910
+ )}
911
+ cx={cxCoord}
912
+ cy={cyCoord}
913
+ r={5}
914
+ fill=""
915
+ stroke={stroke}
916
+ strokeLinecap={slc}
917
+ strokeLinejoin={slj}
918
+ strokeWidth={strokeWidth}
919
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
920
+ onClick={(_: any, event: any) => {
921
+ return onDotClick(dotProps, event);
922
+ }}
923
+ />
924
+ );
925
+ }}
926
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
927
+ dot={(dotProps: any) => {
928
+ const {
929
+ stroke,
930
+ strokeLinecap: slc,
931
+ strokeLinejoin: slj,
932
+ strokeWidth,
933
+ cx: cxCoord,
934
+ cy: cyCoord,
935
+ dataKey,
936
+ index: dotIndex,
937
+ } = dotProps;
938
+
939
+ if (
940
+ (hasOnlyOneValueForKey(data, category) &&
941
+ !(
942
+ activeDot ||
943
+ (activeLegend && activeLegend !== category)
944
+ )) ||
945
+ (activeDot?.index === dotIndex &&
946
+ activeDot?.dataKey === category)
947
+ ) {
948
+ return (
949
+ <Dot
950
+ key={dotIndex}
951
+ cx={cxCoord}
952
+ cy={cyCoord}
953
+ r={5}
954
+ stroke={stroke}
955
+ fill=""
956
+ strokeLinecap={slc}
957
+ strokeLinejoin={slj}
958
+ strokeWidth={strokeWidth}
959
+ className={cx(
960
+ "stroke-white",
961
+ onValueChange ? "cursor-pointer" : "",
962
+ getColorClassName(
963
+ categoryColors.get(
964
+ dataKey,
965
+ ) as AvailableChartColorsKeys,
966
+ "fill",
967
+ ),
968
+ )}
969
+ />
970
+ );
971
+ }
972
+ return <React.Fragment key={dotIndex}></React.Fragment>;
973
+ }}
974
+ />
975
+ );
976
+ })}
977
+ </RechartsAreaChart>
978
+ </ResponsiveContainer>
979
+ </div>
980
+ );
981
+ },
982
+ );
983
+
984
+ AreaChart.displayName = "AreaChart";
985
+
986
+ export { AreaChart, type AreaChartEventProps, type TooltipProps };