diff --git a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx index 65520b84be..d71ba52777 100644 --- a/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/views/components/EditableFilterDropdownButton.tsx @@ -8,6 +8,7 @@ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRe import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { EditableFilterChipDropdownContent } from '@/views/components/EditableFilterChipDropdownContent'; +import { EditableRelationFilterChip } from '@/views/components/EditableRelationFilterChip'; import { useClearVectorSearchInput } from '@/views/hooks/useClearVectorSearchInput'; import { useSetEditableFilterChipDropdownStates } from '@/views/hooks/useSetEditableFilterChipDropdownStates'; @@ -54,11 +55,19 @@ export const EditableFilterDropdownButton = ({ + recordFilter.type === 'RELATION' ? ( + + ) : ( + + ) } dropdownComponents={ diff --git a/packages/twenty-front/src/modules/views/components/EditableRelationFilterChip.tsx b/packages/twenty-front/src/modules/views/components/EditableRelationFilterChip.tsx new file mode 100644 index 0000000000..f62dff4504 --- /dev/null +++ b/packages/twenty-front/src/modules/views/components/EditableRelationFilterChip.tsx @@ -0,0 +1,42 @@ +import { useFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useFieldMetadataItemByIdOrThrow'; +import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { SortOrFilterChip } from '@/views/components/SortOrFilterChip'; +import { useComputeRecordRelationFilterLabelValue } from '@/views/hooks/useComputeRecordRelationFilterLabelValue'; +import { useIcons } from 'twenty-ui/display'; + +type EditableRelationFilterChipProps = { + recordFilter: RecordFilter; + onRemove: () => void; + onClick?: () => void; +}; + +export const EditableRelationFilterChip = ({ + recordFilter, + onRemove, + onClick, +}: EditableRelationFilterChipProps) => { + const { getIcon } = useIcons(); + + const { fieldMetadataItem } = useFieldMetadataItemByIdOrThrow( + recordFilter.fieldMetadataId, + ); + + const FieldMetadataItemIcon = getIcon(fieldMetadataItem.icon); + + const { labelValue } = useComputeRecordRelationFilterLabelValue({ + recordFilter, + }); + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/views/components/SortOrFilterChip.tsx b/packages/twenty-front/src/modules/views/components/SortOrFilterChip.tsx index 19993b7744..eab9512f06 100644 --- a/packages/twenty-front/src/modules/views/components/SortOrFilterChip.tsx +++ b/packages/twenty-front/src/modules/views/components/SortOrFilterChip.tsx @@ -95,6 +95,10 @@ const StyledSortValue = styled.span` font-weight: ${({ theme }) => theme.font.weight.medium}; `; +const StyledKeyLabelContainer = styled.div` + display: flex; +`; + export type SortOrFilterChipVariant = 'default' | 'danger'; export type SortOrFilterChipType = 'sort' | 'filter'; @@ -134,12 +138,14 @@ export const SortOrFilterChip = ({ )} - {labelKey && {labelKey}} - {type === 'sort' ? ( - {labelValue} - ) : ( - {labelValue} - )} + + {labelKey && {labelKey}} + {type === 'sort' ? ( + {labelValue} + ) : ( + {labelValue} + )} + { mutation: CREATE_CORE_VIEW_FIELD, variables: { input: { - id: viewField.id, + id: v4(), fieldMetadataId: viewField.fieldMetadataId, viewId: view.id, isVisible: viewField.isVisible, diff --git a/packages/twenty-front/src/modules/views/hooks/useComputeRecordRelationFilterLabelValue.tsx b/packages/twenty-front/src/modules/views/hooks/useComputeRecordRelationFilterLabelValue.tsx new file mode 100644 index 0000000000..5b8e9d6859 --- /dev/null +++ b/packages/twenty-front/src/modules/views/hooks/useComputeRecordRelationFilterLabelValue.tsx @@ -0,0 +1,91 @@ +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { getRelationObjectMetadataNameSingular } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; +import { getFieldMetadataItemByIdOrThrow } from '@/object-metadata/utils/getFieldMetadataItemByIdOrThrow'; +import { MAX_RECORDS_TO_DISPLAY } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect'; +import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect'; +import { getRecordFilterLabelValue } from '@/views/utils/getRecordFilterLabelValue'; +import { arrayOfUuidOrVariableSchema } from '@/views/view-filter-value/validation-schemas/arrayOfUuidsOrVariablesSchema'; +import { jsonRelationFilterValueSchema } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema'; +import { t } from '@lingui/core/macro'; +import { isDefined } from 'twenty-shared/utils'; + +type ObjectFilterDropdownRecordSelectProps = { + recordFilter: RecordFilter; +}; + +export const useComputeRecordRelationFilterLabelValue = ({ + recordFilter, +}: ObjectFilterDropdownRecordSelectProps) => { + const { objectMetadataItems } = useObjectMetadataItems(); + + if (!isDefined(recordFilter.fieldMetadataId)) { + throw new Error('fieldMetadataItemUsedInFilterDropdown is not defined'); + } + + const { fieldMetadataItem } = getFieldMetadataItemByIdOrThrow({ + fieldMetadataId: recordFilter.fieldMetadataId, + objectMetadataItems, + }); + + const relationObjectMetadataNameSingular = + getRelationObjectMetadataNameSingular({ + field: fieldMetadataItem, + }); + + if (!isDefined(relationObjectMetadataNameSingular)) { + throw new Error('relationObjectMetadataNameSingular is not defined'); + } + + const relationObjectMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.nameSingular === relationObjectMetadataNameSingular, + ); + + if (!isDefined(relationObjectMetadataItem)) { + throw new Error('relationObjectMetadataItem is not defined'); + } + + const relationObjectLabelPlural = relationObjectMetadataItem.labelPlural; + + const { isCurrentWorkspaceMemberSelected, selectedRecordIds } = + jsonRelationFilterValueSchema + .catch({ + isCurrentWorkspaceMemberSelected: false, + selectedRecordIds: arrayOfUuidOrVariableSchema.parse( + recordFilter.value, + ), + }) + .parse(recordFilter.value); + + const { selectedRecords, loading } = useRecordsForSelect({ + searchFilterText: '', + selectedIds: selectedRecordIds, + objectNameSingular: relationObjectMetadataNameSingular, + limit: 10, + }); + + if (loading) { + return { labelValue: t`Loading...` }; + } + + const labelValueItems = [ + ...(isCurrentWorkspaceMemberSelected ? [t`Me`] : []), + ...selectedRecords.map((record) => record.name), + ]; + + const filterDisplayValue = + labelValueItems.length > MAX_RECORDS_TO_DISPLAY + ? `${labelValueItems.length} ${relationObjectLabelPlural.toLowerCase()}` + : labelValueItems.join(', '); + + return { + labelValue: + labelValueItems.length > 0 + ? getRecordFilterLabelValue({ + ...recordFilter, + displayValue: filterDisplayValue, + }) + : getRecordFilterLabelValue(recordFilter), + }; +}; diff --git a/packages/twenty-ui/package.json b/packages/twenty-ui/package.json index 9a0cad138d..d36989f015 100644 --- a/packages/twenty-ui/package.json +++ b/packages/twenty-ui/package.json @@ -46,7 +46,6 @@ }, "files": [ "dist", - "assets", "accessibility", "assets", "components", diff --git a/packages/twenty-ui/scripts/generateBarrels.ts b/packages/twenty-ui/scripts/generateBarrels.ts index a9b44af58b..f0692d602d 100644 --- a/packages/twenty-ui/scripts/generateBarrels.ts +++ b/packages/twenty-ui/scripts/generateBarrels.ts @@ -243,7 +243,7 @@ const computePackageJsonFilesAndExportsConfig = ( return { exports, typesVersions: { '*': typesVersionsEntries }, - files: ['dist', 'assets', ...entrypoints], + files: ['dist', ...entrypoints], }; };