New branch based on feedback in PR #8950 and issue #8016 --------- Co-authored-by: ad-elias <elias@autodiligence.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -8,10 +8,10 @@ import { InternalDatePicker } from '@/ui/input/components/internal/date/componen
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue';
|
||||
import {
|
||||
resolveDateViewFilterValue,
|
||||
VariableDateViewFilterValueDirection,
|
||||
VariableDateViewFilterValueUnit,
|
||||
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
|
||||
import { resolveFilterValue } from '@/views/view-filter-value/utils/resolveFilterValue';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@ -37,7 +37,7 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
| undefined;
|
||||
|
||||
const initialFilterValue = selectedFilter
|
||||
? resolveFilterValue(selectedFilter)
|
||||
? resolveDateViewFilterValue(selectedFilter)
|
||||
: null;
|
||||
const [internalDate, setInternalDate] = useState<Date | null>(
|
||||
initialFilterValue instanceof Date ? initialFilterValue : null,
|
||||
@ -98,7 +98,7 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
selectedOperandInDropdown === ViewFilterOperand.IsRelative;
|
||||
|
||||
const resolvedValue = selectedFilter
|
||||
? resolveFilterValue(selectedFilter)
|
||||
? resolveDateViewFilterValue(selectedFilter)
|
||||
: null;
|
||||
|
||||
const relativeDate =
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
import { StyledMultipleSelectDropdownAvatarChip } from '@/object-record/select/components/StyledMultipleSelectDropdownAvatarChip';
|
||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||
import styled from '@emotion/styled';
|
||||
import { MenuItemMultiSelectAvatar } from 'twenty-ui';
|
||||
|
||||
const StyledPinnedItemsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const ObjectFilterDropdownRecordPinnedItems = (props: {
|
||||
selectableItems: SelectableItem[];
|
||||
onChange: (
|
||||
selectableItem: SelectableItem,
|
||||
isNewCheckedValue: boolean,
|
||||
) => void;
|
||||
}) => {
|
||||
return (
|
||||
<StyledPinnedItemsContainer>
|
||||
{props.selectableItems.map((selectableItem) => {
|
||||
return (
|
||||
<MenuItemMultiSelectAvatar
|
||||
key={selectableItem.id}
|
||||
selected={selectableItem.isSelected}
|
||||
onSelectChange={(newCheckedValue) => {
|
||||
props.onChange(selectableItem, newCheckedValue);
|
||||
}}
|
||||
avatar={
|
||||
<StyledMultipleSelectDropdownAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={selectableItem.name}
|
||||
avatarUrl={selectableItem.avatarUrl}
|
||||
LeftIcon={selectableItem.AvatarIcon}
|
||||
avatarType={selectableItem.avatarType}
|
||||
isIconInverted={selectableItem.isIconInverted}
|
||||
placeholderColorSeed={selectableItem.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledPinnedItemsContainer>
|
||||
);
|
||||
};
|
||||
@ -2,16 +2,26 @@ import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectFilterDropdownRecordPinnedItems } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordPinnedItems';
|
||||
import { CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID } from '@/object-record/object-filter-dropdown/constants/CurrentWorkspaceMemberSelectableItemId';
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { MultipleSelectDropdown } from '@/object-record/select/components/MultipleSelectDropdown';
|
||||
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
|
||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { RelationFilterValue } from '@/views/view-filter-value/types/RelationFilterValue';
|
||||
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
|
||||
import { IconUserCircle } from 'twenty-ui';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const EMPTY_FILTER_VALUE = '[]';
|
||||
export const EMPTY_FILTER_VALUE: string = JSON.stringify({
|
||||
isCurrentWorkspaceMemberSelected: false,
|
||||
selectedRecordIds: [],
|
||||
} satisfies RelationFilterValue);
|
||||
|
||||
export const MAX_RECORDS_TO_DISPLAY = 3;
|
||||
|
||||
type ObjectFilterDropdownRecordSelectProps = {
|
||||
@ -26,15 +36,10 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
objectFilterDropdownSearchInputState,
|
||||
selectedOperandInDropdownState,
|
||||
selectedFilterState,
|
||||
setObjectFilterDropdownSelectedRecordIds,
|
||||
objectFilterDropdownSelectedRecordIdsState,
|
||||
selectFilter,
|
||||
emptyFilterButKeepDefinition,
|
||||
} = useFilterDropdown();
|
||||
|
||||
const { deleteCombinedViewFilter } =
|
||||
useDeleteCombinedViewFilters(viewComponentId);
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } =
|
||||
useGetCurrentView(viewComponentId);
|
||||
|
||||
@ -54,9 +59,26 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
|
||||
const selectedFilter = useRecoilValue(selectedFilterState);
|
||||
|
||||
const { isCurrentWorkspaceMemberSelected } = relationFilterValueSchema
|
||||
.catch({
|
||||
isCurrentWorkspaceMemberSelected: false,
|
||||
selectedRecordIds: [],
|
||||
})
|
||||
.parse(selectedFilter?.value);
|
||||
|
||||
const objectNameSingular =
|
||||
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular;
|
||||
|
||||
if (!isDefined(objectNameSingular)) {
|
||||
throw new Error('relationObjectMetadataNameSingular is not defined');
|
||||
}
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: objectNameSingular,
|
||||
});
|
||||
|
||||
const objectLabelPlural = objectMetadataItem?.labelPlural;
|
||||
|
||||
if (!isDefined(objectNameSingular)) {
|
||||
throw new Error('objectNameSingular is not defined');
|
||||
}
|
||||
@ -69,27 +91,53 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
const currentWorkspaceMemberSelectableItem: SelectableItem = {
|
||||
id: CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID,
|
||||
name: 'Me',
|
||||
isSelected: isCurrentWorkspaceMemberSelected,
|
||||
AvatarIcon: IconUserCircle,
|
||||
};
|
||||
|
||||
const pinnedSelectableItems: SelectableItem[] =
|
||||
objectNameSingular === 'workspaceMember'
|
||||
? [currentWorkspaceMemberSelectableItem]
|
||||
: [];
|
||||
|
||||
const filteredPinnedSelectableItems = pinnedSelectableItems.filter((item) =>
|
||||
item.name
|
||||
.toLowerCase()
|
||||
.includes(objectFilterDropdownSearchInput.toLowerCase()),
|
||||
);
|
||||
|
||||
const handleMultipleRecordSelectChange = (
|
||||
recordToSelect: SelectableItem,
|
||||
newSelectedValue: boolean,
|
||||
itemToSelect: SelectableItem,
|
||||
isNewSelectedValue: boolean,
|
||||
) => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newSelectedRecordIds = newSelectedValue
|
||||
? [...objectFilterDropdownSelectedRecordIds, recordToSelect.id]
|
||||
: objectFilterDropdownSelectedRecordIds.filter(
|
||||
(id) => id !== recordToSelect.id,
|
||||
);
|
||||
const isItemCurrentWorkspaceMember =
|
||||
itemToSelect.id === CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID;
|
||||
|
||||
if (newSelectedRecordIds.length === 0) {
|
||||
emptyFilterButKeepDefinition();
|
||||
deleteCombinedViewFilter(fieldId);
|
||||
return;
|
||||
}
|
||||
const selectedRecordIdsWithAddedRecord = [
|
||||
...objectFilterDropdownSelectedRecordIds,
|
||||
itemToSelect.id,
|
||||
];
|
||||
const selectedRecordIdsWithRemovedRecord =
|
||||
objectFilterDropdownSelectedRecordIds.filter(
|
||||
(id) => id !== itemToSelect.id,
|
||||
);
|
||||
|
||||
setObjectFilterDropdownSelectedRecordIds(newSelectedRecordIds);
|
||||
const newSelectedRecordIds = isItemCurrentWorkspaceMember
|
||||
? objectFilterDropdownSelectedRecordIds
|
||||
: isNewSelectedValue
|
||||
? selectedRecordIdsWithAddedRecord
|
||||
: selectedRecordIdsWithRemovedRecord;
|
||||
|
||||
const newIsCurrentWorkspaceMemberSelected = isItemCurrentWorkspaceMember
|
||||
? isNewSelectedValue
|
||||
: isCurrentWorkspaceMemberSelected;
|
||||
|
||||
const selectedRecordNames = [
|
||||
...recordsToSelect,
|
||||
@ -103,19 +151,32 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
.filter((record) => newSelectedRecordIds.includes(record.id))
|
||||
.map((record) => record.name);
|
||||
|
||||
const selectedPinnedItemNames = newIsCurrentWorkspaceMemberSelected
|
||||
? [currentWorkspaceMemberSelectableItem.name]
|
||||
: [];
|
||||
|
||||
const selectedItemNames = [
|
||||
...selectedPinnedItemNames,
|
||||
...selectedRecordNames,
|
||||
];
|
||||
|
||||
const filterDisplayValue =
|
||||
selectedRecordNames.length > MAX_RECORDS_TO_DISPLAY
|
||||
? `${selectedRecordNames.length} companies`
|
||||
: selectedRecordNames.join(', ');
|
||||
selectedItemNames.length > MAX_RECORDS_TO_DISPLAY
|
||||
? `${selectedItemNames.length} ${objectLabelPlural.toLowerCase()}`
|
||||
: selectedItemNames.join(', ');
|
||||
|
||||
if (
|
||||
isDefined(filterDefinitionUsedInDropdown) &&
|
||||
isDefined(selectedOperandInDropdown)
|
||||
) {
|
||||
const newFilterValue =
|
||||
newSelectedRecordIds.length > 0
|
||||
? JSON.stringify(newSelectedRecordIds)
|
||||
: EMPTY_FILTER_VALUE;
|
||||
newSelectedRecordIds.length > 0 || newIsCurrentWorkspaceMemberSelected
|
||||
? JSON.stringify({
|
||||
isCurrentWorkspaceMemberSelected:
|
||||
newIsCurrentWorkspaceMemberSelected,
|
||||
selectedRecordIds: newSelectedRecordIds,
|
||||
} satisfies RelationFilterValue)
|
||||
: '';
|
||||
|
||||
const viewFilter =
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
||||
@ -139,15 +200,26 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<MultipleSelectDropdown
|
||||
selectableListId="object-filter-record-select-id"
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
itemsToSelect={recordsToSelect}
|
||||
filteredSelectedItems={filteredSelectedRecords}
|
||||
selectedItems={selectedRecords}
|
||||
onChange={handleMultipleRecordSelectChange}
|
||||
searchFilter={objectFilterDropdownSearchInput}
|
||||
loadingItems={loading}
|
||||
/>
|
||||
<>
|
||||
{filteredPinnedSelectableItems.length > 0 && (
|
||||
<>
|
||||
<ObjectFilterDropdownRecordPinnedItems
|
||||
selectableItems={filteredPinnedSelectableItems}
|
||||
onChange={handleMultipleRecordSelectChange}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<MultipleSelectDropdown
|
||||
selectableListId="object-filter-record-select-id"
|
||||
hotkeyScope={RelationPickerHotkeyScope.RelationPicker}
|
||||
itemsToSelect={recordsToSelect}
|
||||
filteredSelectedItems={filteredSelectedRecords}
|
||||
selectedItems={selectedRecords}
|
||||
onChange={handleMultipleRecordSelectChange}
|
||||
searchFilter={objectFilterDropdownSearchInput}
|
||||
loadingItems={loading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
export const CURRENT_WORKSPACE_MEMBER_SELECTABLE_ITEM_ID =
|
||||
'CURRENT_WORKSPACE_MEMBER';
|
||||
@ -3,6 +3,7 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/Record
|
||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||
import { buildRecordGqlFieldsAggregateForRecordBoard } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForRecordBoard';
|
||||
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||
@ -53,7 +54,11 @@ export const useAggregateRecordsForRecordBoardColumn = () => {
|
||||
);
|
||||
|
||||
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const requestFilters = computeViewRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
recordIndexFilters,
|
||||
objectMetadataItem.fields,
|
||||
recordIndexViewFilterGroups,
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useFilterValueDependencies = (): {
|
||||
filterValueDependencies: FilterValueDependencies;
|
||||
} => {
|
||||
const { id: currentWorkspaceMemberId } =
|
||||
useRecoilValue(currentWorkspaceMemberState) ?? {};
|
||||
|
||||
return {
|
||||
filterValueDependencies: {
|
||||
currentWorkspaceMemberId,
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
export interface FilterValueDependencies {
|
||||
currentWorkspaceMemberId?: string;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
@ -14,6 +15,10 @@ const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'person',
|
||||
)!;
|
||||
|
||||
const mockFilterValueDependencies: FilterValueDependencies = {
|
||||
currentWorkspaceMemberId: '32219445-f587-4c40-b2b1-6d3205ed96da',
|
||||
};
|
||||
|
||||
jest.useFakeTimers().setSystemTime(new Date('2020-01-01'));
|
||||
|
||||
describe('computeViewRecordGqlOperationFilter', () => {
|
||||
@ -38,6 +43,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
|
||||
};
|
||||
|
||||
const result = computeViewRecordGqlOperationFilter(
|
||||
mockFilterValueDependencies,
|
||||
[nameFilter],
|
||||
companyMockObjectMetadataItem.fields,
|
||||
[],
|
||||
@ -90,6 +96,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
|
||||
};
|
||||
|
||||
const result = computeViewRecordGqlOperationFilter(
|
||||
mockFilterValueDependencies,
|
||||
[nameFilter, employeesFilter],
|
||||
companyMockObjectMetadataItem.fields,
|
||||
[],
|
||||
@ -176,6 +183,7 @@ describe('should work as expected for the different field types', () => {
|
||||
};
|
||||
|
||||
const result = computeViewRecordGqlOperationFilter(
|
||||
mockFilterValueDependencies,
|
||||
[
|
||||
addressFilterContains,
|
||||
addressFilterDoesNotContain,
|
||||
@ -558,6 +566,7 @@ describe('should work as expected for the different field types', () => {
|
||||
};
|
||||
|
||||
const result = computeViewRecordGqlOperationFilter(
|
||||
mockFilterValueDependencies,
|
||||
[
|
||||
phonesFilterContains,
|
||||
phonesFilterDoesNotContain,
|
||||
@ -759,6 +768,7 @@ describe('should work as expected for the different field types', () => {
|
||||
};
|
||||
|
||||
const result = computeViewRecordGqlOperationFilter(
|
||||
mockFilterValueDependencies,
|
||||
[
|
||||
emailsFilterContains,
|
||||
emailsFilterDoesNotContain,
|
||||
@ -914,6 +924,7 @@ describe('should work as expected for the different field types', () => {
|
||||
};
|
||||
|
||||
const result = computeViewRecordGqlOperationFilter(
|
||||
mockFilterValueDependencies,
|
||||
[
|
||||
dateFilterIsAfter,
|
||||
dateFilterIsBefore,
|
||||
@ -1030,6 +1041,7 @@ describe('should work as expected for the different field types', () => {
|
||||
};
|
||||
|
||||
const result = computeViewRecordGqlOperationFilter(
|
||||
mockFilterValueDependencies,
|
||||
[
|
||||
employeesFilterIsGreaterThan,
|
||||
employeesFilterIsLessThan,
|
||||
|
||||
@ -28,14 +28,18 @@ import {
|
||||
convertRatingToRatingValue,
|
||||
} from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { FilterValueDependencies } from '@/object-record/record-filter/types/FilterValueDependencies';
|
||||
import { getEmptyRecordGqlOperationFilter } from '@/object-record/record-filter/utils/getEmptyRecordGqlOperationFilter';
|
||||
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
|
||||
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
|
||||
import { resolveFilterValue } from '@/views/view-filter-value/utils/resolveFilterValue';
|
||||
import { resolveDateViewFilterValue } from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
|
||||
import { resolveSelectViewFilterValue } from '@/views/view-filter-value/utils/resolveSelectViewFilterValue';
|
||||
import { relationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/relationFilterValueSchema';
|
||||
import { endOfDay, roundToNearestMinutes, startOfDay } from 'date-fns';
|
||||
import { z } from 'zod';
|
||||
|
||||
const computeFilterRecordGqlOperationFilter = (
|
||||
filterValueDependencies: FilterValueDependencies,
|
||||
filter: Filter,
|
||||
fields: Pick<Field, 'id' | 'name'>[],
|
||||
): RecordGqlOperationFilter | undefined => {
|
||||
@ -124,7 +128,7 @@ const computeFilterRecordGqlOperationFilter = (
|
||||
}
|
||||
case 'DATE':
|
||||
case 'DATE_TIME': {
|
||||
const resolvedFilterValue = resolveFilterValue(filter);
|
||||
const resolvedFilterValue = resolveDateViewFilterValue(filter);
|
||||
const now = roundToNearestMinutes(new Date());
|
||||
const date =
|
||||
resolvedFilterValue instanceof Date ? resolvedFilterValue : now;
|
||||
@ -157,11 +161,8 @@ const computeFilterRecordGqlOperationFilter = (
|
||||
.object({ start: z.date(), end: z.date() })
|
||||
.safeParse(resolvedFilterValue).data;
|
||||
|
||||
const defaultDateRange = resolveFilterValue({
|
||||
const defaultDateRange = resolveDateViewFilterValue({
|
||||
value: 'PAST_1_DAY',
|
||||
definition: {
|
||||
type: 'DATE',
|
||||
},
|
||||
operand: ViewFilterOperand.IsRelative,
|
||||
});
|
||||
|
||||
@ -303,32 +304,41 @@ const computeFilterRecordGqlOperationFilter = (
|
||||
}
|
||||
case 'RELATION': {
|
||||
if (!isEmptyOperand) {
|
||||
try {
|
||||
JSON.parse(filter.value);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Cannot parse filter value for RELATION filter : "${filter.value}"`,
|
||||
);
|
||||
}
|
||||
const { isCurrentWorkspaceMemberSelected, selectedRecordIds } =
|
||||
relationFilterValueSchema.parse(filter.value);
|
||||
|
||||
const parsedRecordIds = JSON.parse(filter.value) as string[];
|
||||
const recordIds = isCurrentWorkspaceMemberSelected
|
||||
? [
|
||||
...selectedRecordIds,
|
||||
filterValueDependencies.currentWorkspaceMemberId,
|
||||
]
|
||||
: selectedRecordIds;
|
||||
|
||||
if (parsedRecordIds.length === 0) return;
|
||||
if (recordIds.length === 0) return;
|
||||
switch (filter.operand) {
|
||||
case ViewFilterOperand.Is:
|
||||
return {
|
||||
[correspondingField.name + 'Id']: {
|
||||
in: parsedRecordIds,
|
||||
in: recordIds,
|
||||
} as RelationFilter,
|
||||
};
|
||||
case ViewFilterOperand.IsNot: {
|
||||
if (parsedRecordIds.length === 0) return;
|
||||
if (recordIds.length === 0) return;
|
||||
return {
|
||||
not: {
|
||||
[correspondingField.name + 'Id']: {
|
||||
in: parsedRecordIds,
|
||||
} as RelationFilter,
|
||||
},
|
||||
or: [
|
||||
{
|
||||
not: {
|
||||
[correspondingField.name + 'Id']: {
|
||||
in: recordIds,
|
||||
} as RelationFilter,
|
||||
},
|
||||
},
|
||||
{
|
||||
[correspondingField.name + 'Id']: {
|
||||
is: 'NULL',
|
||||
} as RelationFilter,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
default:
|
||||
@ -611,9 +621,7 @@ const computeFilterRecordGqlOperationFilter = (
|
||||
);
|
||||
}
|
||||
|
||||
const options = resolveFilterValue(
|
||||
filter as Filter & { definition: { type: 'MULTI_SELECT' } },
|
||||
);
|
||||
const options = resolveSelectViewFilterValue(filter);
|
||||
|
||||
if (options.length === 0) return;
|
||||
|
||||
@ -660,9 +668,7 @@ const computeFilterRecordGqlOperationFilter = (
|
||||
filter.definition,
|
||||
);
|
||||
}
|
||||
const options = resolveFilterValue(
|
||||
filter as Filter & { definition: { type: 'SELECT' } },
|
||||
);
|
||||
const options = resolveSelectViewFilterValue(filter);
|
||||
|
||||
if (options.length === 0) return;
|
||||
|
||||
@ -869,6 +875,7 @@ const computeFilterRecordGqlOperationFilter = (
|
||||
};
|
||||
|
||||
const computeViewFilterGroupRecordGqlOperationFilter = (
|
||||
filterValueDependencies: FilterValueDependencies,
|
||||
filters: Filter[],
|
||||
fields: Pick<Field, 'id' | 'name'>[],
|
||||
viewFilterGroups: ViewFilterGroup[],
|
||||
@ -887,7 +894,13 @@ const computeViewFilterGroupRecordGqlOperationFilter = (
|
||||
);
|
||||
|
||||
const groupRecordGqlOperationFilters = groupFilters
|
||||
.map((filter) => computeFilterRecordGqlOperationFilter(filter, fields))
|
||||
.map((filter) =>
|
||||
computeFilterRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
filter,
|
||||
fields,
|
||||
),
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
const subGroupRecordGqlOperationFilters = viewFilterGroups
|
||||
@ -897,6 +910,7 @@ const computeViewFilterGroupRecordGqlOperationFilter = (
|
||||
)
|
||||
.map((subViewFilterGroup) =>
|
||||
computeViewFilterGroupRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
filters,
|
||||
fields,
|
||||
viewFilterGroups,
|
||||
@ -932,6 +946,7 @@ const computeViewFilterGroupRecordGqlOperationFilter = (
|
||||
};
|
||||
|
||||
export const computeViewRecordGqlOperationFilter = (
|
||||
filterValueDependencies: FilterValueDependencies,
|
||||
filters: Filter[],
|
||||
fields: Pick<Field, 'id' | 'name'>[],
|
||||
viewFilterGroups: ViewFilterGroup[],
|
||||
@ -939,7 +954,11 @@ export const computeViewRecordGqlOperationFilter = (
|
||||
const regularRecordGqlOperationFilter: RecordGqlOperationFilter[] = filters
|
||||
.filter((filter) => !filter.viewFilterGroupId)
|
||||
.map((regularFilter) =>
|
||||
computeFilterRecordGqlOperationFilter(regularFilter, fields),
|
||||
computeFilterRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
regularFilter,
|
||||
fields,
|
||||
),
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
@ -949,6 +968,7 @@ export const computeViewRecordGqlOperationFilter = (
|
||||
|
||||
const advancedRecordGqlOperationFilter =
|
||||
computeViewFilterGroupRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
filters,
|
||||
fields,
|
||||
viewFilterGroups,
|
||||
|
||||
@ -5,6 +5,7 @@ import { computeContextStoreFilters } from '@/context-store/utils/computeContext
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
@ -40,6 +41,8 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const { totalCount } = useFindManyRecords({
|
||||
...findManyRecordsParams,
|
||||
recordGqlFields: {
|
||||
@ -49,6 +52,7 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
),
|
||||
limit: 1,
|
||||
skip: contextStoreTargetedRecordsRule.mode === 'selection',
|
||||
|
||||
@ -8,6 +8,7 @@ import { computeContextStoreFilters } from '@/context-store/utils/computeContext
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/object-options-dropdown/constants/ExportTableDataDefaultPageSize';
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
@ -71,10 +72,13 @@ export const useExportFetchRecords = ({
|
||||
contextStoreFiltersComponentState,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const queryFilter = computeContextStoreFilters(
|
||||
contextStoreTargetedRecordsRule,
|
||||
contextStoreFilters,
|
||||
objectMetadataItem,
|
||||
filterValueDependencies,
|
||||
);
|
||||
|
||||
const findManyRecordsParams = useFindManyRecordIndexTableParams(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||
@ -35,7 +36,10 @@ export const useFindManyRecordIndexTableParams = (
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const stateFilter = computeViewRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
tableFilters,
|
||||
objectMetadataItem?.fields ?? [],
|
||||
tableViewFilterGroups,
|
||||
|
||||
@ -7,6 +7,7 @@ import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils
|
||||
import { useSetRecordBoardRecordIds } from '@/object-record/record-board/hooks/useSetRecordBoardRecordIds';
|
||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||
import { recordBoardFieldDefinitionsComponentState } from '@/object-record/record-board/states/recordBoardFieldDefinitionsComponentState';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
|
||||
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
|
||||
@ -56,7 +57,11 @@ export const useLoadRecordIndexBoard = ({
|
||||
|
||||
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
||||
const recordIndexSorts = useRecoilValue(recordIndexSortsState);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const requestFilters = computeViewRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
recordIndexFilters,
|
||||
objectMetadataItem?.fields ?? [],
|
||||
recordIndexViewFilterGroups,
|
||||
|
||||
@ -5,6 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { useSetRecordIdsForColumn } from '@/object-record/record-board/hooks/useSetRecordIdsForColumn';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
|
||||
@ -43,7 +44,10 @@ export const useLoadRecordIndexBoardColumn = ({
|
||||
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
||||
const recordIndexSorts = useRecoilValue(recordIndexSortsState);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const requestFilters = computeViewRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
recordIndexFilters,
|
||||
objectMetadataItem?.fields ?? [],
|
||||
recordIndexViewFilterGroups,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||
@ -26,7 +27,11 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
||||
);
|
||||
|
||||
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
|
||||
|
||||
const { filterValueDependencies } = useFilterValueDependencies();
|
||||
|
||||
const requestFilters = computeViewRecordGqlOperationFilter(
|
||||
filterValueDependencies,
|
||||
recordIndexFilters,
|
||||
objectMetadataItem.fields,
|
||||
recordIndexViewFilterGroups,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { AvatarChip, MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui';
|
||||
import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui';
|
||||
|
||||
import { StyledMultipleSelectDropdownAvatarChip } from '@/object-record/select/components/StyledMultipleSelectDropdownAvatarChip';
|
||||
import { SelectableItem } from '@/object-record/select/types/SelectableItem';
|
||||
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
@ -13,16 +13,6 @@ import { useSelectableListStates } from '@/ui/layout/selectable-list/hooks/inter
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
const StyledAvatarChip = styled(AvatarChip)`
|
||||
&.avatar-icon-container {
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
}
|
||||
`;
|
||||
|
||||
export const MultipleSelectDropdown = ({
|
||||
selectableListId,
|
||||
hotkeyScope,
|
||||
@ -129,7 +119,7 @@ export const MultipleSelectDropdown = ({
|
||||
handleItemSelectChange(item, newCheckedValue);
|
||||
}}
|
||||
avatar={
|
||||
<StyledAvatarChip
|
||||
<StyledMultipleSelectDropdownAvatarChip
|
||||
className="avatar-icon-container"
|
||||
name={item.name}
|
||||
avatarUrl={item.avatarUrl}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { AvatarChip } from 'twenty-ui';
|
||||
|
||||
export const StyledMultipleSelectDropdownAvatarChip = styled(AvatarChip)`
|
||||
&.avatar-icon-container {
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user