@oneuptime/common 7.0.4877 → 7.0.4896

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