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