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