@bygd/nc-report-ui 0.1.28 → 0.1.30
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.
- package/dist/app/esm/index.html +21 -0
- package/dist/app/esm/index.js +93988 -0
- package/dist/default/cjs/index.cjs +6447 -1
- package/dist/default/esm/index.js +255 -44
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import React__default, { useEffect, useMemo, useState, useRef,
|
|
2
|
+
import React__default, { useEffect, useMemo, useState, useRef, useContext, createContext } from 'react';
|
|
3
3
|
import Paper from '@material-ui/core/Paper';
|
|
4
4
|
import { makeStyles } from '@material-ui/core/styles';
|
|
5
5
|
import { CircularProgress } from '@material-ui/core';
|
|
@@ -43,6 +43,9 @@ import CheckIcon from '@mui/icons-material/Check';
|
|
|
43
43
|
import CloseIcon from '@mui/icons-material/Close';
|
|
44
44
|
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
|
45
45
|
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
|
46
|
+
import StorageIcon from '@mui/icons-material/Storage';
|
|
47
|
+
import TrendingUpIcon from '@mui/icons-material/TrendingUp';
|
|
48
|
+
import BoltIcon from '@mui/icons-material/Bolt';
|
|
46
49
|
import FilterAltIcon from '@mui/icons-material/FilterAlt';
|
|
47
50
|
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
|
48
51
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
|
@@ -606,6 +609,7 @@ function Internal(name, props) {
|
|
|
606
609
|
* - inputValue?: string (controlled input)
|
|
607
610
|
* - defaultInputValue?: string (uncontrolled input)
|
|
608
611
|
* - onInputChange?: (text: string) => void (debounced by debounceMs)
|
|
612
|
+
* - onInputChangeImmediate?: (text: string) => void (called immediately without debounce)
|
|
609
613
|
* - debounceMs?: number (default 300)
|
|
610
614
|
* - label?, placeholder?, loading?, disabled?, size? = 'small', error?, helperText?,
|
|
611
615
|
* limitTags? = 3, disableClearable?, id?, textFieldProps?
|
|
@@ -640,6 +644,7 @@ function CheckboxMultiAutocomplete({
|
|
|
640
644
|
inputValue,
|
|
641
645
|
defaultInputValue,
|
|
642
646
|
onInputChange,
|
|
647
|
+
onInputChangeImmediate,
|
|
643
648
|
debounceMs = 300,
|
|
644
649
|
label,
|
|
645
650
|
placeholder,
|
|
@@ -703,6 +708,14 @@ function CheckboxMultiAutocomplete({
|
|
|
703
708
|
},
|
|
704
709
|
onInputChange: (event, newInput, reason) => {
|
|
705
710
|
if (!isInputControlled) setInnerInput(newInput ?? "");
|
|
711
|
+
|
|
712
|
+
// Call immediate handler if provided
|
|
713
|
+
if (reason === "input" || reason === "clear") {
|
|
714
|
+
const text = reason === "clear" ? "" : newInput ?? "";
|
|
715
|
+
onInputChangeImmediate && onInputChangeImmediate(text);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Call debounced handler
|
|
706
719
|
if (reason === "input") {
|
|
707
720
|
debouncedInput(newInput ?? "");
|
|
708
721
|
} else if (reason === "clear") {
|
|
@@ -866,7 +879,8 @@ function SingleSelect({
|
|
|
866
879
|
const {
|
|
867
880
|
key,
|
|
868
881
|
value,
|
|
869
|
-
disabled
|
|
882
|
+
disabled,
|
|
883
|
+
icon
|
|
870
884
|
} = itm;
|
|
871
885
|
return /*#__PURE__*/React__default.createElement(MenuItem, {
|
|
872
886
|
key: key,
|
|
@@ -874,9 +888,18 @@ function SingleSelect({
|
|
|
874
888
|
disabled: disabled,
|
|
875
889
|
sx: {
|
|
876
890
|
fontFamily: "system-ui",
|
|
877
|
-
minHeight: "36px"
|
|
891
|
+
minHeight: "36px",
|
|
892
|
+
display: "flex",
|
|
893
|
+
alignItems: "center",
|
|
894
|
+
gap: "6px"
|
|
878
895
|
}
|
|
879
|
-
},
|
|
896
|
+
}, icon && /*#__PURE__*/React__default.createElement(Box, {
|
|
897
|
+
component: "span",
|
|
898
|
+
sx: {
|
|
899
|
+
display: "inline-flex",
|
|
900
|
+
alignItems: "center"
|
|
901
|
+
}
|
|
902
|
+
}, icon), formatLabel(value));
|
|
880
903
|
}))));
|
|
881
904
|
}
|
|
882
905
|
|
|
@@ -2878,6 +2901,51 @@ const Dimensions = ({
|
|
|
2878
2901
|
})))))));
|
|
2879
2902
|
};
|
|
2880
2903
|
|
|
2904
|
+
const MetricSourceIcon = ({
|
|
2905
|
+
source
|
|
2906
|
+
}) => {
|
|
2907
|
+
const iconSx = {
|
|
2908
|
+
fontSize: '15px'
|
|
2909
|
+
};
|
|
2910
|
+
if (source === 'kpi') {
|
|
2911
|
+
return /*#__PURE__*/React__default.createElement(Tooltip, {
|
|
2912
|
+
title: "KPI Metric",
|
|
2913
|
+
arrow: true,
|
|
2914
|
+
placement: "top"
|
|
2915
|
+
}, /*#__PURE__*/React__default.createElement(TrendingUpIcon, {
|
|
2916
|
+
sx: {
|
|
2917
|
+
...iconSx,
|
|
2918
|
+
color: 'rgb(70, 134, 128)'
|
|
2919
|
+
}
|
|
2920
|
+
}));
|
|
2921
|
+
}
|
|
2922
|
+
if (source === 'dynamic') {
|
|
2923
|
+
return /*#__PURE__*/React__default.createElement(Tooltip, {
|
|
2924
|
+
title: "Dynamic Metric",
|
|
2925
|
+
arrow: true,
|
|
2926
|
+
placement: "top"
|
|
2927
|
+
}, /*#__PURE__*/React__default.createElement(BoltIcon, {
|
|
2928
|
+
sx: {
|
|
2929
|
+
...iconSx,
|
|
2930
|
+
color: '#e65100'
|
|
2931
|
+
}
|
|
2932
|
+
}));
|
|
2933
|
+
}
|
|
2934
|
+
if (source === 'provider') {
|
|
2935
|
+
return /*#__PURE__*/React__default.createElement(Tooltip, {
|
|
2936
|
+
title: "Provider Metric",
|
|
2937
|
+
arrow: true,
|
|
2938
|
+
placement: "top"
|
|
2939
|
+
}, /*#__PURE__*/React__default.createElement(StorageIcon, {
|
|
2940
|
+
sx: {
|
|
2941
|
+
...iconSx,
|
|
2942
|
+
color: '#546e7a'
|
|
2943
|
+
}
|
|
2944
|
+
}));
|
|
2945
|
+
}
|
|
2946
|
+
return null;
|
|
2947
|
+
};
|
|
2948
|
+
|
|
2881
2949
|
// Sortable Chip Component
|
|
2882
2950
|
const SortableChip = ({
|
|
2883
2951
|
id,
|
|
@@ -2892,7 +2960,8 @@ const SortableChip = ({
|
|
|
2892
2960
|
defaultTitle,
|
|
2893
2961
|
customTitle,
|
|
2894
2962
|
onUpdateTitle,
|
|
2895
|
-
onResetTitle
|
|
2963
|
+
onResetTitle,
|
|
2964
|
+
source
|
|
2896
2965
|
}) => {
|
|
2897
2966
|
const [isEditing, setIsEditing] = useState(false);
|
|
2898
2967
|
const [editValue, setEditValue] = useState('');
|
|
@@ -3005,7 +3074,15 @@ const SortableChip = ({
|
|
|
3005
3074
|
cursor: "grab",
|
|
3006
3075
|
color: "rgba(110, 110, 110, 0.62)"
|
|
3007
3076
|
}
|
|
3008
|
-
})), !isEditing ? /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Box$1, {
|
|
3077
|
+
})), !isEditing ? /*#__PURE__*/React__default.createElement(React__default.Fragment, null, source && /*#__PURE__*/React__default.createElement(Box$1, {
|
|
3078
|
+
sx: {
|
|
3079
|
+
display: 'flex',
|
|
3080
|
+
alignItems: 'center',
|
|
3081
|
+
flexShrink: 0
|
|
3082
|
+
}
|
|
3083
|
+
}, /*#__PURE__*/React__default.createElement(MetricSourceIcon, {
|
|
3084
|
+
source: source
|
|
3085
|
+
})), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
3009
3086
|
sx: {
|
|
3010
3087
|
minWidth: 0
|
|
3011
3088
|
}
|
|
@@ -3237,7 +3314,10 @@ const Metrics = ({
|
|
|
3237
3314
|
value: metric.title || metric.name,
|
|
3238
3315
|
metricName: metric.name,
|
|
3239
3316
|
metric: metric,
|
|
3240
|
-
disabled: isAlreadySelected
|
|
3317
|
+
disabled: isAlreadySelected,
|
|
3318
|
+
icon: metric.source ? /*#__PURE__*/React__default.createElement(MetricSourceIcon, {
|
|
3319
|
+
source: metric.source
|
|
3320
|
+
}) : undefined
|
|
3241
3321
|
};
|
|
3242
3322
|
});
|
|
3243
3323
|
return items;
|
|
@@ -3546,7 +3626,8 @@ const Metrics = ({
|
|
|
3546
3626
|
defaultTitle: metric.metricTitle,
|
|
3547
3627
|
customTitle: titleOverrides[metric.fullPath],
|
|
3548
3628
|
onUpdateTitle: onUpdateTitle,
|
|
3549
|
-
onResetTitle: onResetTitle
|
|
3629
|
+
onResetTitle: onResetTitle,
|
|
3630
|
+
source: metric.metric?.source
|
|
3550
3631
|
})))))));
|
|
3551
3632
|
};
|
|
3552
3633
|
|
|
@@ -3573,6 +3654,7 @@ const Filters = ({
|
|
|
3573
3654
|
const [selectedFilterValues, setSelectedFilterValues] = useState([]);
|
|
3574
3655
|
const [loadingFilterValues, setLoadingFilterValues] = useState(false);
|
|
3575
3656
|
const [editingFilterPath, setEditingFilterPath] = useState(null); // Track which filter is being edited
|
|
3657
|
+
const [filterSearchText, setFilterSearchText] = useState(''); // Track search text for server-side filtering
|
|
3576
3658
|
|
|
3577
3659
|
// Date range state for date/timestamp filters
|
|
3578
3660
|
const [dateRangeFrom, setDateRangeFrom] = useState(null);
|
|
@@ -3590,6 +3672,7 @@ const Filters = ({
|
|
|
3590
3672
|
setSelectedFilterValues([]);
|
|
3591
3673
|
setDateRangeFrom(null);
|
|
3592
3674
|
setDateRangeTo(null);
|
|
3675
|
+
setFilterSearchText('');
|
|
3593
3676
|
}, [dimensionSelectionChain]);
|
|
3594
3677
|
|
|
3595
3678
|
// Get the current provider based on selection chain
|
|
@@ -3667,6 +3750,7 @@ const Filters = ({
|
|
|
3667
3750
|
setSelectedFilterValues([]);
|
|
3668
3751
|
setDateRangeFrom(null);
|
|
3669
3752
|
setDateRangeTo(null);
|
|
3753
|
+
setFilterSearchText('');
|
|
3670
3754
|
setEditingFilterPath(null); // Close any editing
|
|
3671
3755
|
};
|
|
3672
3756
|
const handleEditFilter = async (fullPath, filter) => {
|
|
@@ -3744,6 +3828,7 @@ const Filters = ({
|
|
|
3744
3828
|
setSelectedFilterValues([]);
|
|
3745
3829
|
setDateRangeFrom(null);
|
|
3746
3830
|
setDateRangeTo(null);
|
|
3831
|
+
setFilterSearchText('');
|
|
3747
3832
|
};
|
|
3748
3833
|
|
|
3749
3834
|
// Title editing handlers
|
|
@@ -3780,7 +3865,7 @@ const Filters = ({
|
|
|
3780
3865
|
};
|
|
3781
3866
|
|
|
3782
3867
|
// Fetch distinct values for the selected dimension
|
|
3783
|
-
const fetchFilterValues = async fullPath => {
|
|
3868
|
+
const fetchFilterValues = async (fullPath, searchText = '') => {
|
|
3784
3869
|
setLoadingFilterValues(true);
|
|
3785
3870
|
try {
|
|
3786
3871
|
// Get parameters from context if available, otherwise use default
|
|
@@ -3788,17 +3873,31 @@ const Filters = ({
|
|
|
3788
3873
|
base_currency: "EUR"
|
|
3789
3874
|
};
|
|
3790
3875
|
|
|
3876
|
+
// Build query object
|
|
3877
|
+
const queryObj = {
|
|
3878
|
+
dimensions: [fullPath],
|
|
3879
|
+
metrics: [],
|
|
3880
|
+
order_by: [{
|
|
3881
|
+
"name": fullPath
|
|
3882
|
+
}]
|
|
3883
|
+
};
|
|
3884
|
+
|
|
3885
|
+
// Add filter if search text is provided
|
|
3886
|
+
if (searchText && searchText.trim() !== '') {
|
|
3887
|
+
queryObj.filter = {
|
|
3888
|
+
and: [{
|
|
3889
|
+
[fullPath]: {
|
|
3890
|
+
"ilike": `${searchText.trim()}%`
|
|
3891
|
+
}
|
|
3892
|
+
}]
|
|
3893
|
+
};
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3791
3896
|
// Build a temporary report to fetch distinct values
|
|
3792
3897
|
const reportPayload = {
|
|
3793
3898
|
provider: rootProvider,
|
|
3794
3899
|
doc: {
|
|
3795
|
-
query:
|
|
3796
|
-
dimensions: [fullPath],
|
|
3797
|
-
metrics: [],
|
|
3798
|
-
order_by: [{
|
|
3799
|
-
"name": fullPath
|
|
3800
|
-
}]
|
|
3801
|
-
}
|
|
3900
|
+
query: queryObj
|
|
3802
3901
|
},
|
|
3803
3902
|
parameters
|
|
3804
3903
|
};
|
|
@@ -3835,8 +3934,31 @@ const Filters = ({
|
|
|
3835
3934
|
value: String(value)
|
|
3836
3935
|
};
|
|
3837
3936
|
});
|
|
3937
|
+
|
|
3938
|
+
// Merge with currently selected values to ensure they're always available
|
|
3939
|
+
// This is important when editing filters with search text - we don't want to lose
|
|
3940
|
+
// previously selected values that don't match the current search
|
|
3941
|
+
const selectedValueItems = selectedFilterValues.map(key => ({
|
|
3942
|
+
key: String(key),
|
|
3943
|
+
value: String(key)
|
|
3944
|
+
}));
|
|
3945
|
+
|
|
3946
|
+
// Create a map to avoid duplicates
|
|
3947
|
+
const valueMap = new Map();
|
|
3948
|
+
|
|
3949
|
+
// Add selected values first (they should appear at the top or be preserved)
|
|
3950
|
+
selectedValueItems.forEach(item => {
|
|
3951
|
+
valueMap.set(item.key, item);
|
|
3952
|
+
});
|
|
3953
|
+
|
|
3954
|
+
// Add fetched values
|
|
3955
|
+
distinctValues.forEach(item => {
|
|
3956
|
+
valueMap.set(item.key, item);
|
|
3957
|
+
});
|
|
3958
|
+
const mergedValues = Array.from(valueMap.values());
|
|
3838
3959
|
console.log('Transformed distinct values:', distinctValues);
|
|
3839
|
-
|
|
3960
|
+
console.log('Merged with selected values:', mergedValues);
|
|
3961
|
+
setAvailableFilterValues(mergedValues);
|
|
3840
3962
|
} catch (error) {
|
|
3841
3963
|
console.error('Error fetching filter values:', error);
|
|
3842
3964
|
setAvailableFilterValues([]);
|
|
@@ -4026,6 +4148,8 @@ const Filters = ({
|
|
|
4026
4148
|
setSelectedFilterValues([]);
|
|
4027
4149
|
setDateRangeFrom(null);
|
|
4028
4150
|
setDateRangeTo(null);
|
|
4151
|
+
setFilterSearchText(''); // Reset search text when changing dimension
|
|
4152
|
+
|
|
4029
4153
|
if (fullPath) {
|
|
4030
4154
|
// Find the dimension data from existingDimensions
|
|
4031
4155
|
const dimensionData = existingDimensions.find(dim => dim.fullPath === fullPath);
|
|
@@ -4042,6 +4166,56 @@ const Filters = ({
|
|
|
4042
4166
|
setAvailableFilterValues([]);
|
|
4043
4167
|
}
|
|
4044
4168
|
};
|
|
4169
|
+
|
|
4170
|
+
// Handler for search text input in filter values dropdown (debounced - triggers API call)
|
|
4171
|
+
const handleFilterSearchChange = searchText => {
|
|
4172
|
+
// Determine the fullPath based on current mode
|
|
4173
|
+
let fullPath = null;
|
|
4174
|
+
if (isAddingFromDimension && selectedExistingDimension) {
|
|
4175
|
+
// Mode: Adding filter from existing dimension
|
|
4176
|
+
fullPath = selectedExistingDimension;
|
|
4177
|
+
} else if (isAdding && selectedDimension) {
|
|
4178
|
+
// Mode: Adding new filter with provider selection
|
|
4179
|
+
const dimensionItems = getDimensionItems();
|
|
4180
|
+
const selectedItem = dimensionItems.find(item => item.key === selectedDimension);
|
|
4181
|
+
if (selectedItem) {
|
|
4182
|
+
// Build the complete relation objects array
|
|
4183
|
+
const relations = [];
|
|
4184
|
+
let currentProviderKey = rootProvider;
|
|
4185
|
+
dimensionSelectionChain.forEach(selection => {
|
|
4186
|
+
const provider = providersData[currentProviderKey];
|
|
4187
|
+
if (provider && provider.relations) {
|
|
4188
|
+
const relationObj = provider.relations.find(rel => rel.name === selection.relationName);
|
|
4189
|
+
if (relationObj) {
|
|
4190
|
+
relations.push(relationObj);
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
currentProviderKey = selection.targetKey;
|
|
4194
|
+
});
|
|
4195
|
+
|
|
4196
|
+
// Build the alias path
|
|
4197
|
+
const rootProviderData = providersData[rootProvider];
|
|
4198
|
+
const aliasPath = [rootProviderData.default_alias];
|
|
4199
|
+
relations.forEach(rel => {
|
|
4200
|
+
aliasPath.push(rel.alias);
|
|
4201
|
+
});
|
|
4202
|
+
fullPath = `${aliasPath.join('_')}.${selectedItem.dimensionKey}`;
|
|
4203
|
+
}
|
|
4204
|
+
} else if (editingFilterPath) {
|
|
4205
|
+
// Mode: Editing existing filter
|
|
4206
|
+
fullPath = editingFilterPath;
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
// Fetch filter values with search text if we have a fullPath
|
|
4210
|
+
if (fullPath) {
|
|
4211
|
+
fetchFilterValues(fullPath, searchText);
|
|
4212
|
+
}
|
|
4213
|
+
};
|
|
4214
|
+
|
|
4215
|
+
// Immediate handler for input changes (not debounced - updates state immediately)
|
|
4216
|
+
const handleFilterSearchInputChange = searchText => {
|
|
4217
|
+
setFilterSearchText(searchText);
|
|
4218
|
+
};
|
|
4045
4219
|
const formatProviderPath = filter => {
|
|
4046
4220
|
// Build path using root provider + relation names
|
|
4047
4221
|
const pathParts = [rootProvider];
|
|
@@ -4201,10 +4375,14 @@ const Filters = ({
|
|
|
4201
4375
|
items: availableFilterValues,
|
|
4202
4376
|
selectedKeys: selectedFilterValues,
|
|
4203
4377
|
onChange: keys => setSelectedFilterValues(keys),
|
|
4378
|
+
inputValue: filterSearchText,
|
|
4379
|
+
onInputChange: handleFilterSearchChange,
|
|
4380
|
+
onInputChangeImmediate: handleFilterSearchInputChange,
|
|
4204
4381
|
label: "Choose Values",
|
|
4205
|
-
placeholder: "
|
|
4382
|
+
placeholder: "Type to search...",
|
|
4206
4383
|
loading: loadingFilterValues,
|
|
4207
|
-
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value'
|
|
4384
|
+
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
4385
|
+
debounceMs: 1000
|
|
4208
4386
|
})));
|
|
4209
4387
|
})(), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
4210
4388
|
sx: {
|
|
@@ -4343,10 +4521,14 @@ const Filters = ({
|
|
|
4343
4521
|
items: availableFilterValues,
|
|
4344
4522
|
selectedKeys: selectedFilterValues,
|
|
4345
4523
|
onChange: keys => setSelectedFilterValues(keys),
|
|
4524
|
+
inputValue: filterSearchText,
|
|
4525
|
+
onInputChange: handleFilterSearchChange,
|
|
4526
|
+
onInputChangeImmediate: handleFilterSearchInputChange,
|
|
4346
4527
|
label: "Choose Values",
|
|
4347
|
-
placeholder: "
|
|
4528
|
+
placeholder: "Type to search...",
|
|
4348
4529
|
loading: loadingFilterValues,
|
|
4349
|
-
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value'
|
|
4530
|
+
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
4531
|
+
debounceMs: 1000
|
|
4350
4532
|
})));
|
|
4351
4533
|
})(), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
4352
4534
|
sx: {
|
|
@@ -4626,10 +4808,14 @@ const Filters = ({
|
|
|
4626
4808
|
items: availableFilterValues,
|
|
4627
4809
|
selectedKeys: selectedFilterValues,
|
|
4628
4810
|
onChange: keys => setSelectedFilterValues(keys),
|
|
4811
|
+
inputValue: filterSearchText,
|
|
4812
|
+
onInputChange: handleFilterSearchChange,
|
|
4813
|
+
onInputChangeImmediate: handleFilterSearchInputChange,
|
|
4629
4814
|
label: "Choose Values",
|
|
4630
|
-
placeholder: "
|
|
4815
|
+
placeholder: "Type to search...",
|
|
4631
4816
|
loading: loadingFilterValues,
|
|
4632
|
-
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value'
|
|
4817
|
+
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
4818
|
+
debounceMs: 1000
|
|
4633
4819
|
})), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
4634
4820
|
sx: {
|
|
4635
4821
|
display: 'flex',
|
|
@@ -4723,31 +4909,39 @@ const ReportDataGrid = ({
|
|
|
4723
4909
|
});
|
|
4724
4910
|
|
|
4725
4911
|
// Add metric columns
|
|
4726
|
-
//
|
|
4727
|
-
//
|
|
4728
|
-
// -
|
|
4912
|
+
// The API returns metric keys in two different forms depending on the path depth:
|
|
4913
|
+
//
|
|
4914
|
+
// • 2-part path "mc.record_count" → API key: "record_count"
|
|
4915
|
+
// • 2-part path "mc_fa.total_amount" (nested) → API key: "fa_total_amount"
|
|
4916
|
+
// • 3+-part path "mc.dkpi.activeAgreementsCount"→ API key: "mc.dkpi.activeAgreementsCount" (dots)
|
|
4917
|
+
//
|
|
4918
|
+
// For 3+-part paths the API key contains dots. MUI DataGrid interprets dots
|
|
4919
|
+
// in field names as nested-object accessors, so the rows useMemo normalises
|
|
4920
|
+
// those keys (dots → underscores). The column field must match that normalised key.
|
|
4729
4921
|
metrics.forEach(metric => {
|
|
4730
4922
|
const metricDef = metric.metric;
|
|
4731
4923
|
let fieldName;
|
|
4732
4924
|
let headerName;
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
//
|
|
4737
|
-
// Example:
|
|
4925
|
+
const dotCount = (metric.fullPath.match(/\./g) || []).length;
|
|
4926
|
+
if (dotCount >= 2) {
|
|
4927
|
+
// 3+-part namespaced path: API returns the full dotted path as the key.
|
|
4928
|
+
// Normalise dots to underscores to match the row key normalisation below.
|
|
4929
|
+
// Example: "mc.dkpi.activeAgreementsCount" -> "mc_dkpi_activeAgreementsCount"
|
|
4930
|
+
fieldName = metric.fullPath.replace(/\./g, '_');
|
|
4931
|
+
} else if (metric.relations && metric.relations.length > 0) {
|
|
4932
|
+
// 2-part path from a nested provider: API drops the base provider alias.
|
|
4933
|
+
// Example: "mc_fa.total_amount" -> "fa_total_amount"
|
|
4738
4934
|
const parts = metric.fullPath.split('.');
|
|
4739
|
-
const pathWithoutField = parts[0]; // e.g., "
|
|
4935
|
+
const pathWithoutField = parts[0]; // e.g., "mc_fa"
|
|
4740
4936
|
const field = parts[1]; // e.g., "total_amount"
|
|
4741
4937
|
|
|
4742
|
-
// Remove the base provider alias (first part before first underscore)
|
|
4743
4938
|
const pathParts = pathWithoutField.split('_');
|
|
4744
|
-
pathParts.shift(); //
|
|
4745
|
-
const pathWithoutBase = pathParts.join('_');
|
|
4746
|
-
|
|
4939
|
+
pathParts.shift(); // remove base provider alias
|
|
4940
|
+
const pathWithoutBase = pathParts.join('_');
|
|
4747
4941
|
fieldName = pathWithoutBase ? `${pathWithoutBase}_${field}` : field;
|
|
4748
4942
|
} else {
|
|
4749
|
-
//
|
|
4750
|
-
// Example:
|
|
4943
|
+
// 2-part path from the base provider: API returns just the metric name.
|
|
4944
|
+
// Example: "mc.record_count" -> "record_count"
|
|
4751
4945
|
fieldName = metric.metricName;
|
|
4752
4946
|
}
|
|
4753
4947
|
|
|
@@ -4759,6 +4953,15 @@ const ReportDataGrid = ({
|
|
|
4759
4953
|
flex: 1,
|
|
4760
4954
|
minWidth: 150,
|
|
4761
4955
|
type: metricDef?.type === 'integer' || metricDef?.type === 'currency' ? 'number' : 'string',
|
|
4956
|
+
renderHeader: () => /*#__PURE__*/React__default.createElement(Box$1, {
|
|
4957
|
+
sx: {
|
|
4958
|
+
display: 'flex',
|
|
4959
|
+
alignItems: 'center',
|
|
4960
|
+
gap: 0.5
|
|
4961
|
+
}
|
|
4962
|
+
}, /*#__PURE__*/React__default.createElement(MetricSourceIcon, {
|
|
4963
|
+
source: metricDef?.source
|
|
4964
|
+
}), /*#__PURE__*/React__default.createElement("span", null, headerName)),
|
|
4762
4965
|
valueFormatter: value => {
|
|
4763
4966
|
if (value == null) return '';
|
|
4764
4967
|
|
|
@@ -4777,14 +4980,22 @@ const ReportDataGrid = ({
|
|
|
4777
4980
|
return cols;
|
|
4778
4981
|
}, [dimensions, metrics, titleOverrides]);
|
|
4779
4982
|
|
|
4780
|
-
// Transform report data to rows with unique IDs
|
|
4983
|
+
// Transform report data to rows with unique IDs.
|
|
4984
|
+
// Keys are normalised by replacing dots with underscores so that MUI DataGrid
|
|
4985
|
+
// does not misinterpret dotted keys (e.g. "mc.dkpi.exposure") as nested
|
|
4986
|
+
// object paths. Dimension keys returned by the API are already underscore-
|
|
4987
|
+
// based, so normalisation is a no-op for them.
|
|
4781
4988
|
const rows = React__default.useMemo(() => {
|
|
4782
4989
|
if (!reportData || !Array.isArray(reportData)) return [];
|
|
4783
|
-
return reportData.map((row, index) =>
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4990
|
+
return reportData.map((row, index) => {
|
|
4991
|
+
const normalizedRow = {
|
|
4992
|
+
id: index
|
|
4993
|
+
};
|
|
4994
|
+
Object.entries(row).forEach(([key, value]) => {
|
|
4995
|
+
normalizedRow[key.replace(/\./g, '_')] = value;
|
|
4996
|
+
});
|
|
4997
|
+
return normalizedRow;
|
|
4998
|
+
});
|
|
4788
4999
|
}, [reportData]);
|
|
4789
5000
|
|
|
4790
5001
|
// Handle pagination change
|