@solidxai/core-ui 0.1.17 → 0.1.18-beta.0

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.
Files changed (37) hide show
  1. package/dist/components/core/card/SolidCardView.d.ts +1 -0
  2. package/dist/components/core/card/SolidCardView.d.ts.map +1 -1
  3. package/dist/components/core/card/SolidCardView.js +2 -2
  4. package/dist/components/core/card/SolidCardView.js.map +1 -1
  5. package/dist/components/core/card/SolidCardView.tsx +3 -0
  6. package/dist/components/core/common/SolidGlobalSearchElement.d.ts.map +1 -1
  7. package/dist/components/core/common/SolidGlobalSearchElement.js +342 -114
  8. package/dist/components/core/common/SolidGlobalSearchElement.js.map +1 -1
  9. package/dist/components/core/common/SolidGlobalSearchElement.tsx +285 -47
  10. package/dist/components/core/json/SolidJsonEditor.css +89 -0
  11. package/dist/components/core/kanban/SolidKanbanView.d.ts +1 -0
  12. package/dist/components/core/kanban/SolidKanbanView.d.ts.map +1 -1
  13. package/dist/components/core/kanban/SolidKanbanView.js +3 -1
  14. package/dist/components/core/kanban/SolidKanbanView.js.map +1 -1
  15. package/dist/components/core/kanban/SolidKanbanView.tsx +4 -1
  16. package/dist/components/core/list/SolidListView.d.ts +1 -0
  17. package/dist/components/core/list/SolidListView.d.ts.map +1 -1
  18. package/dist/components/core/list/SolidListView.js +2 -1
  19. package/dist/components/core/list/SolidListView.js.map +1 -1
  20. package/dist/components/core/list/SolidListView.tsx +3 -0
  21. package/dist/components/core/tree/SolidTreeView.d.ts +3 -0
  22. package/dist/components/core/tree/SolidTreeView.d.ts.map +1 -1
  23. package/dist/components/core/tree/SolidTreeView.js +7 -1
  24. package/dist/components/core/tree/SolidTreeView.js.map +1 -1
  25. package/dist/components/core/tree/SolidTreeView.tsx +10 -0
  26. package/dist/constants/error-messages.d.ts +4 -0
  27. package/dist/constants/error-messages.d.ts.map +1 -1
  28. package/dist/constants/error-messages.js +4 -0
  29. package/dist/constants/error-messages.js.map +1 -1
  30. package/dist/constants/error-messages.ts +4 -0
  31. package/dist/resources/solid-custom.css +3 -18
  32. package/dist/resources/themes/solid-dark-enterprise/theme.css +4 -481
  33. package/dist/resources/themes/solid-dark-modern/theme.css +1 -167
  34. package/dist/resources/themes/solid-light-enterprise/theme.css +4 -486
  35. package/dist/resources/themes/solid-light-modern/theme.css +7 -204
  36. package/dist/routes/pages/admin/core/DatasourceIntrospectionPage.css +1 -1
  37. package/package.json +1 -1
@@ -1,9 +1,11 @@
1
1
  import React, { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
2
+ import { useDispatch } from "react-redux";
2
3
  import FilterComponent, { FilterOperator, FilterRule, FilterRuleType } from "../../../components/core/common/FilterComponent";
3
4
  import { toTitleCase } from "../../../helpers/helpers";
4
5
  import {clearFilterObjectFromLocalStorage,getFilterObjectFromLocalStorage,setFilterObjectToLocalStorage,
5
6
  } from "./globalSearchPersistence";
6
7
  import { createSolidEntityApi } from "../../../redux/api/solidEntityApi";
8
+ import { showToast } from "../../../redux/features/toastSlice";
7
9
  import qs from "qs";
8
10
  import { SolidSaveCustomFilterForm } from "./SolidSaveCustomFilterForm";
9
11
  import { ERROR_MESSAGES } from "../../../constants/error-messages";
@@ -418,10 +420,12 @@ export const mergeAllDiffFilters = (customFilter: any, searchFilter: any, savedF
418
420
  return filters;
419
421
  }
420
422
 
421
- const SavedFilterList = ({ savedfilter, activeSavedFilterReference, applySavedFilter, openSavedCustomFilter, setSavedFilterTobeDeleted, setIsDeleteSQDialogVisible, isFocused, onMouseEnter, optionId }: any) => {
422
- const isActive = savedfilter?.systemKey
423
- ? activeSavedFilterReference === savedfilter.systemKey
424
- : String(activeSavedFilterReference) === String(savedfilter.id);
423
+ const SavedFilterList = ({ savedfilter, activeSavedFilterReference, activeSavedFilterReferences, applySavedFilter, openSavedCustomFilter, setSavedFilterTobeDeleted, setIsDeleteSQDialogVisible, isFocused, onMouseEnter, optionId }: any) => {
424
+ const isActive = activeSavedFilterReferences
425
+ ? activeSavedFilterReferences.includes(savedfilter?.systemKey ? `system:${savedfilter.systemKey}` : `id:${savedfilter.id}`)
426
+ : savedfilter?.systemKey
427
+ ? activeSavedFilterReference === savedfilter.systemKey
428
+ : String(activeSavedFilterReference) === String(savedfilter.id);
425
429
 
426
430
  return (
427
431
  <div className="solid-saved-filter-item" onMouseEnter={onMouseEnter}>
@@ -436,6 +440,10 @@ const SavedFilterList = ({ savedfilter, activeSavedFilterReference, applySavedFi
436
440
  aria-label={`${savedfilter.name}${isActive ? ", applied" : ""}`}
437
441
  onMouseDown={(e) => {
438
442
  e.preventDefault();
443
+ e.stopPropagation();
444
+ }}
445
+ onClick={(e) => {
446
+ e.stopPropagation();
439
447
  applySavedFilter(savedfilter);
440
448
  }}
441
449
  title={savedfilter?.description}
@@ -571,7 +579,8 @@ type RelationCache = Map<string, { label: string; value: number }>;
571
579
 
572
580
 
573
581
 
574
- export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handleApplyCustomFilter, showSaveFilterPopup, setShowSaveFilterPopup, filterPredicates }: any, ref) => {
582
+ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handleApplyCustomFilter, showSaveFilterPopup, setShowSaveFilterPopup, filterPredicates, allowMultipleSavedFilters = false }: any, ref) => {
583
+ const dispatch = useDispatch();
575
584
  type OverlayOption =
576
585
  | { id: string; kind: "field"; field: any }
577
586
  | { id: string; kind: "predefined"; predefined: any }
@@ -681,6 +690,8 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
681
690
  const [currentSavedFilterQuery, setCurrentSavedFilterQuery] = useState<any>();
682
691
  const [currentSavedFilterVariables, setCurrentSavedFilterVariables] = useState<Record<string, any>>({});
683
692
  const [currentSavedFilterRules, setCurrentSavedFilterRules] = useState<any>();
693
+ const [activeSavedFilters, setActiveSavedFilters] = useState<any[]>([]);
694
+ const [editingSavedFilterKey, setEditingSavedFilterKey] = useState<string | null>(null);
684
695
  const [showSavedFilterComponent, setShowSavedFilterComponent] = useState<boolean>(false);
685
696
 
686
697
 
@@ -834,6 +845,27 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
834
845
  viewData?.data?.solidView?.model?.id,
835
846
  user?.id
836
847
  ])
848
+
849
+ const getSavedFilterKey = (savedFilter: any) =>
850
+ savedFilter?.systemKey ? `system:${savedFilter.systemKey}` : `id:${savedFilter?.id}`;
851
+
852
+ const getPersistedSavedFilterItems = (queryObject: any): any[] => {
853
+ if (!allowMultipleSavedFilters) return [];
854
+ if (Array.isArray(queryObject?.saved_filter_items)) {
855
+ return queryObject.saved_filter_items;
856
+ }
857
+ if (queryObject?.saved_filter_predicate) {
858
+ return [{
859
+ id: queryObject.saved_filter_id,
860
+ systemKey: queryObject.saved_filter_system_key,
861
+ name: queryObject.saved_filter_name || "Saved Filter",
862
+ predicate: queryObject.saved_filter_predicate,
863
+ variables: queryObject.saved_filter_variables || {},
864
+ }];
865
+ }
866
+ return [];
867
+ };
868
+
837
869
  // Updates search, custom filter, grouping, aggregation, and UI state from a single shared filter source.
838
870
  const syncFilterUiStateFromSharedSource = useCallback(async (queryObject: any | null) => {
839
871
  const nextSearchFilter = queryObject?.search_predicate || null;
@@ -847,9 +879,33 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
847
879
  nextPredefinedBaseFilter && queryObject?.predefined_search_chip
848
880
  ? queryObject.predefined_search_chip
849
881
  : null;
850
- const nextSavedFilter = queryObject?.saved_filter_predicate || null;
882
+ const persistedSavedFilterItems = getPersistedSavedFilterItems(queryObject);
883
+ const nextSavedFilterItems = allowMultipleSavedFilters
884
+ ? persistedSavedFilterItems.map((item: any) => {
885
+ const savedFilter = availableSavedFilters.find((candidate: any) =>
886
+ item?.systemKey
887
+ ? candidate?.systemKey === item.systemKey
888
+ : String(candidate?.id) === String(item?.id)
889
+ );
890
+ return {
891
+ data: savedFilter || {
892
+ id: item?.id,
893
+ systemKey: item?.systemKey,
894
+ name: item?.name || "Saved Filter",
895
+ filterQueryJson: JSON.stringify(item?.predicate || {}),
896
+ },
897
+ query: item?.predicate || {},
898
+ variables: item?.variables || {},
899
+ };
900
+ })
901
+ : [];
902
+ const nextSavedFilter = allowMultipleSavedFilters
903
+ ? (nextSavedFilterItems.length > 0 ? { $or: nextSavedFilterItems.map((item: any) => item.query) } : null)
904
+ : queryObject?.saved_filter_predicate || null;
851
905
  const nextSavedFilterVariables = queryObject?.saved_filter_variables ?? {};
852
- const nextSavedFilterData = nextSavedFilter
906
+ const nextSavedFilterData = allowMultipleSavedFilters
907
+ ? (nextSavedFilterItems.length > 0 ? nextSavedFilterItems[nextSavedFilterItems.length - 1].data : null)
908
+ : nextSavedFilter
853
909
  ? availableSavedFilters.find((savedFilter: any) => {
854
910
  if (queryObject?.saved_filter_system_key) {
855
911
  return savedFilter?.systemKey === queryObject.saved_filter_system_key;
@@ -874,6 +930,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
874
930
  setCurrentSavedFilterData((prev: any) => areFilterStateValuesEqual(prev, nextSavedFilterData) ? prev : nextSavedFilterData);
875
931
  setCurrentSavedFilterQuery((prev: any) => areFilterStateValuesEqual(prev, nextSavedFilter) ? prev : nextSavedFilter);
876
932
  setCurrentSavedFilterVariables((prev) => areFilterStateValuesEqual(prev, nextSavedFilterVariables) ? prev : nextSavedFilterVariables);
933
+ setActiveSavedFilters((prev) => areFilterStateValuesEqual(prev, nextSavedFilterItems) ? prev : nextSavedFilterItems);
877
934
 
878
935
  if (nextCustomFilter && viewData?.data) {
879
936
  const rules: FilterRule = transformFiltersToRules(nextCustomFilter);
@@ -909,7 +966,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
909
966
 
910
967
  setGroupingRules((prev) => areFilterStateValuesEqual(prev, nextGroupingRules) ? prev : nextGroupingRules);
911
968
  setAggregationRules((prev) => areFilterStateValuesEqual(prev, nextAggregationRules) ? prev : nextAggregationRules);
912
- }, [availableSavedFilters, initialState, viewData]);
969
+ }, [allowMultipleSavedFilters, availableSavedFilters, initialState, viewData]);
913
970
 
914
971
  const resetAppliedFilters = ({ preserveGrouping = false }: { preserveGrouping?: boolean } = {}) => {
915
972
  setSearchChips([]);
@@ -922,6 +979,8 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
922
979
  setCurrentSavedFilterQuery(null);
923
980
  setCurrentSavedFilterVariables({});
924
981
  setCurrentSavedFilterRules(null);
982
+ setActiveSavedFilters([]);
983
+ setEditingSavedFilterKey(null);
925
984
  if (!preserveGrouping) {
926
985
  setGroupingRules(defaultGroupingRules);
927
986
  setAggregationRules(defaultAggregationRules);
@@ -1221,6 +1280,15 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1221
1280
  // };
1222
1281
  // }
1223
1282
  setCurrentSavedFilterQuery(finalCustomFilter);
1283
+ if (allowMultipleSavedFilters && editingSavedFilterKey) {
1284
+ const nextItems = activeSavedFilters.map((item: any) =>
1285
+ getSavedFilterKey(item.data) === editingSavedFilterKey
1286
+ ? { ...item, query: finalCustomFilter }
1287
+ : item
1288
+ );
1289
+ setActiveSavedFilters(nextItems);
1290
+ persistActiveSavedFilters(nextItems);
1291
+ }
1224
1292
  setShowSavedFilterComponent(false);
1225
1293
  setHasSearched(true)
1226
1294
  setRefreshKey((prev) => prev + 1)
@@ -1262,7 +1330,9 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1262
1330
  const finalSearchFilter = tranformSearchToFilters(formattedChips);
1263
1331
  setSearchFilter(finalSearchFilter);
1264
1332
 
1265
- let finalSavedFilter: any = currentSavedFilterQuery
1333
+ const finalSavedFilter: any = allowMultipleSavedFilters
1334
+ ? (activeSavedFilters.length > 0 ? { $or: activeSavedFilters.map((item: any) => item.query) } : null)
1335
+ : currentSavedFilterQuery;
1266
1336
  const finalPredefinedFilter = predefinedSearchBaseFilter
1267
1337
 
1268
1338
  const finalCustomFilter = customFilter
@@ -1271,19 +1341,31 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1271
1341
  finalPredefinedFilter && predefinedSearchChip
1272
1342
  ? predefinedSearchChip
1273
1343
  : null;
1274
- if (finalSavedFilter && Object.keys(finalSavedFilter).length > 0) {
1344
+ if (allowMultipleSavedFilters && activeSavedFilters.length > 0) {
1345
+ const resolvedItems = activeSavedFilters.map((item: any) => resolveSavedFilterVariables(item.query, user?.id, item.variables || {}));
1346
+ if (resolvedItems.some((item: any) => item.missingVariables.length > 0)) return;
1347
+ finalFilter.resolved_saved_filter_predicate = { $or: resolvedItems.map((item: any) => item.value) };
1348
+ finalFilter.saved_filter_variables = {};
1349
+ finalFilter.saved_filter_items = activeSavedFilters.map((item: any) => ({
1350
+ id: item.data?.id ?? null,
1351
+ systemKey: item.data?.systemKey ?? null,
1352
+ name: item.data?.name ?? null,
1353
+ predicate: item.query,
1354
+ variables: item.variables || {},
1355
+ }));
1356
+ } else if (finalSavedFilter && Object.keys(finalSavedFilter).length > 0) {
1275
1357
  const resolved = resolveSavedFilterVariables(finalSavedFilter, user?.id, currentSavedFilterVariables);
1276
1358
  if (resolved.missingVariables.length > 0) return;
1277
1359
  finalFilter.resolved_saved_filter_predicate = resolved.value;
1278
1360
  finalFilter.saved_filter_variables = currentSavedFilterVariables;
1279
1361
  }
1280
- if (currentSavedFilterData?.id) {
1362
+ if (!allowMultipleSavedFilters && currentSavedFilterData?.id) {
1281
1363
  finalFilter.saved_filter_id = currentSavedFilterData.id;
1282
1364
  }
1283
- if (currentSavedFilterData?.systemKey) {
1365
+ if (!allowMultipleSavedFilters && currentSavedFilterData?.systemKey) {
1284
1366
  finalFilter.saved_filter_system_key = currentSavedFilterData.systemKey;
1285
1367
  }
1286
- if (currentSavedFilterData?.name) {
1368
+ if (!allowMultipleSavedFilters && currentSavedFilterData?.name) {
1287
1369
  finalFilter.saved_filter_name = currentSavedFilterData.name;
1288
1370
  }
1289
1371
  handleApplyCustomFilter(finalFilter, true);
@@ -1315,16 +1397,19 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1315
1397
  // }
1316
1398
 
1317
1399
 
1318
- setSearchChips([]);
1319
- setSearchFilter(null);
1320
- setFilterRules(initialState);
1321
- setCustomFilter(null)
1322
- setPredefinedSearchChip(null);
1323
- setPredefinedSearchBaseFilter(null);
1400
+ if (!allowMultipleSavedFilters) {
1401
+ setSearchChips([]);
1402
+ setSearchFilter(null);
1403
+ setFilterRules(initialState);
1404
+ setCustomFilter(null)
1405
+ setPredefinedSearchChip(null);
1406
+ setPredefinedSearchBaseFilter(null);
1407
+ }
1324
1408
 
1325
1409
  if (savedfilter?.id) {
1326
1410
  const savedfilterId = savedfilter.id;
1327
1411
  setShowOverlay(false);
1412
+ setEditingSavedFilterKey(getSavedFilterKey(savedfilter));
1328
1413
  const currentSavedFilterData: any = availableSavedFilters.find((savedFilter: any) => savedFilter.id === savedfilterId);
1329
1414
  setCurrentSavedFilterData(currentSavedFilterData);
1330
1415
  if (currentSavedFilterData) {
@@ -1346,7 +1431,32 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1346
1431
  }
1347
1432
  }
1348
1433
 
1434
+ const persistActiveSavedFilters = (items: any[]) => {
1435
+ const first = items[0];
1436
+ const persistedFilter = {
1437
+ ...(getFilterObjectFromLocalStorage() || {}),
1438
+ saved_filter_items: items.map((item) => ({
1439
+ id: item.data?.id ?? null,
1440
+ systemKey: item.data?.systemKey ?? null,
1441
+ name: item.data?.name ?? null,
1442
+ predicate: item.query,
1443
+ variables: item.variables || {},
1444
+ })),
1445
+ saved_filter_predicate: items.length > 0 ? (items.length === 1 ? first.query : { $or: items.map((item) => item.query) }) : null,
1446
+ saved_filter_variables: items.length === 1 ? (first.variables || {}) : {},
1447
+ saved_filter_id: items.length === 1 ? first.data?.id ?? null : null,
1448
+ saved_filter_system_key: items.length === 1 ? first.data?.systemKey ?? null : null,
1449
+ saved_filter_name: items.length === 1 ? first.data?.name ?? null : null,
1450
+ };
1451
+ delete persistedFilter.finalFullFilter;
1452
+ setFilterObjectToLocalStorage(persistedFilter);
1453
+ };
1454
+
1349
1455
  const persistActiveSavedFilter = (savedFilter: any, filterJson: any, variables: Record<string, any>) => {
1456
+ if (allowMultipleSavedFilters) {
1457
+ persistActiveSavedFilters([{ data: savedFilter, query: filterJson, variables }]);
1458
+ return;
1459
+ }
1350
1460
  const persistedFilter = {
1351
1461
  ...(getFilterObjectFromLocalStorage() || {}),
1352
1462
  custom_filter_predicate: null,
@@ -1372,6 +1482,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1372
1482
 
1373
1483
  const persistedFilter = {
1374
1484
  ...(getFilterObjectFromLocalStorage() || {}),
1485
+ saved_filter_items: [],
1375
1486
  saved_filter_predicate: null,
1376
1487
  saved_filter_variables: {},
1377
1488
  saved_filter_id: null,
@@ -1386,15 +1497,40 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1386
1497
  };
1387
1498
 
1388
1499
  const deleteSavedFilter = async () => {
1389
- // delte the saved filter with id
1390
- await deleteEntity(savedFilterTobeDeleted);
1391
- if (String(savedFilterTobeDeleted) === String(currentSavedFilterData?.id)) {
1392
- clearActiveSavedFilter();
1500
+ try {
1501
+ await deleteEntity(savedFilterTobeDeleted).unwrap();
1502
+ dispatch(showToast({
1503
+ severity: "success",
1504
+ summary: ERROR_MESSAGES.DELETED,
1505
+ detail: ERROR_MESSAGES.ENTITY_DELETE,
1506
+ }));
1507
+
1508
+ if (allowMultipleSavedFilters) {
1509
+ const nextItems = activeSavedFilters.filter((item: any) => String(item.data?.id) !== String(savedFilterTobeDeleted));
1510
+ setActiveSavedFilters(nextItems);
1511
+ if (String(savedFilterTobeDeleted) === String(currentSavedFilterData?.id)) {
1512
+ setCurrentSavedFilterData(null);
1513
+ setCurrentSavedFilterQuery(null);
1514
+ setCurrentSavedFilterVariables({});
1515
+ }
1516
+ persistActiveSavedFilters(nextItems);
1517
+ setHasSearched(true);
1518
+ setRefreshKey((prev) => prev + 1);
1519
+ } else if (String(savedFilterTobeDeleted) === String(currentSavedFilterData?.id)) {
1520
+ removeSavedFilter();
1521
+ }
1522
+ setIsDeleteSQDialogVisible(false);
1523
+ setTimeout(() => {
1524
+ setSavedFilterFetchDataRefreshKey(prev => prev + 1)
1525
+ }, 500)
1526
+ } catch (error: any) {
1527
+ const errorMessage = error?.data?.message || error?.data?.error || error?.message || ERROR_MESSAGES.SOMETHING_WRONG;
1528
+ dispatch(showToast({
1529
+ severity: "error",
1530
+ summary: ERROR_MESSAGES.DELETE_FAIELD,
1531
+ detail: errorMessage,
1532
+ }));
1393
1533
  }
1394
- setIsDeleteSQDialogVisible(false);
1395
- setTimeout(() => {
1396
- setSavedFilterFetchDataRefreshKey(prev => prev + 1)
1397
- }, 500)
1398
1534
  }
1399
1535
  const handleSaveFilter = async (formValues: any) => {
1400
1536
  setShowSaveFilterPopup(false)
@@ -1411,13 +1547,20 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1411
1547
  formData.append("userId", user?.id);
1412
1548
 
1413
1549
  await updateEntity({ id: +formValues.id, data: formData }).unwrap();
1414
-
1415
- setSearchChips([]);
1416
- setSearchFilter(null);
1417
- setFilterRules(initialState);
1418
- setCustomFilter(null)
1419
- setPredefinedSearchChip(null);
1420
- setPredefinedSearchBaseFilter(null);
1550
+ dispatch(showToast({
1551
+ severity: "success",
1552
+ summary: ERROR_MESSAGES.SAVED_FILTER_UPDATED,
1553
+ detail: ERROR_MESSAGES.SAVED_FILTER_UPDATED_SUCCESSFULLY,
1554
+ }));
1555
+
1556
+ if (!allowMultipleSavedFilters) {
1557
+ setSearchChips([]);
1558
+ setSearchFilter(null);
1559
+ setFilterRules(initialState);
1560
+ setCustomFilter(null)
1561
+ setPredefinedSearchChip(null);
1562
+ setPredefinedSearchBaseFilter(null);
1563
+ }
1421
1564
  // Apply the rename immediately, both to the active filter and to
1422
1565
  // the cached list — otherwise the list still holds the pre-rename
1423
1566
  // entry, which wins the id-based lookup the next time saved-filter
@@ -1427,7 +1570,17 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1427
1570
  setSavedFilters((prev) => prev.map((f: any) => String(f?.id) === String(formValues.id) ? { ...f, ...savedFilterRecord } : f));
1428
1571
  setCurrentSavedFilterData(savedFilterRecord);
1429
1572
  setCurrentSavedFilterQuery(filterJson);
1430
- persistActiveSavedFilter(savedFilterRecord, filterJson, currentSavedFilterVariables);
1573
+ if (allowMultipleSavedFilters) {
1574
+ const nextItems = activeSavedFilters.map((item: any) =>
1575
+ getSavedFilterKey(item.data) === getSavedFilterKey(savedFilterRecord)
1576
+ ? { data: savedFilterRecord, query: filterJson, variables: currentSavedFilterVariables }
1577
+ : item
1578
+ );
1579
+ setActiveSavedFilters(nextItems);
1580
+ persistActiveSavedFilters(nextItems);
1581
+ } else {
1582
+ persistActiveSavedFilter(savedFilterRecord, filterJson, currentSavedFilterVariables);
1583
+ }
1431
1584
  setHasSearched(true);
1432
1585
  setRefreshKey((prev) => prev + 1);
1433
1586
  } else {
@@ -1441,6 +1594,11 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1441
1594
  formData.append("isPrivate", formValues.isPrivate);
1442
1595
  formData.append("userId", user?.id);
1443
1596
  const result = await createEntity(formData).unwrap();
1597
+ dispatch(showToast({
1598
+ severity: "success",
1599
+ summary: ERROR_MESSAGES.SAVED_FILTER_CREATED,
1600
+ detail: ERROR_MESSAGES.SAVED_FILTER_CREATED_SUCCESSFULLY,
1601
+ }));
1444
1602
 
1445
1603
  setSearchChips([]);
1446
1604
  setSearchFilter(null);
@@ -1450,17 +1608,31 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1450
1608
  setPredefinedSearchBaseFilter(null);
1451
1609
  // The custom filter just became this saved filter — apply it, and
1452
1610
  // seed the cached list with it, immediately (see rename branch above).
1611
+ // A newly created saved filter replaces any other active filters/saved
1612
+ // filters rather than joining them — only applying an existing saved
1613
+ // filter is additive when allowMultipleSavedFilters is on.
1453
1614
  const savedFilterRecord = { id: result.data.id, name: formValues.name, isPrivate: formValues.isPrivate, filterQueryJson: JSON.stringify(filterJson, null, 2) };
1454
1615
  setSavedFilters((prev) => [savedFilterRecord, ...prev]);
1455
1616
  setCurrentSavedFilterData(savedFilterRecord);
1456
1617
  setCurrentSavedFilterQuery(filterJson);
1457
1618
  setCurrentSavedFilterVariables({});
1458
- persistActiveSavedFilter(savedFilterRecord, filterJson, {});
1619
+ if (allowMultipleSavedFilters) {
1620
+ const nextItems = [{ data: savedFilterRecord, query: filterJson, variables: {} }];
1621
+ setActiveSavedFilters(nextItems);
1622
+ persistActiveSavedFilters(nextItems);
1623
+ } else {
1624
+ persistActiveSavedFilter(savedFilterRecord, filterJson, {});
1625
+ }
1459
1626
  setHasSearched(true);
1460
1627
  setRefreshKey((prev) => prev + 1);
1461
1628
  }
1462
- } catch (error) {
1463
-
1629
+ } catch (error: any) {
1630
+ const errorMessage = error?.data?.message || error?.data?.error || error?.message || ERROR_MESSAGES.SOMETHING_WRONG;
1631
+ dispatch(showToast({
1632
+ severity: "error",
1633
+ summary: ERROR_MESSAGES.ERROR,
1634
+ detail: errorMessage,
1635
+ }));
1464
1636
  }
1465
1637
  }
1466
1638
 
@@ -1553,6 +1725,28 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1553
1725
  return false;
1554
1726
  }
1555
1727
 
1728
+ if (allowMultipleSavedFilters) {
1729
+ const key = getSavedFilterKey(savedfilter);
1730
+ const nextItem = { data: savedfilter, query: filterJson, variables };
1731
+ const existingItem = activeSavedFilters.find((item: any) => getSavedFilterKey(item.data) === key);
1732
+ if (existingItem && areFilterStateValuesEqual(existingItem.query, filterJson) && areFilterStateValuesEqual(existingItem.variables, variables)) {
1733
+ return true;
1734
+ }
1735
+ const nextItems = existingItem
1736
+ ? activeSavedFilters.map((item: any) => getSavedFilterKey(item.data) === key ? nextItem : item)
1737
+ : [...activeSavedFilters, nextItem];
1738
+ setActiveSavedFilters(nextItems);
1739
+ setCurrentSavedFilterData(savedfilter);
1740
+ setCurrentSavedFilterQuery(filterJson);
1741
+ setCurrentSavedFilterVariables(variables);
1742
+ setEditingSavedFilterKey(null);
1743
+ persistActiveSavedFilters(nextItems);
1744
+ setShowOverlay(false);
1745
+ setHasSearched(true);
1746
+ setRefreshKey((prev) => prev + 1);
1747
+ return true;
1748
+ }
1749
+
1556
1750
  // Applying the already-active filter must be a no-op. This is important
1557
1751
  // for lifecycle extensions: applying a filter causes a new list load,
1558
1752
  // which invokes onBeforeListDataLoad/onListLoad again.
@@ -1589,6 +1783,19 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1589
1783
  }
1590
1784
 
1591
1785
  const removeSavedFilter = () => {
1786
+ if (allowMultipleSavedFilters) {
1787
+ const key = getSavedFilterKey(currentSavedFilterData);
1788
+ const nextItems = activeSavedFilters.filter((item: any) => getSavedFilterKey(item.data) !== key);
1789
+ setActiveSavedFilters(nextItems);
1790
+ setCurrentSavedFilterData(null);
1791
+ setCurrentSavedFilterQuery(null);
1792
+ setCurrentSavedFilterVariables({});
1793
+ setEditingSavedFilterKey(null);
1794
+ persistActiveSavedFilters(nextItems);
1795
+ setHasSearched(true);
1796
+ setRefreshKey((prev) => prev + 1);
1797
+ return;
1798
+ }
1592
1799
  clearActiveSavedFilter();
1593
1800
  }
1594
1801
 
@@ -1698,14 +1905,35 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1698
1905
  const managedChipItems = useMemo<ManagedChipItem[]>(() => {
1699
1906
  const items: ManagedChipItem[] = [];
1700
1907
 
1701
- if (currentSavedFilterData?.name) {
1702
- items.push({
1703
- id: "saved-filter",
1704
- type: "saved",
1705
- label: `Saved: ${currentSavedFilterData.name}`,
1706
- onRemove: () => removeSavedFilter(),
1707
- });
1708
- }
1908
+ const savedItems = allowMultipleSavedFilters
1909
+ ? activeSavedFilters
1910
+ : (currentSavedFilterData?.name ? [{ data: currentSavedFilterData }] : []);
1911
+ savedItems.forEach((item: any) => {
1912
+ if (item.data?.name) {
1913
+ items.push({
1914
+ id: `saved-filter:${getSavedFilterKey(item.data)}`,
1915
+ type: "saved",
1916
+ label: `Saved: ${item.data.name}`,
1917
+ onRemove: () => {
1918
+ if (!allowMultipleSavedFilters) {
1919
+ removeSavedFilter();
1920
+ return;
1921
+ }
1922
+ const nextItems = activeSavedFilters.filter((active: any) => getSavedFilterKey(active.data) !== getSavedFilterKey(item.data));
1923
+ setActiveSavedFilters(nextItems);
1924
+ if (getSavedFilterKey(currentSavedFilterData) === getSavedFilterKey(item.data)) {
1925
+ setCurrentSavedFilterData(null);
1926
+ setCurrentSavedFilterQuery(null);
1927
+ setCurrentSavedFilterVariables({});
1928
+ }
1929
+ persistActiveSavedFilters(nextItems);
1930
+ setHasSearched(true);
1931
+ setRefreshKey((prev) => prev + 1);
1932
+ },
1933
+ ...(allowMultipleSavedFilters ? { onOpen: () => openSavedCustomFilter(item.data) } : {}),
1934
+ });
1935
+ }
1936
+ });
1709
1937
 
1710
1938
  if (predefinedSearchChip?.name && predefinedSearchChip?.value) {
1711
1939
  items.push({
@@ -1751,6 +1979,8 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1751
1979
  return items;
1752
1980
  }, [
1753
1981
  currentSavedFilterData,
1982
+ activeSavedFilters,
1983
+ allowMultipleSavedFilters,
1754
1984
  predefinedSearchChip,
1755
1985
  customFilter,
1756
1986
  customRuleCount,
@@ -1987,7 +2217,6 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
1987
2217
  const removePredefinedSearchChip = () => {
1988
2218
  setPredefinedSearchChip(null);
1989
2219
  setPredefinedSearchBaseFilter(null)
1990
- setCustomFilter(null);
1991
2220
  setHasSearched(true);
1992
2221
  setRefreshKey((prev) => prev + 1)
1993
2222
  };
@@ -2192,6 +2421,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
2192
2421
  className={`text-color solid-search-overlay-option ${focusedIndex === optionIndex ? "solid-search-overlay-option-active" : ""}`}
2193
2422
  onMouseDown={(e) => {
2194
2423
  e.preventDefault();
2424
+ e.stopPropagation();
2425
+ }}
2426
+ onClick={(e) => {
2427
+ e.stopPropagation();
2195
2428
  handlePredefinedSearch(predefinedSearch);
2196
2429
  }}
2197
2430
  onMouseEnter={() => setFocusedIndex(optionIndex)}
@@ -2234,6 +2467,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
2234
2467
  className={`text-color solid-search-overlay-option ${focusedIndex === optionIndex ? "solid-search-overlay-option-active" : ""}`}
2235
2468
  onMouseDown={(e) => {
2236
2469
  e.preventDefault();
2470
+ e.stopPropagation();
2471
+ }}
2472
+ onClick={(e) => {
2473
+ e.stopPropagation();
2237
2474
  const currentValue = inputValue?.trim();
2238
2475
  if (currentValue) {
2239
2476
  const values = currentValue.split(",").map((v) => v.trim()).filter((v) => v !== "");
@@ -2317,6 +2554,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
2317
2554
  key={savedfilter.id}
2318
2555
  savedfilter={savedfilter}
2319
2556
  activeSavedFilterReference={currentSavedFilterData?.systemKey || currentSavedFilterData?.id}
2557
+ activeSavedFilterReferences={allowMultipleSavedFilters ? activeSavedFilters.map((item: any) => getSavedFilterKey(item.data)) : undefined}
2320
2558
  applySavedFilter={applySavedFilter}
2321
2559
  openSavedCustomFilter={openSavedCustomFilter}
2322
2560
  setSavedFilterTobeDeleted={setSavedFilterTobeDeleted}
@@ -200,3 +200,92 @@
200
200
  .solid-json-editor.is-readonly .ace-jsoneditor .ace_cursor {
201
201
  opacity: 0.45;
202
202
  }
203
+
204
+ /* Keep the code editor surface aligned with the application theme. The
205
+ datasource introspection Review Mapping tab shares this editor with the
206
+ model and module explorers. */
207
+ html.dark .solid-json-editor .ace-jsoneditor,
208
+ html.dark .solid-json-editor .ace-jsoneditor .ace_gutter,
209
+ html.dark .solid-json-editor .ace-jsoneditor .ace_scroller,
210
+ html.dark .solid-json-editor .ace-jsoneditor .ace_content,
211
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor,
212
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_gutter,
213
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_scroller,
214
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_content {
215
+ background: var(--surface-ground, var(--background)) !important;
216
+ color: var(--foreground) !important;
217
+ }
218
+
219
+ html.dark .solid-json-editor .ace-jsoneditor .ace_gutter,
220
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_gutter {
221
+ color: var(--muted-foreground) !important;
222
+ border-right: 1px solid var(--border);
223
+ }
224
+
225
+ html.dark .solid-json-editor .ace-jsoneditor .ace_gutter-active-line,
226
+ html.dark .solid-json-editor .ace-jsoneditor .ace_active-line,
227
+ html.dark .solid-json-editor .ace-jsoneditor .ace_marker-layer .ace_active-line,
228
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_gutter-active-line,
229
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_active-line,
230
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_marker-layer .ace_active-line {
231
+ background: color-mix(in srgb, var(--foreground) 10%, transparent) !important;
232
+ }
233
+
234
+ html.dark .solid-json-editor .ace-jsoneditor .ace_marker-layer .ace_selection,
235
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_marker-layer .ace_selection {
236
+ background: color-mix(in srgb, var(--primary) 28%, transparent) !important;
237
+ }
238
+
239
+ html.dark .solid-json-editor .ace-jsoneditor .ace_marker-layer .ace_bracket,
240
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_marker-layer .ace_bracket {
241
+ border: 1px solid color-mix(in srgb, var(--foreground) 58%, transparent) !important;
242
+ }
243
+
244
+ html.dark .solid-json-editor .ace-jsoneditor .ace_cursor,
245
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_cursor {
246
+ border-left-color: var(--foreground) !important;
247
+ }
248
+
249
+ html.dark .solid-json-editor .ace-jsoneditor .ace_print-margin,
250
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_print-margin {
251
+ background: color-mix(in srgb, var(--foreground) 10%, transparent) !important;
252
+ }
253
+
254
+ html.dark .solid-json-editor .ace-jsoneditor .ace_constant,
255
+ html.dark .solid-json-editor .ace-jsoneditor .ace_constant.ace_numeric,
256
+ html.dark .solid-json-editor .ace-jsoneditor .ace_constant.ace_language,
257
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_constant,
258
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_constant.ace_numeric,
259
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_constant.ace_language {
260
+ color: var(--primary) !important;
261
+ }
262
+
263
+ html.dark .solid-json-editor .ace-jsoneditor .ace_string,
264
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_string {
265
+ color: color-mix(in srgb, var(--primary) 68%, var(--foreground)) !important;
266
+ }
267
+
268
+ html.dark .solid-json-editor .ace-jsoneditor .ace_keyword,
269
+ html.dark .solid-json-editor .ace-jsoneditor .ace_meta,
270
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_keyword,
271
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_meta {
272
+ color: color-mix(in srgb, var(--primary) 72%, var(--foreground)) !important;
273
+ }
274
+
275
+ html.dark .solid-json-editor .ace-jsoneditor .ace_variable,
276
+ html.dark .solid-json-editor .ace-jsoneditor .ace_variable.ace_parameter,
277
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_variable,
278
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_variable.ace_parameter {
279
+ color: color-mix(in srgb, var(--destructive) 72%, var(--foreground)) !important;
280
+ }
281
+
282
+ html.dark .solid-json-editor .ace-jsoneditor .ace_comment,
283
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_comment {
284
+ color: var(--muted-foreground) !important;
285
+ }
286
+
287
+ html.dark .solid-json-editor .ace-jsoneditor .ace_invalid,
288
+ [data-theme="dark"] .solid-json-editor .ace-jsoneditor .ace_invalid {
289
+ color: var(--destructive) !important;
290
+ background: color-mix(in srgb, var(--destructive) 28%, transparent) !important;
291
+ }
@@ -9,6 +9,7 @@ type SolidKanbanFilterInput = {
9
9
  custom_filter_predicate?: any;
10
10
  search_predicate?: any;
11
11
  saved_filter_predicate?: any;
12
+ saved_filter_items?: any[];
12
13
  predefined_search_predicate?: any;
13
14
  };
14
15
  export type SolidKanbanViewHandle = {
@@ -1 +1 @@
1
- {"version":3,"file":"SolidKanbanView.d.ts","sourceRoot":"","sources":["../../../../src/components/core/kanban/SolidKanbanView.tsx"],"names":[],"mappings":";AAkDA,KAAK,qBAAqB,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,uBAAuB,CAAC,EAAE,GAAG,CAAC;IAC9B,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,sBAAsB,CAAC,EAAE,GAAG,CAAC;IAC7B,2BAA2B,CAAC,EAAE,GAAG,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACtD,eAAe,EAAE,MAAM,GAAG,EAAE,CAAC;IAC7B,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;IAC7E,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,QAAQ,EAAE,MAAM;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,OAAO,CAAC;QACtB,OAAO,EAAE,GAAG,CAAC;QACb,gBAAgB,EAAE,GAAG,CAAC;QACtB,UAAU,EAAE,GAAG,EAAE,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;CACH,CAAC;AAqEF,eAAO,MAAM,eAAe,yHAqpC1B,CAAC"}
1
+ {"version":3,"file":"SolidKanbanView.d.ts","sourceRoot":"","sources":["../../../../src/components/core/kanban/SolidKanbanView.tsx"],"names":[],"mappings":";AAkDA,KAAK,qBAAqB,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,uBAAuB,CAAC,EAAE,GAAG,CAAC;IAC9B,gBAAgB,CAAC,EAAE,GAAG,CAAC;IACvB,sBAAsB,CAAC,EAAE,GAAG,CAAC;IAC7B,kBAAkB,CAAC,EAAE,GAAG,EAAE,CAAC;IAC3B,2BAA2B,CAAC,EAAE,GAAG,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACtD,eAAe,EAAE,MAAM,GAAG,EAAE,CAAC;IAC7B,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC;IAC7E,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,QAAQ,EAAE,MAAM;QACd,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,OAAO,CAAC;QACtB,OAAO,EAAE,GAAG,CAAC;QACb,gBAAgB,EAAE,GAAG,CAAC;QACtB,UAAU,EAAE,GAAG,EAAE,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;CACH,CAAC;AAqEF,eAAO,MAAM,eAAe,yHAupC1B,CAAC"}