@bygd/nc-report-ui 0.1.43 → 0.1.45
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.js +127 -71
- package/dist/default/cjs/index.cjs +119 -68
- package/dist/default/esm/index.js +119 -68
- package/package.json +1 -1
package/dist/app/esm/index.js
CHANGED
|
@@ -77759,6 +77759,16 @@ const SortableComputedChip = ({
|
|
|
77759
77759
|
handleCancel();
|
|
77760
77760
|
}
|
|
77761
77761
|
};
|
|
77762
|
+
|
|
77763
|
+
// The value field is multiline, so Enter inserts a newline instead of
|
|
77764
|
+
// saving; only Escape (cancel) and Cmd/Ctrl+Enter (save) are handled here.
|
|
77765
|
+
const handleValueKeyDown = e => {
|
|
77766
|
+
if (e.key === "Escape") {
|
|
77767
|
+
handleCancel();
|
|
77768
|
+
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
77769
|
+
handleSave();
|
|
77770
|
+
}
|
|
77771
|
+
};
|
|
77762
77772
|
return /*#__PURE__*/gn.createElement("div", _extends({
|
|
77763
77773
|
ref: setNodeRef,
|
|
77764
77774
|
style: style
|
|
@@ -77862,10 +77872,13 @@ const SortableComputedChip = ({
|
|
|
77862
77872
|
}), /*#__PURE__*/gn.createElement(TextField, {
|
|
77863
77873
|
value: editValue,
|
|
77864
77874
|
onChange: e => setEditValue(e.target.value),
|
|
77865
|
-
onKeyDown:
|
|
77875
|
+
onKeyDown: handleValueKeyDown,
|
|
77866
77876
|
size: "small",
|
|
77867
77877
|
label: "Value",
|
|
77868
|
-
helperText: " ",
|
|
77878
|
+
helperText: "Cmd/Ctrl+Enter to save",
|
|
77879
|
+
multiline: true,
|
|
77880
|
+
minRows: 2,
|
|
77881
|
+
maxRows: 10,
|
|
77869
77882
|
sx: {
|
|
77870
77883
|
flex: 1,
|
|
77871
77884
|
minWidth: "200px",
|
|
@@ -78275,6 +78288,9 @@ const Dimensions = ({
|
|
|
78275
78288
|
value: computedValue,
|
|
78276
78289
|
onChange: e => setComputedValue(e.target.value),
|
|
78277
78290
|
size: "small",
|
|
78291
|
+
multiline: true,
|
|
78292
|
+
minRows: 2,
|
|
78293
|
+
maxRows: 10,
|
|
78278
78294
|
sx: {
|
|
78279
78295
|
width: "400px",
|
|
78280
78296
|
"& .MuiInputBase-input": {
|
|
@@ -90517,6 +90533,50 @@ class AdapterDayjs {
|
|
|
90517
90533
|
};
|
|
90518
90534
|
}
|
|
90519
90535
|
|
|
90536
|
+
// Small text input + button for adding a filter value that isn't present in
|
|
90537
|
+
// the "Select Filter Values" dropdown (e.g. because no existing row has that
|
|
90538
|
+
// value). Kept as an uncontrolled local field so it clears itself after add.
|
|
90539
|
+
const CustomFilterValueInput = ({
|
|
90540
|
+
onAdd
|
|
90541
|
+
}) => {
|
|
90542
|
+
const [value, setValue] = d('');
|
|
90543
|
+
const handleAdd = () => {
|
|
90544
|
+
const trimmed = value.trim();
|
|
90545
|
+
if (!trimmed) return;
|
|
90546
|
+
onAdd(trimmed);
|
|
90547
|
+
setValue('');
|
|
90548
|
+
};
|
|
90549
|
+
return /*#__PURE__*/gn.createElement(Box, {
|
|
90550
|
+
sx: {
|
|
90551
|
+
display: 'flex',
|
|
90552
|
+
gap: 1,
|
|
90553
|
+
mt: 1
|
|
90554
|
+
}
|
|
90555
|
+
}, /*#__PURE__*/gn.createElement(TextField, {
|
|
90556
|
+
size: "small",
|
|
90557
|
+
fullWidth: true,
|
|
90558
|
+
placeholder: "Add a value not in the list above",
|
|
90559
|
+
value: value,
|
|
90560
|
+
onChange: e => setValue(e.target.value),
|
|
90561
|
+
onKeyDown: e => {
|
|
90562
|
+
if (e.key === 'Enter') {
|
|
90563
|
+
e.preventDefault();
|
|
90564
|
+
handleAdd();
|
|
90565
|
+
}
|
|
90566
|
+
}
|
|
90567
|
+
}), /*#__PURE__*/gn.createElement(Button, {
|
|
90568
|
+
variant: "outlined",
|
|
90569
|
+
size: "small",
|
|
90570
|
+
onClick: handleAdd,
|
|
90571
|
+
disabled: !value.trim(),
|
|
90572
|
+
sx: {
|
|
90573
|
+
textTransform: 'none',
|
|
90574
|
+
whiteSpace: 'nowrap',
|
|
90575
|
+
borderColor: "rgb(70, 134, 128)",
|
|
90576
|
+
color: "rgb(70, 134, 128)"
|
|
90577
|
+
}
|
|
90578
|
+
}, "Add"));
|
|
90579
|
+
};
|
|
90520
90580
|
const Filters = ({
|
|
90521
90581
|
providersData,
|
|
90522
90582
|
rootProvider,
|
|
@@ -90656,8 +90716,9 @@ const Filters = ({
|
|
|
90656
90716
|
}
|
|
90657
90717
|
} else {
|
|
90658
90718
|
// For regular filters, set selected values and fetch available values
|
|
90659
|
-
|
|
90660
|
-
|
|
90719
|
+
const currentValues = filter.values || [];
|
|
90720
|
+
setSelectedFilterValues(currentValues);
|
|
90721
|
+
await fetchFilterValues(fullPath, '', currentValues);
|
|
90661
90722
|
}
|
|
90662
90723
|
};
|
|
90663
90724
|
const handleSaveEditedFilter = fullPath => {
|
|
@@ -90666,11 +90727,6 @@ const Filters = ({
|
|
|
90666
90727
|
|
|
90667
90728
|
// Check if this is a date filter
|
|
90668
90729
|
if (isDateDimension(existingFilter.dimension)) {
|
|
90669
|
-
// For date filters, validate that at least one date is provided
|
|
90670
|
-
if (!dateRangeFrom && !dateRangeTo) {
|
|
90671
|
-
return; // Don't save if no dates provided
|
|
90672
|
-
}
|
|
90673
|
-
|
|
90674
90730
|
// Update with new date range values (only include provided dates)
|
|
90675
90731
|
const dateValues = {};
|
|
90676
90732
|
if (dateRangeFrom) {
|
|
@@ -90687,11 +90743,6 @@ const Filters = ({
|
|
|
90687
90743
|
// Save the updated filter
|
|
90688
90744
|
onSaveFilter(fullPath, updatedFilter);
|
|
90689
90745
|
} else {
|
|
90690
|
-
// For regular filters, validate selected values
|
|
90691
|
-
if (selectedFilterValues.length === 0) {
|
|
90692
|
-
return; // Don't save if no values selected
|
|
90693
|
-
}
|
|
90694
|
-
|
|
90695
90746
|
// Update with new values
|
|
90696
90747
|
const updatedFilter = {
|
|
90697
90748
|
...existingFilter,
|
|
@@ -90752,7 +90803,11 @@ const Filters = ({
|
|
|
90752
90803
|
};
|
|
90753
90804
|
|
|
90754
90805
|
// Fetch distinct values for the selected dimension
|
|
90755
|
-
|
|
90806
|
+
// preserveValues lets a caller pass the values that must survive the merge
|
|
90807
|
+
// explicitly, instead of relying on the selectedFilterValues closure - a
|
|
90808
|
+
// setSelectedFilterValues() call right before fetchFilterValues() hasn't
|
|
90809
|
+
// committed yet by the time this function reads that state.
|
|
90810
|
+
const fetchFilterValues = async (fullPath, searchText = '', preserveValues = null) => {
|
|
90756
90811
|
setLoadingFilterValues(true);
|
|
90757
90812
|
try {
|
|
90758
90813
|
// Get parameters from context if available, otherwise use default
|
|
@@ -90825,7 +90880,8 @@ const Filters = ({
|
|
|
90825
90880
|
// Merge with currently selected values to ensure they're always available
|
|
90826
90881
|
// This is important when editing filters with search text - we don't want to lose
|
|
90827
90882
|
// previously selected values that don't match the current search
|
|
90828
|
-
const
|
|
90883
|
+
const valuesToPreserve = preserveValues !== null ? preserveValues : selectedFilterValues;
|
|
90884
|
+
const selectedValueItems = valuesToPreserve.map(key => ({
|
|
90829
90885
|
key: String(key),
|
|
90830
90886
|
value: String(key)
|
|
90831
90887
|
}));
|
|
@@ -90860,13 +90916,6 @@ const Filters = ({
|
|
|
90860
90916
|
|
|
90861
90917
|
// Check if this is a date filter
|
|
90862
90918
|
const isDate = isDateDimension(selectedItem.dimension);
|
|
90863
|
-
|
|
90864
|
-
// Validate based on filter type
|
|
90865
|
-
if (isDate) {
|
|
90866
|
-
if (!dateRangeFrom && !dateRangeTo) return; // At least one date must be provided
|
|
90867
|
-
} else {
|
|
90868
|
-
if (selectedFilterValues.length === 0) return; // Regular filters must have values
|
|
90869
|
-
}
|
|
90870
90919
|
if (!selectedDimension) return;
|
|
90871
90920
|
|
|
90872
90921
|
// Build the complete relation objects array
|
|
@@ -90941,13 +90990,6 @@ const Filters = ({
|
|
|
90941
90990
|
// Check if this is a date filter
|
|
90942
90991
|
const isDate = isDateDimension(dimensionData.dimension);
|
|
90943
90992
|
|
|
90944
|
-
// Validate based on filter type
|
|
90945
|
-
if (isDate) {
|
|
90946
|
-
if (!dateRangeFrom && !dateRangeTo) return; // At least one date must be provided
|
|
90947
|
-
} else {
|
|
90948
|
-
if (selectedFilterValues.length === 0) return; // Regular filters must have values
|
|
90949
|
-
}
|
|
90950
|
-
|
|
90951
90993
|
// Prepare filter values based on type
|
|
90952
90994
|
let filterValues;
|
|
90953
90995
|
if (isDate) {
|
|
@@ -91054,6 +91096,21 @@ const Filters = ({
|
|
|
91054
91096
|
}
|
|
91055
91097
|
};
|
|
91056
91098
|
|
|
91099
|
+
// Add a value typed in the "custom value" box directly into the selected
|
|
91100
|
+
// filter values, and inject it into availableFilterValues too, since
|
|
91101
|
+
// CheckboxMultiAutocomplete only renders a chip for keys it can resolve
|
|
91102
|
+
// against its items list.
|
|
91103
|
+
const handleAddCustomFilterValue = rawValue => {
|
|
91104
|
+
const value = rawValue.trim();
|
|
91105
|
+
if (!value) return;
|
|
91106
|
+
if (selectedFilterValues.includes(value)) return;
|
|
91107
|
+
setAvailableFilterValues(prev => prev.some(item => item.key === value) ? prev : [{
|
|
91108
|
+
key: value,
|
|
91109
|
+
value
|
|
91110
|
+
}, ...prev]);
|
|
91111
|
+
setSelectedFilterValues(prev => [...prev, value]);
|
|
91112
|
+
};
|
|
91113
|
+
|
|
91057
91114
|
// Handler for search text input in filter values dropdown (debounced - triggers API call)
|
|
91058
91115
|
const handleFilterSearchChange = searchText => {
|
|
91059
91116
|
// Determine the fullPath based on current mode
|
|
@@ -91273,6 +91330,8 @@ const Filters = ({
|
|
|
91273
91330
|
loading: loadingFilterValues,
|
|
91274
91331
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
91275
91332
|
debounceMs: 1000
|
|
91333
|
+
}), /*#__PURE__*/gn.createElement(CustomFilterValueInput, {
|
|
91334
|
+
onAdd: handleAddCustomFilterValue
|
|
91276
91335
|
})));
|
|
91277
91336
|
})(), /*#__PURE__*/gn.createElement(Box, {
|
|
91278
91337
|
sx: {
|
|
@@ -91302,14 +91361,7 @@ const Filters = ({
|
|
|
91302
91361
|
}, "Cancel"), /*#__PURE__*/gn.createElement(Button, {
|
|
91303
91362
|
variant: "contained",
|
|
91304
91363
|
onClick: handleSave,
|
|
91305
|
-
disabled:
|
|
91306
|
-
if (!selectedDimension) return true;
|
|
91307
|
-
const dimensionItems = getDimensionItems();
|
|
91308
|
-
const selectedItem = dimensionItems.find(item => item.key === selectedDimension);
|
|
91309
|
-
if (!selectedItem) return true;
|
|
91310
|
-
const isDate = isDateDimension(selectedItem.dimension);
|
|
91311
|
-
return isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0;
|
|
91312
|
-
})(),
|
|
91364
|
+
disabled: !selectedDimension,
|
|
91313
91365
|
sx: {
|
|
91314
91366
|
height: "40px",
|
|
91315
91367
|
fontFamily: "system-ui",
|
|
@@ -91424,6 +91476,8 @@ const Filters = ({
|
|
|
91424
91476
|
loading: loadingFilterValues,
|
|
91425
91477
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
91426
91478
|
debounceMs: 1000
|
|
91479
|
+
}), /*#__PURE__*/gn.createElement(CustomFilterValueInput, {
|
|
91480
|
+
onAdd: handleAddCustomFilterValue
|
|
91427
91481
|
})));
|
|
91428
91482
|
})(), /*#__PURE__*/gn.createElement(Box, {
|
|
91429
91483
|
sx: {
|
|
@@ -91453,13 +91507,7 @@ const Filters = ({
|
|
|
91453
91507
|
}, "Cancel"), /*#__PURE__*/gn.createElement(Button, {
|
|
91454
91508
|
variant: "contained",
|
|
91455
91509
|
onClick: handleSaveFromDimension,
|
|
91456
|
-
disabled:
|
|
91457
|
-
if (!selectedExistingDimension) return true;
|
|
91458
|
-
const dimensionData = existingDimensions.find(dim => dim.fullPath === selectedExistingDimension);
|
|
91459
|
-
if (!dimensionData) return true;
|
|
91460
|
-
const isDate = isDateDimension(dimensionData.dimension);
|
|
91461
|
-
return isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0;
|
|
91462
|
-
})(),
|
|
91510
|
+
disabled: !selectedExistingDimension,
|
|
91463
91511
|
sx: {
|
|
91464
91512
|
height: "40px",
|
|
91465
91513
|
fontFamily: "system-ui",
|
|
@@ -91713,6 +91761,8 @@ const Filters = ({
|
|
|
91713
91761
|
loading: loadingFilterValues,
|
|
91714
91762
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
91715
91763
|
debounceMs: 1000
|
|
91764
|
+
}), /*#__PURE__*/gn.createElement(CustomFilterValueInput, {
|
|
91765
|
+
onAdd: handleAddCustomFilterValue
|
|
91716
91766
|
})), /*#__PURE__*/gn.createElement(Box, {
|
|
91717
91767
|
sx: {
|
|
91718
91768
|
display: 'flex',
|
|
@@ -91731,7 +91781,6 @@ const Filters = ({
|
|
|
91731
91781
|
variant: "contained",
|
|
91732
91782
|
size: "small",
|
|
91733
91783
|
onClick: () => handleSaveEditedFilter(fullPath),
|
|
91734
|
-
disabled: isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0,
|
|
91735
91784
|
sx: {
|
|
91736
91785
|
textTransform: 'none',
|
|
91737
91786
|
borderRadius: '6px',
|
|
@@ -93027,20 +93076,12 @@ const ReportBuilder = ({
|
|
|
93027
93076
|
if (report.filters && Object.keys(report.filters).length > 0) {
|
|
93028
93077
|
Object.entries(report.filters).forEach(([fullPath, filterData]) => {
|
|
93029
93078
|
if (filterData.values) {
|
|
93030
|
-
//
|
|
93031
|
-
|
|
93032
|
-
|
|
93033
|
-
|
|
93034
|
-
|
|
93035
|
-
|
|
93036
|
-
});
|
|
93037
|
-
}
|
|
93038
|
-
} else if (typeof filterData.values === "object") {
|
|
93039
|
-
// Date range filter - add the object as-is
|
|
93040
|
-
conditions.push({
|
|
93041
|
-
[fullPath]: filterData.values
|
|
93042
|
-
});
|
|
93043
|
-
}
|
|
93079
|
+
// Include the condition as-is regardless of whether the array/object
|
|
93080
|
+
// has any entries - an explicitly saved (but empty) filter should
|
|
93081
|
+
// still appear in the query.
|
|
93082
|
+
conditions.push({
|
|
93083
|
+
[fullPath]: filterData.values
|
|
93084
|
+
});
|
|
93044
93085
|
}
|
|
93045
93086
|
});
|
|
93046
93087
|
}
|
|
@@ -93104,14 +93145,24 @@ const ReportBuilder = ({
|
|
|
93104
93145
|
queryObj.ordered_metrics = report.metrics.map(metric => metric.id);
|
|
93105
93146
|
}
|
|
93106
93147
|
|
|
93107
|
-
// Persist the
|
|
93108
|
-
//
|
|
93109
|
-
//
|
|
93110
|
-
//
|
|
93111
|
-
|
|
93112
|
-
|
|
93113
|
-
|
|
93114
|
-
|
|
93148
|
+
// Persist the full resolved column order - the explicit Column Order tab
|
|
93149
|
+
// arrangement when defined, otherwise dimensions/computed (in their own
|
|
93150
|
+
// order) followed by metrics (in their own order), same as what the
|
|
93151
|
+
// results grid renders. Always emitted (not just when a custom order
|
|
93152
|
+
// exists) so the API/results grid get an explicit order even for
|
|
93153
|
+
// untouched reports, rather than having to fall back to guessing it.
|
|
93154
|
+
const orderedDimensionItems = report.dimensionOrder.map(entry => entry.type === "dimension" ? {
|
|
93155
|
+
kind: "dimension",
|
|
93156
|
+
data: dimensionById[entry.key]
|
|
93157
|
+
} : {
|
|
93158
|
+
kind: "computed",
|
|
93159
|
+
data: computedByName[entry.key]
|
|
93160
|
+
}).filter(item => item.data);
|
|
93161
|
+
const resolvedColumnItems = resolveColumnOrder(report.columnOrder, orderedDimensionItems, report.metrics);
|
|
93162
|
+
if (resolvedColumnItems.length > 0) {
|
|
93163
|
+
queryObj.ordered_columns = resolvedColumnItems.map(item => ({
|
|
93164
|
+
type: item.kind,
|
|
93165
|
+
ref: item.key
|
|
93115
93166
|
}));
|
|
93116
93167
|
}
|
|
93117
93168
|
|
|
@@ -93834,6 +93885,7 @@ const Root = () => {
|
|
|
93834
93885
|
const config = useConfig();
|
|
93835
93886
|
const client = config.client;
|
|
93836
93887
|
const merchants = config.merchants;
|
|
93888
|
+
const organizations = config.organizations;
|
|
93837
93889
|
|
|
93838
93890
|
//const test_token = config.test_token;
|
|
93839
93891
|
const token = config.tokens[config.active_user];
|
|
@@ -93851,8 +93903,10 @@ const Root = () => {
|
|
|
93851
93903
|
// client:"7774bcad-05dc-48cf-a52b-9a8a56168818",
|
|
93852
93904
|
// client:"7774bcad-05dc-48cf-a52b-9a8a56168829" // this is some wrong client id for testing
|
|
93853
93905
|
// client: undefined
|
|
93854
|
-
client: client || "7774bcad-05dc-48cf-a52b-9a8a56168818",
|
|
93855
|
-
|
|
93906
|
+
//client: client || "7774bcad-05dc-48cf-a52b-9a8a56168818",
|
|
93907
|
+
client: client || [],
|
|
93908
|
+
merchants: merchants || [],
|
|
93909
|
+
organizations: organizations || []
|
|
93856
93910
|
};
|
|
93857
93911
|
|
|
93858
93912
|
// Api.setBaseUrl(apiDefaults.base_url);
|
|
@@ -93891,8 +93945,9 @@ var tokens = {
|
|
|
93891
93945
|
"serdar.kizilkurt@gmail.com": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJXeFB6alpqaXJUcm5ienYwZW5iWHBhUjhoWklNQWRxb29FdEhaY0Nmbl9zIn0.eyJleHAiOjE3NjU5ODIzMjcsImlhdCI6MTc2NTk4MTQyNywianRpIjoiYjA4ZWMyMmYtN2IxMC00YzVjLWEwODgtNzA5MDhmNzJlNGIxIiwiaXNzIjoiaHR0cHM6Ly9pZHAubmV0Y2FwaXRhbC5wcm8vcmVhbG1zL25ldGNhcGl0YWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNjAzNGI5NzktNzkyNi00NjJlLTljYjQtYjJmZWMzYjNjYzhjIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBwIiwic2lkIjoiMjczYzA0OTItYjQwZS00ZmIwLWEwYWItMTRmMDU5NzUzYTM2IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjMxMDIiLCJodHRwczovL2FwcC5uZXRjYXBpdGFsLnBybyIsImh0dHBzOi8vZGV2Lm5ldGNhcGl0YWwucHJvIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLW5ldGNhcGl0YWwiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiU2VyZGFyIFJlZFdvbGYiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJkYXIua2l6aWxrdXJ0QGdtYWlsLmNvbSIsImdpdmVuX25hbWUiOiJTZXJkYXIiLCJmYW1pbHlfbmFtZSI6IlJlZFdvbGYiLCJlbWFpbCI6InNlcmRhci5raXppbGt1cnRAZ21haWwuY29tIn0.mIrffBy6n4HmCnFJAX7Njl8fR-m-QQtsn2iGbyefXXK17lCP8b_lUlAvHHFaan7Te8OT8P8rHu9FgfSyPQkBMqZ0AVVf6VrhUmEO12wZx71Di59QNHjcZKfljNykPKLXXWEIWlulvScrLUsxuR5emufTJaxD9zU4fJzRh00vDCIuzIyl9o51asLmhNAtNnW8QUtT-rWOM6Fgh3pskP4yrGUe5dI17DpAYafcWgUNx86Qi9E09nzUE5IToKgXfQvVpPs7_ZwlcfOE95VNcO_pwRQQ85sJmFiUCg5_TWzNFJKoTui5barb74oBCP7OCJTb5yj5KeriL2ozg0e2IyVrHA"
|
|
93892
93946
|
};
|
|
93893
93947
|
var active_user = "s.kizilkurt@azerion.com";
|
|
93894
|
-
var client =
|
|
93948
|
+
var client = [];
|
|
93895
93949
|
var merchants = [];
|
|
93950
|
+
var organizations = [];
|
|
93896
93951
|
var base_url_local = "http://localhost:8081";
|
|
93897
93952
|
var base_url_prod = "https://dev-report-api.netcapital.pro";
|
|
93898
93953
|
var base_url = "http://localhost:8081";
|
|
@@ -93904,6 +93959,7 @@ var config = {
|
|
|
93904
93959
|
active_user: active_user,
|
|
93905
93960
|
client: client,
|
|
93906
93961
|
merchants: merchants,
|
|
93962
|
+
organizations: organizations,
|
|
93907
93963
|
base_url_local: base_url_local,
|
|
93908
93964
|
base_url_prod: base_url_prod,
|
|
93909
93965
|
base_url: base_url,
|
|
@@ -3172,6 +3172,16 @@ const SortableComputedChip = ({
|
|
|
3172
3172
|
handleCancel();
|
|
3173
3173
|
}
|
|
3174
3174
|
};
|
|
3175
|
+
|
|
3176
|
+
// The value field is multiline, so Enter inserts a newline instead of
|
|
3177
|
+
// saving; only Escape (cancel) and Cmd/Ctrl+Enter (save) are handled here.
|
|
3178
|
+
const handleValueKeyDown = e => {
|
|
3179
|
+
if (e.key === "Escape") {
|
|
3180
|
+
handleCancel();
|
|
3181
|
+
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
3182
|
+
handleSave();
|
|
3183
|
+
}
|
|
3184
|
+
};
|
|
3175
3185
|
return /*#__PURE__*/React__namespace.default.createElement("div", _extends({
|
|
3176
3186
|
ref: setNodeRef,
|
|
3177
3187
|
style: style
|
|
@@ -3275,10 +3285,13 @@ const SortableComputedChip = ({
|
|
|
3275
3285
|
}), /*#__PURE__*/React__namespace.default.createElement(material.TextField, {
|
|
3276
3286
|
value: editValue,
|
|
3277
3287
|
onChange: e => setEditValue(e.target.value),
|
|
3278
|
-
onKeyDown:
|
|
3288
|
+
onKeyDown: handleValueKeyDown,
|
|
3279
3289
|
size: "small",
|
|
3280
3290
|
label: "Value",
|
|
3281
|
-
helperText: " ",
|
|
3291
|
+
helperText: "Cmd/Ctrl+Enter to save",
|
|
3292
|
+
multiline: true,
|
|
3293
|
+
minRows: 2,
|
|
3294
|
+
maxRows: 10,
|
|
3282
3295
|
sx: {
|
|
3283
3296
|
flex: 1,
|
|
3284
3297
|
minWidth: "200px",
|
|
@@ -3688,6 +3701,9 @@ const Dimensions = ({
|
|
|
3688
3701
|
value: computedValue,
|
|
3689
3702
|
onChange: e => setComputedValue(e.target.value),
|
|
3690
3703
|
size: "small",
|
|
3704
|
+
multiline: true,
|
|
3705
|
+
minRows: 2,
|
|
3706
|
+
maxRows: 10,
|
|
3691
3707
|
sx: {
|
|
3692
3708
|
width: "400px",
|
|
3693
3709
|
"& .MuiInputBase-input": {
|
|
@@ -4582,6 +4598,50 @@ const Metrics = ({
|
|
|
4582
4598
|
})))));
|
|
4583
4599
|
};
|
|
4584
4600
|
|
|
4601
|
+
// Small text input + button for adding a filter value that isn't present in
|
|
4602
|
+
// the "Select Filter Values" dropdown (e.g. because no existing row has that
|
|
4603
|
+
// value). Kept as an uncontrolled local field so it clears itself after add.
|
|
4604
|
+
const CustomFilterValueInput = ({
|
|
4605
|
+
onAdd
|
|
4606
|
+
}) => {
|
|
4607
|
+
const [value, setValue] = React.useState('');
|
|
4608
|
+
const handleAdd = () => {
|
|
4609
|
+
const trimmed = value.trim();
|
|
4610
|
+
if (!trimmed) return;
|
|
4611
|
+
onAdd(trimmed);
|
|
4612
|
+
setValue('');
|
|
4613
|
+
};
|
|
4614
|
+
return /*#__PURE__*/React__namespace.default.createElement(material.Box, {
|
|
4615
|
+
sx: {
|
|
4616
|
+
display: 'flex',
|
|
4617
|
+
gap: 1,
|
|
4618
|
+
mt: 1
|
|
4619
|
+
}
|
|
4620
|
+
}, /*#__PURE__*/React__namespace.default.createElement(material.TextField, {
|
|
4621
|
+
size: "small",
|
|
4622
|
+
fullWidth: true,
|
|
4623
|
+
placeholder: "Add a value not in the list above",
|
|
4624
|
+
value: value,
|
|
4625
|
+
onChange: e => setValue(e.target.value),
|
|
4626
|
+
onKeyDown: e => {
|
|
4627
|
+
if (e.key === 'Enter') {
|
|
4628
|
+
e.preventDefault();
|
|
4629
|
+
handleAdd();
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
}), /*#__PURE__*/React__namespace.default.createElement(material.Button, {
|
|
4633
|
+
variant: "outlined",
|
|
4634
|
+
size: "small",
|
|
4635
|
+
onClick: handleAdd,
|
|
4636
|
+
disabled: !value.trim(),
|
|
4637
|
+
sx: {
|
|
4638
|
+
textTransform: 'none',
|
|
4639
|
+
whiteSpace: 'nowrap',
|
|
4640
|
+
borderColor: "rgb(70, 134, 128)",
|
|
4641
|
+
color: "rgb(70, 134, 128)"
|
|
4642
|
+
}
|
|
4643
|
+
}, "Add"));
|
|
4644
|
+
};
|
|
4585
4645
|
const Filters = ({
|
|
4586
4646
|
providersData,
|
|
4587
4647
|
rootProvider,
|
|
@@ -4721,8 +4781,9 @@ const Filters = ({
|
|
|
4721
4781
|
}
|
|
4722
4782
|
} else {
|
|
4723
4783
|
// For regular filters, set selected values and fetch available values
|
|
4724
|
-
|
|
4725
|
-
|
|
4784
|
+
const currentValues = filter.values || [];
|
|
4785
|
+
setSelectedFilterValues(currentValues);
|
|
4786
|
+
await fetchFilterValues(fullPath, '', currentValues);
|
|
4726
4787
|
}
|
|
4727
4788
|
};
|
|
4728
4789
|
const handleSaveEditedFilter = fullPath => {
|
|
@@ -4731,11 +4792,6 @@ const Filters = ({
|
|
|
4731
4792
|
|
|
4732
4793
|
// Check if this is a date filter
|
|
4733
4794
|
if (isDateDimension(existingFilter.dimension)) {
|
|
4734
|
-
// For date filters, validate that at least one date is provided
|
|
4735
|
-
if (!dateRangeFrom && !dateRangeTo) {
|
|
4736
|
-
return; // Don't save if no dates provided
|
|
4737
|
-
}
|
|
4738
|
-
|
|
4739
4795
|
// Update with new date range values (only include provided dates)
|
|
4740
4796
|
const dateValues = {};
|
|
4741
4797
|
if (dateRangeFrom) {
|
|
@@ -4752,11 +4808,6 @@ const Filters = ({
|
|
|
4752
4808
|
// Save the updated filter
|
|
4753
4809
|
onSaveFilter(fullPath, updatedFilter);
|
|
4754
4810
|
} else {
|
|
4755
|
-
// For regular filters, validate selected values
|
|
4756
|
-
if (selectedFilterValues.length === 0) {
|
|
4757
|
-
return; // Don't save if no values selected
|
|
4758
|
-
}
|
|
4759
|
-
|
|
4760
4811
|
// Update with new values
|
|
4761
4812
|
const updatedFilter = {
|
|
4762
4813
|
...existingFilter,
|
|
@@ -4817,7 +4868,11 @@ const Filters = ({
|
|
|
4817
4868
|
};
|
|
4818
4869
|
|
|
4819
4870
|
// Fetch distinct values for the selected dimension
|
|
4820
|
-
|
|
4871
|
+
// preserveValues lets a caller pass the values that must survive the merge
|
|
4872
|
+
// explicitly, instead of relying on the selectedFilterValues closure - a
|
|
4873
|
+
// setSelectedFilterValues() call right before fetchFilterValues() hasn't
|
|
4874
|
+
// committed yet by the time this function reads that state.
|
|
4875
|
+
const fetchFilterValues = async (fullPath, searchText = '', preserveValues = null) => {
|
|
4821
4876
|
setLoadingFilterValues(true);
|
|
4822
4877
|
try {
|
|
4823
4878
|
// Get parameters from context if available, otherwise use default
|
|
@@ -4890,7 +4945,8 @@ const Filters = ({
|
|
|
4890
4945
|
// Merge with currently selected values to ensure they're always available
|
|
4891
4946
|
// This is important when editing filters with search text - we don't want to lose
|
|
4892
4947
|
// previously selected values that don't match the current search
|
|
4893
|
-
const
|
|
4948
|
+
const valuesToPreserve = preserveValues !== null ? preserveValues : selectedFilterValues;
|
|
4949
|
+
const selectedValueItems = valuesToPreserve.map(key => ({
|
|
4894
4950
|
key: String(key),
|
|
4895
4951
|
value: String(key)
|
|
4896
4952
|
}));
|
|
@@ -4925,13 +4981,6 @@ const Filters = ({
|
|
|
4925
4981
|
|
|
4926
4982
|
// Check if this is a date filter
|
|
4927
4983
|
const isDate = isDateDimension(selectedItem.dimension);
|
|
4928
|
-
|
|
4929
|
-
// Validate based on filter type
|
|
4930
|
-
if (isDate) {
|
|
4931
|
-
if (!dateRangeFrom && !dateRangeTo) return; // At least one date must be provided
|
|
4932
|
-
} else {
|
|
4933
|
-
if (selectedFilterValues.length === 0) return; // Regular filters must have values
|
|
4934
|
-
}
|
|
4935
4984
|
if (!selectedDimension) return;
|
|
4936
4985
|
|
|
4937
4986
|
// Build the complete relation objects array
|
|
@@ -5006,13 +5055,6 @@ const Filters = ({
|
|
|
5006
5055
|
// Check if this is a date filter
|
|
5007
5056
|
const isDate = isDateDimension(dimensionData.dimension);
|
|
5008
5057
|
|
|
5009
|
-
// Validate based on filter type
|
|
5010
|
-
if (isDate) {
|
|
5011
|
-
if (!dateRangeFrom && !dateRangeTo) return; // At least one date must be provided
|
|
5012
|
-
} else {
|
|
5013
|
-
if (selectedFilterValues.length === 0) return; // Regular filters must have values
|
|
5014
|
-
}
|
|
5015
|
-
|
|
5016
5058
|
// Prepare filter values based on type
|
|
5017
5059
|
let filterValues;
|
|
5018
5060
|
if (isDate) {
|
|
@@ -5119,6 +5161,21 @@ const Filters = ({
|
|
|
5119
5161
|
}
|
|
5120
5162
|
};
|
|
5121
5163
|
|
|
5164
|
+
// Add a value typed in the "custom value" box directly into the selected
|
|
5165
|
+
// filter values, and inject it into availableFilterValues too, since
|
|
5166
|
+
// CheckboxMultiAutocomplete only renders a chip for keys it can resolve
|
|
5167
|
+
// against its items list.
|
|
5168
|
+
const handleAddCustomFilterValue = rawValue => {
|
|
5169
|
+
const value = rawValue.trim();
|
|
5170
|
+
if (!value) return;
|
|
5171
|
+
if (selectedFilterValues.includes(value)) return;
|
|
5172
|
+
setAvailableFilterValues(prev => prev.some(item => item.key === value) ? prev : [{
|
|
5173
|
+
key: value,
|
|
5174
|
+
value
|
|
5175
|
+
}, ...prev]);
|
|
5176
|
+
setSelectedFilterValues(prev => [...prev, value]);
|
|
5177
|
+
};
|
|
5178
|
+
|
|
5122
5179
|
// Handler for search text input in filter values dropdown (debounced - triggers API call)
|
|
5123
5180
|
const handleFilterSearchChange = searchText => {
|
|
5124
5181
|
// Determine the fullPath based on current mode
|
|
@@ -5338,6 +5395,8 @@ const Filters = ({
|
|
|
5338
5395
|
loading: loadingFilterValues,
|
|
5339
5396
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
5340
5397
|
debounceMs: 1000
|
|
5398
|
+
}), /*#__PURE__*/React__namespace.default.createElement(CustomFilterValueInput, {
|
|
5399
|
+
onAdd: handleAddCustomFilterValue
|
|
5341
5400
|
})));
|
|
5342
5401
|
})(), /*#__PURE__*/React__namespace.default.createElement(material.Box, {
|
|
5343
5402
|
sx: {
|
|
@@ -5367,14 +5426,7 @@ const Filters = ({
|
|
|
5367
5426
|
}, "Cancel"), /*#__PURE__*/React__namespace.default.createElement(material.Button, {
|
|
5368
5427
|
variant: "contained",
|
|
5369
5428
|
onClick: handleSave,
|
|
5370
|
-
disabled:
|
|
5371
|
-
if (!selectedDimension) return true;
|
|
5372
|
-
const dimensionItems = getDimensionItems();
|
|
5373
|
-
const selectedItem = dimensionItems.find(item => item.key === selectedDimension);
|
|
5374
|
-
if (!selectedItem) return true;
|
|
5375
|
-
const isDate = isDateDimension(selectedItem.dimension);
|
|
5376
|
-
return isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0;
|
|
5377
|
-
})(),
|
|
5429
|
+
disabled: !selectedDimension,
|
|
5378
5430
|
sx: {
|
|
5379
5431
|
height: "40px",
|
|
5380
5432
|
fontFamily: "system-ui",
|
|
@@ -5489,6 +5541,8 @@ const Filters = ({
|
|
|
5489
5541
|
loading: loadingFilterValues,
|
|
5490
5542
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
5491
5543
|
debounceMs: 1000
|
|
5544
|
+
}), /*#__PURE__*/React__namespace.default.createElement(CustomFilterValueInput, {
|
|
5545
|
+
onAdd: handleAddCustomFilterValue
|
|
5492
5546
|
})));
|
|
5493
5547
|
})(), /*#__PURE__*/React__namespace.default.createElement(material.Box, {
|
|
5494
5548
|
sx: {
|
|
@@ -5518,13 +5572,7 @@ const Filters = ({
|
|
|
5518
5572
|
}, "Cancel"), /*#__PURE__*/React__namespace.default.createElement(material.Button, {
|
|
5519
5573
|
variant: "contained",
|
|
5520
5574
|
onClick: handleSaveFromDimension,
|
|
5521
|
-
disabled:
|
|
5522
|
-
if (!selectedExistingDimension) return true;
|
|
5523
|
-
const dimensionData = existingDimensions.find(dim => dim.fullPath === selectedExistingDimension);
|
|
5524
|
-
if (!dimensionData) return true;
|
|
5525
|
-
const isDate = isDateDimension(dimensionData.dimension);
|
|
5526
|
-
return isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0;
|
|
5527
|
-
})(),
|
|
5575
|
+
disabled: !selectedExistingDimension,
|
|
5528
5576
|
sx: {
|
|
5529
5577
|
height: "40px",
|
|
5530
5578
|
fontFamily: "system-ui",
|
|
@@ -5778,6 +5826,8 @@ const Filters = ({
|
|
|
5778
5826
|
loading: loadingFilterValues,
|
|
5779
5827
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
5780
5828
|
debounceMs: 1000
|
|
5829
|
+
}), /*#__PURE__*/React__namespace.default.createElement(CustomFilterValueInput, {
|
|
5830
|
+
onAdd: handleAddCustomFilterValue
|
|
5781
5831
|
})), /*#__PURE__*/React__namespace.default.createElement(material.Box, {
|
|
5782
5832
|
sx: {
|
|
5783
5833
|
display: 'flex',
|
|
@@ -5796,7 +5846,6 @@ const Filters = ({
|
|
|
5796
5846
|
variant: "contained",
|
|
5797
5847
|
size: "small",
|
|
5798
5848
|
onClick: () => handleSaveEditedFilter(fullPath),
|
|
5799
|
-
disabled: isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0,
|
|
5800
5849
|
sx: {
|
|
5801
5850
|
textTransform: 'none',
|
|
5802
5851
|
borderRadius: '6px',
|
|
@@ -7088,20 +7137,12 @@ const ReportBuilder = ({
|
|
|
7088
7137
|
if (report.filters && Object.keys(report.filters).length > 0) {
|
|
7089
7138
|
Object.entries(report.filters).forEach(([fullPath, filterData]) => {
|
|
7090
7139
|
if (filterData.values) {
|
|
7091
|
-
//
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
});
|
|
7098
|
-
}
|
|
7099
|
-
} else if (typeof filterData.values === "object") {
|
|
7100
|
-
// Date range filter - add the object as-is
|
|
7101
|
-
conditions.push({
|
|
7102
|
-
[fullPath]: filterData.values
|
|
7103
|
-
});
|
|
7104
|
-
}
|
|
7140
|
+
// Include the condition as-is regardless of whether the array/object
|
|
7141
|
+
// has any entries - an explicitly saved (but empty) filter should
|
|
7142
|
+
// still appear in the query.
|
|
7143
|
+
conditions.push({
|
|
7144
|
+
[fullPath]: filterData.values
|
|
7145
|
+
});
|
|
7105
7146
|
}
|
|
7106
7147
|
});
|
|
7107
7148
|
}
|
|
@@ -7165,14 +7206,24 @@ const ReportBuilder = ({
|
|
|
7165
7206
|
queryObj.ordered_metrics = report.metrics.map(metric => metric.id);
|
|
7166
7207
|
}
|
|
7167
7208
|
|
|
7168
|
-
// Persist the
|
|
7169
|
-
//
|
|
7170
|
-
//
|
|
7171
|
-
//
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7209
|
+
// Persist the full resolved column order - the explicit Column Order tab
|
|
7210
|
+
// arrangement when defined, otherwise dimensions/computed (in their own
|
|
7211
|
+
// order) followed by metrics (in their own order), same as what the
|
|
7212
|
+
// results grid renders. Always emitted (not just when a custom order
|
|
7213
|
+
// exists) so the API/results grid get an explicit order even for
|
|
7214
|
+
// untouched reports, rather than having to fall back to guessing it.
|
|
7215
|
+
const orderedDimensionItems = report.dimensionOrder.map(entry => entry.type === "dimension" ? {
|
|
7216
|
+
kind: "dimension",
|
|
7217
|
+
data: dimensionById[entry.key]
|
|
7218
|
+
} : {
|
|
7219
|
+
kind: "computed",
|
|
7220
|
+
data: computedByName[entry.key]
|
|
7221
|
+
}).filter(item => item.data);
|
|
7222
|
+
const resolvedColumnItems = resolveColumnOrder(report.columnOrder, orderedDimensionItems, report.metrics);
|
|
7223
|
+
if (resolvedColumnItems.length > 0) {
|
|
7224
|
+
queryObj.ordered_columns = resolvedColumnItems.map(item => ({
|
|
7225
|
+
type: item.kind,
|
|
7226
|
+
ref: item.key
|
|
7176
7227
|
}));
|
|
7177
7228
|
}
|
|
7178
7229
|
|
|
@@ -3091,6 +3091,16 @@ const SortableComputedChip = ({
|
|
|
3091
3091
|
handleCancel();
|
|
3092
3092
|
}
|
|
3093
3093
|
};
|
|
3094
|
+
|
|
3095
|
+
// The value field is multiline, so Enter inserts a newline instead of
|
|
3096
|
+
// saving; only Escape (cancel) and Cmd/Ctrl+Enter (save) are handled here.
|
|
3097
|
+
const handleValueKeyDown = e => {
|
|
3098
|
+
if (e.key === "Escape") {
|
|
3099
|
+
handleCancel();
|
|
3100
|
+
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
3101
|
+
handleSave();
|
|
3102
|
+
}
|
|
3103
|
+
};
|
|
3094
3104
|
return /*#__PURE__*/React__default.createElement("div", _extends({
|
|
3095
3105
|
ref: setNodeRef,
|
|
3096
3106
|
style: style
|
|
@@ -3194,10 +3204,13 @@ const SortableComputedChip = ({
|
|
|
3194
3204
|
}), /*#__PURE__*/React__default.createElement(TextField, {
|
|
3195
3205
|
value: editValue,
|
|
3196
3206
|
onChange: e => setEditValue(e.target.value),
|
|
3197
|
-
onKeyDown:
|
|
3207
|
+
onKeyDown: handleValueKeyDown,
|
|
3198
3208
|
size: "small",
|
|
3199
3209
|
label: "Value",
|
|
3200
|
-
helperText: " ",
|
|
3210
|
+
helperText: "Cmd/Ctrl+Enter to save",
|
|
3211
|
+
multiline: true,
|
|
3212
|
+
minRows: 2,
|
|
3213
|
+
maxRows: 10,
|
|
3201
3214
|
sx: {
|
|
3202
3215
|
flex: 1,
|
|
3203
3216
|
minWidth: "200px",
|
|
@@ -3607,6 +3620,9 @@ const Dimensions = ({
|
|
|
3607
3620
|
value: computedValue,
|
|
3608
3621
|
onChange: e => setComputedValue(e.target.value),
|
|
3609
3622
|
size: "small",
|
|
3623
|
+
multiline: true,
|
|
3624
|
+
minRows: 2,
|
|
3625
|
+
maxRows: 10,
|
|
3610
3626
|
sx: {
|
|
3611
3627
|
width: "400px",
|
|
3612
3628
|
"& .MuiInputBase-input": {
|
|
@@ -4501,6 +4517,50 @@ const Metrics = ({
|
|
|
4501
4517
|
})))));
|
|
4502
4518
|
};
|
|
4503
4519
|
|
|
4520
|
+
// Small text input + button for adding a filter value that isn't present in
|
|
4521
|
+
// the "Select Filter Values" dropdown (e.g. because no existing row has that
|
|
4522
|
+
// value). Kept as an uncontrolled local field so it clears itself after add.
|
|
4523
|
+
const CustomFilterValueInput = ({
|
|
4524
|
+
onAdd
|
|
4525
|
+
}) => {
|
|
4526
|
+
const [value, setValue] = useState('');
|
|
4527
|
+
const handleAdd = () => {
|
|
4528
|
+
const trimmed = value.trim();
|
|
4529
|
+
if (!trimmed) return;
|
|
4530
|
+
onAdd(trimmed);
|
|
4531
|
+
setValue('');
|
|
4532
|
+
};
|
|
4533
|
+
return /*#__PURE__*/React__default.createElement(Box$1, {
|
|
4534
|
+
sx: {
|
|
4535
|
+
display: 'flex',
|
|
4536
|
+
gap: 1,
|
|
4537
|
+
mt: 1
|
|
4538
|
+
}
|
|
4539
|
+
}, /*#__PURE__*/React__default.createElement(TextField, {
|
|
4540
|
+
size: "small",
|
|
4541
|
+
fullWidth: true,
|
|
4542
|
+
placeholder: "Add a value not in the list above",
|
|
4543
|
+
value: value,
|
|
4544
|
+
onChange: e => setValue(e.target.value),
|
|
4545
|
+
onKeyDown: e => {
|
|
4546
|
+
if (e.key === 'Enter') {
|
|
4547
|
+
e.preventDefault();
|
|
4548
|
+
handleAdd();
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
}), /*#__PURE__*/React__default.createElement(Button, {
|
|
4552
|
+
variant: "outlined",
|
|
4553
|
+
size: "small",
|
|
4554
|
+
onClick: handleAdd,
|
|
4555
|
+
disabled: !value.trim(),
|
|
4556
|
+
sx: {
|
|
4557
|
+
textTransform: 'none',
|
|
4558
|
+
whiteSpace: 'nowrap',
|
|
4559
|
+
borderColor: "rgb(70, 134, 128)",
|
|
4560
|
+
color: "rgb(70, 134, 128)"
|
|
4561
|
+
}
|
|
4562
|
+
}, "Add"));
|
|
4563
|
+
};
|
|
4504
4564
|
const Filters = ({
|
|
4505
4565
|
providersData,
|
|
4506
4566
|
rootProvider,
|
|
@@ -4640,8 +4700,9 @@ const Filters = ({
|
|
|
4640
4700
|
}
|
|
4641
4701
|
} else {
|
|
4642
4702
|
// For regular filters, set selected values and fetch available values
|
|
4643
|
-
|
|
4644
|
-
|
|
4703
|
+
const currentValues = filter.values || [];
|
|
4704
|
+
setSelectedFilterValues(currentValues);
|
|
4705
|
+
await fetchFilterValues(fullPath, '', currentValues);
|
|
4645
4706
|
}
|
|
4646
4707
|
};
|
|
4647
4708
|
const handleSaveEditedFilter = fullPath => {
|
|
@@ -4650,11 +4711,6 @@ const Filters = ({
|
|
|
4650
4711
|
|
|
4651
4712
|
// Check if this is a date filter
|
|
4652
4713
|
if (isDateDimension(existingFilter.dimension)) {
|
|
4653
|
-
// For date filters, validate that at least one date is provided
|
|
4654
|
-
if (!dateRangeFrom && !dateRangeTo) {
|
|
4655
|
-
return; // Don't save if no dates provided
|
|
4656
|
-
}
|
|
4657
|
-
|
|
4658
4714
|
// Update with new date range values (only include provided dates)
|
|
4659
4715
|
const dateValues = {};
|
|
4660
4716
|
if (dateRangeFrom) {
|
|
@@ -4671,11 +4727,6 @@ const Filters = ({
|
|
|
4671
4727
|
// Save the updated filter
|
|
4672
4728
|
onSaveFilter(fullPath, updatedFilter);
|
|
4673
4729
|
} else {
|
|
4674
|
-
// For regular filters, validate selected values
|
|
4675
|
-
if (selectedFilterValues.length === 0) {
|
|
4676
|
-
return; // Don't save if no values selected
|
|
4677
|
-
}
|
|
4678
|
-
|
|
4679
4730
|
// Update with new values
|
|
4680
4731
|
const updatedFilter = {
|
|
4681
4732
|
...existingFilter,
|
|
@@ -4736,7 +4787,11 @@ const Filters = ({
|
|
|
4736
4787
|
};
|
|
4737
4788
|
|
|
4738
4789
|
// Fetch distinct values for the selected dimension
|
|
4739
|
-
|
|
4790
|
+
// preserveValues lets a caller pass the values that must survive the merge
|
|
4791
|
+
// explicitly, instead of relying on the selectedFilterValues closure - a
|
|
4792
|
+
// setSelectedFilterValues() call right before fetchFilterValues() hasn't
|
|
4793
|
+
// committed yet by the time this function reads that state.
|
|
4794
|
+
const fetchFilterValues = async (fullPath, searchText = '', preserveValues = null) => {
|
|
4740
4795
|
setLoadingFilterValues(true);
|
|
4741
4796
|
try {
|
|
4742
4797
|
// Get parameters from context if available, otherwise use default
|
|
@@ -4809,7 +4864,8 @@ const Filters = ({
|
|
|
4809
4864
|
// Merge with currently selected values to ensure they're always available
|
|
4810
4865
|
// This is important when editing filters with search text - we don't want to lose
|
|
4811
4866
|
// previously selected values that don't match the current search
|
|
4812
|
-
const
|
|
4867
|
+
const valuesToPreserve = preserveValues !== null ? preserveValues : selectedFilterValues;
|
|
4868
|
+
const selectedValueItems = valuesToPreserve.map(key => ({
|
|
4813
4869
|
key: String(key),
|
|
4814
4870
|
value: String(key)
|
|
4815
4871
|
}));
|
|
@@ -4844,13 +4900,6 @@ const Filters = ({
|
|
|
4844
4900
|
|
|
4845
4901
|
// Check if this is a date filter
|
|
4846
4902
|
const isDate = isDateDimension(selectedItem.dimension);
|
|
4847
|
-
|
|
4848
|
-
// Validate based on filter type
|
|
4849
|
-
if (isDate) {
|
|
4850
|
-
if (!dateRangeFrom && !dateRangeTo) return; // At least one date must be provided
|
|
4851
|
-
} else {
|
|
4852
|
-
if (selectedFilterValues.length === 0) return; // Regular filters must have values
|
|
4853
|
-
}
|
|
4854
4903
|
if (!selectedDimension) return;
|
|
4855
4904
|
|
|
4856
4905
|
// Build the complete relation objects array
|
|
@@ -4925,13 +4974,6 @@ const Filters = ({
|
|
|
4925
4974
|
// Check if this is a date filter
|
|
4926
4975
|
const isDate = isDateDimension(dimensionData.dimension);
|
|
4927
4976
|
|
|
4928
|
-
// Validate based on filter type
|
|
4929
|
-
if (isDate) {
|
|
4930
|
-
if (!dateRangeFrom && !dateRangeTo) return; // At least one date must be provided
|
|
4931
|
-
} else {
|
|
4932
|
-
if (selectedFilterValues.length === 0) return; // Regular filters must have values
|
|
4933
|
-
}
|
|
4934
|
-
|
|
4935
4977
|
// Prepare filter values based on type
|
|
4936
4978
|
let filterValues;
|
|
4937
4979
|
if (isDate) {
|
|
@@ -5038,6 +5080,21 @@ const Filters = ({
|
|
|
5038
5080
|
}
|
|
5039
5081
|
};
|
|
5040
5082
|
|
|
5083
|
+
// Add a value typed in the "custom value" box directly into the selected
|
|
5084
|
+
// filter values, and inject it into availableFilterValues too, since
|
|
5085
|
+
// CheckboxMultiAutocomplete only renders a chip for keys it can resolve
|
|
5086
|
+
// against its items list.
|
|
5087
|
+
const handleAddCustomFilterValue = rawValue => {
|
|
5088
|
+
const value = rawValue.trim();
|
|
5089
|
+
if (!value) return;
|
|
5090
|
+
if (selectedFilterValues.includes(value)) return;
|
|
5091
|
+
setAvailableFilterValues(prev => prev.some(item => item.key === value) ? prev : [{
|
|
5092
|
+
key: value,
|
|
5093
|
+
value
|
|
5094
|
+
}, ...prev]);
|
|
5095
|
+
setSelectedFilterValues(prev => [...prev, value]);
|
|
5096
|
+
};
|
|
5097
|
+
|
|
5041
5098
|
// Handler for search text input in filter values dropdown (debounced - triggers API call)
|
|
5042
5099
|
const handleFilterSearchChange = searchText => {
|
|
5043
5100
|
// Determine the fullPath based on current mode
|
|
@@ -5257,6 +5314,8 @@ const Filters = ({
|
|
|
5257
5314
|
loading: loadingFilterValues,
|
|
5258
5315
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
5259
5316
|
debounceMs: 1000
|
|
5317
|
+
}), /*#__PURE__*/React__default.createElement(CustomFilterValueInput, {
|
|
5318
|
+
onAdd: handleAddCustomFilterValue
|
|
5260
5319
|
})));
|
|
5261
5320
|
})(), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
5262
5321
|
sx: {
|
|
@@ -5286,14 +5345,7 @@ const Filters = ({
|
|
|
5286
5345
|
}, "Cancel"), /*#__PURE__*/React__default.createElement(Button, {
|
|
5287
5346
|
variant: "contained",
|
|
5288
5347
|
onClick: handleSave,
|
|
5289
|
-
disabled:
|
|
5290
|
-
if (!selectedDimension) return true;
|
|
5291
|
-
const dimensionItems = getDimensionItems();
|
|
5292
|
-
const selectedItem = dimensionItems.find(item => item.key === selectedDimension);
|
|
5293
|
-
if (!selectedItem) return true;
|
|
5294
|
-
const isDate = isDateDimension(selectedItem.dimension);
|
|
5295
|
-
return isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0;
|
|
5296
|
-
})(),
|
|
5348
|
+
disabled: !selectedDimension,
|
|
5297
5349
|
sx: {
|
|
5298
5350
|
height: "40px",
|
|
5299
5351
|
fontFamily: "system-ui",
|
|
@@ -5408,6 +5460,8 @@ const Filters = ({
|
|
|
5408
5460
|
loading: loadingFilterValues,
|
|
5409
5461
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
5410
5462
|
debounceMs: 1000
|
|
5463
|
+
}), /*#__PURE__*/React__default.createElement(CustomFilterValueInput, {
|
|
5464
|
+
onAdd: handleAddCustomFilterValue
|
|
5411
5465
|
})));
|
|
5412
5466
|
})(), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
5413
5467
|
sx: {
|
|
@@ -5437,13 +5491,7 @@ const Filters = ({
|
|
|
5437
5491
|
}, "Cancel"), /*#__PURE__*/React__default.createElement(Button, {
|
|
5438
5492
|
variant: "contained",
|
|
5439
5493
|
onClick: handleSaveFromDimension,
|
|
5440
|
-
disabled:
|
|
5441
|
-
if (!selectedExistingDimension) return true;
|
|
5442
|
-
const dimensionData = existingDimensions.find(dim => dim.fullPath === selectedExistingDimension);
|
|
5443
|
-
if (!dimensionData) return true;
|
|
5444
|
-
const isDate = isDateDimension(dimensionData.dimension);
|
|
5445
|
-
return isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0;
|
|
5446
|
-
})(),
|
|
5494
|
+
disabled: !selectedExistingDimension,
|
|
5447
5495
|
sx: {
|
|
5448
5496
|
height: "40px",
|
|
5449
5497
|
fontFamily: "system-ui",
|
|
@@ -5697,6 +5745,8 @@ const Filters = ({
|
|
|
5697
5745
|
loading: loadingFilterValues,
|
|
5698
5746
|
helperText: selectedFilterValues.length > 0 ? `${selectedFilterValues.length} value(s) selected` : 'Select at least one value',
|
|
5699
5747
|
debounceMs: 1000
|
|
5748
|
+
}), /*#__PURE__*/React__default.createElement(CustomFilterValueInput, {
|
|
5749
|
+
onAdd: handleAddCustomFilterValue
|
|
5700
5750
|
})), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
5701
5751
|
sx: {
|
|
5702
5752
|
display: 'flex',
|
|
@@ -5715,7 +5765,6 @@ const Filters = ({
|
|
|
5715
5765
|
variant: "contained",
|
|
5716
5766
|
size: "small",
|
|
5717
5767
|
onClick: () => handleSaveEditedFilter(fullPath),
|
|
5718
|
-
disabled: isDate ? !dateRangeFrom && !dateRangeTo : selectedFilterValues.length === 0,
|
|
5719
5768
|
sx: {
|
|
5720
5769
|
textTransform: 'none',
|
|
5721
5770
|
borderRadius: '6px',
|
|
@@ -7007,20 +7056,12 @@ const ReportBuilder = ({
|
|
|
7007
7056
|
if (report.filters && Object.keys(report.filters).length > 0) {
|
|
7008
7057
|
Object.entries(report.filters).forEach(([fullPath, filterData]) => {
|
|
7009
7058
|
if (filterData.values) {
|
|
7010
|
-
//
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
});
|
|
7017
|
-
}
|
|
7018
|
-
} else if (typeof filterData.values === "object") {
|
|
7019
|
-
// Date range filter - add the object as-is
|
|
7020
|
-
conditions.push({
|
|
7021
|
-
[fullPath]: filterData.values
|
|
7022
|
-
});
|
|
7023
|
-
}
|
|
7059
|
+
// Include the condition as-is regardless of whether the array/object
|
|
7060
|
+
// has any entries - an explicitly saved (but empty) filter should
|
|
7061
|
+
// still appear in the query.
|
|
7062
|
+
conditions.push({
|
|
7063
|
+
[fullPath]: filterData.values
|
|
7064
|
+
});
|
|
7024
7065
|
}
|
|
7025
7066
|
});
|
|
7026
7067
|
}
|
|
@@ -7084,14 +7125,24 @@ const ReportBuilder = ({
|
|
|
7084
7125
|
queryObj.ordered_metrics = report.metrics.map(metric => metric.id);
|
|
7085
7126
|
}
|
|
7086
7127
|
|
|
7087
|
-
// Persist the
|
|
7088
|
-
//
|
|
7089
|
-
//
|
|
7090
|
-
//
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7128
|
+
// Persist the full resolved column order - the explicit Column Order tab
|
|
7129
|
+
// arrangement when defined, otherwise dimensions/computed (in their own
|
|
7130
|
+
// order) followed by metrics (in their own order), same as what the
|
|
7131
|
+
// results grid renders. Always emitted (not just when a custom order
|
|
7132
|
+
// exists) so the API/results grid get an explicit order even for
|
|
7133
|
+
// untouched reports, rather than having to fall back to guessing it.
|
|
7134
|
+
const orderedDimensionItems = report.dimensionOrder.map(entry => entry.type === "dimension" ? {
|
|
7135
|
+
kind: "dimension",
|
|
7136
|
+
data: dimensionById[entry.key]
|
|
7137
|
+
} : {
|
|
7138
|
+
kind: "computed",
|
|
7139
|
+
data: computedByName[entry.key]
|
|
7140
|
+
}).filter(item => item.data);
|
|
7141
|
+
const resolvedColumnItems = resolveColumnOrder(report.columnOrder, orderedDimensionItems, report.metrics);
|
|
7142
|
+
if (resolvedColumnItems.length > 0) {
|
|
7143
|
+
queryObj.ordered_columns = resolvedColumnItems.map(item => ({
|
|
7144
|
+
type: item.kind,
|
|
7145
|
+
ref: item.key
|
|
7095
7146
|
}));
|
|
7096
7147
|
}
|
|
7097
7148
|
|