@quillsql/react 1.7.5 → 1.7.6

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 (73) hide show
  1. package/lib/ReportBuilder.js +1 -0
  2. package/lib/ReportBuilder.js.map +1 -1
  3. package/lib/SQLEditor.js +3 -3
  4. package/lib/SQLEditor.js.map +1 -1
  5. package/lib/Table.js +1 -1
  6. package/lib/Table.js.map +1 -1
  7. package/lib/components/BigModal/BigModal.js +1 -0
  8. package/lib/components/BigModal/BigModal.js.map +1 -1
  9. package/lib/components/Modal/Modal.js +1 -0
  10. package/lib/components/Modal/Modal.js.map +1 -1
  11. package/lib/hooks/useQuill.js +1 -0
  12. package/lib/hooks/useQuill.js.map +1 -1
  13. package/package.json +11 -4
  14. package/.eslintrc.json +0 -19
  15. package/.prettierrc +0 -11
  16. package/.vscode/settings.json +0 -10
  17. package/src/AddToDashboardModal.tsx +0 -1220
  18. package/src/BarList.tsx +0 -580
  19. package/src/Chart.tsx +0 -1337
  20. package/src/Context.tsx +0 -252
  21. package/src/Dashboard.tsx +0 -820
  22. package/src/DateRangePicker/Calendar.tsx +0 -442
  23. package/src/DateRangePicker/DateRangePicker.tsx +0 -261
  24. package/src/DateRangePicker/DateRangePickerButton.tsx +0 -250
  25. package/src/DateRangePicker/dateRangePickerUtils.tsx +0 -480
  26. package/src/DateRangePicker/index.ts +0 -4
  27. package/src/PieChart.tsx +0 -845
  28. package/src/QuillProvider.tsx +0 -81
  29. package/src/ReportBuilder.tsx +0 -2208
  30. package/src/SQLEditor.tsx +0 -1093
  31. package/src/Table.tsx +0 -1074
  32. package/src/TableChart.tsx +0 -428
  33. package/src/assets/ArrowDownHeadIcon.tsx +0 -11
  34. package/src/assets/ArrowDownIcon.tsx +0 -14
  35. package/src/assets/ArrowDownRightIcon.tsx +0 -14
  36. package/src/assets/ArrowLeftHeadIcon.tsx +0 -11
  37. package/src/assets/ArrowRightHeadIcon.tsx +0 -11
  38. package/src/assets/ArrowRightIcon.tsx +0 -14
  39. package/src/assets/ArrowUpHeadIcon.tsx +0 -11
  40. package/src/assets/ArrowUpIcon.tsx +0 -14
  41. package/src/assets/ArrowUpRightIcon.tsx +0 -14
  42. package/src/assets/CalendarIcon.tsx +0 -14
  43. package/src/assets/DoubleArrowLeftHeadIcon.tsx +0 -18
  44. package/src/assets/DoubleArrowRightHeadIcon.tsx +0 -20
  45. package/src/assets/ExclamationFilledIcon.tsx +0 -14
  46. package/src/assets/LoadingSpinner.tsx +0 -11
  47. package/src/assets/SearchIcon.tsx +0 -14
  48. package/src/assets/XCircleIcon.tsx +0 -14
  49. package/src/assets/index.ts +0 -16
  50. package/src/components/BigModal/BigModal.tsx +0 -108
  51. package/src/components/Dropdown/Dropdown.tsx +0 -169
  52. package/src/components/Dropdown/DropdownItem.tsx +0 -68
  53. package/src/components/Dropdown/index.ts +0 -2
  54. package/src/components/Modal/Modal.tsx +0 -132
  55. package/src/components/Modal/index.ts +0 -1
  56. package/src/components/selectUtils.ts +0 -60
  57. package/src/contexts/BaseColorContext.tsx +0 -5
  58. package/src/contexts/HoveredValueContext.tsx +0 -12
  59. package/src/contexts/RootStylesContext.tsx +0 -5
  60. package/src/contexts/SelectedValueContext.tsx +0 -13
  61. package/src/contexts/index.ts +0 -4
  62. package/src/hooks/index.ts +0 -4
  63. package/src/hooks/useInternalState.tsx +0 -18
  64. package/src/hooks/useOnClickOutside.tsx +0 -23
  65. package/src/hooks/useOnWindowResize.tsx +0 -17
  66. package/src/hooks/useQuill.ts +0 -138
  67. package/src/hooks/useSelectOnKeyDown.tsx +0 -80
  68. package/src/index.ts +0 -9
  69. package/src/lib/font.ts +0 -14
  70. package/src/lib/index.ts +0 -3
  71. package/src/lib/inputTypes.ts +0 -81
  72. package/src/lib/utils.tsx +0 -46
  73. package/tsconfig.json +0 -22
package/src/Chart.tsx DELETED
@@ -1,1337 +0,0 @@
1
- /* eslint-disable @typescript-eslint/ban-ts-comment */
2
- // @ts-nocheck
3
- import React, { useState, useEffect, useContext, useMemo } from 'react';
4
- import {
5
- Area,
6
- CartesianGrid,
7
- ComposedChart as ReChartsAreaChart,
8
- ResponsiveContainer,
9
- Tooltip,
10
- XAxis,
11
- YAxis,
12
- Bar,
13
- BarChart as ReChartsBarChart,
14
- } from 'recharts';
15
- import { endOfWeek, format, isValid, startOfWeek } from 'date-fns';
16
- import { utcToZonedTime } from 'date-fns-tz';
17
- import BarList from './BarList';
18
- import PieChart, { findComplementaryAndAnalogousColors } from './PieChart';
19
- import axios from 'axios';
20
- import {
21
- ClientContext,
22
- DashboardContext,
23
- ThemeContext,
24
- DashboardFiltersContext,
25
- } from './Context';
26
- import TableChart from './TableChart';
27
- import { QuillTheme } from './QuillProvider';
28
-
29
- const colorValues = [
30
- 'slate',
31
- 'gray',
32
- 'zinc',
33
- 'neutral',
34
- 'stone',
35
- 'red',
36
- 'orange',
37
- 'amber',
38
- 'yellow',
39
- 'lime',
40
- 'green',
41
- 'emerald',
42
- 'teal',
43
- 'cyan',
44
- 'sky',
45
- 'blue',
46
- 'indigo',
47
- 'violet',
48
- 'purple',
49
- 'fuchsia',
50
- 'pink',
51
- 'rose',
52
- ] as const;
53
-
54
- export type Color = (typeof colorValues)[number];
55
-
56
- interface ButtonProps {
57
- text: string;
58
- onClick?: () => void;
59
- className?: string;
60
- }
61
-
62
- // const data = [
63
- // {
64
- // date: 'Jan 22',
65
- // SemiAnalysis: 2890,
66
- // 'The Pragmatic Engineer': 2338,
67
- // },
68
- // {
69
- // date: 'Feb 22',
70
- // SemiAnalysis: 2756,
71
- // 'The Pragmatic Engineer': 2103,
72
- // },
73
- // {
74
- // date: 'Mar 22',
75
- // SemiAnalysis: 3322,
76
- // 'The Pragmatic Engineer': 2194,
77
- // },
78
- // {
79
- // date: 'Apr 22',
80
- // SemiAnalysis: 3470,
81
- // 'The Pragmatic Engineer': 2108,
82
- // },
83
- // {
84
- // date: 'May 22',
85
- // SemiAnalysis: 3475,
86
- // 'The Pragmatic Engineer': 1812,
87
- // },
88
- // {
89
- // date: 'Jun 22',
90
- // SemiAnalysis: 3129,
91
- // 'The Pragmatic Engineer': 1726,
92
- // },
93
- // ];
94
-
95
- export const valueFormatter = ({ value, field, fields }) => {
96
- if (
97
- value === undefined ||
98
- value === null ||
99
- field === undefined ||
100
- field === null
101
- ) {
102
- return '';
103
- }
104
-
105
- const fieldInfo = fields.find(f => f.field === field);
106
-
107
- if (!fieldInfo) {
108
- return '';
109
- }
110
-
111
- const formatType = fieldInfo.format;
112
- const numValue = Number(value);
113
- const absNumber = isNaN(numValue) ? 0 : Math.abs(numValue);
114
-
115
- if (formatType === 'percent') {
116
- if (absNumber < 1) {
117
- return (absNumber * 100).toFixed(0) + '%';
118
- } else {
119
- return absNumber.toFixed(0) + '%';
120
- }
121
- }
122
-
123
- if (formatType === 'dollar_amount') {
124
- const formatter = new Intl.NumberFormat('en-US', {
125
- style: 'currency',
126
- currency: 'USD',
127
- minimumFractionDigits: 0,
128
- maximumFractionDigits: 0,
129
- });
130
-
131
- if (absNumber >= 1000000) {
132
- return formatter.format(numValue / 1000000) + 'M';
133
- } else if (absNumber >= 1000) {
134
- return formatter.format(numValue / 1000) + 'K';
135
- } else {
136
- return formatter.format(numValue);
137
- }
138
- }
139
-
140
- if (formatType === 'whole_number') {
141
- return Math.round(numValue).toString();
142
- }
143
-
144
- if (formatType === 'one_decimal_place') {
145
- return numValue.toFixed(1);
146
- }
147
-
148
- if (
149
- formatType === 'MMM_yyyy' ||
150
- formatType === 'MMM_dd_yyyy' ||
151
- formatType === 'hh_ap_pm' ||
152
- formatType === 'MMM_dd-MMM_dd' ||
153
- formatType === 'MMM_dd_hh:mm_ap_pm'
154
- ) {
155
- const dateValue = new Date(value);
156
- const utcDate = utcToZonedTime(dateValue, 'UTC');
157
- if (!isValid(utcDate)) {
158
- return 'Invalid date';
159
- }
160
- if (formatType === 'MMM_yyyy') {
161
- return format(utcDate, 'MMM yyyy');
162
- }
163
- if (formatType === 'MMM_dd_yyyy') {
164
- return format(utcDate, 'dd MMM yyyy');
165
- }
166
- if (formatType === 'hh_ap_pm') {
167
- return format(utcDate, 'hh:mm aa');
168
- }
169
- if (formatType === 'MMM_dd-MMM_dd') {
170
- const start = startOfWeek(utcDate, { weekStartsOn: 1 }); // Monday
171
- const end = endOfWeek(utcDate, { weekStartsOn: 1 }); // Sunday
172
- let formattedDate = '';
173
- // Check if start and end are in the same month
174
- if (format(start, 'MMM') === format(end, 'MMM')) {
175
- formattedDate = `${format(start, 'MMM dd')} - ${format(end, 'dd')}`;
176
- } else {
177
- formattedDate = `${format(start, 'MMM dd')} - ${format(end, 'MMM dd')}`;
178
- }
179
- return formattedDate;
180
- }
181
- if (formatType === 'MMM_dd_hh:mm_ap_pm') {
182
- const formatStr =
183
- utcDate.getMinutes() === 0 ? 'MMM do h a' : 'MMM do h:mm a';
184
- let formattedTime = format(utcDate, formatStr);
185
- formattedTime =
186
- formattedTime.slice(0, -2) + formattedTime.slice(-2).toLowerCase();
187
- return formattedTime;
188
- }
189
- }
190
-
191
- if (formatType === 'string') {
192
- return value.toString();
193
- }
194
-
195
- return value.toString();
196
- };
197
-
198
- const yAxisFields = [
199
- { field: 'avg_days', label: 'average days' },
200
- { field: 'median_days', label: 'median days' },
201
- ];
202
-
203
- const labelFormatter = (name: string) => {
204
- // return yAxisFields.filter(elem => elem.field === name)[0].label;
205
- return name;
206
- };
207
-
208
- export const ChartTooltipFrame = ({
209
- children,
210
- theme,
211
- }: {
212
- children: React.ReactNode;
213
- }) => (
214
- <div
215
- style={{
216
- borderStyle: 'solid',
217
- borderColor: theme?.borderColor || '#E5E7EB',
218
- overflow: 'hidden',
219
- borderWidth: 1,
220
- background: theme?.backgroundColor || '#ffffff',
221
- borderRadius: '6px',
222
- boxShadow:
223
- '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
224
- }}
225
- // className="qq-bg-white qq-text-sm qq-rounded-md qq-shadow-lg"
226
- // className={twMerge(
227
- // 'bg-white',
228
- // 'font-normal',
229
- // 'border-[12px]',
230
- // 'border-[1px]',
231
- // 'text-[#212121]'
232
- // // boxShadow.lg
233
- // )}
234
- >
235
- {children}
236
- </div>
237
- );
238
-
239
- export interface ChartTooltipRowProps {
240
- value: string;
241
- name: string;
242
- color: Color;
243
- thee: any;
244
- }
245
-
246
- export const ChartTooltipRow = ({
247
- value,
248
- name,
249
- color,
250
- theme,
251
- }: ChartTooltipRowProps) => (
252
- <div
253
- style={{
254
- marginRight: '24px',
255
- display: 'flex',
256
- alignItems: 'center',
257
- justifyContent: 'space-between',
258
- width: '100%',
259
- paddingTop: 2,
260
- paddingBottom: 2,
261
- }}
262
- // className="qq-flex qq-items-center qq-justify-between qq-space-x-8"
263
- >
264
- <div
265
- // className="qq-flex qq-items-center qq-space-x-2"
266
- style={{
267
- // marginLeft: '0.5rem',
268
- display: 'flex',
269
- alignItems: 'center',
270
- }}
271
- >
272
- <span
273
- style={{
274
- background: color,
275
- borderWidth: 2,
276
- borderColor: 'white',
277
- borderStyle: 'solid',
278
- borderColor: 'white',
279
- boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
280
- height: '8px',
281
- width: '8px',
282
- borderRadius: 100,
283
- marginRight: 4,
284
- }}
285
- // className={twMerge(
286
- // 'shrink-0',
287
- // 'bg-black',
288
- // 'bg-white',
289
- // 'border-black',
290
- // sizing.sm.height,
291
- // sizing.sm.width,
292
- // 'qq-h-3',
293
- // 'qq-w-3',
294
- // 'qq-shadow',
295
- // 'qq-rounded-full'
296
- // border.md.all,
297
- // boxShadow.md
298
- // )}
299
- />
300
- <p
301
- // className={twMerge(
302
- // 'font-medium tabular-nums text-right whitespace-nowrap',
303
- // 'text-[#212121] !important'
304
- // )}
305
- style={{
306
- marginTop: 0,
307
- marginBottom: 0,
308
- fontFamily: theme?.fontFamily,
309
- color: theme?.primaryTextColor,
310
- fontSize: theme?.fontSizeSmall || '14px',
311
- fontWeight: theme?.fontWeightMedium || '500',
312
- }}
313
- // className="qq-font-medium qq-tabular-nums qq-text-right qq-whitespace-nowrap qq-text-black"
314
- >
315
- {value}
316
- </p>
317
- </div>
318
- <p
319
- style={{
320
- marginTop: 0,
321
- marginBottom: 0,
322
- fontFamily: theme?.fontFamily,
323
- color: theme?.secondaryTextColor,
324
- textAlign: 'right',
325
- whiteSpace: 'nowrap',
326
- overflow: 'hidden',
327
- textOverflow: 'ellipsis',
328
- fontSize: theme?.fontSizeSmall || '14px',
329
- fontWeight: theme?.fontWeightNormal || '400',
330
- }}
331
- // className={twMerge(
332
- // 'qq-text-right qq-whitespace-nowrap',
333
- // getColorClassNames(DEFAULT_COLOR, colorPalette.text).textColor,
334
- // 'qq-text-gray-500'
335
- // fontWeight.sm
336
- // )}
337
- >
338
- {name}
339
- </p>
340
- </div>
341
- );
342
-
343
- export interface ChartTooltipProps {
344
- active: boolean | undefined;
345
- payload: any;
346
- label: string;
347
- colors: string[];
348
- valueFormatter: any;
349
- theme: any;
350
- }
351
-
352
- const ChartTooltip = ({
353
- active,
354
- payload,
355
- label,
356
- colors,
357
- valueFormatter,
358
- dateFormatter,
359
- theme,
360
- }: ChartTooltipProps) => {
361
- if (active && payload) {
362
- return (
363
- <ChartTooltipFrame theme={theme}>
364
- <div
365
- style={{
366
- borderStyle: 'solid',
367
- borderBottomColor: theme?.borderColor || '#E5E7EB',
368
- background: theme?.backgroundColor || 'white',
369
- borderTop: 'none',
370
- borderLeft: 'none',
371
- borderRight: 'none',
372
- borderBottomWidth: 1,
373
- display: 'flex',
374
- flexDirection: 'column',
375
- paddingLeft: '16px',
376
- paddingRight: '16px',
377
- paddingTop: '8px',
378
- paddingBottom: '8px',
379
- }}
380
- // className="qq-flex qq-flex-col qq-py-2 qq-px-4 qq-gray-200"
381
- // className={
382
- // twMerge()
383
- // getColorClassNames(DEFAULT_COLOR, colorPalette.lightBorder)
384
- // .borderColor,
385
- // spacing.twoXl.paddingX,
386
- // spacing.sm.paddingY,
387
- // border.sm.bottom
388
- // }
389
- >
390
- <p
391
- style={{
392
- textAlign: 'left',
393
- marginTop: 0,
394
- marginBottom: 0,
395
- fontFamily: theme?.fontFamily,
396
- color: theme?.primaryTextColor,
397
- fontSize: theme?.fontSizeSmall || '14px',
398
- fontWeight: theme?.boldFontWeight || '500',
399
- paddingTop: 2,
400
- paddingBottom: 2,
401
- }}
402
- // className={twMerge(
403
- // 'qq-text-elem',
404
- // 'qq-text-black',
405
- // 'qq-font-medium',
406
- // 'qq-gray-700'
407
- // getColorClassNames(DEFAULT_COLOR, colorPalette.darkText)
408
- // .textColor,
409
- // fontWeight.md
410
- // )}
411
- >
412
- {!isNaN(new Date(label)) && dateFormatter
413
- ? dateFormatter(label)
414
- : !isNaN(new Date(label))
415
- ? format(new Date(label), 'MMM yyyy')
416
- : label}
417
- {/* {label} */}
418
- </p>
419
- </div>
420
-
421
- <div
422
- // className="qq-px-4 qq-space-y-1 qq-py-2 qq-px-4"
423
- style={{
424
- paddingRight: '16px',
425
- paddingLeft: '16px',
426
- paddingTop: '8px',
427
- paddingBottom: '8px',
428
- // marginTop: '0.25rem',
429
- }}
430
- // className={
431
- // twMerge()
432
- // spacing.twoXl.paddingX,
433
- // spacing.sm.paddingY,
434
- // 'space-y-1'
435
- // }
436
- >
437
- {payload.map(
438
- ({ value, name }: { value: number; name: string }, idx: number) => (
439
- <ChartTooltipRow
440
- key={`id-${idx}`}
441
- value={valueFormatter(value)}
442
- name={labelFormatter(name)}
443
- // color={categoryColors.get(name) ?? BaseColors.Blue}
444
- color={colors[idx]}
445
- theme={theme}
446
- />
447
- )
448
- )}
449
- </div>
450
- </ChartTooltipFrame>
451
- );
452
- }
453
- return null;
454
- };
455
-
456
- interface ChartProps {
457
- colors: string[];
458
- config?: any;
459
- chartId?: string;
460
- containerStyle?: React.CSSProperties;
461
- dashboard: any;
462
- dispatch: any;
463
- client: any;
464
- dateFilter?: any;
465
- theme: any;
466
- isDateFilter?: boolean;
467
- query?: string;
468
- }
469
-
470
- // @ts-ignore
471
- function sumByKey(arr, key) {
472
- // @ts-ignore
473
- return arr.reduce((acc, cur) => {
474
- const val = parseInt(cur[key], 10);
475
- return isNaN(val) ? acc : acc + val;
476
- }, 0);
477
- }
478
-
479
- const Chart = ({
480
- chartId,
481
- config,
482
- colors,
483
- containerStyle,
484
- dateFilter,
485
- query,
486
- }: {
487
- chartId: string;
488
- config?: any;
489
- colors?: string[];
490
- containerStyle?: React.CSSProperties;
491
- dateFilter?: boolean;
492
- query?: string;
493
- }) => {
494
- const { dispatch, dashboard } = useContext(DashboardContext);
495
- const { dashboardFilters } = useContext(DashboardFiltersContext);
496
- const [client, _] = useContext(ClientContext);
497
- const [theme] =
498
- useContext<[QuillTheme, (theme: QuillTheme) => void]>(ThemeContext);
499
- const chartColors = useMemo(() => {
500
- return colors?.length ? colors : ['#6269E9', '#E14F62'];
501
- }, [colors]);
502
- return (
503
- <ChartUpdater
504
- config={config}
505
- dispatch={dispatch}
506
- dashboard={dashboard}
507
- chartId={chartId}
508
- containerStyle={containerStyle}
509
- colors={chartColors}
510
- client={client}
511
- theme={theme}
512
- isDateFilter={dateFilter}
513
- dashboardFilters={dashboardFilters}
514
- query={query}
515
- />
516
- );
517
- };
518
-
519
- const ChartUpdater: React.FC<ChartProps> = ({
520
- colors,
521
- chartId,
522
- config,
523
- containerStyle,
524
- dashboard,
525
- dispatch,
526
- client,
527
- theme,
528
- isDateFilter,
529
- dashboardFilters,
530
- query,
531
- }) => {
532
- const [loading, setLoading] = useState(true);
533
- // console.log('chartConfig:', chartConfig);
534
- // console.log('chartConfig.rows:', chartConfig.rows);
535
- // console.log('chartConfig.xAxisField:', chartConfig.xAxisField);
536
-
537
- useEffect(() => {
538
- if (isDateFilter && !dateFilter) {
539
- return;
540
- }
541
-
542
- let isSubscribed = true;
543
- async function getChartOptions() {
544
- if (isSubscribed) {
545
- const {
546
- publicKey,
547
- customerId,
548
- environment,
549
- queryEndpoint,
550
- queryHeaders,
551
- withCredentials,
552
- } = client;
553
- //queryEndpoint = undefined;
554
- setLoading(true);
555
- try {
556
- // if self-hosting option, only get metadata and query
557
- if (queryEndpoint) {
558
- // const resp = await axios.get(
559
- // 'https://quill-344421.uc.r.appspot.com/selfhostitem',
560
- // {
561
- // params: {
562
- // id: chartId,
563
- // orgId: customerId,
564
- // publicKey: publicKey,
565
- // },
566
- // environment: environment || undefined,
567
- // }
568
- // );
569
- if (dashboardFilters['date_range'] && dashboard[chartId]) {
570
- const dateFilter = dashboardFilters['date_range'];
571
- dateFilter.dateField = dashboard[chartId].dateField;
572
- }
573
- const resp = await axios.post(
574
- queryEndpoint,
575
- {
576
- metadata: {
577
- id: chartId,
578
- task: 'item',
579
- filters: {
580
- ...Object.entries(dashboardFilters)
581
- .filter(([key]) => key !== 'date_range')
582
- .map(([, value]) => value),
583
- dateFilter,
584
- },
585
- },
586
- },
587
- { headers: queryHeaders, withCredentials }
588
- );
589
-
590
- if (resp.data) {
591
- setLoading(false);
592
- dispatch({
593
- type: 'UPDATE_DASHBOARD_ITEM',
594
- id: chartId,
595
- data: {
596
- ...resp.data,
597
- },
598
- });
599
- }
600
- } else {
601
- const resp = await axios.get(
602
- 'https://quill-344421.uc.r.appspot.com/item',
603
- {
604
- params: {
605
- id: chartId,
606
- orgId: customerId,
607
- publicKey: publicKey,
608
- filters: Object.keys(dashboardFilters).map(
609
- key => dashboardFilters[key]
610
- ),
611
- // startDate:
612
- // dateFilter &&
613
- // dateFilter.startDate &&
614
- // dateFilter.startDate.toISOString(),
615
- // endDate:
616
- // dateFilter &&
617
- // dateFilter.endDate &&
618
- // dateFilter.endDate.toISOString(),
619
- },
620
- headers: {
621
- environment: environment || undefined,
622
- },
623
- }
624
- );
625
- // console.log('RESP: ', resp.data);
626
- if (resp.data) {
627
- setLoading(false);
628
- dispatch({
629
- type: 'UPDATE_DASHBOARD_ITEM',
630
- id: chartId,
631
- data: { ...resp.data },
632
- });
633
- }
634
- }
635
- } catch (e) {
636
- console.log('Error fetching chart: ', e);
637
- setLoading(false);
638
- }
639
- }
640
- }
641
- if (config) {
642
- setLoading(false);
643
- return;
644
- }
645
-
646
- getChartOptions(); //when wouldn't you run this????? saw if statements before that implied you might not run this sometimes
647
-
648
- return () => {
649
- isSubscribed = false;
650
- };
651
- }, [dashboardFilters]);
652
-
653
- // console.log('DASHBOARD FILTERS: ', dashboardFilters);
654
-
655
- useEffect(() => {
656
- if (config) {
657
- dispatch({
658
- type: 'UPDATE_DASHBOARD_ITEM',
659
- id: chartId,
660
- data: { ...config },
661
- });
662
- }
663
- }, [config]);
664
-
665
- const dateFilter = Object.values(dashboardFilters).find(
666
- filter => filter && filter.filterType === 'DATE_RANGE'
667
- );
668
-
669
- if (!dashboard[chartId] || loading) {
670
- return (
671
- <div
672
- // className="flex flex-col flex-1 h-[100%]"
673
- style={{
674
- ...containerStyle,
675
- marginLeft: 25,
676
- marginRight: 25,
677
- boxSizing: 'content-box',
678
- height: '100%',
679
- display: 'flex',
680
- flexDirection: 'column',
681
- flex: 1,
682
- }}
683
- >
684
- <div
685
- style={{
686
- height: containerStyle?.height || 300,
687
- width: '100%',
688
- boxSizing: 'content-box',
689
- borderRadius: 8,
690
- overflow: 'hidden',
691
- }}
692
- >
693
- <svg
694
- width="100%"
695
- height="100%"
696
- xmlns="http://www.w3.org/2000/svg"
697
- xmlns:xlink="http://www.w3.org/1999/xlink"
698
- >
699
- <rect width="100%" height="100%" fill="#F9F9FA" />
700
-
701
- <defs fill="#F9F9FA">
702
- <linearGradient
703
- id="skeletonGradient"
704
- x1="0%"
705
- y1="0%"
706
- x2="10%"
707
- y2="0%"
708
- gradientUnits="userSpaceOnUse"
709
- >
710
- <stop offset="0%" stop-color="rgba(255,255,255,0)" />
711
- <stop offset="50%" stop-color="#FEFEFE" />
712
- <stop offset="100%" stop-color="rgba(255,255,255,0)" />
713
- <animate
714
- attributeName="x1"
715
- from="-100%"
716
- to="100%"
717
- dur="2s"
718
- repeatCount="indefinite"
719
- />
720
- <animate
721
- attributeName="x2"
722
- from="-50%"
723
- to="150%"
724
- dur="2s"
725
- repeatCount="indefinite"
726
- />
727
- </linearGradient>
728
- </defs>
729
-
730
- <rect width="50%" height="100%" fill="url(#skeletonGradient)">
731
- <animate
732
- attributeName="x"
733
- from="-100%"
734
- to="100%"
735
- dur="2s"
736
- repeatCount="indefinite"
737
- />
738
- </rect>
739
- </svg>
740
- </div>
741
- </div>
742
- );
743
- }
744
-
745
- if (dashboard[chartId]?.chartType === 'pie') {
746
- const { xAxisField, yAxisFields } = dashboard[chartId];
747
- return (
748
- <PieChart
749
- // @ts-ignore
750
- containerStyle={containerStyle}
751
- data={dashboard[chartId].rows.map(row => {
752
- return {
753
- ...row,
754
- count:
755
- parseInt(row[yAxisFields[0].field]) /
756
- sumByKey(dashboard[chartId].rows, yAxisFields[0].field),
757
- };
758
- })}
759
- category={yAxisFields[0].field}
760
- index={xAxisField}
761
- colors={colors}
762
- theme={theme}
763
- />
764
- );
765
- }
766
-
767
- if (dashboard[chartId]?.chartType === 'table') {
768
- return (
769
- <TableChart
770
- data={dashboard[chartId].rows}
771
- yAxisFields={dashboard[chartId].yAxisFields}
772
- theme={theme}
773
- containerStyle={containerStyle}
774
- />
775
- );
776
- }
777
-
778
- if (dashboard[chartId]?.chartType === 'bar') {
779
- return (
780
- <BarList
781
- data={dashboard[chartId].rows}
782
- theme={theme}
783
- yAxisFields={dashboard[chartId].yAxisFields}
784
- xAxisFormat={dashboard[chartId].xAxisFormat}
785
- colors={colors}
786
- xAxisField={dashboard[chartId].xAxisField}
787
- containerStyle={containerStyle}
788
- />
789
- );
790
- }
791
-
792
- if (dashboard[chartId]?.chartType === 'column') {
793
- return (
794
- <BarChart
795
- colors={colors}
796
- theme={theme}
797
- yAxisFields={dashboard[chartId].yAxisFields}
798
- // @ts-ignore
799
- data={dashboard[chartId].rows}
800
- xAxisField={dashboard[chartId].xAxisField}
801
- xAxisLabel={dashboard[chartId].xAxisLabel}
802
- xAxisFormat={dashboard[chartId].xAxisFormat}
803
- containerStyle={containerStyle}
804
- />
805
- );
806
- }
807
-
808
- if (dashboard[chartId]?.chartType === 'metric') {
809
- return (
810
- <div
811
- style={{
812
- fontFamily: theme?.fontFamily,
813
- fontSize: 32,
814
- color: theme?.primaryTextColor,
815
- fontWeight: '600',
816
- textOverflow: 'ellipsis',
817
- margin: 0,
818
- paddingTop: 20,
819
- paddingBottom: 20,
820
- paddingLeft: 25,
821
- paddingRight: 25,
822
- whiteSpace: 'nowrap',
823
- boxSizing: 'content-box',
824
- display: 'block',
825
- maxWidth: '100%',
826
- textAlign: 'left',
827
- overflow: 'hidden',
828
- height: containerStyle?.height || '100%',
829
- display: 'flex',
830
- flexDirection: 'column',
831
- // background: 'red',
832
- }}
833
- // className="qq-flex qq-flex-col qq-text-xl"
834
- >
835
- {dashboard[chartId].rows.length > 0 &&
836
- valueFormatter({
837
- value: dashboard[chartId].rows[0][dashboard[chartId].xAxisField],
838
- field: dashboard[chartId].xAxisField,
839
- fields: [
840
- {
841
- field: dashboard[chartId].xAxisField,
842
- format: dashboard[chartId].xAxisFormat,
843
- },
844
- ],
845
- })}
846
- </div>
847
- );
848
- }
849
-
850
- return (
851
- <LineChart
852
- colors={colors}
853
- yAxisFields={dashboard[chartId].yAxisFields}
854
- // @ts-ignore
855
- data={dashboard[chartId].rows}
856
- xAxisField={dashboard[chartId].xAxisField}
857
- xAxisLabel={dashboard[chartId].xAxisLabel}
858
- xAxisFormat={dashboard[chartId].xAxisFormat}
859
- containerStyle={containerStyle}
860
- theme={theme}
861
- />
862
- );
863
- };
864
-
865
- function formatNumber(num, label) {
866
- num = Number(num);
867
- if (num < 1000) {
868
- return num.toFixed(2) + ' ' + label;
869
- }
870
- if (num >= 1.0e6) {
871
- return Math.round(num / 1.0e6).toLocaleString() + 'M';
872
- } else {
873
- return Math.round(num / 1.0e6).toLocaleString() + 'M';
874
- }
875
- }
876
-
877
- // @ts-ignore
878
- // function getDomain(data, fields) {
879
- // // @ts-ignore
880
- // const fieldsArray = fields.map(elem => elem.field);
881
- // const [minValue, maxValue] = data.reduce(
882
- // // @ts-ignore
883
- // ([min, max], item) => [
884
- // // @ts-ignore
885
- // Math.min(min, ...fieldsArray.map(field => item[field])),
886
- // // @ts-ignore
887
- // Math.max(max, ...fieldsArray.map(field => item[field])),
888
- // ],
889
- // [Infinity, -Infinity]
890
- // );
891
-
892
- // const adjustedMin = Math.min(minValue, 0);
893
- // const padding = Math.round(0.2 * (maxValue - minValue));
894
- // const adjustedMax = maxValue + padding;
895
-
896
- // return [adjustedMin, adjustedMax];
897
- // }
898
- function getDomain(data, fields) {
899
- const fieldsArray = fields.map(elem => elem.field);
900
- const numericValues = [];
901
-
902
- data.forEach(item => {
903
- fieldsArray.forEach(field => {
904
- let value = item[field];
905
- // Try to cast the value to a number
906
- const numericValue = parseFloat(value);
907
-
908
- if (!isNaN(numericValue)) {
909
- value = numericValue;
910
- }
911
-
912
- if (typeof value === 'number') {
913
- numericValues.push(value);
914
- }
915
- });
916
- });
917
-
918
- const [minValue, maxValue] = numericValues.reduce(
919
- ([min, max], value) => [Math.min(min, value), Math.max(max, value)],
920
- [Infinity, -Infinity]
921
- );
922
-
923
- const adjustedMin = Math.min(minValue, 0);
924
- const padding = Math.round(0.2 * (maxValue - minValue));
925
- const adjustedMax = maxValue + padding;
926
-
927
- return [adjustedMin, adjustedMax];
928
- }
929
-
930
- const CustomizedTick = ({
931
- x,
932
- y,
933
- payload,
934
- theme,
935
- }: {
936
- x: number;
937
- y: number;
938
- payload: any;
939
- theme: any;
940
- }) => {
941
- // const { x, y, payload } = props;
942
- const maxLength = 10;
943
- const value = payload.value;
944
- const truncatedValue =
945
- value.length > maxLength ? value.substring(0, maxLength) + '...' : value;
946
- return (
947
- <text
948
- xlinkTitle={value}
949
- fill={theme?.chartLabelColor}
950
- fontSize={12}
951
- x={x}
952
- y={y + 15}
953
- width={30}
954
- textAnchor="middle"
955
- >
956
- {truncatedValue}
957
- </text>
958
- );
959
- };
960
-
961
- function BarChart({
962
- colors,
963
- yAxisFields,
964
- data,
965
- containerStyle,
966
- xAxisField,
967
- xAxisLabel,
968
- xAxisFormat,
969
- theme,
970
- }: {
971
- colors: string[];
972
- yAxisFields: any[];
973
- data: any[];
974
- containerStyle?: React.CSSProperties;
975
- xAxisField: string;
976
- xAxisLabel: string;
977
- xAxisFormat: string;
978
- theme: any;
979
- }) {
980
- const newColors = findComplementaryAndAnalogousColors(colors[0], colors[1]);
981
- return (
982
- <div
983
- style={{
984
- ...containerStyle,
985
- boxSizing: 'content-box',
986
- // display: 'flex',
987
- // flexDirection: 'column',
988
- // flex: 1,
989
- // height: '100%',
990
- // minHeight: 400,
991
- // minWidth: 400,
992
- // height: 400,
993
- // width: 800,
994
- // width: '100%',
995
- }}
996
- // className="qq-flex qq-flex-col qq-flex-1 qq-h-full qq-w-full"
997
- >
998
- <ResponsiveContainer width="100%" height={'100%'}>
999
- <ReChartsBarChart
1000
- data={data}
1001
- // stackOffset={'none'}
1002
- layout={'horizontal'}
1003
- >
1004
- <CartesianGrid
1005
- strokeDasharray="3 3"
1006
- horizontal={true}
1007
- vertical={false}
1008
- />
1009
-
1010
- <YAxis
1011
- // width={56}
1012
- // width={30}
1013
- // textAnchor="start"
1014
- hide={false}
1015
- axisLine={false}
1016
- tickLine={false}
1017
- type="number"
1018
- domain={getDomain(data, yAxisFields)}
1019
- // domain={[0, 150]}
1020
- tick={{ transform: 'translate(-3, 0)' }}
1021
- style={{
1022
- fontSize: '12px',
1023
- fontFamily: theme.chartLabelFontFamily,
1024
- }}
1025
- tickFormatter={tick =>
1026
- valueFormatter({
1027
- value: tick,
1028
- field: yAxisFields[0].field || 'what',
1029
- fields: yAxisFields,
1030
- })
1031
- }
1032
- // tickFormatter={valueFormatter}
1033
- />
1034
- <XAxis
1035
- hide={false}
1036
- dataKey={xAxisField}
1037
- // interval="preserveStartEnd"
1038
- tick={{ transform: 'translate(0, 6)' }}
1039
- //padding between labels and axis
1040
- style={{
1041
- fontSize: '12px',
1042
- // TODO: generalize
1043
- fontFamily: theme.chartLabelFontFamily,
1044
- marginTop: '20px',
1045
- }}
1046
- tickLine={false}
1047
- axisLine={false}
1048
- tickFormatter={tick =>
1049
- valueFormatter({
1050
- value: tick,
1051
- field: xAxisField,
1052
- fields: [{ field: xAxisField, format: xAxisFormat }],
1053
- })
1054
- }
1055
- // tickFormatter={tick => valueFormatter(tick, xAxisFormat)}
1056
- />
1057
- {/* <XAxis
1058
- hide={false}
1059
- dataKey={xAxisField}
1060
- interval="preserveStartEnd"
1061
- tick={{ transform: 'translate(0, 6)' }} //padding between labels and axis
1062
- style={{
1063
- fontSize: '12px',
1064
- // TODO: generalize
1065
- fontFamily: 'Inter; Helvetica',
1066
- marginTop: '20px',
1067
- }}
1068
- tickLine={false}
1069
- axisLine={false}
1070
- />
1071
- <YAxis
1072
- width={56}
1073
- hide={false}
1074
- axisLine={false}
1075
- tickLine={false}
1076
- type="number"
1077
- // domain={getDomain(data, yAxisFields)}
1078
- domain={[0, 5]}
1079
- tick={{ transform: 'translate(-3, 0)' }}
1080
- style={{
1081
- fontSize: '12px',
1082
- fontFamily: 'Inter; Helvetica',
1083
- }}
1084
- tickFormatter={valueFormatter}
1085
- /> */}
1086
- <Tooltip
1087
- wrapperStyle={{ outline: 'none' }}
1088
- isAnimationActive={false}
1089
- cursor={{ fill: '#d1d5db', opacity: '0.15' }}
1090
- content={({ active, payload, label }) => {
1091
- return (
1092
- <ChartTooltip
1093
- theme={theme}
1094
- active={active}
1095
- payload={payload}
1096
- label={label}
1097
- dateFormatter={value =>
1098
- valueFormatter({
1099
- value,
1100
- field: xAxisField,
1101
- fields: [{ field: xAxisField, format: xAxisFormat }],
1102
- })
1103
- }
1104
- valueFormatter={value =>
1105
- valueFormatter({
1106
- value,
1107
- field: payload[0].dataKey,
1108
- fields: yAxisFields,
1109
- })
1110
- }
1111
- colors={newColors}
1112
- />
1113
- );
1114
- }}
1115
- position={{ y: 0 }}
1116
- />
1117
- {/* <Legend
1118
- verticalAlign="top"
1119
- height={100}
1120
- content={({ payload }) =>
1121
- ChartLegend({ payload }, categoryColors, setLegendHeight)
1122
- }
1123
- /> */}
1124
- {yAxisFields.map((elem, index) => (
1125
- <Bar
1126
- key={elem.field}
1127
- name={elem.label}
1128
- dataKey={elem.field}
1129
- type="linear"
1130
- // stackId={stack || relative ? "a" : undefined}
1131
- fill={newColors[index]}
1132
- // barSize={20}
1133
- isAnimationActive={true}
1134
- />
1135
- ))}
1136
- </ReChartsBarChart>
1137
- </ResponsiveContainer>
1138
- </div>
1139
- );
1140
- }
1141
-
1142
- function LineChart({
1143
- colors,
1144
- yAxisFields,
1145
- data,
1146
- containerStyle,
1147
- xAxisField,
1148
- xAxisLabel,
1149
- xAxisFormat,
1150
- theme,
1151
- }: {
1152
- colors: string[];
1153
- yAxisFields: any[];
1154
- data: any[];
1155
- containerStyle?: React.CSSProperties;
1156
- xAxisField: string;
1157
- xAxisLabel: string;
1158
- xAxisFormat: string;
1159
- theme: any;
1160
- }) {
1161
- // console.log('CONTAINER: ', containerStyle);
1162
- if (!yAxisFields || !yAxisFields.length) {
1163
- return null;
1164
- }
1165
- return (
1166
- <div
1167
- style={{ ...containerStyle, boxSizing: 'content-box' }}
1168
- // className="qq-flex qq-flex-col qq-flex-1 qq-h-full qq-w-full"
1169
- >
1170
- <ResponsiveContainer width="100%" height={'100%'}>
1171
- <ReChartsAreaChart data={data}>
1172
- <CartesianGrid
1173
- strokeDasharray="3 3"
1174
- horizontal={true}
1175
- vertical={false}
1176
- />
1177
- <XAxis
1178
- // hide={!showXAxis}
1179
- dataKey={xAxisField}
1180
- tick={{ transform: 'translate(0, 6)' }}
1181
- // ticks={
1182
- // startEndOnly
1183
- // ? [data[0][index], data[data.length - 1][index]]
1184
- // : undefined
1185
- // }
1186
- style={{
1187
- fontSize: '12px',
1188
- fontFamily:
1189
- theme?.chartLabelFontFamily ||
1190
- theme?.fontFamily ||
1191
- 'Inter; Helvetica',
1192
- color: theme?.chartLabelColor || '#666666',
1193
- }}
1194
- // interval="preserveStartEnd"
1195
- interval="preserveStartEnd"
1196
- // interval={0}
1197
- tickLine={false}
1198
- axisLine={false}
1199
- padding={{ left: 10, right: 10 }}
1200
- minTickGap={5}
1201
- tickFormatter={tick =>
1202
- valueFormatter({
1203
- value: tick,
1204
- field: xAxisField,
1205
- fields: [{ field: xAxisField, format: xAxisFormat }],
1206
- })
1207
- }
1208
- // tickFormatter={tick => format(new Date(tick), 'MMM yyyy')}
1209
- />
1210
- <YAxis
1211
- // textAnchor="left"
1212
- // tickMargin={0}
1213
- // width={56}
1214
- // width={30}
1215
- // hide={!showYAxis}
1216
- axisLine={false}
1217
- tickLine={false}
1218
- type="number"
1219
- // domain={yAxisDomain as AxisDomain}
1220
- domain={getDomain(data, yAxisFields)}
1221
- tick={{ transform: 'translate(-3, 0)' }}
1222
- style={{
1223
- fontSize: '12px',
1224
- fontFamily:
1225
- theme?.chartLabelFontFamily ||
1226
- theme?.fontFamily ||
1227
- 'Inter; Helvetica',
1228
- color: theme?.chartLabelColor || '#666666',
1229
- }}
1230
- tickFormatter={value =>
1231
- valueFormatter({
1232
- value,
1233
- field: yAxisFields[0].field,
1234
- fields: [
1235
- ...yAxisFields,
1236
- { field: xAxisField, format: xAxisFormat },
1237
- ],
1238
- })
1239
- }
1240
- />
1241
- <Tooltip
1242
- wrapperStyle={{ outline: 'none' }}
1243
- isAnimationActive={false}
1244
- cursor={{ stroke: '#d1d5db', strokeWidth: 1 }}
1245
- content={({ active, payload, label }) => {
1246
- return (
1247
- <ChartTooltip
1248
- active={active}
1249
- payload={payload}
1250
- label={label}
1251
- dateFormatter={value =>
1252
- valueFormatter({
1253
- value,
1254
- field: xAxisField,
1255
- fields: [{ field: xAxisField, format: xAxisFormat }],
1256
- })
1257
- }
1258
- valueFormatter={value =>
1259
- valueFormatter({
1260
- value,
1261
- field: payload[0].dataKey,
1262
- fields: [
1263
- ...yAxisFields,
1264
- { field: xAxisField, format: xAxisFormat },
1265
- ],
1266
- })
1267
- }
1268
- colors={colors}
1269
- theme={theme}
1270
- />
1271
- );
1272
- }}
1273
- position={{ y: 0 }}
1274
- />
1275
- {/* <Legend
1276
- verticalAlign="top"
1277
- height={100}
1278
- content={({ payload }) =>
1279
- ChartLegend({ payload }, categoryColors, setLegendHeight)
1280
- }
1281
- /> */}
1282
-
1283
- {/* <defs>
1284
- <linearGradient id={'colorUj'} x1="0" y1="0" x2="0" y2="1">
1285
- <stop offset="5%" stopColor={'#E14F62'} stopOpacity={0.4} />
1286
- <stop
1287
- offset="95%"
1288
- stopColor={'rgba(0,0,0,0)'}
1289
- stopOpacity={0}
1290
- />
1291
- </linearGradient>
1292
- </defs> */}
1293
-
1294
- {yAxisFields.map((elem, index) => (
1295
- <defs key={`defs${elem.field + index}`}>
1296
- <linearGradient
1297
- id={`gradient${index}`}
1298
- x1="0"
1299
- y1="0"
1300
- x2="0"
1301
- y2="1"
1302
- >
1303
- <stop offset="5%" stopColor={colors[index]} stopOpacity={0.4} />
1304
- <stop
1305
- offset="95%"
1306
- stopColor={'rgba(0,0,0,0)'}
1307
- stopOpacity={0}
1308
- />
1309
- </linearGradient>
1310
- </defs>
1311
- ))}
1312
-
1313
- {/* {categories.map(category => {
1314
- console.log('category: ', category);
1315
- return ( */}
1316
- {yAxisFields.map((elem, index) => (
1317
- <Area
1318
- key={elem.field}
1319
- name={elem.label}
1320
- type="linear"
1321
- dataKey={elem.field}
1322
- stroke={colors[index]}
1323
- // if hide area
1324
- // fill="transparent"
1325
- fill={`url(#gradient${index})`}
1326
- strokeWidth={2}
1327
- dot={false}
1328
- isAnimationActive={true}
1329
- />
1330
- ))}
1331
- </ReChartsAreaChart>
1332
- </ResponsiveContainer>
1333
- </div>
1334
- );
1335
- }
1336
-
1337
- export default Chart;