@bygd/nc-report-ui 0.1.4 → 0.1.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.
@@ -1,1054 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var React = require('react');
6
- var Paper = require('@material-ui/core/Paper');
7
- var styles = require('@material-ui/core/styles');
8
- var LinearProgress = require('@material-ui/core/LinearProgress');
9
- var reactGoogleCharts = require('react-google-charts');
10
- var numeral = require('numeral');
11
- var axios = require('axios');
12
- var Typography = require('@material-ui/core/Typography');
13
- var nunjucks = require('nunjucks');
14
- var FormControl$1 = require('@material-ui/core/FormControl');
15
- var Select$1 = require('@material-ui/core/Select');
16
- var MenuItem$1 = require('@material-ui/core/MenuItem');
17
- var reactIntersectionObserver = require('react-intersection-observer');
18
- var cron = require('cron');
19
- var material = require('@mui/material');
20
- var CheckBoxOutlineBlankIcon = require('@mui/icons-material/CheckBoxOutlineBlank');
21
- var CheckBoxIcon = require('@mui/icons-material/CheckBox');
22
- var Box = require('@mui/material/Box');
23
- var InputLabel = require('@mui/material/InputLabel');
24
- var MenuItem = require('@mui/material/MenuItem');
25
- var FormControl = require('@mui/material/FormControl');
26
- var Select = require('@mui/material/Select');
27
- var EventEmitter = require('eventemitter3');
28
- var Grid = require('@material-ui/core/Grid');
29
- var Container = require('@material-ui/core/Container');
30
-
31
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
32
-
33
- function _interopNamespace(e) {
34
- if (e && e.__esModule) return e;
35
- var n = Object.create(null);
36
- if (e) {
37
- Object.keys(e).forEach(function (k) {
38
- if (k !== 'default') {
39
- var d = Object.getOwnPropertyDescriptor(e, k);
40
- Object.defineProperty(n, k, d.get ? d : {
41
- enumerable: true,
42
- get: function () { return e[k]; }
43
- });
44
- }
45
- });
46
- }
47
- n.default = e;
48
- return Object.freeze(n);
49
- }
50
-
51
- var React__namespace = /*#__PURE__*/_interopNamespace(React);
52
- var Paper__default = /*#__PURE__*/_interopDefault(Paper);
53
- var LinearProgress__default = /*#__PURE__*/_interopDefault(LinearProgress);
54
- var numeral__default = /*#__PURE__*/_interopDefault(numeral);
55
- var axios__default = /*#__PURE__*/_interopDefault(axios);
56
- var Typography__default = /*#__PURE__*/_interopDefault(Typography);
57
- var nunjucks__default = /*#__PURE__*/_interopDefault(nunjucks);
58
- var FormControl__default$1 = /*#__PURE__*/_interopDefault(FormControl$1);
59
- var Select__default$1 = /*#__PURE__*/_interopDefault(Select$1);
60
- var MenuItem__default$1 = /*#__PURE__*/_interopDefault(MenuItem$1);
61
- var CheckBoxOutlineBlankIcon__default = /*#__PURE__*/_interopDefault(CheckBoxOutlineBlankIcon);
62
- var CheckBoxIcon__default = /*#__PURE__*/_interopDefault(CheckBoxIcon);
63
- var Box__default = /*#__PURE__*/_interopDefault(Box);
64
- var InputLabel__default = /*#__PURE__*/_interopDefault(InputLabel);
65
- var MenuItem__default = /*#__PURE__*/_interopDefault(MenuItem);
66
- var FormControl__default = /*#__PURE__*/_interopDefault(FormControl);
67
- var Select__default = /*#__PURE__*/_interopDefault(Select);
68
- var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
69
- var Grid__default = /*#__PURE__*/_interopDefault(Grid);
70
- var Container__default = /*#__PURE__*/_interopDefault(Container);
71
-
72
- function _extends() {
73
- return _extends = Object.assign ? Object.assign.bind() : function (n) {
74
- for (var e = 1; e < arguments.length; e++) {
75
- var t = arguments[e];
76
- for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
77
- }
78
- return n;
79
- }, _extends.apply(null, arguments);
80
- }
81
-
82
- const useStyles$1 = styles.makeStyles(theme => ({
83
- root: {
84
- width: '100%',
85
- '& > * + *': {
86
- marginTop: theme.spacing(2)
87
- }
88
- }
89
- }));
90
- function LinearIndeterminate(props) {
91
- const classes = useStyles$1();
92
- return /*#__PURE__*/React__namespace.default.createElement("div", _extends({
93
- className: classes.root
94
- }, props), /*#__PURE__*/React__namespace.default.createElement(LinearProgress__default.default, null));
95
- }
96
-
97
- function ValuePicker(view, schema, outputFormat = 'array') {
98
- const columns = view.columns;
99
- const metrics = schema.doc.metrics;
100
- const dimensions = schema.doc.dimensions;
101
- const all = [...metrics, ...dimensions];
102
- const mapped = all.reduce((result, item) => {
103
- result[item.name] = item;
104
- return result;
105
- }, {});
106
- const _get = row => {
107
- const output = outputFormat === 'array' ? [] : {};
108
- const resp = columns.reduce((result, item) => {
109
- const value = row[item.name];
110
- const ref = mapped[item.name];
111
- let formatted = value;
112
- if (ref.type === 'timestamp') formatted = new Date(value);else if (!ref.format) formatted = value;else if (ref.format && ref.prefix) formatted = {
113
- v: +value,
114
- f: ref.prefix + " " + numeral__default.default(value).format(ref.format)
115
- };else if (ref.format) formatted = {
116
- v: +value,
117
- f: numeral__default.default(value).format(ref.format)
118
- };else formatted = value;
119
- if (outputFormat === 'array') result.push(formatted);else result[item.name] = formatted?.f || formatted;
120
- return result;
121
- }, output);
122
- return resp;
123
- };
124
- return _get;
125
- }
126
- function HeaderPicker(view, schema) {
127
- const metrics = schema.doc.metrics;
128
- const dimensions = schema.doc.dimensions;
129
- const all = [...metrics, ...dimensions];
130
- const result = view.columns.reduce((result, item) => {
131
- const column = all.find(f => f.name === item.name) || item;
132
- result.push(column.title || column.name);
133
- return result;
134
- }, []);
135
- return result;
136
- }
137
-
138
- //const BASE_URL = 'http://localhost:8081';
139
- const BASE_URL = 'https://report-api.netcapital.pro';
140
- const apiClient = axios__default.default.create({
141
- baseURL: BASE_URL,
142
- headers: {
143
- 'Content-Type': 'application/json'
144
- }
145
- });
146
- const Api = {
147
- getDashboard: async ({
148
- id
149
- }) => {
150
- const {
151
- data
152
- } = await apiClient.get(`/entity/dashboards/${id}`);
153
- return data;
154
- },
155
- /**
156
- * Get chart by ID
157
- * @param {Object} params - Parameters object
158
- * @param {string} params.id - Chart ID
159
- * @returns {Promise} Axios response promise
160
- */
161
- getChart: async ({
162
- id
163
- }) => {
164
- const {
165
- data
166
- } = await apiClient.get(`/entity/charts/${id}`);
167
- return data;
168
- },
169
- /**
170
- * Get report by ID
171
- * @param {Object} params - Parameters object
172
- * @param {string} params.id - Report ID
173
- * @returns {Promise} Axios response promise
174
- */
175
- getReport: async ({
176
- id
177
- }) => {
178
- const {
179
- data
180
- } = await apiClient.get(`/entity/reports/${id}`);
181
- return data;
182
- },
183
- /**
184
- * Get report schema/metadata by ID
185
- * @param {Object} params - Parameters object
186
- * @param {string} params.id - Report ID
187
- * @returns {Promise} Axios response promise
188
- */
189
- getReportSchema: async ({
190
- id,
191
- query = {}
192
- }) => {
193
- // console.log({getReportSchema:{id,query}});
194
- const {
195
- data
196
- } = await apiClient.post(`/reports/${id}/metadata`, query);
197
- return data;
198
- },
199
- /**
200
- * Run report with optional query parameters
201
- * @param {Object} params - Parameters object
202
- * @param {string} params.id - Report ID
203
- * @param {Object} [params.query] - Optional query object to send in request body
204
- * @returns {Promise} Axios response promise
205
- */
206
- runReport: async ({
207
- id,
208
- query = {}
209
- }) => {
210
- // console.log({runReport:{id,query}});
211
-
212
- const {
213
- data
214
- } = await apiClient.post(`/reports/${id}/run`, query);
215
- return data;
216
- },
217
- getDateRanges: async () => {
218
- const {
219
- data
220
- } = await apiClient.get(`/globals/date-ranges`);
221
- return data;
222
- }
223
- };
224
-
225
- const useStyles = styles.makeStyles(theme => ({
226
- headerRow: {
227
- fontFamily: theme.typography.fontFamily,
228
- fontSize: theme.typography.fontSize
229
- },
230
- tableRow: {
231
- fontFamily: theme.typography.fontFamily,
232
- fontSize: theme.typography.fontSize
233
- },
234
- oddTableRow: {
235
- fontFamily: theme.typography.fontFamily,
236
- fontSize: theme.typography.fontSize
237
- },
238
- headerCell: {
239
- backgroundColor: "white",
240
- padding: "4px !important"
241
- }
242
- // tableCell:{
243
- // // padding:"4px !important"
244
- // },
245
- }));
246
- const dateRangeFormats = {
247
- "none": "YYYY-MM-dd",
248
- "day": "MMM dd",
249
- "week": "MMM dd",
250
- "month": "YYYY MMM",
251
- "year": "YYYY"
252
- };
253
- function GoogleChart({
254
- chart,
255
- source,
256
- view,
257
- report,
258
- schema,
259
- dashboard,
260
- query
261
- }) {
262
- const [data, setData] = React__namespace.default.useState([]);
263
- const classes = useStyles();
264
- const [dateRanges, setDateRanges] = React__namespace.default.useState([]);
265
- React.useEffect(() => {
266
- (async () => {
267
- const data = await Api.getDateRanges();
268
- setDateRanges(data);
269
- })();
270
- }, []);
271
- const dateRangeFormat = React.useMemo(() => {
272
- const defaultFormat = 'YYYY-MM-dd';
273
- const name = query?.date_range?.name;
274
- const dateRange = dateRanges.find(f => f.name === name);
275
- if (!dateRange) return defaultFormat;
276
- const format = dateRangeFormats[dateRange?.granularity] || defaultFormat;
277
- return format;
278
- }, [dateRanges, query]);
279
- const getOptions = data => {
280
- if (chart?.doc.chart?.type === 'Table') {
281
- return {
282
- ...chart?.doc.chart?.options,
283
- cssClassNames: classes
284
- };
285
- } else if (chart?.doc.chart?.type === 'AreaChart') {
286
- const hAxisFromChart = chart?.doc.chart?.options?.hAxis || {};
287
- const hAxisDefaults = {
288
- ticks: data.slice(1).map(row => row[0]),
289
- format: dateRangeFormat
290
- };
291
- const hAxis = {
292
- ...hAxisDefaults,
293
- ...hAxisFromChart
294
- };
295
-
296
- // console.log({GoogleChart:{chart,source,view,report,schema,query,dateRanges,dateRangeFormat,ticks:hAxis.ticks}})
297
-
298
- return {
299
- ...chart?.doc.chart?.options,
300
- ...{
301
- hAxis,
302
- focusTarget: 'category'
303
- }
304
- };
305
- } else return chart?.doc.chart?.options;
306
- };
307
- React__namespace.default.useEffect(() => {
308
- if (!chart || !view || !source || !report || !schema) return;
309
- let result = [];
310
- result.push(HeaderPicker(view, schema));
311
- const valuePicker = ValuePicker(view, schema);
312
- source?.forEach(item => {
313
- const val = valuePicker(item);
314
- result.push(val);
315
- });
316
- source?.length ? setData(result) : setData();
317
- }, [chart, source, view, report, schema]);
318
- return data ? /*#__PURE__*/React__namespace.default.createElement(reactGoogleCharts.Chart, _extends({
319
- width: chart?.doc.size?.width || '100%',
320
- height: dashboard ? chart?.doc.size?.height || '300px' : '100%',
321
- chartType: chart?.doc.chart?.type,
322
- data: data,
323
- options: getOptions(data)
324
- }, chart?.doc.chart?.props)) : /*#__PURE__*/React__namespace.default.createElement("div", null);
325
- }
326
-
327
- function LabelChart({
328
- chart,
329
- source,
330
- view,
331
- report,
332
- schema
333
- }) {
334
- const [data, setData] = React__namespace.default.useState([]);
335
- const [value, setValue] = React__namespace.default.useState();
336
- const [subText, setSubText] = React__namespace.default.useState();
337
- React__namespace.default.useEffect(() => {
338
- if (!chart || !view || !source || !report || !schema) return;
339
- let result = [];
340
- // result.push(HeaderPicker(view, schema));
341
- const valuePicker = ValuePicker(view, schema);
342
- source.forEach(item => result.push(valuePicker(item)));
343
- setData(result);
344
- const options = chart?.doc?.chart?.options || {};
345
- const valueIndex = options?.index || 0;
346
- if (result.length > 0) setValue(result[0][valueIndex]);
347
- if (options.subText?.format) {
348
- nunjucks__default.default.configure({
349
- autoescape: false
350
- });
351
- const valuePickerObj = ValuePicker(view, schema, 'object');
352
- const val = valuePickerObj(source[0]);
353
- const subTextRendered = nunjucks__default.default.renderString(options.subText?.format, val);
354
- setSubText(subTextRendered);
355
- }
356
- }, [chart, source, view, report, schema]);
357
- return /*#__PURE__*/React__namespace.default.createElement("div", {
358
- style: {
359
- display: "flex",
360
- justifyContent: "center",
361
- alignContent: "center",
362
- alignItems: "center",
363
- height: "100%",
364
- flexDirection: "column"
365
- }
366
- }, /*#__PURE__*/React__namespace.default.createElement(Typography__default.default, {
367
- variant: "h6"
368
- }, value?.f || value), /*#__PURE__*/React__namespace.default.createElement(Typography__default.default, {
369
- variant: "subtitle1"
370
- }, subText));
371
- }
372
-
373
- const Components = {
374
- GoogleChart,
375
- LabelChart
376
- };
377
- function Internal(name, props) {
378
- if (typeof Components[name] !== "undefined") {
379
- return /*#__PURE__*/React__namespace.default.createElement(Components[name], props);
380
- } else return /*#__PURE__*/React__namespace.default.createElement(LinearIndeterminate, props);
381
- }
382
-
383
- /**
384
- * CheckboxMultiAutocomplete
385
- *
386
- * Props:
387
- * - items: Array<{ key: string, value: string }>
388
- * - selectedKeys?: string[] (controlled)
389
- * - defaultSelectedKeys?: string[] (uncontrolled)
390
- * - onChange?: (selectedKeys: string[], selectedItems: {key, value}[]) => void
391
- * - inputValue?: string (controlled input)
392
- * - defaultInputValue?: string (uncontrolled input)
393
- * - onInputChange?: (text: string) => void (debounced by debounceMs)
394
- * - debounceMs?: number (default 300)
395
- * - label?, placeholder?, loading?, disabled?, size? = 'small', error?, helperText?,
396
- * limitTags? = 3, disableClearable?, id?, textFieldProps?
397
- */
398
-
399
- function useDebouncedCallback(fn, delay = 300) {
400
- const fnRef = React.useRef(fn);
401
- const timer = React.useRef(null);
402
- fnRef.current = fn;
403
- React.useEffect(() => () => {
404
- if (timer.current) window.clearTimeout(timer.current);
405
- }, []);
406
- return React__namespace.useCallback((...args) => {
407
- if (!fnRef.current) return;
408
- if (timer.current) window.clearTimeout(timer.current);
409
- timer.current = window.setTimeout(() => {
410
- fnRef.current && fnRef.current(...args);
411
- }, delay);
412
- }, [delay]);
413
- }
414
- const icon = /*#__PURE__*/React__namespace.createElement(CheckBoxOutlineBlankIcon__default.default, {
415
- fontSize: "small"
416
- });
417
- const checkedIcon = /*#__PURE__*/React__namespace.createElement(CheckBoxIcon__default.default, {
418
- fontSize: "small"
419
- });
420
- function CheckboxMultiAutocomplete({
421
- items,
422
- selectedKeys,
423
- defaultSelectedKeys,
424
- onChange,
425
- inputValue,
426
- defaultInputValue,
427
- onInputChange,
428
- debounceMs = 300,
429
- label,
430
- placeholder,
431
- loading,
432
- disabled,
433
- size = "small",
434
- error,
435
- helperText,
436
- limitTags = 3,
437
- disableClearable,
438
- id,
439
- textFieldProps
440
- }) {
441
- // Normalize props to avoid "is not iterable" and similar runtime errors
442
- const safeItems = React.useMemo(() => Array.isArray(items) ? items : [], [items]);
443
-
444
- // Controlled/uncontrolled selection
445
- const isSelectionControlled = Array.isArray(selectedKeys);
446
- const [innerKeys, setInnerKeys] = React.useState(Array.isArray(defaultSelectedKeys) ? defaultSelectedKeys : []);
447
- const currentKeys = isSelectionControlled ? selectedKeys : innerKeys;
448
-
449
- // Map lookups for efficiency
450
- const byKey = React.useMemo(() => {
451
- const m = new Map();
452
- for (const it of safeItems) m.set(it && it.key, it);
453
- return m;
454
- }, [safeItems]);
455
- const selectedItems = React.useMemo(() => (Array.isArray(currentKeys) ? currentKeys : []).map(k => byKey.get(k)).filter(Boolean), [currentKeys, byKey]);
456
-
457
- // Controlled/uncontrolled input
458
- const isInputControlled = typeof inputValue === "string";
459
- const [innerInput, setInnerInput] = React.useState(typeof defaultInputValue === "string" ? defaultInputValue : "");
460
- const currentInput = isInputControlled ? inputValue : innerInput;
461
- const debouncedInput = useDebouncedCallback(text => {
462
- onInputChange && onInputChange(text);
463
- }, debounceMs);
464
- const valueForAutocomplete = selectedItems; // MUI expects full option objects
465
-
466
- return /*#__PURE__*/React__namespace.createElement(material.FormControl, {
467
- fullWidth: true,
468
- error: error,
469
- disabled: disabled
470
- }, /*#__PURE__*/React__namespace.createElement(material.Autocomplete, {
471
- openOnFocus: true,
472
- forcePopupIcon: true,
473
- id: id,
474
- multiple: true,
475
- disableCloseOnSelect: true,
476
- options: safeItems,
477
- value: valueForAutocomplete,
478
- inputValue: currentInput,
479
- getOptionLabel: o => o && typeof o === "object" ? o.value ?? "" : "",
480
- isOptionEqualToValue: (option, value) => option && value && option.key === value.key,
481
- loading: loading,
482
- limitTags: limitTags,
483
- disableClearable: disableClearable,
484
- onChange: (event, newValue /* array of option objects */) => {
485
- const newKeys = Array.isArray(newValue) ? newValue.map(o => o && o.key) : [];
486
- if (!isSelectionControlled) setInnerKeys(newKeys);
487
- onChange && onChange(newKeys, Array.isArray(newValue) ? newValue : []);
488
- },
489
- onInputChange: (event, newInput, reason) => {
490
- if (!isInputControlled) setInnerInput(newInput ?? "");
491
- if (reason === "input") {
492
- debouncedInput(newInput ?? "");
493
- } else if (reason === "clear") {
494
- debouncedInput("");
495
- }
496
- },
497
- renderOption: (props, option, {
498
- selected
499
- }) => /*#__PURE__*/React__namespace.createElement("li", _extends({}, props, {
500
- key: option && option.key || Math.random().toString(36)
501
- }), /*#__PURE__*/React__namespace.createElement(material.Checkbox, {
502
- icon: icon,
503
- checkedIcon: checkedIcon,
504
- style: {
505
- marginRight: 8
506
- },
507
- checked: !!selected
508
- }), option && option.value),
509
- renderTags: (tagValue, getTagProps) => (Array.isArray(tagValue) ? tagValue : []).map((option, index) => /*#__PURE__*/React__namespace.createElement(material.Chip, _extends({}, getTagProps({
510
- index
511
- }), {
512
- key: option && option.key || index,
513
- label: option && option.value || ""
514
- }))),
515
- renderInput: params => /*#__PURE__*/React__namespace.createElement(material.TextField, _extends({}, params, {
516
- label: label,
517
- placeholder: placeholder,
518
- InputProps: {
519
- ...params.InputProps,
520
- endAdornment: /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, loading ? /*#__PURE__*/React__namespace.createElement(material.CircularProgress, {
521
- size: 18
522
- }) : null, params.InputProps.endAdornment)
523
- },
524
- size: size
525
- }, textFieldProps))
526
- }), helperText ? /*#__PURE__*/React__namespace.createElement(material.FormHelperText, null, helperText) : null);
527
- }
528
-
529
- async function fetchItems(query, filter) {
530
- if (!filter?.source) return [];
531
- const data = await Api.runReport({
532
- id: filter.source
533
- });
534
- return data.map(d => {
535
- const m = {
536
- key: d[filter.field],
537
- value: d[filter.field]
538
- };
539
- return m;
540
- });
541
- }
542
- const ChartFilterMultiSelect = ({
543
- filter,
544
- channel,
545
- query
546
- }) => {
547
- const [items, setItems] = React.useState([]);
548
- const [selected, setSelected] = React.useState([]);
549
- const [loading, setLoading] = React.useState(false);
550
- const [inputQuery, setInputQuery] = React.useState("");
551
- React.useEffect(() => {
552
- setSelected(query?.filter?.[filter?.filter] || []);
553
- }, [query]);
554
- React.useEffect(() => {
555
- (async () => {
556
- setLoading(true);
557
- const data = await fetchItems(query, filter);
558
- setItems(data);
559
- setLoading(false);
560
- })();
561
- }, [inputQuery]);
562
- return /*#__PURE__*/React__namespace.default.createElement("div", {
563
- style: {
564
- display: "flex"
565
- }
566
- }, /*#__PURE__*/React__namespace.default.createElement("div", {
567
- style: {
568
- maxWidth: 640,
569
- minWidth: 200
570
- }
571
- }, /*#__PURE__*/React__namespace.default.createElement(CheckboxMultiAutocomplete, {
572
- id: filter?.title,
573
- label: filter?.title,
574
- placeholder: "Type to search\u2026",
575
- items: items,
576
- selectedKeys: selected,
577
- onChange: (keys, selectedItems) => {
578
- setSelected(keys);
579
- channel.emit('filterChanged', filter, {
580
- [filter.filter]: keys
581
- });
582
- },
583
- onInputChange: text => {
584
- setInputQuery(text);
585
- },
586
- loading: loading,
587
- debounceMs: 300,
588
- helperText: selected.length ? `${selected.length} selected` : ""
589
- })));
590
- };
591
-
592
- function SingleSelect({
593
- items,
594
- value,
595
- label,
596
- onChange,
597
- sx = {
598
- width: '100%'
599
- }
600
- }) {
601
- // Check if the current value exists in items, otherwise use empty string
602
- const validValue = items.some(itm => itm.key === value) ? value : '';
603
- return /*#__PURE__*/React__namespace.default.createElement(Box__default.default, {
604
- sx: sx
605
- }, /*#__PURE__*/React__namespace.default.createElement(FormControl__default.default, {
606
- fullWidth: true
607
- }, /*#__PURE__*/React__namespace.default.createElement(InputLabel__default.default, {
608
- id: "demo-simple-select-label"
609
- }, label), /*#__PURE__*/React__namespace.default.createElement(Select__default.default, {
610
- labelId: "demo-simple-select-label",
611
- id: "demo-simple-select",
612
- value: validValue,
613
- label: label,
614
- onChange: onChange,
615
- MenuProps: {
616
- style: {
617
- maxHeight: '300px'
618
- }
619
- }
620
- // sx={{height:'40px'}}
621
- ,
622
- size: "small"
623
- }, items.map(itm => {
624
- const {
625
- key,
626
- value
627
- } = itm;
628
- return /*#__PURE__*/React__namespace.default.createElement(MenuItem__default.default, {
629
- key: key,
630
- value: key
631
- }, value);
632
- }))));
633
- }
634
-
635
- const ChartFilterDateRange = ({
636
- filter,
637
- channel,
638
- query
639
- }) => {
640
- const [items, setItems] = React.useState([]);
641
- const [selected, setSelected] = React.useState("");
642
- React.useEffect(() => {
643
- setSelected(query?.date_range?.name);
644
- }, [query]);
645
- React.useEffect(() => {
646
- (async () => {
647
- const data = await Api.getDateRanges();
648
- const items = data.filter(f => filter.allowedOptions.length > 0 ? filter.allowedOptions.some(o => o === f.name) : true).map(d => {
649
- return {
650
- key: d.name,
651
- value: d.title
652
- };
653
- });
654
- setItems(items);
655
- })();
656
- }, []);
657
- return /*#__PURE__*/React__namespace.default.createElement("div", {
658
- style: {
659
- minWidth: "200px",
660
- maxWidth: "640px"
661
- }
662
- }, /*#__PURE__*/React__namespace.default.createElement(SingleSelect, {
663
- items: items,
664
- value: selected,
665
- label: filter?.title,
666
- onChange: (e, newValue) => {
667
- const val = e.target.value;
668
- setSelected(val);
669
- channel.emit('filterChanged', filter, {
670
- date_range: {
671
- name: val
672
- }
673
- });
674
- }
675
- }));
676
- };
677
-
678
- const ChartFilters = ({
679
- filters = [],
680
- channel,
681
- query
682
- }) => {
683
- return /*#__PURE__*/React__namespace.default.createElement("div", {
684
- style: {
685
- display: "flex",
686
- flexWrap: "wrap",
687
- gap: "8px"
688
- }
689
- }, filters.map(f => {
690
- if (f.type === 'multi-select') {
691
- return /*#__PURE__*/React__namespace.default.createElement(ChartFilterMultiSelect, {
692
- key: f.title,
693
- channel: channel,
694
- filter: f,
695
- query: query
696
- });
697
- } else if (f.type === 'date-range') {
698
- return /*#__PURE__*/React__namespace.default.createElement(ChartFilterDateRange, {
699
- key: f.title,
700
- channel: channel,
701
- filter: f,
702
- query: query
703
- });
704
- } else {
705
- return null;
706
- }
707
- }));
708
- };
709
-
710
- var useChannel = channel => {
711
- const channelRef = React__namespace.default.useRef(channel || new EventEmitter__default.default());
712
- // React.useEffect(() => {
713
- // }, []);
714
- return channelRef.current;
715
- };
716
-
717
- var useFilterManager = channel => {
718
- const filterRef = React__namespace.default.useState({});
719
- const onFilterChanged = React__namespace.default.useCallback((entity, value) => {
720
- let merged = {
721
- ...filterRef.current,
722
- ...value
723
- };
724
- filterRef.current = merged;
725
- for (var field in merged) {
726
- if (field !== 'date_range' && !merged[field].length) delete merged[field];
727
- }
728
- channel?.emit('mergedFilterChanged', {
729
- value: merged
730
- });
731
- }, [channel]);
732
- React__namespace.default.useEffect(() => {
733
- channel?.on('filterChanged', onFilterChanged);
734
- return () => {
735
- channel?.off('filterChanged', onFilterChanged);
736
- };
737
- }, [channel]);
738
- };
739
-
740
- var Chart = ({
741
- id,
742
- cache,
743
- dashboard,
744
- channel,
745
- ...props
746
- }) => {
747
- const [chart, setChart] = React__namespace.default.useState();
748
- const [report, setReport] = React__namespace.default.useState();
749
- const [schema, setSchema] = React__namespace.default.useState();
750
- const [source, setSource] = React__namespace.default.useState();
751
- const [chartType, setChartType] = React__namespace.default.useState(Internal());
752
- const [activeView, setActiveView] = React__namespace.default.useState();
753
- const [activeViewIndex, setActiveViewIndex] = React__namespace.default.useState(0);
754
- const [loading, setLoading] = React__namespace.default.useState(false);
755
- const [filter, setFilter] = React__namespace.default.useState();
756
- const [dirty, setDirty] = React__namespace.default.useState(false);
757
- const filterRef = React__namespace.default.useRef({
758
- value: props?.filter
759
- });
760
- const [currentQuery, setCurrentQuery] = React__namespace.default.useState({});
761
- const {
762
- ref,
763
- inView,
764
- entry
765
- } = reactIntersectionObserver.useInView({
766
- /* Optional options */
767
- threshold: 0.10,
768
- delay: 1000
769
- });
770
- const channelUsed = useChannel(channel);
771
- useFilterManager(channelUsed);
772
-
773
- // console.log(id, inView);
774
-
775
- const init = async () => {
776
- // CHART
777
- const chartId = id;
778
- const chartTemp = cache?.[chartId] || (await Api.getChart({
779
- id: chartId
780
- }));
781
-
782
- // console.log({chartTemp});
783
-
784
- // REPORT
785
- const reportId = chartTemp.doc.source?.id;
786
- const reportTemp = reportId ? cache?.[reportId] || (await Api.getReport({
787
- id: reportId
788
- })) : undefined;
789
-
790
- // SCHEMA
791
- const schemaTemp = cache?.[`schema_${reportId}`] || props?.schema || (await Api.getReportSchema({
792
- id: reportId
793
- }));
794
-
795
- // console.log({schemaTemp});
796
-
797
- // ACTIVE VIEW
798
- const activeViewTemp = chartTemp.doc?.view?.[0];
799
- setActiveViewIndex(0);
800
- setReport(reportTemp);
801
- setChart(chartTemp);
802
- setActiveView(activeViewTemp);
803
- setSchema(schemaTemp);
804
- //setSchema({});
805
-
806
- setDirty(true);
807
-
808
- // // SOURCE
809
- // if (inView) {
810
- // const sourceId = chartTemp.doc.source.id;
811
- // const sourceTemp = await Report.run(undefined, { id: sourceId });
812
- // setSource(chartTemp.doc.source.reverse ? sourceTemp.reverse() : sourceTemp);
813
- // }
814
- };
815
- const reload = async () => {
816
- setChartType(Internal(chart.doc.chart?.component, {
817
- view: activeView,
818
- source: source,
819
- chart: chart,
820
- report: report,
821
- schema: schema,
822
- dashboard: dashboard,
823
- query: currentQuery
824
- }));
825
- };
826
- const onViewChanged = React__namespace.default.useCallback(event => {
827
- const viewIndex = event.target.value;
828
- setActiveViewIndex(viewIndex);
829
- setActiveView(chart.doc.view[viewIndex]);
830
- }, [chart]);
831
- React__namespace.default.useEffect(() => {
832
- let cron$1;
833
- if (chart?.doc?.refresh?.enabled === true) {
834
- cron$1 = new cron.CronJob(chart.doc.refresh.cron, () => {
835
- if (inView) {
836
- loadSource(filterRef.current);
837
- }
838
- });
839
- cron$1.start();
840
- }
841
- return () => {
842
- cron$1 && cron$1.stop();
843
- };
844
- }, [chart, inView]);
845
- React__namespace.default.useEffect(() => {
846
- if (!id) return;
847
- init().catch(error => {
848
- console.error(error.message);
849
- });
850
- }, [id]);
851
- React__namespace.default.useEffect(() => {
852
- if (!chart || !source || chart?.doc?.source?.id && !report || !schema) return;
853
- reload().catch(error => {
854
- console.error(error.message);
855
- });
856
- }, [chart, source, activeView, report, schema]);
857
- const loadSource = userFilter => {
858
- const queryFilter = report?.doc.query.filter || {};
859
- const finalFilter = {
860
- ...queryFilter,
861
- ...props?.filter,
862
- ...userFilter?.value
863
- };
864
-
865
- // const finalFilter = {...props?.filter,...userFilter?.value };
866
-
867
- // console.log({queryFilter,userFilter,propsFilter:props.filter,finalFilter});
868
-
869
- const date_range = finalFilter.date_range || report?.doc.query.date_range;
870
- delete finalFilter.date_range;
871
- setDirty(false);
872
- setFilter();
873
- setLoading(true);
874
- if (chart?.doc?.source?.type === 'context') {
875
- setSource({
876
- filter: finalFilter
877
- });
878
- setLoading(false);
879
- return;
880
- }
881
- const query = {
882
- filter: finalFilter,
883
- date_range,
884
- parameters: props?.params
885
- };
886
-
887
- //console.log('loadSource',{reportId: report.id, query});
888
-
889
- setCurrentQuery(query);
890
- Api.runReport({
891
- id: report.id,
892
- query
893
- }).then(response => {
894
- setSource(chart.doc.source.reverse ? response.reverse() : response);
895
- }).catch(error => {
896
- setSource([]);
897
- }).finally(() => {
898
- setLoading(false);
899
- });
900
- };
901
- const onMergedFilterChanged = merged => {
902
- // console.log({onMergedFilterChanged:merged});
903
- filterRef.current = merged;
904
- if (inView) loadSource(merged);else {
905
- setFilter(merged);
906
- setDirty(true);
907
- }
908
- };
909
- React__namespace.default.useEffect(() => {
910
- channelUsed?.on('mergedFilterChanged', onMergedFilterChanged);
911
- return () => {
912
- channelUsed?.off('mergedFilterChanged', onMergedFilterChanged);
913
- };
914
- }, [channelUsed, report, chart, inView]);
915
- React__namespace.default.useEffect(() => {
916
- if (inView && dirty) loadSource(filter);
917
- }, [inView, dirty]);
918
- return /*#__PURE__*/React__namespace.default.createElement(Paper__default.default, {
919
- ref: ref,
920
- style: {
921
- margin: "4px",
922
- padding: "16px",
923
- flexGrow: dashboard ? undefined : 1,
924
- display: "flex",
925
- flexDirection: "column",
926
- gap: '16px'
927
- },
928
- elevation: 0
929
- }, /*#__PURE__*/React__namespace.default.createElement(LinearIndeterminate, {
930
- style: {
931
- visibility: loading ? 'visible' : 'hidden'
932
- }
933
- }), /*#__PURE__*/React__namespace.default.createElement("div", {
934
- style: {
935
- display: "flex"
936
- }
937
- }, /*#__PURE__*/React__namespace.default.createElement("div", null, chart && /*#__PURE__*/React__namespace.default.createElement("div", {
938
- style: {
939
- display: "flex",
940
- alignItems: "center",
941
- lineHeight: 0
942
- }
943
- }, /*#__PURE__*/React__namespace.default.createElement(Typography__default.default, {
944
- noWrap: true,
945
- variant: "h6"
946
- }, props?.title || chart.doc.name, " "))), /*#__PURE__*/React__namespace.default.createElement("div", {
947
- style: {
948
- flexGrow: 1
949
- }
950
- }), /*#__PURE__*/React__namespace.default.createElement("div", null, chart?.doc?.view?.length > 1 && /*#__PURE__*/React__namespace.default.createElement(FormControl__default$1.default, null, /*#__PURE__*/React__namespace.default.createElement(Select__default$1.default, {
951
- labelId: "date-range-select-label",
952
- id: "date-range-select",
953
- value: activeViewIndex,
954
- onChange: onViewChanged,
955
- style: {
956
- fontSize: "12px"
957
- },
958
- disableUnderline: true
959
- }, chart.doc.view.map((item, index) => /*#__PURE__*/React__namespace.default.createElement(MenuItem__default$1.default, {
960
- key: index,
961
- value: index
962
- }, item.title)))))), /*#__PURE__*/React__namespace.default.createElement("div", {
963
- style: {
964
- minHeight: chart?.doc?.size?.height || (dashboard ? "300px" : "0"),
965
- position: "relative",
966
- paddingTop: chart?.doc?.size?.vpad,
967
- paddingBottom: chart?.doc?.size?.vpad,
968
- flexGrow: dashboard ? undefined : 1
969
- }
970
- }, /*#__PURE__*/React__namespace.default.createElement(ChartFilters, {
971
- filters: chart?.doc?.filters,
972
- channel: channelUsed,
973
- query: currentQuery
974
- }), chartType));
975
- };
976
-
977
- function Dashboard({
978
- id,
979
- auth,
980
- params
981
- }) {
982
- const [dashboard, setDashboard] = React__namespace.default.useState();
983
- //const [schema, setSchema] = React.useState();
984
- const [schema] = React__namespace.default.useState();
985
- const [rows, setRows] = React__namespace.default.useState([]);
986
- const cache = React__namespace.default.useRef({});
987
- const channel = useChannel();
988
- useFilterManager(channel);
989
- const init = async () => {
990
- // get dashboard entity
991
- const dashboardTemp = await Api.getDashboard({
992
- id: id
993
- });
994
- cache.current[dashboardTemp?.id] = dashboardTemp;
995
- setDashboard(dashboardTemp);
996
-
997
- // create rows and columns
998
- await create_rows_and_columns(dashboardTemp);
999
- };
1000
- const create_rows_and_columns = async entity => {
1001
- const srcRows = entity?.doc?.rows || [];
1002
- const targetRows = [];
1003
- for (let i = 0; i < srcRows.length; i++) {
1004
- const row = srcRows[i];
1005
- row.columns?.forEach(column => {
1006
- // filter
1007
- if (column.override?.filter?.length) {
1008
- const filter = {};
1009
- column.override.filter.forEach(item => {
1010
- if (item.hasOwnProperty('value')) filter[item.field] = [item.value];
1011
- // else filter[item.field] = [sourceDataRow[item.field]];
1012
- });
1013
- column.filter = filter;
1014
- }
1015
- });
1016
- targetRows.push(row);
1017
- }
1018
- setRows(targetRows);
1019
- };
1020
- React__namespace.default.useEffect(() => {
1021
- if (!id) return;
1022
- init().catch(error => console.error(error.message));
1023
- }, [id]);
1024
- return /*#__PURE__*/React__namespace.default.createElement(Container__default.default, null, /*#__PURE__*/React__namespace.default.createElement(Grid__default.default, {
1025
- container: true
1026
- }, rows?.map((row, index) =>
1027
- /*#__PURE__*/
1028
- // hasResourceRole(row) &&
1029
- React__namespace.default.createElement(Grid__default.default, {
1030
- key: index,
1031
- container: true,
1032
- item: true
1033
- }, row?.columns?.map((column, index) => /*#__PURE__*/React__namespace.default.createElement(Grid__default.default, _extends({
1034
- key: index,
1035
- item: true
1036
- }, column.layout), /*#__PURE__*/React__namespace.default.createElement(Chart, {
1037
- auth: auth,
1038
- cache: cache.current,
1039
- id: column.id,
1040
- dashboard: dashboard,
1041
- schema: schema,
1042
- channel: null,
1043
- title: column.title,
1044
- filter: column.filter,
1045
- params: params
1046
- })))))));
1047
- }
1048
-
1049
- var index = {
1050
- Chart,
1051
- Dashboard
1052
- };
1053
-
1054
- exports.default = index;