@solidxai/core-ui 0.1.14 → 0.1.15-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.
- package/dist/components/auth/AuthLayout.js +1 -1
- package/dist/components/auth/AuthLayout.js.map +1 -1
- package/dist/components/auth/AuthLayout.tsx +2 -2
- package/dist/components/common/DropzonePlaceholder.d.ts +2 -1
- package/dist/components/common/DropzonePlaceholder.d.ts.map +1 -1
- package/dist/components/common/DropzonePlaceholder.js +10 -7
- package/dist/components/common/DropzonePlaceholder.js.map +1 -1
- package/dist/components/common/DropzonePlaceholder.tsx +14 -9
- package/dist/components/common/SettingsComponent.module.css +4 -1
- package/dist/components/common/SolidFormStepper.d.ts.map +1 -1
- package/dist/components/common/SolidFormStepper.js +7 -6
- package/dist/components/common/SolidFormStepper.js.map +1 -1
- package/dist/components/common/SolidFormStepper.tsx +7 -1
- package/dist/components/common/SolidThemeProvider.d.ts.map +1 -1
- package/dist/components/common/SolidThemeProvider.js +127 -11
- package/dist/components/common/SolidThemeProvider.js.map +1 -1
- package/dist/components/common/SolidThemeProvider.tsx +104 -22
- package/dist/components/core/card/CardGrid.d.ts +1 -1
- package/dist/components/core/card/CardGrid.d.ts.map +1 -1
- package/dist/components/core/card/CardGrid.js +2 -2
- package/dist/components/core/card/CardGrid.js.map +1 -1
- package/dist/components/core/card/CardGrid.tsx +4 -0
- package/dist/components/core/card/CardItem.d.ts +2 -0
- package/dist/components/core/card/CardItem.d.ts.map +1 -1
- package/dist/components/core/card/CardItem.js +5 -4
- package/dist/components/core/card/CardItem.js.map +1 -1
- package/dist/components/core/card/CardItem.tsx +16 -1
- package/dist/components/core/card/SolidCardView.d.ts +28 -1
- package/dist/components/core/card/SolidCardView.d.ts.map +1 -1
- package/dist/components/core/card/SolidCardView.js +99 -44
- package/dist/components/core/card/SolidCardView.js.map +1 -1
- package/dist/components/core/card/SolidCardView.tsx +119 -7
- package/dist/components/core/card/SolidCardViewConfigure.d.ts +1 -1
- package/dist/components/core/card/SolidCardViewConfigure.d.ts.map +1 -1
- package/dist/components/core/card/SolidCardViewConfigure.js +15 -6
- package/dist/components/core/card/SolidCardViewConfigure.js.map +1 -1
- package/dist/components/core/card/SolidCardViewConfigure.tsx +34 -0
- package/dist/components/core/common/PDFViewer.d.ts +9 -5
- package/dist/components/core/common/PDFViewer.d.ts.map +1 -1
- package/dist/components/core/common/PDFViewer.js +10 -102
- package/dist/components/core/common/PDFViewer.js.map +1 -1
- package/dist/components/core/common/PDFViewer.tsx +29 -88
- package/dist/components/core/common/SolidCreateButton.d.ts.map +1 -1
- package/dist/components/core/common/SolidCreateButton.js +2 -2
- package/dist/components/core/common/SolidCreateButton.js.map +1 -1
- package/dist/components/core/common/SolidCreateButton.tsx +22 -2
- package/dist/components/core/common/SolidGlobalSearchElement.d.ts.map +1 -1
- package/dist/components/core/common/SolidGlobalSearchElement.js +216 -198
- package/dist/components/core/common/SolidGlobalSearchElement.js.map +1 -1
- package/dist/components/core/common/SolidGlobalSearchElement.tsx +287 -216
- package/dist/components/core/form/SolidFormActionHeader.d.ts.map +1 -1
- package/dist/components/core/form/SolidFormActionHeader.js +21 -21
- package/dist/components/core/form/SolidFormActionHeader.js.map +1 -1
- package/dist/components/core/form/SolidFormActionHeader.tsx +62 -53
- package/dist/components/core/form/SolidFormView.d.ts.map +1 -1
- package/dist/components/core/form/SolidFormView.js +101 -74
- package/dist/components/core/form/SolidFormView.js.map +1 -1
- package/dist/components/core/form/SolidFormView.tsx +46 -19
- package/dist/components/core/form/fields/SolidMediaMultipleField.d.ts.map +1 -1
- package/dist/components/core/form/fields/SolidMediaMultipleField.js +3 -2
- package/dist/components/core/form/fields/SolidMediaMultipleField.js.map +1 -1
- package/dist/components/core/form/fields/SolidMediaMultipleField.tsx +3 -1
- package/dist/components/core/form/fields/SolidMediaSingleField.d.ts.map +1 -1
- package/dist/components/core/form/fields/SolidMediaSingleField.js +3 -2
- package/dist/components/core/form/fields/SolidMediaSingleField.js.map +1 -1
- package/dist/components/core/form/fields/SolidMediaSingleField.tsx +3 -1
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.d.ts +1 -0
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.d.ts.map +1 -1
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.js +20 -2
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.js.map +1 -1
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.tsx +29 -2
- package/dist/components/core/form/fields/solidFields.module.css +7 -0
- package/dist/components/core/kanban/KanbanBoard.d.ts +1 -1
- package/dist/components/core/kanban/KanbanBoard.d.ts.map +1 -1
- package/dist/components/core/kanban/KanbanBoard.js +2 -2
- package/dist/components/core/kanban/KanbanBoard.js.map +1 -1
- package/dist/components/core/kanban/KanbanBoard.tsx +3 -1
- package/dist/components/core/kanban/KanbanCard.d.ts +2 -0
- package/dist/components/core/kanban/KanbanCard.d.ts.map +1 -1
- package/dist/components/core/kanban/KanbanCard.js +5 -3
- package/dist/components/core/kanban/KanbanCard.js.map +1 -1
- package/dist/components/core/kanban/KanbanCard.tsx +15 -2
- package/dist/components/core/kanban/KanbanColumn.d.ts +3 -1
- package/dist/components/core/kanban/KanbanColumn.d.ts.map +1 -1
- package/dist/components/core/kanban/KanbanColumn.js +2 -2
- package/dist/components/core/kanban/KanbanColumn.js.map +1 -1
- package/dist/components/core/kanban/KanbanColumn.tsx +4 -2
- package/dist/components/core/kanban/SolidKanbanView.d.ts +25 -1
- package/dist/components/core/kanban/SolidKanbanView.d.ts.map +1 -1
- package/dist/components/core/kanban/SolidKanbanView.js +95 -50
- package/dist/components/core/kanban/SolidKanbanView.js.map +1 -1
- package/dist/components/core/kanban/SolidKanbanView.tsx +87 -9
- package/dist/components/core/list/PLAN.md +1 -1
- package/dist/components/core/list/SolidListView.d.ts +11 -0
- package/dist/components/core/list/SolidListView.d.ts.map +1 -1
- package/dist/components/core/list/SolidListView.js +51 -90
- package/dist/components/core/list/SolidListView.js.map +1 -1
- package/dist/components/core/list/SolidListView.tsx +77 -112
- package/dist/components/core/list/SolidListViewColumn.d.ts +2 -1
- package/dist/components/core/list/SolidListViewColumn.d.ts.map +1 -1
- package/dist/components/core/list/SolidListViewColumn.js +2 -2
- package/dist/components/core/list/SolidListViewColumn.js.map +1 -1
- package/dist/components/core/list/SolidListViewColumn.tsx +4 -3
- package/dist/components/core/list/SolidListViewConfigure.js +1 -1
- package/dist/components/core/list/SolidListViewConfigure.js.map +1 -1
- package/dist/components/core/list/SolidListViewConfigure.tsx +1 -1
- package/dist/components/core/list/columns/SolidRelationColumn.d.ts +1 -1
- package/dist/components/core/list/columns/SolidRelationColumn.d.ts.map +1 -1
- package/dist/components/core/list/columns/SolidRelationColumn.js +2 -2
- package/dist/components/core/list/columns/SolidRelationColumn.js.map +1 -1
- package/dist/components/core/list/columns/SolidRelationColumn.tsx +2 -2
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.d.ts +2 -2
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.d.ts.map +1 -1
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.js +5 -4
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.js.map +1 -1
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.tsx +5 -4
- package/dist/components/core/model/CreateModel.d.ts.map +1 -1
- package/dist/components/core/model/CreateModel.js +2 -1
- package/dist/components/core/model/CreateModel.js.map +1 -1
- package/dist/components/core/model/CreateModel.tsx +2 -0
- package/dist/components/core/model/FieldMetaData.d.ts +1 -1
- package/dist/components/core/model/FieldMetaData.d.ts.map +1 -1
- package/dist/components/core/model/FieldMetaData.js +8 -8
- package/dist/components/core/model/FieldMetaData.js.map +1 -1
- package/dist/components/core/model/FieldMetaData.tsx +2 -2
- package/dist/components/core/model/FieldMetaDataForm.css +119 -0
- package/dist/components/core/model/FieldMetaDataForm.d.ts.map +1 -1
- package/dist/components/core/model/FieldMetaDataForm.js +147 -97
- package/dist/components/core/model/FieldMetaDataForm.js.map +1 -1
- package/dist/components/core/model/FieldMetaDataForm.tsx +124 -32
- package/dist/components/core/module/ModuleMetadataExplorer.css +64 -0
- package/dist/components/core/module/ModulePackageDialog.css +117 -12
- package/dist/components/core/tree/SolidTreeView.d.ts.map +1 -1
- package/dist/components/core/tree/SolidTreeView.js +6 -3
- package/dist/components/core/tree/SolidTreeView.js.map +1 -1
- package/dist/components/core/tree/SolidTreeView.tsx +27 -13
- package/dist/components/layout/AdminHeaderActions.d.ts.map +1 -1
- package/dist/components/layout/AdminHeaderActions.js +16 -11
- package/dist/components/layout/AdminHeaderActions.js.map +1 -1
- package/dist/components/layout/AdminHeaderActions.tsx +16 -9
- package/dist/components/layout/AdminTopHeader.d.ts.map +1 -1
- package/dist/components/layout/AdminTopHeader.js +1 -18
- package/dist/components/layout/AdminTopHeader.js.map +1 -1
- package/dist/components/layout/AdminTopHeader.tsx +3 -21
- package/dist/helpers/getAcceptedFileTypes.d.ts +12 -2
- package/dist/helpers/getAcceptedFileTypes.d.ts.map +1 -1
- package/dist/helpers/getAcceptedFileTypes.js +72 -16
- package/dist/helpers/getAcceptedFileTypes.js.map +1 -1
- package/dist/helpers/getAcceptedFileTypes.tsx +101 -18
- package/dist/helpers/registry.js +2 -1
- package/dist/helpers/registry.js.map +1 -1
- package/dist/helpers/registry.ts +3 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/index.ts +6 -2
- package/dist/resources/globals.css +274 -109
- package/dist/resources/solid-custom.css +11 -0
- package/dist/resources/solid-responsive.css +1 -5
- package/dist/resources/solid-tailwind-generated.css +1 -1
- package/dist/routes/pages/admin/core/CardPage.d.ts.map +1 -1
- package/dist/routes/pages/admin/core/CardPage.js +15 -2
- package/dist/routes/pages/admin/core/CardPage.js.map +1 -1
- package/dist/routes/pages/admin/core/CardPage.tsx +17 -2
- package/dist/routes/pages/admin/core/DashboardPage.d.ts.map +1 -1
- package/dist/routes/pages/admin/core/DashboardPage.js +31 -3
- package/dist/routes/pages/admin/core/DashboardPage.js.map +1 -1
- package/dist/routes/pages/admin/core/DashboardPage.tsx +39 -11
- package/dist/routes/pages/admin/core/KanbanPage.d.ts.map +1 -1
- package/dist/routes/pages/admin/core/KanbanPage.js +15 -2
- package/dist/routes/pages/admin/core/KanbanPage.js.map +1 -1
- package/dist/routes/pages/admin/core/KanbanPage.tsx +17 -2
- package/dist/routes/pages/admin/core/ListPage.d.ts.map +1 -1
- package/dist/routes/pages/admin/core/ListPage.js +5 -13
- package/dist/routes/pages/admin/core/ListPage.js.map +1 -1
- package/dist/routes/pages/admin/core/ListPage.tsx +5 -15
- package/dist/types/index.d.ts +2 -4
- package/dist/types/solid-core.d.ts +12 -13
- package/package.json +1 -1
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
1
|
+
import React, { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
2
2
|
import FilterComponent, { FilterOperator, FilterRule, FilterRuleType } from "../../../components/core/common/FilterComponent";
|
|
3
3
|
import { toTitleCase } from "../../../helpers/helpers";
|
|
4
|
-
import {
|
|
5
|
-
import { useRouter } from "../../../hooks/useRouter";
|
|
6
|
-
import { useSearchParams } from "../../../hooks/useSearchParams";
|
|
7
|
-
import {clearFilterObjectFromLocalStorage,getFilterObjectFromLocalStorage,
|
|
4
|
+
import {clearFilterObjectFromLocalStorage,getFilterObjectFromLocalStorage,setFilterObjectToLocalStorage,
|
|
8
5
|
} from "./globalSearchPersistence";
|
|
9
6
|
import { createSolidEntityApi } from "../../../redux/api/solidEntityApi";
|
|
10
7
|
import qs from "qs";
|
|
@@ -12,8 +9,9 @@ import { SolidSaveCustomFilterForm } from "./SolidSaveCustomFilterForm";
|
|
|
12
9
|
import { ERROR_MESSAGES } from "../../../constants/error-messages";
|
|
13
10
|
import { hydrateRelationRules } from "../../../helpers/hydrateRelationRules";
|
|
14
11
|
import { useSession } from '../../../hooks/useSession'
|
|
12
|
+
import { getUnboundSavedFilterVariableNames, parseSavedFilterQueryJson, resolveSavedFilterVariables } from '../../../helpers/resolveActiveUserId';
|
|
15
13
|
import GroupingComponent, { AggregationRule, GroupingRule, DateGroupingFormat } from "./GroupingComponent";
|
|
16
|
-
import { Bookmark, Check, Filter,
|
|
14
|
+
import { Bookmark, Check, Filter, Pencil, Plus, Search, SearchX, Sparkles, Trash2, X } from "lucide-react";
|
|
17
15
|
import { SolidButton } from "../../shad-cn-ui/SolidButton";
|
|
18
16
|
import { SolidInput } from "../../shad-cn-ui/SolidInput";
|
|
19
17
|
import {
|
|
@@ -420,8 +418,10 @@ export const mergeAllDiffFilters = (customFilter: any, searchFilter: any, savedF
|
|
|
420
418
|
return filters;
|
|
421
419
|
}
|
|
422
420
|
|
|
423
|
-
const SavedFilterList = ({ savedfilter,
|
|
424
|
-
const isActive = savedfilter?.systemKey
|
|
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);
|
|
425
425
|
|
|
426
426
|
return (
|
|
427
427
|
<div className="solid-saved-filter-item" onMouseEnter={onMouseEnter}>
|
|
@@ -430,13 +430,32 @@ const SavedFilterList = ({ savedfilter, activeSavedFilter, applySavedFilter, ope
|
|
|
430
430
|
variant="ghost"
|
|
431
431
|
size="sm"
|
|
432
432
|
className={`solid-saved-filter-main w-full ${isActive ? "is-active" : ""} ${isFocused ? "solid-search-overlay-option-active" : ""}`}
|
|
433
|
+
id={optionId}
|
|
434
|
+
role="option"
|
|
435
|
+
aria-selected={isFocused}
|
|
436
|
+
aria-label={`${savedfilter.name}${isActive ? ", applied" : ""}`}
|
|
433
437
|
onMouseDown={(e) => {
|
|
434
438
|
e.preventDefault();
|
|
435
439
|
applySavedFilter(savedfilter);
|
|
436
440
|
}}
|
|
437
441
|
title={savedfilter?.description}
|
|
438
442
|
>
|
|
439
|
-
|
|
443
|
+
<span className="solid-search-overlay-option-content">
|
|
444
|
+
<span className="solid-search-overlay-option-icon" aria-hidden="true">
|
|
445
|
+
<Bookmark size={15} />
|
|
446
|
+
</span>
|
|
447
|
+
<span className="solid-search-overlay-option-copy">
|
|
448
|
+
<span className="solid-search-overlay-option-title">{savedfilter.name}</span>
|
|
449
|
+
{savedfilter?.description && (
|
|
450
|
+
<span className="solid-search-overlay-option-description">{savedfilter.description}</span>
|
|
451
|
+
)}
|
|
452
|
+
</span>
|
|
453
|
+
{isActive && (
|
|
454
|
+
<span className="solid-search-overlay-option-check" aria-hidden="true">
|
|
455
|
+
<Check size={14} />
|
|
456
|
+
</span>
|
|
457
|
+
)}
|
|
458
|
+
</span>
|
|
440
459
|
</SolidButton>
|
|
441
460
|
</div>
|
|
442
461
|
<div className="solid-saved-filter-actions">
|
|
@@ -450,6 +469,7 @@ const SavedFilterList = ({ savedfilter, activeSavedFilter, applySavedFilter, ope
|
|
|
450
469
|
e.preventDefault();
|
|
451
470
|
openSavedCustomFilter(savedfilter);
|
|
452
471
|
}}
|
|
472
|
+
aria-label={`Edit ${savedfilter.name}`}
|
|
453
473
|
>
|
|
454
474
|
<Pencil size={14} />
|
|
455
475
|
</SolidButton>
|
|
@@ -462,6 +482,7 @@ const SavedFilterList = ({ savedfilter, activeSavedFilter, applySavedFilter, ope
|
|
|
462
482
|
setSavedFilterTobeDeleted(savedfilter.id),
|
|
463
483
|
setIsDeleteSQDialogVisible(true);
|
|
464
484
|
}}
|
|
485
|
+
aria-label={`Delete ${savedfilter.name}`}
|
|
465
486
|
>
|
|
466
487
|
<Trash2 size={14} />
|
|
467
488
|
</SolidButton>
|
|
@@ -550,16 +571,19 @@ type RelationCache = Map<string, { label: string; value: number }>;
|
|
|
550
571
|
|
|
551
572
|
|
|
552
573
|
|
|
553
|
-
export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handleApplyCustomFilter, showSaveFilterPopup, setShowSaveFilterPopup, filterPredicates
|
|
574
|
+
export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handleApplyCustomFilter, showSaveFilterPopup, setShowSaveFilterPopup, filterPredicates }: any, ref) => {
|
|
554
575
|
type OverlayOption =
|
|
555
576
|
| { id: string; kind: "field"; field: any }
|
|
556
577
|
| { id: string; kind: "predefined"; predefined: any }
|
|
557
578
|
| { id: string; kind: "saved"; saved: any }
|
|
558
579
|
| { id: string; kind: "custom" }
|
|
559
580
|
| { id: string; kind: "grouping" };
|
|
581
|
+
const overlayInstanceId = useId();
|
|
582
|
+
const overlayListboxId = `${overlayInstanceId}-search-options`;
|
|
583
|
+
const getOverlayOptionId = (index: number) => `${overlayInstanceId}-search-option-${index}`;
|
|
560
584
|
type ManagedChipItem = {
|
|
561
585
|
id: string;
|
|
562
|
-
type: "saved" | "search" | "predefined" | "custom" | "grouping"
|
|
586
|
+
type: "saved" | "search" | "predefined" | "custom" | "grouping";
|
|
563
587
|
label: string;
|
|
564
588
|
onRemove: () => void;
|
|
565
589
|
onOpen?: () => void;
|
|
@@ -608,18 +632,11 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
608
632
|
];
|
|
609
633
|
|
|
610
634
|
const [initialState, setInitialState] = useState(defaultState);
|
|
611
|
-
const pathname = usePathname();
|
|
612
635
|
const searchableEntityLabel =
|
|
613
636
|
viewData?.data?.solidView?.displayName ||
|
|
614
637
|
viewData?.data?.solidView?.model?.displayName ||
|
|
615
638
|
"records";
|
|
616
639
|
|
|
617
|
-
|
|
618
|
-
const searchParams = useSearchParams() // Converts the query params to a string
|
|
619
|
-
const activeSavedFilter = searchParams?.get("savedQuery");
|
|
620
|
-
|
|
621
|
-
const router = useRouter();
|
|
622
|
-
|
|
623
640
|
const chipsRef = useRef<HTMLDivElement | null | any>(null);
|
|
624
641
|
|
|
625
642
|
// filterRules is used to maintian the ui of custom filter
|
|
@@ -662,6 +679,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
662
679
|
// currentSavedFilterData is used to save the whole object of saved filter
|
|
663
680
|
const [currentSavedFilterData, setCurrentSavedFilterData] = useState<any>();
|
|
664
681
|
const [currentSavedFilterQuery, setCurrentSavedFilterQuery] = useState<any>();
|
|
682
|
+
const [currentSavedFilterVariables, setCurrentSavedFilterVariables] = useState<Record<string, any>>({});
|
|
665
683
|
const [currentSavedFilterRules, setCurrentSavedFilterRules] = useState<any>();
|
|
666
684
|
const [showSavedFilterComponent, setShowSavedFilterComponent] = useState<boolean>(false);
|
|
667
685
|
|
|
@@ -684,7 +702,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
684
702
|
|
|
685
703
|
useEffect(() => {
|
|
686
704
|
if (focusedIndex >= 0 && showOverlay) {
|
|
687
|
-
const activeElement =
|
|
705
|
+
const activeElement = overlayRef.current?.querySelector(`.solid-search-overlay-option-active`);
|
|
688
706
|
if (activeElement) {
|
|
689
707
|
activeElement.scrollIntoView({
|
|
690
708
|
block: 'nearest',
|
|
@@ -720,12 +738,23 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
720
738
|
};
|
|
721
739
|
}, [isDraftPublishWorkflowEnabled]);
|
|
722
740
|
|
|
723
|
-
const
|
|
741
|
+
const allAvailableSavedFilters = useMemo(
|
|
724
742
|
() => workflowPublishedVersionsSavedFilter
|
|
725
743
|
? [workflowPublishedVersionsSavedFilter, ...(savedFilters || [])]
|
|
726
744
|
: (savedFilters || []),
|
|
727
745
|
[workflowPublishedVersionsSavedFilter, savedFilters]
|
|
728
746
|
);
|
|
747
|
+
const availableSavedFilters = allAvailableSavedFilters;
|
|
748
|
+
const selectableSavedFilters = useMemo(
|
|
749
|
+
() => allAvailableSavedFilters.filter((filter: any) => {
|
|
750
|
+
try {
|
|
751
|
+
return getUnboundSavedFilterVariableNames(parseSavedFilterQueryJson(filter.filterQueryJson || "{}")).length === 0;
|
|
752
|
+
} catch {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
}),
|
|
756
|
+
[allAvailableSavedFilters]
|
|
757
|
+
);
|
|
729
758
|
|
|
730
759
|
const entityApi = createSolidEntityApi("savedFilters");
|
|
731
760
|
const {
|
|
@@ -755,20 +784,6 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
755
784
|
|
|
756
785
|
const [savedFilterFetchDataRefreshKey, setSavedFilterFetchDataRefreshKey] = useState(0);
|
|
757
786
|
|
|
758
|
-
const buildSavedFilterUrl = (savedQueryId?: string | number | null) => {
|
|
759
|
-
const params = new URLSearchParams(searchParams.toString());
|
|
760
|
-
|
|
761
|
-
if (savedQueryId === null || savedQueryId === undefined || savedQueryId === "") {
|
|
762
|
-
params.delete("savedQuery");
|
|
763
|
-
} else {
|
|
764
|
-
params.set("savedQuery", String(savedQueryId));
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
const queryString = params.toString();
|
|
768
|
-
return queryString ? `${pathname}?${queryString}` : pathname;
|
|
769
|
-
};
|
|
770
|
-
|
|
771
|
-
|
|
772
787
|
useEffect(() => {
|
|
773
788
|
const fn = async () => {
|
|
774
789
|
if (!viewData?.data?.solidView?.model?.id || !viewData?.data?.solidView?.id || !user?.id) {
|
|
@@ -814,7 +829,6 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
814
829
|
}
|
|
815
830
|
fn()
|
|
816
831
|
}, [
|
|
817
|
-
activeSavedFilter,
|
|
818
832
|
savedFilterFetchDataRefreshKey,
|
|
819
833
|
viewData?.data?.solidView?.id,
|
|
820
834
|
viewData?.data?.solidView?.model?.id,
|
|
@@ -829,7 +843,12 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
829
843
|
? queryObject.custom_filter_predicate
|
|
830
844
|
: null;
|
|
831
845
|
const nextPredefinedBaseFilter = queryObject?.predefined_search_predicate || null;
|
|
846
|
+
const nextPredefinedSearchChip =
|
|
847
|
+
nextPredefinedBaseFilter && queryObject?.predefined_search_chip
|
|
848
|
+
? queryObject.predefined_search_chip
|
|
849
|
+
: null;
|
|
832
850
|
const nextSavedFilter = queryObject?.saved_filter_predicate || null;
|
|
851
|
+
const nextSavedFilterVariables = queryObject?.saved_filter_variables ?? {};
|
|
833
852
|
const nextSavedFilterData = nextSavedFilter
|
|
834
853
|
? availableSavedFilters.find((savedFilter: any) => {
|
|
835
854
|
if (queryObject?.saved_filter_system_key) {
|
|
@@ -851,8 +870,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
851
870
|
setSearchChips((prev) => areFilterStateValuesEqual(prev, nextSearchChips) ? prev : nextSearchChips);
|
|
852
871
|
setCustomFilter((prev: any) => areFilterStateValuesEqual(prev, nextCustomFilter) ? prev : nextCustomFilter);
|
|
853
872
|
setPredefinedSearchBaseFilter((prev: any) => areFilterStateValuesEqual(prev, nextPredefinedBaseFilter) ? prev : nextPredefinedBaseFilter);
|
|
873
|
+
setPredefinedSearchChip((prev) => areFilterStateValuesEqual(prev, nextPredefinedSearchChip) ? prev : nextPredefinedSearchChip);
|
|
854
874
|
setCurrentSavedFilterData((prev: any) => areFilterStateValuesEqual(prev, nextSavedFilterData) ? prev : nextSavedFilterData);
|
|
855
875
|
setCurrentSavedFilterQuery((prev: any) => areFilterStateValuesEqual(prev, nextSavedFilter) ? prev : nextSavedFilter);
|
|
876
|
+
setCurrentSavedFilterVariables((prev) => areFilterStateValuesEqual(prev, nextSavedFilterVariables) ? prev : nextSavedFilterVariables);
|
|
856
877
|
|
|
857
878
|
if (nextCustomFilter && viewData?.data) {
|
|
858
879
|
const rules: FilterRule = transformFiltersToRules(nextCustomFilter);
|
|
@@ -899,6 +920,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
899
920
|
setPredefinedSearchBaseFilter(null);
|
|
900
921
|
setCurrentSavedFilterData(null);
|
|
901
922
|
setCurrentSavedFilterQuery(null);
|
|
923
|
+
setCurrentSavedFilterVariables({});
|
|
902
924
|
setCurrentSavedFilterRules(null);
|
|
903
925
|
if (!preserveGrouping) {
|
|
904
926
|
setGroupingRules(defaultGroupingRules);
|
|
@@ -909,13 +931,6 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
909
931
|
setInputValue("");
|
|
910
932
|
clearFilterObjectFromLocalStorage();
|
|
911
933
|
|
|
912
|
-
if (activeSavedFilter) {
|
|
913
|
-
const params = new URLSearchParams(searchParams.toString());
|
|
914
|
-
params.delete("savedQuery");
|
|
915
|
-
const nextUrl = params.toString() ? `${pathname}?${params.toString()}` : pathname;
|
|
916
|
-
router.replace(nextUrl);
|
|
917
|
-
}
|
|
918
|
-
|
|
919
934
|
setHasSearched(true);
|
|
920
935
|
setRefreshKey((prev) => prev + 1);
|
|
921
936
|
};
|
|
@@ -924,6 +939,15 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
924
939
|
clearFilter: () => {
|
|
925
940
|
resetAppliedFilters();
|
|
926
941
|
},
|
|
942
|
+
getSavedFilters: () => availableSavedFilters.map((filter: any) => ({ ...filter })),
|
|
943
|
+
applySavedFilterByName: (name: string, variables: Record<string, any> = {}) => {
|
|
944
|
+
const savedFilter = allAvailableSavedFilters.find((filter: any) => filter?.name === name);
|
|
945
|
+
if (!savedFilter) {
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return applySavedFilter(savedFilter, variables);
|
|
950
|
+
},
|
|
927
951
|
clearAppliedFilters: (options?: { preserveGrouping?: boolean }) => {
|
|
928
952
|
resetAppliedFilters(options);
|
|
929
953
|
},
|
|
@@ -945,35 +969,15 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
945
969
|
useEffect(() => {
|
|
946
970
|
const fn = async () => {
|
|
947
971
|
if (savedFiltersLoaded) {
|
|
948
|
-
|
|
949
|
-
if (activeSavedFilter && availableSavedFilters.length === 0) return;
|
|
950
|
-
|
|
951
972
|
const queryObject = getFilterObjectFromLocalStorage();
|
|
952
|
-
|
|
953
|
-
if (activeSavedFilter) {
|
|
954
|
-
const currentSavedFilterId = Number(activeSavedFilter);
|
|
955
|
-
const currentSavedFilterData: any = availableSavedFilters.find((savedFilter: any) => savedFilter.id === currentSavedFilterId);
|
|
956
|
-
if (currentSavedFilterData) {
|
|
957
|
-
const filterJson = JSON.parse(currentSavedFilterData?.filterQueryJson);
|
|
958
|
-
if (filterJson) {
|
|
959
|
-
nextQueryObject = {
|
|
960
|
-
...(queryObject || {}),
|
|
961
|
-
saved_filter_predicate: filterJson,
|
|
962
|
-
saved_filter_id: currentSavedFilterData.id,
|
|
963
|
-
saved_filter_system_key: currentSavedFilterData.systemKey,
|
|
964
|
-
saved_filter_name: currentSavedFilterData.name,
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
await syncFilterUiStateFromSharedSource(nextQueryObject);
|
|
973
|
+
await syncFilterUiStateFromSharedSource(queryObject);
|
|
970
974
|
|
|
971
975
|
setRefreshKey((prev) => prev + 1)
|
|
972
976
|
setHasSearched(true);
|
|
973
977
|
}
|
|
974
978
|
}
|
|
975
979
|
fn()
|
|
976
|
-
}, [
|
|
980
|
+
}, [availableSavedFilters, savedFiltersLoaded, syncFilterUiStateFromSharedSource, viewData?.data?.solidView?.id])
|
|
977
981
|
|
|
978
982
|
useEffect(() => {
|
|
979
983
|
const fn = async () => {
|
|
@@ -1263,6 +1267,16 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1263
1267
|
|
|
1264
1268
|
const finalCustomFilter = customFilter
|
|
1265
1269
|
const finalFilter = mergeAllDiffFilters(finalCustomFilter, finalSearchFilter, finalSavedFilter, finalPredefinedFilter, groupingRules, aggregationRules)
|
|
1270
|
+
finalFilter.predefined_search_chip =
|
|
1271
|
+
finalPredefinedFilter && predefinedSearchChip
|
|
1272
|
+
? predefinedSearchChip
|
|
1273
|
+
: null;
|
|
1274
|
+
if (finalSavedFilter && Object.keys(finalSavedFilter).length > 0) {
|
|
1275
|
+
const resolved = resolveSavedFilterVariables(finalSavedFilter, user?.id, currentSavedFilterVariables);
|
|
1276
|
+
if (resolved.missingVariables.length > 0) return;
|
|
1277
|
+
finalFilter.resolved_saved_filter_predicate = resolved.value;
|
|
1278
|
+
finalFilter.saved_filter_variables = currentSavedFilterVariables;
|
|
1279
|
+
}
|
|
1266
1280
|
if (currentSavedFilterData?.id) {
|
|
1267
1281
|
finalFilter.saved_filter_id = currentSavedFilterData.id;
|
|
1268
1282
|
}
|
|
@@ -1291,7 +1305,6 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1291
1305
|
|
|
1292
1306
|
const openSavedCustomFilter = async (savedfilter: any) => {
|
|
1293
1307
|
//Open custom filter popup
|
|
1294
|
-
// router.push(`?savedQuery=${savedfilter.id}`);
|
|
1295
1308
|
// setShowGlobalSearchElement(true);
|
|
1296
1309
|
// // dont refetch the data yet
|
|
1297
1310
|
// const customFilter = JSON.parse(savedfilter.filterQueryJson);
|
|
@@ -1309,15 +1322,13 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1309
1322
|
setPredefinedSearchChip(null);
|
|
1310
1323
|
setPredefinedSearchBaseFilter(null);
|
|
1311
1324
|
|
|
1312
|
-
// push the savedQuery=1 in url
|
|
1313
1325
|
if (savedfilter?.id) {
|
|
1314
1326
|
const savedfilterId = savedfilter.id;
|
|
1315
|
-
// router.push(`?savedQuery=${savedfilter.id}`);
|
|
1316
1327
|
setShowOverlay(false);
|
|
1317
1328
|
const currentSavedFilterData: any = availableSavedFilters.find((savedFilter: any) => savedFilter.id === savedfilterId);
|
|
1318
1329
|
setCurrentSavedFilterData(currentSavedFilterData);
|
|
1319
1330
|
if (currentSavedFilterData) {
|
|
1320
|
-
const filterJson =
|
|
1331
|
+
const filterJson = parseSavedFilterQueryJson(currentSavedFilterData?.filterQueryJson);
|
|
1321
1332
|
if (filterJson) {
|
|
1322
1333
|
let finalSavedFilter = filterJson
|
|
1323
1334
|
setCurrentSavedFilterQuery(finalSavedFilter)
|
|
@@ -1335,14 +1346,50 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1335
1346
|
}
|
|
1336
1347
|
}
|
|
1337
1348
|
|
|
1349
|
+
const persistActiveSavedFilter = (savedFilter: any, filterJson: any, variables: Record<string, any>) => {
|
|
1350
|
+
const persistedFilter = {
|
|
1351
|
+
...(getFilterObjectFromLocalStorage() || {}),
|
|
1352
|
+
custom_filter_predicate: null,
|
|
1353
|
+
search_predicate: null,
|
|
1354
|
+
predefined_search_predicate: null,
|
|
1355
|
+
predefined_search_chip: null,
|
|
1356
|
+
saved_filter_predicate: filterJson,
|
|
1357
|
+
saved_filter_variables: variables,
|
|
1358
|
+
saved_filter_id: savedFilter?.id ?? null,
|
|
1359
|
+
saved_filter_system_key: savedFilter?.systemKey ?? null,
|
|
1360
|
+
saved_filter_name: savedFilter?.name ?? null,
|
|
1361
|
+
};
|
|
1362
|
+
delete persistedFilter.finalFullFilter;
|
|
1363
|
+
setFilterObjectToLocalStorage(persistedFilter);
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
const clearActiveSavedFilter = () => {
|
|
1367
|
+
setCurrentSavedFilterData(null);
|
|
1368
|
+
setCurrentSavedFilterQuery(null);
|
|
1369
|
+
setCurrentSavedFilterVariables({});
|
|
1370
|
+
setCurrentSavedFilterRules(null);
|
|
1371
|
+
setShowSavedFilterComponent(false);
|
|
1372
|
+
|
|
1373
|
+
const persistedFilter = {
|
|
1374
|
+
...(getFilterObjectFromLocalStorage() || {}),
|
|
1375
|
+
saved_filter_predicate: null,
|
|
1376
|
+
saved_filter_variables: {},
|
|
1377
|
+
saved_filter_id: null,
|
|
1378
|
+
saved_filter_system_key: null,
|
|
1379
|
+
saved_filter_name: null,
|
|
1380
|
+
};
|
|
1381
|
+
delete persistedFilter.finalFullFilter;
|
|
1382
|
+
setFilterObjectToLocalStorage(persistedFilter);
|
|
1383
|
+
|
|
1384
|
+
setHasSearched(true);
|
|
1385
|
+
setRefreshKey((prev) => prev + 1);
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1338
1388
|
const deleteSavedFilter = async () => {
|
|
1339
1389
|
// delte the saved filter with id
|
|
1340
1390
|
await deleteEntity(savedFilterTobeDeleted);
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
const savedQuery = parsedSearchParams?.get("savedQuery");
|
|
1344
|
-
if (savedFilterTobeDeleted == savedQuery) {
|
|
1345
|
-
router.push(buildSavedFilterUrl(null));
|
|
1391
|
+
if (String(savedFilterTobeDeleted) === String(currentSavedFilterData?.id)) {
|
|
1392
|
+
clearActiveSavedFilter();
|
|
1346
1393
|
}
|
|
1347
1394
|
setIsDeleteSQDialogVisible(false);
|
|
1348
1395
|
setTimeout(() => {
|
|
@@ -1371,8 +1418,6 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1371
1418
|
setCustomFilter(null)
|
|
1372
1419
|
setPredefinedSearchChip(null);
|
|
1373
1420
|
setPredefinedSearchBaseFilter(null);
|
|
1374
|
-
clearFilterObjectFromLocalStorage();
|
|
1375
|
-
|
|
1376
1421
|
// Apply the rename immediately, both to the active filter and to
|
|
1377
1422
|
// the cached list — otherwise the list still holds the pre-rename
|
|
1378
1423
|
// entry, which wins the id-based lookup the next time saved-filter
|
|
@@ -1382,12 +1427,9 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1382
1427
|
setSavedFilters((prev) => prev.map((f: any) => String(f?.id) === String(formValues.id) ? { ...f, ...savedFilterRecord } : f));
|
|
1383
1428
|
setCurrentSavedFilterData(savedFilterRecord);
|
|
1384
1429
|
setCurrentSavedFilterQuery(filterJson);
|
|
1430
|
+
persistActiveSavedFilter(savedFilterRecord, filterJson, currentSavedFilterVariables);
|
|
1385
1431
|
setHasSearched(true);
|
|
1386
1432
|
setRefreshKey((prev) => prev + 1);
|
|
1387
|
-
|
|
1388
|
-
setTimeout(() => {
|
|
1389
|
-
router.push(buildSavedFilterUrl(formValues.id));
|
|
1390
|
-
}, 500)
|
|
1391
1433
|
} else {
|
|
1392
1434
|
|
|
1393
1435
|
const filterJson = customFilter;
|
|
@@ -1406,21 +1448,16 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1406
1448
|
setCustomFilter(null)
|
|
1407
1449
|
setPredefinedSearchChip(null);
|
|
1408
1450
|
setPredefinedSearchBaseFilter(null);
|
|
1409
|
-
clearFilterObjectFromLocalStorage();
|
|
1410
|
-
|
|
1411
1451
|
// The custom filter just became this saved filter — apply it, and
|
|
1412
1452
|
// seed the cached list with it, immediately (see rename branch above).
|
|
1413
1453
|
const savedFilterRecord = { id: result.data.id, name: formValues.name, isPrivate: formValues.isPrivate, filterQueryJson: JSON.stringify(filterJson, null, 2) };
|
|
1414
1454
|
setSavedFilters((prev) => [savedFilterRecord, ...prev]);
|
|
1415
1455
|
setCurrentSavedFilterData(savedFilterRecord);
|
|
1416
1456
|
setCurrentSavedFilterQuery(filterJson);
|
|
1457
|
+
setCurrentSavedFilterVariables({});
|
|
1458
|
+
persistActiveSavedFilter(savedFilterRecord, filterJson, {});
|
|
1417
1459
|
setHasSearched(true);
|
|
1418
1460
|
setRefreshKey((prev) => prev + 1);
|
|
1419
|
-
|
|
1420
|
-
setTimeout(() => {
|
|
1421
|
-
router.push(buildSavedFilterUrl(result.data.id));
|
|
1422
|
-
}, 500)
|
|
1423
|
-
|
|
1424
1461
|
}
|
|
1425
1462
|
} catch (error) {
|
|
1426
1463
|
|
|
@@ -1496,7 +1533,44 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1496
1533
|
|
|
1497
1534
|
//saved filter related code start
|
|
1498
1535
|
|
|
1499
|
-
const applySavedFilter = (savedfilter: any) => {
|
|
1536
|
+
const applySavedFilter = (savedfilter: any, variables: Record<string, any> = {}) => {
|
|
1537
|
+
if (!savedfilter?.id && !savedfilter?.systemKey) {
|
|
1538
|
+
console.error(ERROR_MESSAGES.SAVE_FILTER_UNDEFINED_NULL);
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
let filterJson: any;
|
|
1543
|
+
try {
|
|
1544
|
+
filterJson = parseSavedFilterQueryJson(savedfilter?.filterQueryJson);
|
|
1545
|
+
} catch {
|
|
1546
|
+
console.error(ERROR_MESSAGES.SAVE_FILTER_UNDEFINED_NULL);
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
const resolved = resolveSavedFilterVariables(filterJson, user?.id, variables);
|
|
1551
|
+
if (resolved.missingVariables.length > 0) {
|
|
1552
|
+
console.error(`Unable to apply saved filter '${savedfilter?.name}': missing variables ${resolved.missingVariables.join(", ")}`);
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// Applying the already-active filter must be a no-op. This is important
|
|
1557
|
+
// for lifecycle extensions: applying a filter causes a new list load,
|
|
1558
|
+
// which invokes onBeforeListDataLoad/onListLoad again.
|
|
1559
|
+
const sameSavedFilterReference =
|
|
1560
|
+
(currentSavedFilterData?.id != null &&
|
|
1561
|
+
savedfilter?.id != null &&
|
|
1562
|
+
areFilterStateValuesEqual(currentSavedFilterData.id, savedfilter.id)) ||
|
|
1563
|
+
(currentSavedFilterData?.systemKey != null &&
|
|
1564
|
+
savedfilter?.systemKey != null &&
|
|
1565
|
+
areFilterStateValuesEqual(currentSavedFilterData.systemKey, savedfilter.systemKey));
|
|
1566
|
+
const sameSavedFilter =
|
|
1567
|
+
sameSavedFilterReference &&
|
|
1568
|
+
areFilterStateValuesEqual(currentSavedFilterQuery, filterJson) &&
|
|
1569
|
+
areFilterStateValuesEqual(currentSavedFilterVariables, variables);
|
|
1570
|
+
|
|
1571
|
+
if (sameSavedFilter) {
|
|
1572
|
+
return true;
|
|
1573
|
+
}
|
|
1500
1574
|
|
|
1501
1575
|
setSearchChips([]);
|
|
1502
1576
|
setSearchFilter(null);
|
|
@@ -1504,41 +1578,18 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1504
1578
|
setCustomFilter(null)
|
|
1505
1579
|
setPredefinedSearchChip(null);
|
|
1506
1580
|
setPredefinedSearchBaseFilter(null);
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
} catch {
|
|
1516
|
-
console.error(ERROR_MESSAGES.SAVE_FILTER_UNDEFINED_NULL);
|
|
1517
|
-
}
|
|
1518
|
-
setShowOverlay(false);
|
|
1519
|
-
setHasSearched(true);
|
|
1520
|
-
setRefreshKey((prev) => prev + 1);
|
|
1521
|
-
return;
|
|
1522
|
-
}
|
|
1523
|
-
// push the savedQuery=1 in url
|
|
1524
|
-
if (savedfilter?.id) {
|
|
1525
|
-
router.push(buildSavedFilterUrl(savedfilter.id));
|
|
1526
|
-
setShowOverlay(false);
|
|
1527
|
-
} else {
|
|
1528
|
-
console.error(ERROR_MESSAGES.SAVE_FILTER_UNDEFINED_NULL);
|
|
1529
|
-
}
|
|
1581
|
+
setCurrentSavedFilterData(savedfilter);
|
|
1582
|
+
setCurrentSavedFilterQuery(filterJson);
|
|
1583
|
+
setCurrentSavedFilterVariables(variables);
|
|
1584
|
+
persistActiveSavedFilter(savedfilter, filterJson, variables);
|
|
1585
|
+
setShowOverlay(false);
|
|
1586
|
+
setHasSearched(true);
|
|
1587
|
+
setRefreshKey((prev) => prev + 1);
|
|
1588
|
+
return true;
|
|
1530
1589
|
}
|
|
1531
1590
|
|
|
1532
1591
|
const removeSavedFilter = () => {
|
|
1533
|
-
|
|
1534
|
-
setCurrentSavedFilterQuery(null);
|
|
1535
|
-
setCurrentSavedFilterRules(null);
|
|
1536
|
-
setShowSavedFilterComponent(false);
|
|
1537
|
-
if (searchParams.has("savedQuery")) {
|
|
1538
|
-
router.replace(buildSavedFilterUrl(null));
|
|
1539
|
-
}
|
|
1540
|
-
setHasSearched(true);
|
|
1541
|
-
setRefreshKey((prev) => prev + 1);
|
|
1592
|
+
clearActiveSavedFilter();
|
|
1542
1593
|
}
|
|
1543
1594
|
|
|
1544
1595
|
const SavedFiltersChip = () => {
|
|
@@ -1697,16 +1748,6 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1697
1748
|
});
|
|
1698
1749
|
});
|
|
1699
1750
|
|
|
1700
|
-
(definedFilters || []).forEach((definedFilter: any) => {
|
|
1701
|
-
if (!definedFilter?.applied) return;
|
|
1702
|
-
items.push({
|
|
1703
|
-
id: `handler:${definedFilter.key}`,
|
|
1704
|
-
type: "handler",
|
|
1705
|
-
label: definedFilter.label,
|
|
1706
|
-
onRemove: () => onRemoveDefinedFilter?.(definedFilter.key),
|
|
1707
|
-
});
|
|
1708
|
-
});
|
|
1709
|
-
|
|
1710
1751
|
return items;
|
|
1711
1752
|
}, [
|
|
1712
1753
|
currentSavedFilterData,
|
|
@@ -1716,8 +1757,6 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1716
1757
|
groupingRules,
|
|
1717
1758
|
groupedSearchChips,
|
|
1718
1759
|
searchableFields,
|
|
1719
|
-
definedFilters,
|
|
1720
|
-
onRemoveDefinedFilter,
|
|
1721
1760
|
]);
|
|
1722
1761
|
|
|
1723
1762
|
const MAX_VISIBLE_CHIPS = 3;
|
|
@@ -1759,7 +1798,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1759
1798
|
predefined: p,
|
|
1760
1799
|
}))
|
|
1761
1800
|
: [];
|
|
1762
|
-
const saved: OverlayOption[] = (
|
|
1801
|
+
const saved: OverlayOption[] = (selectableSavedFilters || []).map((s: any) => ({
|
|
1763
1802
|
id: `saved:${s.id}`,
|
|
1764
1803
|
kind: "saved" as const,
|
|
1765
1804
|
saved: s,
|
|
@@ -1776,8 +1815,8 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1776
1815
|
}]
|
|
1777
1816
|
: [];
|
|
1778
1817
|
|
|
1779
|
-
return [...
|
|
1780
|
-
}, [inputValue, searchableFields, predefinedSearches,
|
|
1818
|
+
return [...predefined, ...fields, ...saved, ...custom, ...grouping];
|
|
1819
|
+
}, [inputValue, searchableFields, predefinedSearches, selectableSavedFilters, viewType]);
|
|
1781
1820
|
|
|
1782
1821
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
1783
1822
|
const currentValue = inputValue?.trim() || "";
|
|
@@ -1800,6 +1839,12 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1800
1839
|
setShowOverlay(false);
|
|
1801
1840
|
};
|
|
1802
1841
|
|
|
1842
|
+
if (e.key === "Escape") {
|
|
1843
|
+
e.preventDefault();
|
|
1844
|
+
setShowOverlay(false);
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1803
1848
|
if ((e.key === "ArrowDown" || e.key === "ArrowUp") && overlayOptions.length > 0) {
|
|
1804
1849
|
e.preventDefault();
|
|
1805
1850
|
setShowOverlay(true);
|
|
@@ -1811,6 +1856,12 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1811
1856
|
return;
|
|
1812
1857
|
}
|
|
1813
1858
|
|
|
1859
|
+
if ((e.key === "Home" || e.key === "End") && showOverlay && overlayOptions.length > 0) {
|
|
1860
|
+
e.preventDefault();
|
|
1861
|
+
setFocusedIndex(e.key === "Home" ? 0 : overlayOptions.length - 1);
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1814
1865
|
if (e.key === "Enter") {
|
|
1815
1866
|
if (currentValue || focusedIndex >= 0) {
|
|
1816
1867
|
e.preventDefault();
|
|
@@ -1842,7 +1893,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
1842
1893
|
} else if (predefinedSearchChip) {
|
|
1843
1894
|
setPredefinedSearchChip(null);
|
|
1844
1895
|
setPredefinedSearchBaseFilter(null);
|
|
1845
|
-
} else if (
|
|
1896
|
+
} else if (currentSavedFilterData) {
|
|
1846
1897
|
removeSavedFilter();
|
|
1847
1898
|
}
|
|
1848
1899
|
setHasSearched(true);
|
|
@@ -2028,7 +2079,13 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2028
2079
|
ref={searchInputRef}
|
|
2029
2080
|
className="solid-global-search-input"
|
|
2030
2081
|
value={inputValue || ""}
|
|
2031
|
-
placeholder="Search
|
|
2082
|
+
placeholder="Search…"
|
|
2083
|
+
role="combobox"
|
|
2084
|
+
aria-label="Search and filter"
|
|
2085
|
+
aria-autocomplete="list"
|
|
2086
|
+
aria-expanded={showOverlay}
|
|
2087
|
+
aria-controls={showOverlay ? overlayListboxId : undefined}
|
|
2088
|
+
aria-activedescendant={showOverlay && focusedIndex >= 0 ? getOverlayOptionId(focusedIndex) : undefined}
|
|
2032
2089
|
onChange={(e) => {
|
|
2033
2090
|
setInputValue(e.target.value);
|
|
2034
2091
|
setShowChipManager(false);
|
|
@@ -2059,6 +2116,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2059
2116
|
focusSearchInput();
|
|
2060
2117
|
}}
|
|
2061
2118
|
className="custom-filter-button solid-global-search-trigger"
|
|
2119
|
+
aria-label="Open search options"
|
|
2062
2120
|
>
|
|
2063
2121
|
<Search size={14} />
|
|
2064
2122
|
</SolidButton>
|
|
@@ -2103,53 +2161,77 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2103
2161
|
)} */}
|
|
2104
2162
|
|
|
2105
2163
|
{showOverlay && (
|
|
2106
|
-
<div
|
|
2164
|
+
<div
|
|
2165
|
+
ref={overlayRef}
|
|
2166
|
+
id={overlayListboxId}
|
|
2167
|
+
role="listbox"
|
|
2168
|
+
aria-label="Search and filter options"
|
|
2169
|
+
className="solid-search-overlay-panel"
|
|
2170
|
+
>
|
|
2107
2171
|
<div className="solid-search-overlay-scroll">
|
|
2108
|
-
{(definedFilters || []).some((f: any) => !f?.applied) && (
|
|
2109
|
-
<>
|
|
2110
|
-
<div className="custom-filter-search-options solid-search-overlay-section flex flex-col px-3 py-2">
|
|
2111
|
-
<h6 className="my-1 solid-search-overlay-heading solid-search-overlay-section-title solid-search-overlay-heading-with-icon">
|
|
2112
|
-
<ListFilter size={13} />
|
|
2113
|
-
<span>Defined filters</span>
|
|
2114
|
-
</h6>
|
|
2115
|
-
<div className="solid-defined-filter-badge-row">
|
|
2116
|
-
{(definedFilters || [])
|
|
2117
|
-
.filter((f: any) => !f?.applied)
|
|
2118
|
-
.map((definedFilter: any) => (
|
|
2119
|
-
<button
|
|
2120
|
-
key={definedFilter.key}
|
|
2121
|
-
type="button"
|
|
2122
|
-
className="solid-defined-filter-badge"
|
|
2123
|
-
onMouseDown={(e) => {
|
|
2124
|
-
e.preventDefault();
|
|
2125
|
-
onApplyDefinedFilter?.(definedFilter.key);
|
|
2126
|
-
setShowOverlay(false);
|
|
2127
|
-
}}
|
|
2128
|
-
>
|
|
2129
|
-
<Plus size={12} />
|
|
2130
|
-
<span className="solid-defined-filter-badge-label">{definedFilter.label}</span>
|
|
2131
|
-
</button>
|
|
2132
|
-
))}
|
|
2133
|
-
</div>
|
|
2134
|
-
</div>
|
|
2135
|
-
<div className="solid-filter-dialog-sep" />
|
|
2136
|
-
</>
|
|
2137
|
-
)}
|
|
2138
2172
|
{inputValue ? (
|
|
2139
2173
|
<>
|
|
2140
|
-
|
|
2174
|
+
{predefinedSearches && predefinedSearches.length > 0 && (
|
|
2175
|
+
<>
|
|
2176
|
+
<div className="solid-search-overlay-section solid-search-overlay-section--predefined">
|
|
2177
|
+
<h6 className="my-1 solid-search-overlay-heading solid-search-overlay-section-title solid-search-overlay-heading-with-icon">
|
|
2178
|
+
<Sparkles size={14} />
|
|
2179
|
+
<span>Suggested searches</span>
|
|
2180
|
+
<span className="solid-search-overlay-section-count">{predefinedSearches.length}</span>
|
|
2181
|
+
</h6>
|
|
2182
|
+
{predefinedSearches.map((predefinedSearch: any, index: number) => {
|
|
2183
|
+
const optionIndex = overlayOptions.findIndex((option) => option.id === `predefined:${predefinedSearch.name || index}`);
|
|
2184
|
+
return (
|
|
2185
|
+
<SolidButton
|
|
2186
|
+
key={index}
|
|
2187
|
+
id={getOverlayOptionId(optionIndex)}
|
|
2188
|
+
role="option"
|
|
2189
|
+
aria-selected={focusedIndex === optionIndex}
|
|
2190
|
+
variant="ghost"
|
|
2191
|
+
size="sm"
|
|
2192
|
+
className={`text-color solid-search-overlay-option ${focusedIndex === optionIndex ? "solid-search-overlay-option-active" : ""}`}
|
|
2193
|
+
onMouseDown={(e) => {
|
|
2194
|
+
e.preventDefault();
|
|
2195
|
+
handlePredefinedSearch(predefinedSearch);
|
|
2196
|
+
}}
|
|
2197
|
+
onMouseEnter={() => setFocusedIndex(optionIndex)}
|
|
2198
|
+
>
|
|
2199
|
+
<span className="solid-search-overlay-option-content">
|
|
2200
|
+
<span className="solid-search-overlay-option-icon" aria-hidden="true">
|
|
2201
|
+
<Sparkles size={15} />
|
|
2202
|
+
</span>
|
|
2203
|
+
<span className="solid-search-overlay-option-copy">
|
|
2204
|
+
<span className="solid-search-overlay-option-title">{predefinedSearch.name}</span>
|
|
2205
|
+
<span className="solid-search-overlay-option-description">
|
|
2206
|
+
Search for “{inputValue}”{predefinedSearch.description ? ` — ${predefinedSearch.description}` : ""}
|
|
2207
|
+
</span>
|
|
2208
|
+
</span>
|
|
2209
|
+
</span>
|
|
2210
|
+
</SolidButton>
|
|
2211
|
+
);
|
|
2212
|
+
})}
|
|
2213
|
+
</div>
|
|
2214
|
+
<div className="solid-filter-dialog-sep" />
|
|
2215
|
+
</>
|
|
2216
|
+
)}
|
|
2217
|
+
<div className="solid-search-overlay-section solid-search-overlay-section--search">
|
|
2141
2218
|
<h6 className="my-1 solid-search-overlay-heading solid-search-overlay-section-title solid-search-overlay-heading-with-icon">
|
|
2142
2219
|
<Search size={13} />
|
|
2143
|
-
<span>Search
|
|
2220
|
+
<span>Search individual fields</span>
|
|
2221
|
+
<span className="solid-search-overlay-section-count">{searchableFields.length}</span>
|
|
2144
2222
|
</h6>
|
|
2145
2223
|
{searchableFields.length > 0 ? (
|
|
2146
2224
|
searchableFields.map((value: any, index: number) => {
|
|
2225
|
+
const optionIndex = overlayOptions.findIndex((option) => option.id === `field:${value.fieldName}`);
|
|
2147
2226
|
return (
|
|
2148
2227
|
<SolidButton
|
|
2149
2228
|
key={index}
|
|
2229
|
+
id={getOverlayOptionId(optionIndex)}
|
|
2230
|
+
role="option"
|
|
2231
|
+
aria-selected={focusedIndex === optionIndex}
|
|
2150
2232
|
variant="ghost"
|
|
2151
2233
|
size="sm"
|
|
2152
|
-
className={`
|
|
2234
|
+
className={`text-color solid-search-overlay-option ${focusedIndex === optionIndex ? "solid-search-overlay-option-active" : ""}`}
|
|
2153
2235
|
onMouseDown={(e) => {
|
|
2154
2236
|
e.preventDefault();
|
|
2155
2237
|
const currentValue = inputValue?.trim();
|
|
@@ -2169,9 +2251,17 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2169
2251
|
setShowOverlay(false);
|
|
2170
2252
|
}
|
|
2171
2253
|
}}
|
|
2172
|
-
onMouseEnter={() => setFocusedIndex(
|
|
2254
|
+
onMouseEnter={() => setFocusedIndex(optionIndex)}
|
|
2173
2255
|
>
|
|
2174
|
-
|
|
2256
|
+
<span className="solid-search-overlay-option-content">
|
|
2257
|
+
<span className="solid-search-overlay-option-icon" aria-hidden="true">
|
|
2258
|
+
<Search size={15} />
|
|
2259
|
+
</span>
|
|
2260
|
+
<span className="solid-search-overlay-option-copy">
|
|
2261
|
+
<span className="solid-search-overlay-option-title">{toTitleCase(value.displayName.trim())}</span>
|
|
2262
|
+
<span className="solid-search-overlay-option-description">Search for “{inputValue}”</span>
|
|
2263
|
+
</span>
|
|
2264
|
+
</span>
|
|
2175
2265
|
</SolidButton>
|
|
2176
2266
|
)
|
|
2177
2267
|
})
|
|
@@ -2186,37 +2276,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2186
2276
|
</div>
|
|
2187
2277
|
)}
|
|
2188
2278
|
</div>
|
|
2189
|
-
{predefinedSearches && predefinedSearches.length > 0 && (
|
|
2190
|
-
<>
|
|
2191
|
-
<div className="solid-filter-dialog-sep" />
|
|
2192
|
-
<div className="custom-filter-search-options solid-search-overlay-section flex flex-col px-3 py-1">
|
|
2193
|
-
<h6 className="my-1 solid-search-overlay-heading solid-search-overlay-section-title">Predefined searches</h6>
|
|
2194
|
-
{predefinedSearches.map((predefinedSearch: any, index: number) => (
|
|
2195
|
-
<SolidButton
|
|
2196
|
-
key={index}
|
|
2197
|
-
variant="ghost"
|
|
2198
|
-
size="sm"
|
|
2199
|
-
className={`flex flex-col items-start gap-1 text-color solid-search-overlay-option solid-search-overlay-option-stacked ${focusedIndex === searchableFields.length + index ? "solid-search-overlay-option-active" : ""}`}
|
|
2200
|
-
onMouseDown={(e) => {
|
|
2201
|
-
e.preventDefault();
|
|
2202
|
-
handlePredefinedSearch(predefinedSearch);
|
|
2203
|
-
}}
|
|
2204
|
-
onMouseEnter={() => setFocusedIndex(searchableFields.length + index)}
|
|
2205
|
-
>
|
|
2206
|
-
<div className="flex items-center gap-1">
|
|
2207
|
-
<strong>{predefinedSearch.name}:</strong>
|
|
2208
|
-
<span className="font-bold text-color pr-1">{inputValue} </span>
|
|
2209
|
-
</div>
|
|
2210
|
-
<div className="text-xs">{predefinedSearch.description}</div>
|
|
2211
|
-
</SolidButton>
|
|
2212
|
-
))}
|
|
2213
|
-
</div>
|
|
2214
|
-
</>
|
|
2215
|
-
)}
|
|
2216
2279
|
</>
|
|
2217
2280
|
) :
|
|
2218
2281
|
<>
|
|
2219
|
-
<div className="
|
|
2282
|
+
<div className="solid-search-overlay-empty">
|
|
2220
2283
|
<div className="solid-search-overlay-panel-callout">
|
|
2221
2284
|
<div className="solid-search-overlay-panel-callout-icon">
|
|
2222
2285
|
<Search size={13} />
|
|
@@ -2226,7 +2289,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2226
2289
|
Start typing to search
|
|
2227
2290
|
</div>
|
|
2228
2291
|
<div className="solid-search-overlay-empty-subtitle">
|
|
2229
|
-
|
|
2292
|
+
Enter a value to see suggested searches and searchable {searchableEntityLabel} fields.
|
|
2230
2293
|
</div>
|
|
2231
2294
|
</div>
|
|
2232
2295
|
</div>
|
|
@@ -2234,26 +2297,27 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2234
2297
|
<div className="solid-filter-dialog-sep" />
|
|
2235
2298
|
</>
|
|
2236
2299
|
}
|
|
2237
|
-
{
|
|
2300
|
+
{selectableSavedFilters.length > 0 &&
|
|
2238
2301
|
<>
|
|
2239
|
-
<
|
|
2302
|
+
<hr className="solid-search-overlay-section-separator" />
|
|
2303
|
+
<div className="solid-search-overlay-section solid-search-overlay-section--saved">
|
|
2240
2304
|
<div className="solid-search-overlay-panel-callout solid-search-overlay-panel-callout-compact">
|
|
2241
2305
|
<div className="solid-search-overlay-panel-callout-icon">
|
|
2242
2306
|
<Bookmark size={13} />
|
|
2243
2307
|
</div>
|
|
2244
2308
|
<div className="solid-search-overlay-panel-callout-copy">
|
|
2245
|
-
<p className="solid-search-overlay-heading solid-search-overlay-section-title">
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
</
|
|
2309
|
+
<p className="solid-search-overlay-heading solid-search-overlay-section-title">
|
|
2310
|
+
<span>Saved filters</span>
|
|
2311
|
+
<span className="solid-search-overlay-section-count">{selectableSavedFilters.length}</span>
|
|
2312
|
+
</p>
|
|
2249
2313
|
</div>
|
|
2250
2314
|
</div>
|
|
2251
|
-
<div className="
|
|
2252
|
-
{
|
|
2315
|
+
<div className="solid-search-overlay-saved-list">
|
|
2316
|
+
{selectableSavedFilters.map((savedfilter: any, index: number) => (
|
|
2253
2317
|
<SavedFilterList
|
|
2254
2318
|
key={savedfilter.id}
|
|
2255
2319
|
savedfilter={savedfilter}
|
|
2256
|
-
|
|
2320
|
+
activeSavedFilterReference={currentSavedFilterData?.systemKey || currentSavedFilterData?.id}
|
|
2257
2321
|
applySavedFilter={applySavedFilter}
|
|
2258
2322
|
openSavedCustomFilter={openSavedCustomFilter}
|
|
2259
2323
|
setSavedFilterTobeDeleted={setSavedFilterTobeDeleted}
|
|
@@ -2263,6 +2327,7 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2263
2327
|
const optIndex = overlayOptions.findIndex(o => o.id === `saved:${savedfilter.id}`);
|
|
2264
2328
|
if (optIndex !== -1) setFocusedIndex(optIndex);
|
|
2265
2329
|
}}
|
|
2330
|
+
optionId={getOverlayOptionId(overlayOptions.findIndex(o => o.id === `saved:${savedfilter.id}`))}
|
|
2266
2331
|
/>
|
|
2267
2332
|
))}
|
|
2268
2333
|
</div>
|
|
@@ -2274,7 +2339,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2274
2339
|
<div className="solid-search-overlay-footer">
|
|
2275
2340
|
<button
|
|
2276
2341
|
type="button"
|
|
2277
|
-
|
|
2342
|
+
id={getOverlayOptionId(overlayOptions.findIndex((option) => option.id === "footer:custom"))}
|
|
2343
|
+
role="option"
|
|
2344
|
+
aria-selected={overlayOptions[focusedIndex]?.id === "footer:custom"}
|
|
2345
|
+
className={`solid-search-overlay-footer-action solid-search-overlay-footer-action--custom ${overlayOptions[focusedIndex]?.id === "footer:custom" ? "solid-search-overlay-option-active" : ""}`}
|
|
2278
2346
|
onMouseDown={(event) => {
|
|
2279
2347
|
event.preventDefault();
|
|
2280
2348
|
openCustomFilterDialog();
|
|
@@ -2290,10 +2358,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2290
2358
|
</div>
|
|
2291
2359
|
<div className="solid-search-overlay-panel-callout-copy">
|
|
2292
2360
|
<div className="solid-search-overlay-footer-title solid-search-overlay-section-title">
|
|
2293
|
-
|
|
2361
|
+
Advanced filters
|
|
2294
2362
|
</div>
|
|
2295
2363
|
<div className="solid-search-overlay-footer-subtitle">
|
|
2296
|
-
|
|
2364
|
+
Combine fields, operators and values.
|
|
2297
2365
|
</div>
|
|
2298
2366
|
</div>
|
|
2299
2367
|
</div>
|
|
@@ -2301,7 +2369,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2301
2369
|
{viewType === "tree" && (
|
|
2302
2370
|
<button
|
|
2303
2371
|
type="button"
|
|
2304
|
-
|
|
2372
|
+
id={getOverlayOptionId(overlayOptions.findIndex((option) => option.id === "footer:grouping"))}
|
|
2373
|
+
role="option"
|
|
2374
|
+
aria-selected={overlayOptions[focusedIndex]?.id === "footer:grouping"}
|
|
2375
|
+
className={`solid-search-overlay-footer-action solid-search-overlay-footer-action-secondary ${overlayOptions[focusedIndex]?.id === "footer:grouping" ? "solid-search-overlay-option-active" : ""}`}
|
|
2305
2376
|
onMouseDown={(event) => {
|
|
2306
2377
|
event.preventDefault();
|
|
2307
2378
|
openGroupingDialog();
|
|
@@ -2317,10 +2388,10 @@ export const SolidGlobalSearchElement = forwardRef(({ viewData, viewType, handle
|
|
|
2317
2388
|
</div>
|
|
2318
2389
|
<div className="solid-search-overlay-panel-callout-copy">
|
|
2319
2390
|
<div className="solid-search-overlay-footer-title solid-search-overlay-section-title">
|
|
2320
|
-
|
|
2391
|
+
Group and aggregate
|
|
2321
2392
|
</div>
|
|
2322
2393
|
<div className="solid-search-overlay-footer-subtitle">
|
|
2323
|
-
|
|
2394
|
+
Organise records and calculate aggregate values.
|
|
2324
2395
|
</div>
|
|
2325
2396
|
</div>
|
|
2326
2397
|
</div>
|