@quillsql/react 1.5.0 → 1.5.1
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.
- package/lib/Chart.js +70 -15
- package/lib/Chart.js.map +1 -1
- package/lib/Context.d.ts +1 -0
- package/lib/Context.js +19 -1
- package/lib/Context.js.map +1 -1
- package/lib/Dashboard.js +133 -9
- package/lib/Dashboard.js.map +1 -1
- package/lib/components/Dropdown/DropdownItem.js +1 -1
- package/lib/components/Dropdown/DropdownItem.js.map +1 -1
- package/lib/hooks/useQuill.d.ts +13 -1
- package/lib/hooks/useQuill.js +36 -10
- package/lib/hooks/useQuill.js.map +1 -1
- package/package.json +1 -1
- package/src/Chart.tsx +94 -12
- package/src/Context.tsx +35 -1
- package/src/Dashboard.tsx +279 -12
- package/src/components/Dropdown/DropdownItem.tsx +1 -1
- package/src/hooks/useQuill.ts +53 -19
package/src/Dashboard.tsx
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
-
import React, { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import React, { useContext, useEffect, useState, useRef } from 'react';
|
|
3
3
|
import Chart from './Chart';
|
|
4
4
|
import {
|
|
5
5
|
ClientContext,
|
|
6
6
|
DashboardContext,
|
|
7
7
|
DateFilterContext,
|
|
8
8
|
ThemeContext,
|
|
9
|
+
DashboardFiltersContext,
|
|
9
10
|
} from './Context';
|
|
10
11
|
import { startOfToday, sub } from 'date-fns';
|
|
11
12
|
import { DateRangePicker } from './DateRangePicker/index';
|
|
12
13
|
import axios from 'axios';
|
|
14
|
+
import Modal from './components/Modal/Modal';
|
|
15
|
+
import { HoveredValueContext, SelectedValueContext } from './contexts';
|
|
16
|
+
import { DropdownItem } from './components/Dropdown';
|
|
17
|
+
import { ArrowDownHeadIcon } from './assets';
|
|
18
|
+
import { useInternalState, useSelectOnKeyDown } from './hooks';
|
|
13
19
|
|
|
14
20
|
interface DashboardProps {
|
|
15
21
|
name?: string;
|
|
@@ -42,6 +48,8 @@ export default function Dashboard({
|
|
|
42
48
|
const [client, _] = useContext(ClientContext);
|
|
43
49
|
const [theme, _] = useContext(ThemeContext);
|
|
44
50
|
const { dateFilter, dateFilterDispatch } = useContext(DateFilterContext);
|
|
51
|
+
const { dashboardFiltersDispatch } = useContext(DashboardFiltersContext);
|
|
52
|
+
const [configFilters, setConfigFilters] = useState([]);
|
|
45
53
|
|
|
46
54
|
const setGlobalDateFilter = (startDate, endDate) => {
|
|
47
55
|
dateFilterDispatch({
|
|
@@ -59,7 +67,7 @@ export default function Dashboard({
|
|
|
59
67
|
// );
|
|
60
68
|
|
|
61
69
|
const response = await axios.get(
|
|
62
|
-
'https://quill-344421.uc.r.appspot.com/
|
|
70
|
+
'https://quill-344421.uc.r.appspot.com/dashconfig',
|
|
63
71
|
{
|
|
64
72
|
params: {
|
|
65
73
|
publicKey: publicKey,
|
|
@@ -76,7 +84,8 @@ export default function Dashboard({
|
|
|
76
84
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
setDashboardSections(response.data);
|
|
87
|
+
setDashboardSections(response.data.sections);
|
|
88
|
+
setConfigFilters(response.data.filters || []);
|
|
80
89
|
} catch (error) {
|
|
81
90
|
console.error('Error fetching data:', error);
|
|
82
91
|
}
|
|
@@ -105,21 +114,68 @@ export default function Dashboard({
|
|
|
105
114
|
}
|
|
106
115
|
};
|
|
107
116
|
|
|
117
|
+
const onChangeFilter = (value, filter) => {
|
|
118
|
+
dashboardFiltersDispatch({
|
|
119
|
+
type: 'ADD_DASHBOARD_FILTER',
|
|
120
|
+
id: filter._id,
|
|
121
|
+
data: {
|
|
122
|
+
...filter,
|
|
123
|
+
selectedValue: filter.options.find(
|
|
124
|
+
elem => elem[removeQuotes(filter.field)] === value[2]
|
|
125
|
+
)[removeQuotes(filter.field)],
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
108
130
|
if (!dashboardSections) {
|
|
109
131
|
return null;
|
|
110
132
|
}
|
|
111
133
|
return (
|
|
112
134
|
<div style={containerStyle}>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
<div
|
|
136
|
+
style={{
|
|
137
|
+
display: 'flex',
|
|
138
|
+
boxSizing: 'content-box',
|
|
139
|
+
flexDirection: 'row',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{!hideDateFilter ? (
|
|
144
|
+
<div style={{ width: 420, marginBottom: 25, marginLeft: 25 }}>
|
|
145
|
+
<div
|
|
146
|
+
style={{
|
|
147
|
+
marginBottom: 6,
|
|
148
|
+
fontWeight: '600',
|
|
149
|
+
color: theme.secondaryTextColor,
|
|
150
|
+
fontSize: 14,
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
Date
|
|
154
|
+
</div>
|
|
155
|
+
<DateRangePicker
|
|
156
|
+
// change to be set on the dashboard / section as default date range
|
|
157
|
+
defaultValue={[undefined, undefined, '90d']}
|
|
158
|
+
onValueChange={onChangeDateFilter}
|
|
159
|
+
theme={theme}
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
) : null}
|
|
163
|
+
<div style={{ width: 280, marginBottom: 25, marginLeft: 25 }}>
|
|
164
|
+
{configFilters.map((elem, index) => (
|
|
165
|
+
<Filter
|
|
166
|
+
key={'filter' + configFilters.field + index}
|
|
167
|
+
onValueChange={value => onChangeFilter(value, elem)}
|
|
168
|
+
defaultValue={[
|
|
169
|
+
undefined,
|
|
170
|
+
undefined,
|
|
171
|
+
elem.options[removeQuotes(elem.field)],
|
|
172
|
+
]}
|
|
173
|
+
theme={theme}
|
|
174
|
+
filter={elem}
|
|
175
|
+
/>
|
|
176
|
+
))}
|
|
121
177
|
</div>
|
|
122
|
-
|
|
178
|
+
</div>
|
|
123
179
|
{Object.keys(dashboardSections)
|
|
124
180
|
.sort(function (a, b) {
|
|
125
181
|
return a.length - b.length;
|
|
@@ -504,3 +560,214 @@ export default function Dashboard({
|
|
|
504
560
|
</div>
|
|
505
561
|
);
|
|
506
562
|
}
|
|
563
|
+
|
|
564
|
+
function Filter({ defaultValue, theme, onValueChange, filter }) {
|
|
565
|
+
const dropdownOptions = [
|
|
566
|
+
{ value: 'popupbagelsmardens', text: 'popupbagelsmardens' },
|
|
567
|
+
{ value: 'popupbagelswestport', text: 'popupbagelswestport' },
|
|
568
|
+
{ value: 'popupbagelseasthampton', text: 'popupbagelseasthampton' },
|
|
569
|
+
{ value: 'popupbagelsgreenwich', text: 'popupbagelsgreenwich' },
|
|
570
|
+
{ value: 'popupbagelsredding', text: 'popupbagelsredding' },
|
|
571
|
+
{ value: 'popupbagelsthompson', text: 'popupbagelsthompson' },
|
|
572
|
+
];
|
|
573
|
+
const dropdownRef = useRef(null);
|
|
574
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
575
|
+
const [selectedValue, setSelectedValue] = useInternalState(
|
|
576
|
+
defaultValue,
|
|
577
|
+
undefined
|
|
578
|
+
);
|
|
579
|
+
const selectedDropdownValue = selectedValue ? selectedValue[2] ?? null : null;
|
|
580
|
+
|
|
581
|
+
const handleDropdownOptionClick = (dropdownValue: string) => {
|
|
582
|
+
setSelectedValue([undefined, undefined, dropdownValue]);
|
|
583
|
+
onValueChange?.([undefined, undefined, dropdownValue]);
|
|
584
|
+
setShowDropdown(false);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
const [hoveredDropdownValue, handleDropdownKeyDown] = useSelectOnKeyDown(
|
|
588
|
+
handleDropdownOptionClick,
|
|
589
|
+
dropdownOptions.map((option: DateRangePickerOption) => option.value),
|
|
590
|
+
showDropdown,
|
|
591
|
+
setShowDropdown,
|
|
592
|
+
selectedDropdownValue as string
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
return (
|
|
596
|
+
<div style={{ position: 'relative', width: '100%' }}>
|
|
597
|
+
<div
|
|
598
|
+
style={{
|
|
599
|
+
marginBottom: 6,
|
|
600
|
+
fontWeight: '600',
|
|
601
|
+
color: theme.secondaryTextColor,
|
|
602
|
+
fontSize: 14,
|
|
603
|
+
}}
|
|
604
|
+
>
|
|
605
|
+
{filter.label}
|
|
606
|
+
</div>
|
|
607
|
+
<FilterDropdown
|
|
608
|
+
showDropdown={showDropdown}
|
|
609
|
+
setShowDropdown={setShowDropdown}
|
|
610
|
+
handleDropdownKeyDown={handleDropdownKeyDown}
|
|
611
|
+
dropdownRef={dropdownRef}
|
|
612
|
+
theme={theme}
|
|
613
|
+
selectedDropdownValue={selectedDropdownValue}
|
|
614
|
+
dropdownOptions={filter.options}
|
|
615
|
+
field={filter.field}
|
|
616
|
+
label={filter.labelField}
|
|
617
|
+
/>
|
|
618
|
+
<FilterModal
|
|
619
|
+
showDropdown={showDropdown}
|
|
620
|
+
setShowDropdown={setShowDropdown}
|
|
621
|
+
dropdownRef={dropdownRef}
|
|
622
|
+
theme={theme}
|
|
623
|
+
hoveredDropdownValue={hoveredDropdownValue}
|
|
624
|
+
selectedDropdownValue={selectedDropdownValue}
|
|
625
|
+
dropdownOptions={filter.options}
|
|
626
|
+
handleDropdownOptionClick={handleDropdownOptionClick}
|
|
627
|
+
field={filter.field}
|
|
628
|
+
label={filter.labelField}
|
|
629
|
+
/>
|
|
630
|
+
</div>
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function removeQuotes(str) {
|
|
635
|
+
if (str.startsWith('"') && str.endsWith('"')) {
|
|
636
|
+
return str.slice(1, -1);
|
|
637
|
+
} else {
|
|
638
|
+
return str;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function FilterDropdown({
|
|
643
|
+
setShowDropdown,
|
|
644
|
+
dropdownRef,
|
|
645
|
+
showDropdown,
|
|
646
|
+
handleDropdownKeyDown,
|
|
647
|
+
theme,
|
|
648
|
+
dropdownPlaceholder = 'Select',
|
|
649
|
+
selectedDropdownValue,
|
|
650
|
+
dropdownOptions,
|
|
651
|
+
field,
|
|
652
|
+
label,
|
|
653
|
+
}) {
|
|
654
|
+
const dropdownText = selectedDropdownValue
|
|
655
|
+
? String(
|
|
656
|
+
dropdownOptions.find(
|
|
657
|
+
option => option[removeQuotes(field)] === selectedDropdownValue
|
|
658
|
+
)[removeQuotes(label)]
|
|
659
|
+
)
|
|
660
|
+
: dropdownPlaceholder;
|
|
661
|
+
return (
|
|
662
|
+
<div
|
|
663
|
+
style={{
|
|
664
|
+
display: 'flex',
|
|
665
|
+
alignItems: 'center',
|
|
666
|
+
justifyContent: 'space-between',
|
|
667
|
+
borderRadius: '0.375rem',
|
|
668
|
+
background: theme?.backgroundColor,
|
|
669
|
+
fontFamily: theme?.fontFamily,
|
|
670
|
+
boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
|
671
|
+
}}
|
|
672
|
+
>
|
|
673
|
+
<button
|
|
674
|
+
type="button"
|
|
675
|
+
style={{
|
|
676
|
+
// fontFamily: theme?.fontFamily,
|
|
677
|
+
borderColor: theme?.borderColor || '#E5E7EB',
|
|
678
|
+
borderStyle: 'solid',
|
|
679
|
+
borderWidth: 1,
|
|
680
|
+
cursor: 'pointer',
|
|
681
|
+
marginLeft: -1,
|
|
682
|
+
borderRadius: '0.375rem',
|
|
683
|
+
// width: '12rem',
|
|
684
|
+
width: '100%',
|
|
685
|
+
overflow: 'hidden',
|
|
686
|
+
textOverflow: 'ellipsis',
|
|
687
|
+
whiteSpace: 'nowrap',
|
|
688
|
+
paddingLeft: '1rem',
|
|
689
|
+
paddingRight: '1rem',
|
|
690
|
+
display: 'inline-flex',
|
|
691
|
+
minHeight: 38,
|
|
692
|
+
justifyContent: 'space-between',
|
|
693
|
+
alignItems: 'center',
|
|
694
|
+
background: theme?.backgroundColor,
|
|
695
|
+
fontSize: theme?.fontSizeSmall || '0.875rem',
|
|
696
|
+
// fontWeight: theme?.fontWeightMedium || '800',
|
|
697
|
+
}}
|
|
698
|
+
ref={dropdownRef}
|
|
699
|
+
onClick={() => setShowDropdown(!showDropdown)}
|
|
700
|
+
onKeyDown={handleDropdownKeyDown}
|
|
701
|
+
// disabled={disabled}
|
|
702
|
+
>
|
|
703
|
+
<p
|
|
704
|
+
style={{
|
|
705
|
+
margin: 0,
|
|
706
|
+
fontFamily: theme?.fontFamily,
|
|
707
|
+
color: theme?.primaryTextColor || '#364153',
|
|
708
|
+
overflow: 'hidden',
|
|
709
|
+
textOverflow: 'ellipsis',
|
|
710
|
+
whiteSpace: 'nowrap',
|
|
711
|
+
fontWeight: theme?.fontWeightMedium || '500',
|
|
712
|
+
fontSize: theme?.fontSizeSmall || '0.875rem',
|
|
713
|
+
}}
|
|
714
|
+
>
|
|
715
|
+
{dropdownText}
|
|
716
|
+
</p>
|
|
717
|
+
<ArrowDownHeadIcon
|
|
718
|
+
style={{
|
|
719
|
+
height: '1.25rem',
|
|
720
|
+
width: '1.25rem',
|
|
721
|
+
flex: 'none',
|
|
722
|
+
color: theme?.secondaryTextColor,
|
|
723
|
+
marginRight: '-0.25rem',
|
|
724
|
+
}}
|
|
725
|
+
aria-hidden="true"
|
|
726
|
+
/>
|
|
727
|
+
</button>
|
|
728
|
+
</div>
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function FilterModal({
|
|
733
|
+
setShowDropdown,
|
|
734
|
+
dropdownRef,
|
|
735
|
+
showDropdown,
|
|
736
|
+
theme,
|
|
737
|
+
selectedDropdownValue,
|
|
738
|
+
hoveredDropdownValue,
|
|
739
|
+
dropdownOptions,
|
|
740
|
+
handleDropdownOptionClick,
|
|
741
|
+
field,
|
|
742
|
+
label,
|
|
743
|
+
}) {
|
|
744
|
+
return (
|
|
745
|
+
<Modal
|
|
746
|
+
showModal={showDropdown}
|
|
747
|
+
setShowModal={setShowDropdown}
|
|
748
|
+
parentRef={dropdownRef}
|
|
749
|
+
theme={theme}
|
|
750
|
+
>
|
|
751
|
+
<SelectedValueContext.Provider
|
|
752
|
+
value={{
|
|
753
|
+
selectedValue: selectedDropdownValue,
|
|
754
|
+
handleValueChange: handleDropdownOptionClick,
|
|
755
|
+
}}
|
|
756
|
+
>
|
|
757
|
+
<HoveredValueContext.Provider
|
|
758
|
+
value={{ hoveredValue: hoveredDropdownValue }}
|
|
759
|
+
>
|
|
760
|
+
{dropdownOptions.map((row, index: number) => (
|
|
761
|
+
<DropdownItem
|
|
762
|
+
key={row[removeQuotes(field)]}
|
|
763
|
+
value={row[removeQuotes(field)]}
|
|
764
|
+
text={row[removeQuotes(label)]}
|
|
765
|
+
theme={theme}
|
|
766
|
+
lastItem={dropdownOptions.length - 1 === index}
|
|
767
|
+
/>
|
|
768
|
+
))}
|
|
769
|
+
</HoveredValueContext.Provider>
|
|
770
|
+
</SelectedValueContext.Provider>
|
|
771
|
+
</Modal>
|
|
772
|
+
);
|
|
773
|
+
}
|
|
@@ -32,7 +32,7 @@ const DropdownItem = React.forwardRef<HTMLButtonElement, DropdownItemProps>(
|
|
|
32
32
|
width: '100%',
|
|
33
33
|
overflow: 'hidden',
|
|
34
34
|
textOverflow: 'ellipsis',
|
|
35
|
-
padding: '0.75rem
|
|
35
|
+
padding: '0.75rem 1.25rem',
|
|
36
36
|
fontSize: '0.875rem',
|
|
37
37
|
borderStyle: 'solid',
|
|
38
38
|
borderBottomWidth: lastItem ? '0px' : '1px',
|
package/src/hooks/useQuill.ts
CHANGED
|
@@ -2,40 +2,63 @@ import { useContext, useEffect, useState } from 'react';
|
|
|
2
2
|
import { ClientContext, DashboardContext } from '../Context';
|
|
3
3
|
import axios from 'axios';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface Report {
|
|
6
|
+
name: string;
|
|
7
|
+
rows: any[];
|
|
8
|
+
columns: Array<{ name: string }>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const useQuill = (
|
|
12
|
+
chartId: string
|
|
13
|
+
): {
|
|
14
|
+
data: Report | null;
|
|
15
|
+
loading: boolean;
|
|
16
|
+
error: string | null;
|
|
17
|
+
} => {
|
|
6
18
|
// @ts-ignore
|
|
7
19
|
const { dashboard, dispatch } = useContext(DashboardContext);
|
|
8
20
|
const [client] = useContext(ClientContext);
|
|
9
|
-
const [
|
|
21
|
+
const [data, setData] = useState<Report | null>(null);
|
|
22
|
+
const [loading, setLoading] = useState(true);
|
|
23
|
+
const [error, setError] = useState<string | null>(null);
|
|
10
24
|
|
|
11
25
|
useEffect(() => {
|
|
12
26
|
let isSubscribed = true;
|
|
13
27
|
async function getChartOptions(id: string) {
|
|
14
28
|
if (isSubscribed) {
|
|
15
29
|
if (dashboard[id]) {
|
|
16
|
-
|
|
30
|
+
setData(dashboard[id]);
|
|
17
31
|
return;
|
|
18
32
|
}
|
|
19
33
|
// @ts-ignore
|
|
20
34
|
const { publicKey, customerId, environment } = client;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
try {
|
|
36
|
+
const resp = await axios.get(
|
|
37
|
+
'https://quill-344421.uc.r.appspot.com/item',
|
|
38
|
+
{
|
|
39
|
+
params: {
|
|
40
|
+
id: chartId,
|
|
41
|
+
orgId: customerId,
|
|
42
|
+
publicKey: publicKey,
|
|
43
|
+
},
|
|
44
|
+
headers: {
|
|
45
|
+
environment: environment || undefined,
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
setLoading(false);
|
|
50
|
+
setData(resp.data);
|
|
51
|
+
dispatch({ type: 'UPDATE_DASHBOARD_ITEM', id, data: resp.data });
|
|
52
|
+
} catch (e) {
|
|
53
|
+
if (typeof e === 'string' || (typeof e === 'object' && e !== null)) {
|
|
54
|
+
setError(stringifyIfObject(e));
|
|
55
|
+
} else {
|
|
56
|
+
setError('error');
|
|
32
57
|
}
|
|
33
|
-
|
|
34
|
-
setReport(resp.data);
|
|
35
|
-
dispatch({ type: 'UPDATE_DASHBOARD_ITEM', id, data: resp.data });
|
|
58
|
+
}
|
|
36
59
|
}
|
|
37
60
|
}
|
|
38
|
-
if (chartId && !
|
|
61
|
+
if (chartId && !data) {
|
|
39
62
|
getChartOptions(chartId);
|
|
40
63
|
}
|
|
41
64
|
return () => {
|
|
@@ -43,5 +66,16 @@ export const useQuill = (chartId: string) => {
|
|
|
43
66
|
};
|
|
44
67
|
}, [chartId, dashboard]);
|
|
45
68
|
|
|
46
|
-
return
|
|
69
|
+
return { data, loading, error };
|
|
47
70
|
};
|
|
71
|
+
|
|
72
|
+
function stringifyIfObject(e: string | object) {
|
|
73
|
+
if (typeof e === 'string') {
|
|
74
|
+
// do nothing if e is a string
|
|
75
|
+
return e;
|
|
76
|
+
} else if (typeof e === 'object' && e !== null) {
|
|
77
|
+
// stringify e if it's an object
|
|
78
|
+
return JSON.stringify(e);
|
|
79
|
+
}
|
|
80
|
+
return e; // returns the input as is for other types
|
|
81
|
+
}
|