Feat/multi relation filter (#2858)
* WIP * Finished multi select filter * Cleaned console log * Fix naming * Fixed naming
This commit is contained in:
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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'];
|
||||
};
|
||||
@ -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';
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import { AvatarType } from '@/users/components/Avatar';
|
||||
|
||||
export type ObjectRecordIdentifier = {
|
||||
id: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
avatarType?: AvatarType;
|
||||
};
|
||||
@ -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 />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -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,
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
|
||||
|
||||
export const objectFilterDropdownSelectedRecordIdsScopedState =
|
||||
createScopedState<string[]>({
|
||||
key: 'objectFilterDropdownSelectedRecordIdsScopedState',
|
||||
defaultValue: [],
|
||||
});
|
||||
@ -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) {
|
||||
|
||||
@ -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 <></>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user