@quillsql/react 1.4.9 → 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/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;
@@ -17,6 +23,7 @@ interface DashboardProps {
17
23
  maxColumnWidth?: number;
18
24
  rowHeight?: number;
19
25
  onClickDashboardItem?: (item: any) => void;
26
+ hideDateFilter?: boolean;
20
27
  }
21
28
 
22
29
  // const theme = {
@@ -34,12 +41,15 @@ export default function Dashboard({
34
41
  maxColumnWidth,
35
42
  rowHeight,
36
43
  onClickDashboardItem,
44
+ hideDateFilter,
37
45
  }: DashboardProps) {
38
46
  const [dashboardSections, setDashboardSections] = useState<any>(null);
39
47
  const { dashboard } = useContext(DashboardContext);
40
48
  const [client, _] = useContext(ClientContext);
41
49
  const [theme, _] = useContext(ThemeContext);
42
50
  const { dateFilter, dateFilterDispatch } = useContext(DateFilterContext);
51
+ const { dashboardFiltersDispatch } = useContext(DashboardFiltersContext);
52
+ const [configFilters, setConfigFilters] = useState([]);
43
53
 
44
54
  const setGlobalDateFilter = (startDate, endDate) => {
45
55
  dateFilterDispatch({
@@ -57,7 +67,7 @@ export default function Dashboard({
57
67
  // );
58
68
 
59
69
  const response = await axios.get(
60
- 'https://quill-344421.uc.r.appspot.com/dashsections',
70
+ 'https://quill-344421.uc.r.appspot.com/dashconfig',
61
71
  {
62
72
  params: {
63
73
  publicKey: publicKey,
@@ -74,7 +84,8 @@ export default function Dashboard({
74
84
  throw new Error(`HTTP error! Status: ${response.status}`);
75
85
  }
76
86
 
77
- setDashboardSections(response.data);
87
+ setDashboardSections(response.data.sections);
88
+ setConfigFilters(response.data.filters || []);
78
89
  } catch (error) {
79
90
  console.error('Error fetching data:', error);
80
91
  }
@@ -103,18 +114,67 @@ export default function Dashboard({
103
114
  }
104
115
  };
105
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
+
106
130
  if (!dashboardSections) {
107
131
  return null;
108
132
  }
109
133
  return (
110
134
  <div style={containerStyle}>
111
- <div style={{ width: 420, marginBottom: 25, marginLeft: 25 }}>
112
- <DateRangePicker
113
- // change to be set on the dashboard / section as default date range
114
- defaultValue={[undefined, undefined, '90d']}
115
- onValueChange={onChangeDateFilter}
116
- theme={theme}
117
- />
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
+ ))}
177
+ </div>
118
178
  </div>
119
179
  {Object.keys(dashboardSections)
120
180
  .sort(function (a, b) {
@@ -500,3 +560,214 @@ export default function Dashboard({
500
560
  </div>
501
561
  );
502
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 2rem',
35
+ padding: '0.75rem 1.25rem',
36
36
  fontSize: '0.875rem',
37
37
  borderStyle: 'solid',
38
38
  borderBottomWidth: lastItem ? '0px' : '1px',
@@ -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
- export const useQuill = (chartId: string) => {
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 [report, setReport] = useState(null);
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
- setReport(dashboard[id]);
30
+ setData(dashboard[id]);
17
31
  return;
18
32
  }
19
33
  // @ts-ignore
20
34
  const { publicKey, customerId, environment } = client;
21
- const resp = await axios.get(
22
- 'https://quill-344421.uc.r.appspot.com/item',
23
- {
24
- params: {
25
- id: chartId,
26
- orgId: customerId,
27
- publicKey: publicKey,
28
- },
29
- headers: {
30
- environment: environment || undefined,
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 && !report) {
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 report;
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
+ }