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