@bygd/nc-report-ui 0.1.6 → 0.1.8

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