diff --git a/front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts
index 2503893cc..2cdeb9196 100644
--- a/front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts
+++ b/front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts
@@ -12,7 +12,9 @@ import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerate
import { useGenerateUpdateOneRecordMutation } from '@/object-record/hooks/useGenerateUpdateOneRecordMutation';
import { useGetRecordFromCache } from '@/object-record/hooks/useGetRecordFromCache';
import { useModifyRecordFromCache } from '@/object-record/hooks/useModifyRecordFromCache';
+import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
import { generateDeleteOneRecordMutation } from '@/object-record/utils/generateDeleteOneRecordMutation';
+import { getLogoUrlFromDomainName } from '~/utils';
import { isDefined } from '~/utils/isDefined';
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({
objectMetadataItem,
});
@@ -108,5 +147,6 @@ export const useObjectMetadataItem = (
createOneRecordMutation,
updateOneRecordMutation,
deleteOneRecordMutation,
+ mapToObjectRecordIdentifier,
};
};
diff --git a/front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx b/front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx
new file mode 100644
index 000000000..15914204d
--- /dev/null
+++ b/front/src/modules/object-record/select/components/MultipleRecordSelectDropdown.tsx
@@ -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 (
+
+ {recordsInDropdown?.map((record) => (
+
+ handleRecordSelectChange(record, newCheckedValue)
+ }
+ avatar={
+
+ }
+ text={record.name}
+ />
+ ))}
+ {showNoResult && }
+ {loadingRecords && }
+
+ );
+};
diff --git a/front/src/modules/object-record/select/hooks/useRecordsForSelect.ts b/front/src/modules/object-record/select/hooks/useRecordsForSelect.ts
new file mode 100644
index 000000000..de5ae39e1
--- /dev/null
+++ b/front/src/modules/object-record/select/hooks/useRecordsForSelect.ts
@@ -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,
+ };
+};
diff --git a/front/src/modules/object-record/select/types/SelectableRecord.ts b/front/src/modules/object-record/select/types/SelectableRecord.ts
new file mode 100644
index 000000000..202a565e0
--- /dev/null
+++ b/front/src/modules/object-record/select/types/SelectableRecord.ts
@@ -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;
+};
diff --git a/front/src/modules/object-record/select/utils/getObjectFilterFields.ts b/front/src/modules/object-record/select/utils/getObjectFilterFields.ts
new file mode 100644
index 000000000..6ad643eaa
--- /dev/null
+++ b/front/src/modules/object-record/select/utils/getObjectFilterFields.ts
@@ -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'];
+};
diff --git a/front/src/modules/object-record/select/utils/getObjectOrderByField.ts b/front/src/modules/object-record/select/utils/getObjectOrderByField.ts
new file mode 100644
index 000000000..0f33e3fec
--- /dev/null
+++ b/front/src/modules/object-record/select/utils/getObjectOrderByField.ts
@@ -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';
+};
diff --git a/front/src/modules/object-record/types/ObjectRecordIdentifier.ts b/front/src/modules/object-record/types/ObjectRecordIdentifier.ts
new file mode 100644
index 000000000..2401982b3
--- /dev/null
+++ b/front/src/modules/object-record/types/ObjectRecordIdentifier.ts
@@ -0,0 +1,8 @@
+import { AvatarType } from '@/users/components/Avatar';
+
+export type ObjectRecordIdentifier = {
+ id: string;
+ name: string;
+ avatarUrl?: string;
+ avatarType?: AvatarType;
+};
diff --git a/front/src/modules/ui/object/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx b/front/src/modules/ui/object/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx
index 468f29010..ddd9bd14e 100644
--- a/front/src/modules/ui/object/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx
+++ b/front/src/modules/ui/object/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx
@@ -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 = () => {
)}
{filterDefinitionUsedInDropdown.type === 'RELATION' && (
-
- )}
- {filterDefinitionUsedInDropdown.type === 'RELATION' && (
-
+ <>
+
+
+
+ >
)}
>
)
diff --git a/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput.tsx b/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput.tsx
index 69be5af68..82d5f1bf0 100644
--- a/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput.tsx
+++ b/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchInput.tsx
@@ -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,
diff --git a/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySelect.tsx b/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySelect.tsx
deleted file mode 100644
index a946d2cbc..000000000
--- a/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySelect.tsx
+++ /dev/null
@@ -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 (
- <>
-
- >
- );
-};
diff --git a/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx b/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx
new file mode 100644
index 000000000..5c529c8d3
--- /dev/null
+++ b/front/src/modules/ui/object/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx
@@ -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 (
+
+ );
+};
diff --git a/front/src/modules/ui/object/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx b/front/src/modules/ui/object/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx
index 3d10be112..15ccfff0a 100644
--- a/front/src/modules/ui/object/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx
+++ b/front/src/modules/ui/object/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx
@@ -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={
<>
-
-
+
+
>
}
/>
diff --git a/front/src/modules/ui/object/object-filter-dropdown/hooks/useFilterDropdown.ts b/front/src/modules/ui/object/object-filter-dropdown/hooks/useFilterDropdown.ts
index 07cb65569..eca5f810f 100644
--- a/front/src/modules/ui/object/object-filter-dropdown/hooks/useFilterDropdown.ts
+++ b/front/src/modules/ui/object/object-filter-dropdown/hooks/useFilterDropdown.ts
@@ -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,
diff --git a/front/src/modules/ui/object/object-filter-dropdown/hooks/useFilterDropdownStates.ts b/front/src/modules/ui/object/object-filter-dropdown/hooks/useFilterDropdownStates.ts
index 37acb6428..b9b156890 100644
--- a/front/src/modules/ui/object/object-filter-dropdown/hooks/useFilterDropdownStates.ts
+++ b/front/src/modules/ui/object/object-filter-dropdown/hooks/useFilterDropdownStates.ts
@@ -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,
diff --git a/front/src/modules/ui/object/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsScopedState.ts b/front/src/modules/ui/object/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsScopedState.ts
new file mode 100644
index 000000000..70d897971
--- /dev/null
+++ b/front/src/modules/ui/object/object-filter-dropdown/states/objectFilterDropdownSelectedRecordIdsScopedState.ts
@@ -0,0 +1,7 @@
+import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
+
+export const objectFilterDropdownSelectedRecordIdsScopedState =
+ createScopedState({
+ key: 'objectFilterDropdownSelectedRecordIdsScopedState',
+ defaultValue: [],
+ });
diff --git a/front/src/modules/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause.ts b/front/src/modules/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause.ts
index 1f471b70c..c320afa77 100644
--- a/front/src/modules/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause.ts
+++ b/front/src/modules/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause.ts
@@ -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:
diff --git a/front/src/modules/ui/object/record-table/components/RecordTableBodyEffect.tsx b/front/src/modules/ui/object/record-table/components/RecordTableBodyEffect.tsx
index e92e1d109..764b820e2 100644
--- a/front/src/modules/ui/object/record-table/components/RecordTableBodyEffect.tsx
+++ b/front/src/modules/ui/object/record-table/components/RecordTableBodyEffect.tsx
@@ -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) {
diff --git a/front/src/modules/views/components/ViewBarFilterEffect.tsx b/front/src/modules/views/components/ViewBarFilterEffect.tsx
index 32600fad2..8b1424586 100644
--- a/front/src/modules/views/components/ViewBarFilterEffect.tsx
+++ b/front/src/modules/views/components/ViewBarFilterEffect.tsx
@@ -14,13 +14,19 @@ export const ViewBarFilterEffect = ({
filterDropdownId,
onFilterSelect,
}: ViewBarFilterEffectProps) => {
- const { availableFilterDefinitionsState } = useViewScopedStates();
+ const { availableFilterDefinitionsState, currentViewFiltersState } =
+ useViewScopedStates();
const availableFilterDefinitions = useRecoilValue(
availableFilterDefinitionsState,
);
- const { setAvailableFilterDefinitions, setOnFilterSelect } =
- useFilterDropdown({ filterDropdownId: filterDropdownId });
+ const {
+ setAvailableFilterDefinitions,
+ setOnFilterSelect,
+ filterDefinitionUsedInDropdown,
+ setObjectFilterDropdownSelectedRecordIds,
+ isObjectFilterDropdownUnfolded,
+ } = useFilterDropdown({ filterDropdownId: filterDropdownId });
useEffect(() => {
if (availableFilterDefinitions) {
@@ -37,5 +43,28 @@ export const ViewBarFilterEffect = ({
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 <>>;
};