Feat/multi relation filter (#2858)
* WIP * Finished multi select filter * Cleaned console log * Fix naming * Fixed naming
This commit is contained in:
+7
-6
@@ -1,14 +1,14 @@
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { ObjectFilterDropdownRecordSearchInput } from '@/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput';
|
||||
import { useFilterDropdown } from '@/ui/object/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
|
||||
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
|
||||
import { ObjectFilterDropdownDateSearchInput } from './ObjectFilterDropdownDateSearchInput';
|
||||
import { ObjectFilterDropdownEntitySearchInput } from './ObjectFilterDropdownEntitySearchInput';
|
||||
import { ObjectFilterDropdownEntitySelect } from './ObjectFilterDropdownEntitySelect';
|
||||
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
|
||||
import { ObjectFilterDropdownNumberSearchInput } from './ObjectFilterDropdownNumberSearchInput';
|
||||
import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton';
|
||||
import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect';
|
||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput';
|
||||
|
||||
export const MultipleFiltersDropdownContent = () => {
|
||||
@@ -39,10 +39,11 @@ export const MultipleFiltersDropdownContent = () => {
|
||||
<ObjectFilterDropdownDateSearchInput />
|
||||
)}
|
||||
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
||||
<ObjectFilterDropdownEntitySearchInput />
|
||||
)}
|
||||
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
|
||||
<ObjectFilterDropdownEntitySelect />
|
||||
<>
|
||||
<ObjectFilterDropdownRecordSearchInput />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownRecordSelect />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { ChangeEvent } from 'react';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { useFilterDropdown } from '@/ui/object/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
|
||||
export const ObjectFilterDropdownEntitySearchInput = () => {
|
||||
export const ObjectFilterDropdownRecordSearchInput = () => {
|
||||
const {
|
||||
filterDefinitionUsedInDropdown,
|
||||
selectedOperandInDropdown,
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker';
|
||||
import { ObjectFilterDropdownEntitySearchSelect } from '@/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect';
|
||||
import { useFilterDropdown } from '@/ui/object/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
|
||||
export const ObjectFilterDropdownEntitySelect = () => {
|
||||
const {
|
||||
filterDefinitionUsedInDropdown,
|
||||
objectFilterDropdownSearchInput,
|
||||
objectFilterDropdownSelectedEntityId,
|
||||
} = useFilterDropdown();
|
||||
|
||||
const objectMetadataNameSingular =
|
||||
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular ?? '';
|
||||
|
||||
// TODO: refactor useFilteredSearchEntityQuery
|
||||
const { findManyRecordsQuery } = useObjectMetadataItem({
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
});
|
||||
|
||||
const useFindManyQuery = (options: any) =>
|
||||
useQuery(findManyRecordsQuery, options);
|
||||
|
||||
const { identifiersMapper, searchQuery } = useRelationPicker();
|
||||
|
||||
const filteredSearchEntityResults = useFilteredSearchEntityQuery({
|
||||
queryHook: useFindManyQuery,
|
||||
filters: [
|
||||
{
|
||||
fieldNames:
|
||||
searchQuery?.computeFilterFields?.(objectMetadataNameSingular) ?? [],
|
||||
filter: objectFilterDropdownSearchInput,
|
||||
},
|
||||
],
|
||||
orderByField: 'createdAt',
|
||||
selectedIds: objectFilterDropdownSelectedEntityId
|
||||
? [objectFilterDropdownSelectedEntityId]
|
||||
: [],
|
||||
mappingFunction: (record: any) =>
|
||||
identifiersMapper?.(record, objectMetadataNameSingular),
|
||||
objectNameSingular: objectMetadataNameSingular,
|
||||
});
|
||||
|
||||
if (filterDefinitionUsedInDropdown?.type !== 'RELATION') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ObjectFilterDropdownEntitySearchSelect
|
||||
entitiesForSelect={filteredSearchEntityResults}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { MultipleRecordSelectDropdown } from '@/object-record/select/components/MultipleRecordSelectDropdown';
|
||||
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
|
||||
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
|
||||
import { useFilterDropdown } from '@/ui/object/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
|
||||
export const EMPTY_FILTER_VALUE = '';
|
||||
export const MAX_RECORDS_TO_DISPLAY = 3;
|
||||
|
||||
export const ObjectFilterDropdownRecordSelect = () => {
|
||||
const {
|
||||
filterDefinitionUsedInDropdown,
|
||||
objectFilterDropdownSearchInput,
|
||||
selectedOperandInDropdown,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
objectFilterDropdownSelectedRecordIds,
|
||||
selectFilter,
|
||||
} = useFilterDropdown();
|
||||
|
||||
const objectNameSingular =
|
||||
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular ?? '';
|
||||
|
||||
const { loading, filteredSelectedRecords, recordsToSelect, selectedRecords } =
|
||||
useRecordsForSelect({
|
||||
searchFilterText: objectFilterDropdownSearchInput,
|
||||
selectedIds: objectFilterDropdownSelectedRecordIds,
|
||||
objectNameSingular,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
const handleMultipleRecordSelectChange = (
|
||||
recordToSelect: SelectableRecord,
|
||||
newSelectedValue: boolean,
|
||||
) => {
|
||||
const newSelectedRecordIds = newSelectedValue
|
||||
? [...objectFilterDropdownSelectedRecordIds, recordToSelect.id]
|
||||
: objectFilterDropdownSelectedRecordIds.filter(
|
||||
(id) => id !== recordToSelect.id,
|
||||
);
|
||||
|
||||
setObjectFilterDropdownSelectedRecordIds(newSelectedRecordIds);
|
||||
|
||||
const selectedRecordNames = [
|
||||
...recordsToSelect,
|
||||
...selectedRecords,
|
||||
...filteredSelectedRecords,
|
||||
]
|
||||
.filter(
|
||||
(record, index, self) =>
|
||||
self.findIndex((r) => r.id === record.id) === index,
|
||||
)
|
||||
.filter((record) => newSelectedRecordIds.includes(record.id))
|
||||
.map((record) => record.name);
|
||||
|
||||
const filterDisplayValue =
|
||||
selectedRecordNames.length > MAX_RECORDS_TO_DISPLAY
|
||||
? `${selectedRecordNames.length} companies`
|
||||
: selectedRecordNames.join(', ');
|
||||
|
||||
if (filterDefinitionUsedInDropdown && selectedOperandInDropdown) {
|
||||
const newFilterValue =
|
||||
newSelectedRecordIds.length > 0
|
||||
? JSON.stringify(newSelectedRecordIds)
|
||||
: EMPTY_FILTER_VALUE;
|
||||
|
||||
selectFilter({
|
||||
definition: filterDefinitionUsedInDropdown,
|
||||
operand: selectedOperandInDropdown,
|
||||
displayValue: filterDisplayValue,
|
||||
fieldMetadataId: filterDefinitionUsedInDropdown.fieldMetadataId,
|
||||
value: newFilterValue,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MultipleRecordSelectDropdown
|
||||
recordsToSelect={recordsToSelect}
|
||||
filteredSelectedRecords={filteredSelectedRecords}
|
||||
selectedRecords={selectedRecords}
|
||||
onChange={handleMultipleRecordSelectChange}
|
||||
searchFilter={objectFilterDropdownSearchInput}
|
||||
loadingRecords={loading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -12,8 +12,8 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
|
||||
|
||||
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
|
||||
import { ObjectFilterDropdownEntitySearchInput } from './ObjectFilterDropdownEntitySearchInput';
|
||||
import { ObjectFilterDropdownEntitySelect } from './ObjectFilterDropdownEntitySelect';
|
||||
import { ObjectFilterDropdownRecordSearchInput } from './ObjectFilterDropdownEntitySearchInput';
|
||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||
|
||||
export const SingleEntityObjectFilterDropdownButton = ({
|
||||
hotkeyScope,
|
||||
@@ -65,8 +65,8 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<ObjectFilterDropdownEntitySearchInput />
|
||||
<ObjectFilterDropdownEntitySelect />
|
||||
<ObjectFilterDropdownRecordSearchInput />
|
||||
<ObjectFilterDropdownRecordSelect />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -25,6 +25,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
setObjectFilterDropdownSearchInput,
|
||||
objectFilterDropdownSelectedEntityId,
|
||||
setObjectFilterDropdownSelectedEntityId,
|
||||
objectFilterDropdownSelectedRecordIds,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
isObjectFilterDropdownOperandSelectUnfolded,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
isObjectFilterDropdownUnfolded,
|
||||
@@ -48,6 +50,7 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
const resetFilter = useCallback(() => {
|
||||
setObjectFilterDropdownSearchInput('');
|
||||
setObjectFilterDropdownSelectedEntityId(null);
|
||||
setObjectFilterDropdownSelectedRecordIds([]);
|
||||
setSelectedFilter(undefined);
|
||||
setFilterDefinitionUsedInDropdown(null);
|
||||
setSelectedOperandInDropdown(null);
|
||||
@@ -55,6 +58,7 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setObjectFilterDropdownSearchInput,
|
||||
setObjectFilterDropdownSelectedEntityId,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
setSelectedFilter,
|
||||
setSelectedOperandInDropdown,
|
||||
]);
|
||||
@@ -69,6 +73,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
setObjectFilterDropdownSearchInput,
|
||||
objectFilterDropdownSelectedEntityId,
|
||||
setObjectFilterDropdownSelectedEntityId,
|
||||
objectFilterDropdownSelectedRecordIds,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
isObjectFilterDropdownOperandSelectUnfolded,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
isObjectFilterDropdownUnfolded,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { objectFilterDropdownSelectedRecordIdsScopedState } from '@/ui/object/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsScopedState';
|
||||
import { onFilterSelectScopedState } from '@/ui/object/object-filter-dropdown/states/onFilterSelectScopedState';
|
||||
import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2';
|
||||
|
||||
@@ -28,6 +29,14 @@ export const useFilterDropdownStates = (scopeId: string) => {
|
||||
scopeId,
|
||||
);
|
||||
|
||||
const [
|
||||
objectFilterDropdownSelectedRecordIds,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
] = useRecoilScopedStateV2(
|
||||
objectFilterDropdownSelectedRecordIdsScopedState,
|
||||
scopeId,
|
||||
);
|
||||
|
||||
const [
|
||||
isObjectFilterDropdownOperandSelectUnfolded,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
@@ -61,6 +70,8 @@ export const useFilterDropdownStates = (scopeId: string) => {
|
||||
setObjectFilterDropdownSearchInput,
|
||||
objectFilterDropdownSelectedEntityId,
|
||||
setObjectFilterDropdownSelectedEntityId,
|
||||
objectFilterDropdownSelectedRecordIds,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
isObjectFilterDropdownOperandSelectUnfolded,
|
||||
setIsObjectFilterDropdownOperandSelectUnfolded,
|
||||
isObjectFilterDropdownUnfolded,
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
|
||||
|
||||
export const objectFilterDropdownSelectedRecordIdsScopedState =
|
||||
createScopedState<string[]>({
|
||||
key: 'objectFilterDropdownSelectedRecordIdsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
||||
+34
-19
@@ -94,26 +94,41 @@ export const turnFiltersIntoWhereClause = (
|
||||
);
|
||||
}
|
||||
case 'RELATION':
|
||||
switch (filter.operand) {
|
||||
case ViewFilterOperand.Is:
|
||||
whereClause.push({
|
||||
[correspondingField.name + 'Id']: {
|
||||
eq: filter.value,
|
||||
},
|
||||
});
|
||||
return;
|
||||
case ViewFilterOperand.IsNot:
|
||||
whereClause.push({
|
||||
[correspondingField.name + 'Id']: {
|
||||
neq: filter.value,
|
||||
},
|
||||
});
|
||||
return;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
||||
);
|
||||
try {
|
||||
JSON.parse(filter.value);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Cannot parse filter value for RELATION filter : "${filter.value}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const parsedRecordIds = JSON.parse(filter.value) as string[];
|
||||
|
||||
if (parsedRecordIds.length > 0) {
|
||||
switch (filter.operand) {
|
||||
case ViewFilterOperand.Is:
|
||||
whereClause.push({
|
||||
[correspondingField.name + 'Id']: {
|
||||
in: parsedRecordIds,
|
||||
},
|
||||
});
|
||||
return;
|
||||
case ViewFilterOperand.IsNot:
|
||||
whereClause.push({
|
||||
not: {
|
||||
[correspondingField.name + 'Id']: {
|
||||
in: parsedRecordIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CURRENCY':
|
||||
switch (filter.operand) {
|
||||
case ViewFilterOperand.GreaterThan:
|
||||
|
||||
@@ -11,6 +11,7 @@ export const RecordTableBodyEffect = () => {
|
||||
records,
|
||||
setRecordTableData,
|
||||
queryStateIdentifier,
|
||||
loading,
|
||||
} = useObjectRecordTable();
|
||||
const { tableLastRowVisibleState } = useRecordTableScopedStates();
|
||||
const [tableLastRowVisible, setTableLastRowVisible] = useRecoilState(
|
||||
@@ -22,8 +23,10 @@ export const RecordTableBodyEffect = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setRecordTableData(records);
|
||||
}, [records, setRecordTableData]);
|
||||
if (!loading) {
|
||||
setRecordTableData(records);
|
||||
}
|
||||
}, [records, setRecordTableData, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tableLastRowVisible && !isFetchingMoreObjects) {
|
||||
|
||||
Reference in New Issue
Block a user