Feat/multi relation filter (#2858)

* WIP

* Finished multi select filter

* Cleaned console log

* Fix naming

* Fixed naming
This commit is contained in:
Lucas Bordeau
2023-12-07 12:08:48 +01:00
committed by GitHub
parent b2912f4b4b
commit 06936c3c2a
18 changed files with 516 additions and 93 deletions

View File

@ -12,7 +12,9 @@ import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerate
import { useGenerateUpdateOneRecordMutation } from '@/object-record/hooks/useGenerateUpdateOneRecordMutation'; import { useGenerateUpdateOneRecordMutation } from '@/object-record/hooks/useGenerateUpdateOneRecordMutation';
import { useGetRecordFromCache } from '@/object-record/hooks/useGetRecordFromCache'; import { useGetRecordFromCache } from '@/object-record/hooks/useGetRecordFromCache';
import { useModifyRecordFromCache } from '@/object-record/hooks/useModifyRecordFromCache'; import { useModifyRecordFromCache } from '@/object-record/hooks/useModifyRecordFromCache';
import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
import { generateDeleteOneRecordMutation } from '@/object-record/utils/generateDeleteOneRecordMutation'; import { generateDeleteOneRecordMutation } from '@/object-record/utils/generateDeleteOneRecordMutation';
import { getLogoUrlFromDomainName } from '~/utils';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier'; import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
@ -61,6 +63,43 @@ export const useObjectMetadataItem = (
); );
} }
const mapToObjectRecordIdentifier = (record: any): ObjectRecordIdentifier => {
if (objectNameSingular === 'company') {
return {
id: record.id,
name: record.name,
avatarUrl: getLogoUrlFromDomainName(record.domainName ?? ''),
avatarType: 'squared',
};
}
if (['workspaceMember', 'person'].includes(objectNameSingular)) {
return {
id: record.id,
name:
(record.name?.firstName ?? '') + ' ' + (record.name?.lastName ?? ''),
avatarUrl: record.avatarUrl,
avatarType: 'rounded',
};
}
if (['opportunity'].includes(objectNameSingular)) {
return {
id: record.id,
name: record?.company?.name,
avatarUrl: record.avatarUrl,
avatarType: 'rounded',
};
}
return {
id: record.id,
name: record.name,
avatarUrl: record.avatarUrl,
avatarType: 'rounded',
};
};
const getRecordFromCache = useGetRecordFromCache({ const getRecordFromCache = useGetRecordFromCache({
objectMetadataItem, objectMetadataItem,
}); });
@ -108,5 +147,6 @@ export const useObjectMetadataItem = (
createOneRecordMutation, createOneRecordMutation,
updateOneRecordMutation, updateOneRecordMutation,
deleteOneRecordMutation, deleteOneRecordMutation,
mapToObjectRecordIdentifier,
}; };
}; };

View File

@ -0,0 +1,85 @@
import { useEffect, useState } from 'react';
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { MenuItemMultiSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemMultiSelectAvatar';
import { Avatar } from '@/users/components/Avatar';
export const MultipleRecordSelectDropdown = ({
recordsToSelect,
loadingRecords,
filteredSelectedRecords,
onChange,
searchFilter,
}: {
recordsToSelect: SelectableRecord[];
filteredSelectedRecords: SelectableRecord[];
selectedRecords: SelectableRecord[];
searchFilter: string;
onChange: (
changedRecordToSelect: SelectableRecord,
newSelectedValue: boolean,
) => void;
loadingRecords: boolean;
}) => {
const handleRecordSelectChange = (
recordToSelect: SelectableRecord,
newSelectedValue: boolean,
) => {
onChange(
{
...recordToSelect,
isSelected: newSelectedValue,
},
newSelectedValue,
);
};
const [recordsInDropdown, setRecordInDropdown] = useState([
...(filteredSelectedRecords ?? []),
...(recordsToSelect ?? []),
]);
useEffect(() => {
if (!loadingRecords) {
setRecordInDropdown([
...(filteredSelectedRecords ?? []),
...(recordsToSelect ?? []),
]);
}
}, [recordsToSelect, filteredSelectedRecords, loadingRecords]);
const showNoResult =
recordsToSelect?.length === 0 &&
searchFilter !== '' &&
filteredSelectedRecords?.length === 0 &&
!loadingRecords;
return (
<DropdownMenuItemsContainer hasMaxHeight>
{recordsInDropdown?.map((record) => (
<MenuItemMultiSelectAvatar
key={record.id}
selected={record.isSelected}
onSelectChange={(newCheckedValue) =>
handleRecordSelectChange(record, newCheckedValue)
}
avatar={
<Avatar
avatarUrl={record.avatarUrl}
colorId={record.id}
placeholder={record.name}
size="md"
type={record.avatarType ?? 'rounded'}
/>
}
text={record.name}
/>
))}
{showNoResult && <MenuItem text="No result" />}
{loadingRecords && <DropdownMenuSkeletonItem />}
</DropdownMenuItemsContainer>
);
};

View File

@ -0,0 +1,159 @@
import { isNonEmptyString } from '@sniptt/guards';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
import { getObjectFilterFields } from '@/object-record/select/utils/getObjectFilterFields';
import { getObjectOrderByField } from '@/object-record/select/utils/getObjectOrderByField';
import { isDefined } from '~/utils/isDefined';
export type OrderBy =
| 'AscNullsLast'
| 'DescNullsLast'
| 'AscNullsFirst'
| 'DescNullsFirst';
export const DEFAULT_SEARCH_REQUEST_LIMIT = 60;
export const useRecordsForSelect = ({
searchFilterText,
sortOrder = 'AscNullsLast',
selectedIds,
limit,
excludeEntityIds = [],
objectNameSingular,
}: {
searchFilterText: string;
sortOrder?: OrderBy;
selectedIds: string[];
limit?: number;
excludeEntityIds?: string[];
objectNameSingular: string;
}) => {
const { mapToObjectRecordIdentifier } = useObjectMetadataItem({
objectNameSingular,
});
const filters = [
{
fieldNames: getObjectFilterFields(objectNameSingular) ?? [],
filter: searchFilterText,
},
];
const orderByField = getObjectOrderByField(objectNameSingular);
const { loading: selectedRecordsLoading, records: selectedRecordsData } =
useFindManyRecords({
filter: {
id: {
in: selectedIds,
},
},
orderBy: {
[orderByField]: sortOrder,
},
objectNameSingular,
});
const searchFilter = filters
.map(({ fieldNames, filter }) => {
if (!isNonEmptyString(filter)) {
return undefined;
}
return {
or: fieldNames.map((fieldName) => {
const fieldNameParts = fieldName.split('.');
if (fieldNameParts.length > 1) {
// Composite field
return {
[fieldNameParts[0]]: {
[fieldNameParts[1]]: {
ilike: `%${filter}%`,
},
},
};
}
return {
[fieldName]: {
ilike: `%${filter}%`,
},
};
}),
};
})
.filter(isDefined);
const {
loading: filteredSelectedRecordsLoading,
records: filteredSelectedRecordsData,
} = useFindManyRecords({
filter: {
and: [
{
and: searchFilter,
},
{
id: {
in: selectedIds,
},
},
],
},
orderBy: {
[orderByField]: sortOrder,
},
objectNameSingular,
});
const { loading: recordsToSelectLoading, records: recordsToSelectData } =
useFindManyRecords({
filter: {
and: [
{
and: searchFilter,
},
{
not: {
id: {
in: [...selectedIds, ...excludeEntityIds],
},
},
},
],
},
limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT,
orderBy: {
[orderByField]: sortOrder,
},
objectNameSingular,
});
return {
selectedRecords: selectedRecordsData
.map(mapToObjectRecordIdentifier)
.map((record) => ({
...record,
isSelected: true,
})) as SelectableRecord[],
filteredSelectedRecords: filteredSelectedRecordsData
.map(mapToObjectRecordIdentifier)
.map((record) => ({
...record,
isSelected: true,
})) as SelectableRecord[],
recordsToSelect: recordsToSelectData
.map(mapToObjectRecordIdentifier)
.map((record) => ({
...record,
isSelected: false,
})) as SelectableRecord[],
loading:
recordsToSelectLoading ||
filteredSelectedRecordsLoading ||
selectedRecordsLoading,
};
};

View File

@ -0,0 +1,10 @@
import { AvatarType } from '@/users/components/Avatar';
export type SelectableRecord = {
id: string;
name: string;
avatarUrl?: string;
avatarType?: AvatarType;
record: any;
isSelected: boolean;
};

View File

@ -0,0 +1,11 @@
export const getObjectFilterFields = (objectSingleName: string) => {
if (objectSingleName === 'company') {
return ['name'];
}
if (['workspaceMember', 'person'].includes(objectSingleName)) {
return ['name.firstName', 'name.lastName'];
}
return ['name'];
};

View File

@ -0,0 +1,11 @@
export const getObjectOrderByField = (objectSingleName: string): string => {
if (objectSingleName === 'company') {
return 'name';
}
if (['workspaceMember', 'person'].includes(objectSingleName)) {
return 'name.firstName';
}
return 'createdAt';
};

View File

@ -0,0 +1,8 @@
import { AvatarType } from '@/users/components/Avatar';
export type ObjectRecordIdentifier = {
id: string;
name: string;
avatarUrl?: string;
avatarType?: AvatarType;
};

View File

@ -1,14 +1,14 @@
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; 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 { useFilterDropdown } from '@/ui/object/object-filter-dropdown/hooks/useFilterDropdown';
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect'; import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
import { ObjectFilterDropdownDateSearchInput } from './ObjectFilterDropdownDateSearchInput'; import { ObjectFilterDropdownDateSearchInput } from './ObjectFilterDropdownDateSearchInput';
import { ObjectFilterDropdownEntitySearchInput } from './ObjectFilterDropdownEntitySearchInput';
import { ObjectFilterDropdownEntitySelect } from './ObjectFilterDropdownEntitySelect';
import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect'; import { ObjectFilterDropdownFilterSelect } from './ObjectFilterDropdownFilterSelect';
import { ObjectFilterDropdownNumberSearchInput } from './ObjectFilterDropdownNumberSearchInput'; import { ObjectFilterDropdownNumberSearchInput } from './ObjectFilterDropdownNumberSearchInput';
import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton'; import { ObjectFilterDropdownOperandButton } from './ObjectFilterDropdownOperandButton';
import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect'; import { ObjectFilterDropdownOperandSelect } from './ObjectFilterDropdownOperandSelect';
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput'; import { ObjectFilterDropdownTextSearchInput } from './ObjectFilterDropdownTextSearchInput';
export const MultipleFiltersDropdownContent = () => { export const MultipleFiltersDropdownContent = () => {
@ -39,10 +39,11 @@ export const MultipleFiltersDropdownContent = () => {
<ObjectFilterDropdownDateSearchInput /> <ObjectFilterDropdownDateSearchInput />
)} )}
{filterDefinitionUsedInDropdown.type === 'RELATION' && ( {filterDefinitionUsedInDropdown.type === 'RELATION' && (
<ObjectFilterDropdownEntitySearchInput /> <>
)} <ObjectFilterDropdownRecordSearchInput />
{filterDefinitionUsedInDropdown.type === 'RELATION' && ( <DropdownMenuSeparator />
<ObjectFilterDropdownEntitySelect /> <ObjectFilterDropdownRecordSelect />
</>
)} )}
</> </>
) )

View File

@ -3,7 +3,7 @@ import { ChangeEvent } from 'react';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { useFilterDropdown } from '@/ui/object/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/ui/object/object-filter-dropdown/hooks/useFilterDropdown';
export const ObjectFilterDropdownEntitySearchInput = () => { export const ObjectFilterDropdownRecordSearchInput = () => {
const { const {
filterDefinitionUsedInDropdown, filterDefinitionUsedInDropdown,
selectedOperandInDropdown, selectedOperandInDropdown,

View File

@ -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}
/>
</>
);
};

View File

@ -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}
/>
);
};

View File

@ -12,8 +12,8 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType'; import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
import { GenericEntityFilterChip } from './GenericEntityFilterChip'; import { GenericEntityFilterChip } from './GenericEntityFilterChip';
import { ObjectFilterDropdownEntitySearchInput } from './ObjectFilterDropdownEntitySearchInput'; import { ObjectFilterDropdownRecordSearchInput } from './ObjectFilterDropdownEntitySearchInput';
import { ObjectFilterDropdownEntitySelect } from './ObjectFilterDropdownEntitySelect'; import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
export const SingleEntityObjectFilterDropdownButton = ({ export const SingleEntityObjectFilterDropdownButton = ({
hotkeyScope, hotkeyScope,
@ -65,8 +65,8 @@ export const SingleEntityObjectFilterDropdownButton = ({
} }
dropdownComponents={ dropdownComponents={
<> <>
<ObjectFilterDropdownEntitySearchInput /> <ObjectFilterDropdownRecordSearchInput />
<ObjectFilterDropdownEntitySelect /> <ObjectFilterDropdownRecordSelect />
</> </>
} }
/> />

View File

@ -25,6 +25,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
setObjectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput,
objectFilterDropdownSelectedEntityId, objectFilterDropdownSelectedEntityId,
setObjectFilterDropdownSelectedEntityId, setObjectFilterDropdownSelectedEntityId,
objectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedRecordIds,
isObjectFilterDropdownOperandSelectUnfolded, isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded, setIsObjectFilterDropdownOperandSelectUnfolded,
isObjectFilterDropdownUnfolded, isObjectFilterDropdownUnfolded,
@ -48,6 +50,7 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
const resetFilter = useCallback(() => { const resetFilter = useCallback(() => {
setObjectFilterDropdownSearchInput(''); setObjectFilterDropdownSearchInput('');
setObjectFilterDropdownSelectedEntityId(null); setObjectFilterDropdownSelectedEntityId(null);
setObjectFilterDropdownSelectedRecordIds([]);
setSelectedFilter(undefined); setSelectedFilter(undefined);
setFilterDefinitionUsedInDropdown(null); setFilterDefinitionUsedInDropdown(null);
setSelectedOperandInDropdown(null); setSelectedOperandInDropdown(null);
@ -55,6 +58,7 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
setFilterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown,
setObjectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput,
setObjectFilterDropdownSelectedEntityId, setObjectFilterDropdownSelectedEntityId,
setObjectFilterDropdownSelectedRecordIds,
setSelectedFilter, setSelectedFilter,
setSelectedOperandInDropdown, setSelectedOperandInDropdown,
]); ]);
@ -69,6 +73,8 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
setObjectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput,
objectFilterDropdownSelectedEntityId, objectFilterDropdownSelectedEntityId,
setObjectFilterDropdownSelectedEntityId, setObjectFilterDropdownSelectedEntityId,
objectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedRecordIds,
isObjectFilterDropdownOperandSelectUnfolded, isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded, setIsObjectFilterDropdownOperandSelectUnfolded,
isObjectFilterDropdownUnfolded, isObjectFilterDropdownUnfolded,

View File

@ -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 { onFilterSelectScopedState } from '@/ui/object/object-filter-dropdown/states/onFilterSelectScopedState';
import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2'; import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2';
@ -28,6 +29,14 @@ export const useFilterDropdownStates = (scopeId: string) => {
scopeId, scopeId,
); );
const [
objectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedRecordIds,
] = useRecoilScopedStateV2(
objectFilterDropdownSelectedRecordIdsScopedState,
scopeId,
);
const [ const [
isObjectFilterDropdownOperandSelectUnfolded, isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded, setIsObjectFilterDropdownOperandSelectUnfolded,
@ -61,6 +70,8 @@ export const useFilterDropdownStates = (scopeId: string) => {
setObjectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput,
objectFilterDropdownSelectedEntityId, objectFilterDropdownSelectedEntityId,
setObjectFilterDropdownSelectedEntityId, setObjectFilterDropdownSelectedEntityId,
objectFilterDropdownSelectedRecordIds,
setObjectFilterDropdownSelectedRecordIds,
isObjectFilterDropdownOperandSelectUnfolded, isObjectFilterDropdownOperandSelectUnfolded,
setIsObjectFilterDropdownOperandSelectUnfolded, setIsObjectFilterDropdownOperandSelectUnfolded,
isObjectFilterDropdownUnfolded, isObjectFilterDropdownUnfolded,

View File

@ -0,0 +1,7 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const objectFilterDropdownSelectedRecordIdsScopedState =
createScopedState<string[]>({
key: 'objectFilterDropdownSelectedRecordIdsScopedState',
defaultValue: [],
});

View File

@ -94,26 +94,41 @@ export const turnFiltersIntoWhereClause = (
); );
} }
case 'RELATION': case 'RELATION':
switch (filter.operand) { try {
case ViewFilterOperand.Is: JSON.parse(filter.value);
whereClause.push({ } catch (e) {
[correspondingField.name + 'Id']: { throw new Error(
eq: filter.value, `Cannot parse filter value for RELATION filter : "${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`,
);
} }
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': case 'CURRENCY':
switch (filter.operand) { switch (filter.operand) {
case ViewFilterOperand.GreaterThan: case ViewFilterOperand.GreaterThan:

View File

@ -11,6 +11,7 @@ export const RecordTableBodyEffect = () => {
records, records,
setRecordTableData, setRecordTableData,
queryStateIdentifier, queryStateIdentifier,
loading,
} = useObjectRecordTable(); } = useObjectRecordTable();
const { tableLastRowVisibleState } = useRecordTableScopedStates(); const { tableLastRowVisibleState } = useRecordTableScopedStates();
const [tableLastRowVisible, setTableLastRowVisible] = useRecoilState( const [tableLastRowVisible, setTableLastRowVisible] = useRecoilState(
@ -22,8 +23,10 @@ export const RecordTableBodyEffect = () => {
); );
useEffect(() => { useEffect(() => {
setRecordTableData(records); if (!loading) {
}, [records, setRecordTableData]); setRecordTableData(records);
}
}, [records, setRecordTableData, loading]);
useEffect(() => { useEffect(() => {
if (tableLastRowVisible && !isFetchingMoreObjects) { if (tableLastRowVisible && !isFetchingMoreObjects) {

View File

@ -14,13 +14,19 @@ export const ViewBarFilterEffect = ({
filterDropdownId, filterDropdownId,
onFilterSelect, onFilterSelect,
}: ViewBarFilterEffectProps) => { }: ViewBarFilterEffectProps) => {
const { availableFilterDefinitionsState } = useViewScopedStates(); const { availableFilterDefinitionsState, currentViewFiltersState } =
useViewScopedStates();
const availableFilterDefinitions = useRecoilValue( const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState, availableFilterDefinitionsState,
); );
const { setAvailableFilterDefinitions, setOnFilterSelect } = const {
useFilterDropdown({ filterDropdownId: filterDropdownId }); setAvailableFilterDefinitions,
setOnFilterSelect,
filterDefinitionUsedInDropdown,
setObjectFilterDropdownSelectedRecordIds,
isObjectFilterDropdownUnfolded,
} = useFilterDropdown({ filterDropdownId: filterDropdownId });
useEffect(() => { useEffect(() => {
if (availableFilterDefinitions) { if (availableFilterDefinitions) {
@ -37,5 +43,28 @@ export const ViewBarFilterEffect = ({
setOnFilterSelect, setOnFilterSelect,
]); ]);
const currentViewFilters = useRecoilValue(currentViewFiltersState);
useEffect(() => {
if (filterDefinitionUsedInDropdown?.type === 'RELATION') {
const viewFilterUsedInDropdown = currentViewFilters.find(
(filter) =>
filter.fieldMetadataId ===
filterDefinitionUsedInDropdown.fieldMetadataId,
);
const viewFilterSelectedRecordIds = JSON.parse(
viewFilterUsedInDropdown?.value ?? '[]',
);
setObjectFilterDropdownSelectedRecordIds(viewFilterSelectedRecordIds);
}
}, [
filterDefinitionUsedInDropdown,
currentViewFilters,
setObjectFilterDropdownSelectedRecordIds,
isObjectFilterDropdownUnfolded,
]);
return <></>; return <></>;
}; };