@oneuptime/common 10.0.36 → 10.0.38

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 (93) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +819 -0
  3. package/Server/API/StatusPageAPI.ts +7 -0
  4. package/Server/API/TelemetryAPI.ts +10 -0
  5. package/Server/API/WorkspaceNotificationSummaryAPI.ts +67 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.ts +51 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.ts +29 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  9. package/Server/Middleware/MasterAdminAuthorization.ts +55 -0
  10. package/Server/Services/Index.ts +2 -0
  11. package/Server/Services/LogAggregationService.ts +24 -1
  12. package/Server/Services/WorkspaceNotificationSummaryService.ts +1450 -0
  13. package/Server/Utils/Greenlock/Greenlock.ts +1 -0
  14. package/Server/Utils/Telemetry/Telemetry.ts +38 -19
  15. package/Types/Permission.ts +42 -0
  16. package/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.ts +13 -0
  17. package/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.ts +8 -0
  18. package/UI/Components/Charts/Area/AreaChart.tsx +81 -0
  19. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +106 -63
  20. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +986 -0
  21. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +1 -1
  22. package/UI/Components/Charts/ChartLibrary/Utils/ChartColors.ts +18 -1
  23. package/UI/Components/Charts/Utils/XAxis.ts +26 -21
  24. package/UI/Components/ConditionsTable/ConditionsTable.tsx +86 -67
  25. package/UI/Components/Dictionary/DictionaryOfStingsViewer.tsx +48 -28
  26. package/UI/Components/Filters/FiltersForm.tsx +19 -13
  27. package/UI/Components/GanttChart/Bar/Index.tsx +23 -5
  28. package/UI/Components/InfoCard/InfoCard.tsx +3 -1
  29. package/UI/Components/LogsViewer/LogsViewer.tsx +9 -4
  30. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +29 -2
  31. package/UI/Components/LogsViewer/types.ts +1 -0
  32. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  33. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  34. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +857 -0
  35. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -0
  36. package/build/dist/Server/API/StatusPageAPI.js +2 -0
  37. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  38. package/build/dist/Server/API/TelemetryAPI.js +8 -0
  39. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  40. package/build/dist/Server/API/WorkspaceNotificationSummaryAPI.js +40 -0
  41. package/build/dist/Server/API/WorkspaceNotificationSummaryAPI.js.map +1 -0
  42. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.js +24 -0
  43. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.js.map +1 -0
  44. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.js +16 -0
  45. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.js.map +1 -0
  46. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  47. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  48. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +25 -0
  49. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -0
  50. package/build/dist/Server/Services/Index.js +2 -0
  51. package/build/dist/Server/Services/Index.js.map +1 -1
  52. package/build/dist/Server/Services/LogAggregationService.js +12 -0
  53. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  54. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +1122 -0
  55. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -0
  56. package/build/dist/Server/Utils/Greenlock/Greenlock.js +1 -0
  57. package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
  58. package/build/dist/Server/Utils/Telemetry/Telemetry.js +29 -15
  59. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  60. package/build/dist/Types/Permission.js +36 -0
  61. package/build/dist/Types/Permission.js.map +1 -1
  62. package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.js +14 -0
  63. package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.js.map +1 -0
  64. package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.js +9 -0
  65. package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.js.map +1 -0
  66. package/build/dist/UI/Components/Charts/Area/AreaChart.js +39 -0
  67. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -0
  68. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +28 -9
  69. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  70. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +376 -0
  71. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -0
  72. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +1 -1
  73. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  74. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/ChartColors.js +15 -0
  75. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/ChartColors.js.map +1 -1
  76. package/build/dist/UI/Components/Charts/Utils/XAxis.js +25 -21
  77. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  78. package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js +51 -30
  79. package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js.map +1 -1
  80. package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js +23 -11
  81. package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js.map +1 -1
  82. package/build/dist/UI/Components/Filters/FiltersForm.js +10 -6
  83. package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
  84. package/build/dist/UI/Components/GanttChart/Bar/Index.js +15 -3
  85. package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
  86. package/build/dist/UI/Components/InfoCard/InfoCard.js +1 -1
  87. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  88. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +5 -1
  89. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  90. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +17 -2
  91. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  92. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  93. 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 };