Fix show page relation record count (#11459)
This PR fixes the incorrect relation count on a show page relation section title, when there are more than 60 records. An aggregate COUNT query has been used to rely on the backend. A new component RecordDetailRelationSectionDropdown has been created to abstract a chunk of the parent RecordDetailRelationSection component. Fixes https://github.com/twentyhq/twenty/issues/11032
This commit is contained in:
@ -1,65 +1,46 @@
|
|||||||
import styled from '@emotion/styled';
|
import { useContext } from 'react';
|
||||||
import { useCallback, useContext } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||||
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
|
||||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
|
||||||
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
|
|
||||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker';
|
|
||||||
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
|
|
||||||
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
|
||||||
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
|
|
||||||
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
|
|
||||||
import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker';
|
|
||||||
import { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState';
|
|
||||||
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
|
|
||||||
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
|
||||||
import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList';
|
import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList';
|
||||||
|
import { RecordDetailRelationSectionDropdown } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdown';
|
||||||
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
|
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
|
||||||
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector';
|
import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||||
import { getAppPath } from '~/utils/navigation/getAppPath';
|
import { getAppPath } from '~/utils/navigation/getAppPath';
|
||||||
import { IconForbid, IconPencil, IconPlus } from 'twenty-ui/display';
|
|
||||||
import { LightIconButton } from 'twenty-ui/input';
|
|
||||||
|
|
||||||
type RecordDetailRelationSectionProps = {
|
type RecordDetailRelationSectionProps = {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledAddDropdown = styled(Dropdown)`
|
|
||||||
margin-left: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RecordDetailRelationSection = ({
|
export const RecordDetailRelationSection = ({
|
||||||
loading,
|
loading,
|
||||||
}: RecordDetailRelationSectionProps) => {
|
}: RecordDetailRelationSectionProps) => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fieldName,
|
fieldName,
|
||||||
relationFieldMetadataId,
|
relationFieldMetadataId,
|
||||||
relationObjectMetadataNameSingular,
|
relationObjectMetadataNameSingular,
|
||||||
relationType,
|
relationType,
|
||||||
} = fieldDefinition.metadata as FieldRelationMetadata;
|
} = fieldDefinition.metadata as FieldRelationMetadata;
|
||||||
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const { objectMetadataItem: relationObjectMetadataItem } =
|
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||||
@ -86,69 +67,7 @@ export const RecordDetailRelationSection = ({
|
|||||||
|
|
||||||
const dropdownId = `record-field-card-relation-picker-${fieldDefinition.fieldMetadataId}-${recordId}`;
|
const dropdownId = `record-field-card-relation-picker-${fieldDefinition.fieldMetadataId}-${recordId}`;
|
||||||
|
|
||||||
const { closeDropdown, isDropdownOpen, dropdownPlacement } =
|
const { isDropdownOpen } = useDropdown(dropdownId);
|
||||||
useDropdown(dropdownId);
|
|
||||||
|
|
||||||
const setMultipleRecordPickerSearchFilter = useSetRecoilComponentStateV2(
|
|
||||||
multipleRecordPickerSearchFilterComponentState,
|
|
||||||
dropdownId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setMultipleRecordPickerPickableMorphItems =
|
|
||||||
useSetRecoilComponentStateV2(
|
|
||||||
multipleRecordPickerPickableMorphItemsComponentState,
|
|
||||||
dropdownId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setMultipleRecordPickerSearchableObjectMetadataItems =
|
|
||||||
useSetRecoilComponentStateV2(
|
|
||||||
multipleRecordPickerSearchableObjectMetadataItemsComponentState,
|
|
||||||
dropdownId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { performSearch: multipleRecordPickerPerformSearch } =
|
|
||||||
useMultipleRecordPickerPerformSearch();
|
|
||||||
|
|
||||||
const setSingleRecordPickerSearchFilter = useSetRecoilComponentStateV2(
|
|
||||||
singleRecordPickerSearchFilterComponentState,
|
|
||||||
dropdownId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setSingleRecordPickerSelectedId = useSetRecoilComponentStateV2(
|
|
||||||
singleRecordPickerSelectedIdComponentState,
|
|
||||||
dropdownId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCloseRelationPickerDropdown = useCallback(() => {
|
|
||||||
setMultipleRecordPickerSearchFilter('');
|
|
||||||
}, [setMultipleRecordPickerSearchFilter]);
|
|
||||||
|
|
||||||
const persistField = usePersistField();
|
|
||||||
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleRelationPickerEntitySelected = (
|
|
||||||
selectedRelationEntity?: SingleRecordPickerRecord,
|
|
||||||
) => {
|
|
||||||
closeDropdown();
|
|
||||||
|
|
||||||
if (!selectedRelationEntity?.id || !relationFieldMetadataItem?.name) return;
|
|
||||||
|
|
||||||
if (isToOneObject) {
|
|
||||||
persistField(selectedRelationEntity.record);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOneRelationRecord({
|
|
||||||
idToUpdate: selectedRelationEntity.id,
|
|
||||||
updateOneRecordInput: {
|
|
||||||
[relationFieldMetadataItem.name]: record,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const { updateRelation } = useUpdateRelationFromManyFieldInput();
|
|
||||||
|
|
||||||
const indexViewId = useRecoilValue(
|
const indexViewId = useRecoilValue(
|
||||||
prefetchIndexViewIdFromObjectMetadataItemFamilySelector({
|
prefetchIndexViewIdFromObjectMetadataItemFamilySelector({
|
||||||
@ -175,20 +94,24 @@ export const RecordDetailRelationSection = ({
|
|||||||
filterQueryParams,
|
filterQueryParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const showContent = () => {
|
const filtersForAggregate = isToManyObjects
|
||||||
return (
|
? ({
|
||||||
relationRecords.length > 0 && (
|
[`${relationFieldMetadataItem?.name}Id`]: {
|
||||||
<RecordDetailRelationRecordsList relationRecords={relationRecords} />
|
in: [recordId],
|
||||||
)
|
},
|
||||||
);
|
} satisfies RecordGqlOperationFilter)
|
||||||
};
|
: {};
|
||||||
|
|
||||||
const { createNewRecordAndOpenRightDrawer } =
|
const { data: relationAggregateResult, loading: aggregateLoading } =
|
||||||
useAddNewRecordAndOpenRightDrawer({
|
useAggregateRecords<{
|
||||||
relationObjectMetadataNameSingular,
|
id: { COUNT: number };
|
||||||
relationObjectMetadataItem,
|
}>({
|
||||||
relationFieldMetadataItem,
|
objectNameSingular: relationObjectMetadataItem.nameSingular,
|
||||||
recordId,
|
filter: filtersForAggregate,
|
||||||
|
skip: !isToManyObjects,
|
||||||
|
recordGqlFieldsAggregate: {
|
||||||
|
id: [AGGREGATE_OPERATIONS.count],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isRecordReadOnly = useIsRecordReadOnly({
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
@ -200,45 +123,9 @@ export const RecordDetailRelationSection = ({
|
|||||||
isRecordReadOnly,
|
isRecordReadOnly,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading) return null;
|
if (loading || aggregateLoading || isFieldReadOnly) return null;
|
||||||
|
|
||||||
const relationRecordsCount = relationRecords.length;
|
const relationRecordsCount = relationAggregateResult?.id?.COUNT ?? 0;
|
||||||
|
|
||||||
const handleOpenRelationPickerDropdown = () => {
|
|
||||||
if (isToOneObject) {
|
|
||||||
setSingleRecordPickerSearchFilter('');
|
|
||||||
if (relationRecords.length > 0) {
|
|
||||||
setSingleRecordPickerSelectedId(relationRecords[0].id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isToManyObjects) {
|
|
||||||
setMultipleRecordPickerSearchableObjectMetadataItems([
|
|
||||||
relationObjectMetadataItem,
|
|
||||||
]);
|
|
||||||
setMultipleRecordPickerSearchFilter('');
|
|
||||||
setMultipleRecordPickerPickableMorphItems(
|
|
||||||
relationRecords.map((record) => ({
|
|
||||||
recordId: record.id,
|
|
||||||
objectMetadataId: relationObjectMetadataItem.id,
|
|
||||||
isSelected: true,
|
|
||||||
isMatchingSearchFilter: true,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
multipleRecordPickerPerformSearch({
|
|
||||||
multipleRecordPickerInstanceId: dropdownId,
|
|
||||||
forceSearchFilter: '',
|
|
||||||
forceSearchableObjectMetadataItems: [relationObjectMetadataItem],
|
|
||||||
forcePickableMorphItems: relationRecords.map((record) => ({
|
|
||||||
recordId: record.id,
|
|
||||||
objectMetadataId: relationObjectMetadataItem.id,
|
|
||||||
isSelected: true,
|
|
||||||
isMatchingSearchFilter: true,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordDetailSection>
|
<RecordDetailSection>
|
||||||
@ -258,61 +145,12 @@ export const RecordDetailRelationSection = ({
|
|||||||
hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile}
|
hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile}
|
||||||
areRecordsAvailable={relationRecords.length > 0}
|
areRecordsAvailable={relationRecords.length > 0}
|
||||||
rightAdornment={
|
rightAdornment={
|
||||||
!isFieldReadOnly && (
|
<RecordDetailRelationSectionDropdown loading={loading} />
|
||||||
<DropdownScope dropdownScopeId={dropdownId}>
|
|
||||||
<StyledAddDropdown
|
|
||||||
dropdownId={dropdownId}
|
|
||||||
dropdownPlacement="left-start"
|
|
||||||
onClose={handleCloseRelationPickerDropdown}
|
|
||||||
onOpen={handleOpenRelationPickerDropdown}
|
|
||||||
clickableComponent={
|
|
||||||
<LightIconButton
|
|
||||||
className="displayOnHover"
|
|
||||||
Icon={isToOneObject ? IconPencil : IconPlus}
|
|
||||||
accent="tertiary"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
|
||||||
dropdownComponents={
|
|
||||||
isToOneObject ? (
|
|
||||||
<SingleRecordPicker
|
|
||||||
componentInstanceId={dropdownId}
|
|
||||||
EmptyIcon={IconForbid}
|
|
||||||
onRecordSelected={handleRelationPickerEntitySelected}
|
|
||||||
objectNameSingular={relationObjectMetadataNameSingular}
|
|
||||||
recordPickerInstanceId={dropdownId}
|
|
||||||
onCreate={createNewRecordAndOpenRightDrawer}
|
|
||||||
onCancel={closeDropdown}
|
|
||||||
layoutDirection={
|
|
||||||
dropdownPlacement?.includes('end')
|
|
||||||
? 'search-bar-on-bottom'
|
|
||||||
: 'search-bar-on-top'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<MultipleRecordPicker
|
|
||||||
componentInstanceId={dropdownId}
|
|
||||||
onCreate={() => {
|
|
||||||
closeDropdown();
|
|
||||||
createNewRecordAndOpenRightDrawer?.();
|
|
||||||
}}
|
|
||||||
onChange={updateRelation}
|
|
||||||
onSubmit={closeDropdown}
|
|
||||||
onClickOutside={closeDropdown}
|
|
||||||
layoutDirection={
|
|
||||||
dropdownPlacement?.includes('end')
|
|
||||||
? 'search-bar-on-bottom'
|
|
||||||
: 'search-bar-on-top'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</DropdownScope>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{showContent()}
|
{relationRecords.length > 0 && (
|
||||||
|
<RecordDetailRelationRecordsList relationRecords={relationRecords} />
|
||||||
|
)}
|
||||||
</RecordDetailSection>
|
</RecordDetailSection>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,249 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useCallback, useContext } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||||
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
|
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||||
|
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
|
||||||
|
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { MultipleRecordPicker } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPicker';
|
||||||
|
import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-picker/multiple-record-picker/hooks/useMultipleRecordPickerPerformSearch';
|
||||||
|
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
||||||
|
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
|
||||||
|
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
|
||||||
|
import { SingleRecordPicker } from '@/object-record/record-picker/single-record-picker/components/SingleRecordPicker';
|
||||||
|
import { singleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSearchFilterComponentState';
|
||||||
|
import { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
|
||||||
|
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
|
import { IconForbid, IconPencil, IconPlus } from 'twenty-ui/display';
|
||||||
|
import { LightIconButton } from 'twenty-ui/input';
|
||||||
|
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
type RecordDetailRelationSectionDropdownProps = {
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledAddDropdown = styled(Dropdown)`
|
||||||
|
margin-left: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RecordDetailRelationSectionDropdown = ({
|
||||||
|
loading,
|
||||||
|
}: RecordDetailRelationSectionDropdownProps) => {
|
||||||
|
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||||
|
const {
|
||||||
|
fieldName,
|
||||||
|
relationFieldMetadataId,
|
||||||
|
relationObjectMetadataNameSingular,
|
||||||
|
relationType,
|
||||||
|
} = fieldDefinition.metadata as FieldRelationMetadata;
|
||||||
|
|
||||||
|
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
||||||
|
|
||||||
|
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||||
|
useObjectMetadataItem({
|
||||||
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const relationFieldMetadataItem = relationObjectMetadataItem.fields.find(
|
||||||
|
({ id }) => id === relationFieldMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldValue = useRecoilValue<
|
||||||
|
({ id: string } & Record<string, any>) | ObjectRecord[] | null
|
||||||
|
>(recordStoreFamilySelector({ recordId, fieldName }));
|
||||||
|
|
||||||
|
// TODO: use new relation type
|
||||||
|
const isToOneObject = relationType === RelationDefinitionType.MANY_TO_ONE;
|
||||||
|
const isToManyObjects = relationType === RelationDefinitionType.ONE_TO_MANY;
|
||||||
|
|
||||||
|
const relationRecords: ObjectRecord[] =
|
||||||
|
fieldValue && isToOneObject
|
||||||
|
? [fieldValue as ObjectRecord]
|
||||||
|
: ((fieldValue as ObjectRecord[]) ?? []);
|
||||||
|
|
||||||
|
const dropdownId = `record-field-card-relation-picker-${fieldDefinition.fieldMetadataId}-${recordId}`;
|
||||||
|
|
||||||
|
const { closeDropdown, dropdownPlacement } = useDropdown(dropdownId);
|
||||||
|
|
||||||
|
const setMultipleRecordPickerSearchFilter = useSetRecoilComponentStateV2(
|
||||||
|
multipleRecordPickerSearchFilterComponentState,
|
||||||
|
dropdownId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setMultipleRecordPickerPickableMorphItems =
|
||||||
|
useSetRecoilComponentStateV2(
|
||||||
|
multipleRecordPickerPickableMorphItemsComponentState,
|
||||||
|
dropdownId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setMultipleRecordPickerSearchableObjectMetadataItems =
|
||||||
|
useSetRecoilComponentStateV2(
|
||||||
|
multipleRecordPickerSearchableObjectMetadataItemsComponentState,
|
||||||
|
dropdownId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { performSearch: multipleRecordPickerPerformSearch } =
|
||||||
|
useMultipleRecordPickerPerformSearch();
|
||||||
|
|
||||||
|
const setSingleRecordPickerSearchFilter = useSetRecoilComponentStateV2(
|
||||||
|
singleRecordPickerSearchFilterComponentState,
|
||||||
|
dropdownId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setSingleRecordPickerSelectedId = useSetRecoilComponentStateV2(
|
||||||
|
singleRecordPickerSelectedIdComponentState,
|
||||||
|
dropdownId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCloseRelationPickerDropdown = useCallback(() => {
|
||||||
|
setMultipleRecordPickerSearchFilter('');
|
||||||
|
}, [setMultipleRecordPickerSearchFilter]);
|
||||||
|
|
||||||
|
const persistField = usePersistField();
|
||||||
|
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
||||||
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRelationPickerEntitySelected = (
|
||||||
|
selectedRelationEntity?: SingleRecordPickerRecord,
|
||||||
|
) => {
|
||||||
|
closeDropdown();
|
||||||
|
|
||||||
|
if (!selectedRelationEntity?.id || !relationFieldMetadataItem?.name) return;
|
||||||
|
|
||||||
|
if (isToOneObject) {
|
||||||
|
persistField(selectedRelationEntity.record);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOneRelationRecord({
|
||||||
|
idToUpdate: selectedRelationEntity.id,
|
||||||
|
updateOneRecordInput: {
|
||||||
|
[relationFieldMetadataItem.name]: record,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const { updateRelation } = useUpdateRelationFromManyFieldInput();
|
||||||
|
|
||||||
|
const { createNewRecordAndOpenRightDrawer } =
|
||||||
|
useAddNewRecordAndOpenRightDrawer({
|
||||||
|
relationObjectMetadataNameSingular,
|
||||||
|
relationObjectMetadataItem,
|
||||||
|
relationFieldMetadataItem,
|
||||||
|
recordId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
|
recordId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||||
|
fieldDefinition,
|
||||||
|
isRecordReadOnly,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loading || isFieldReadOnly) return null;
|
||||||
|
|
||||||
|
const handleOpenRelationPickerDropdown = () => {
|
||||||
|
if (isToOneObject) {
|
||||||
|
setSingleRecordPickerSearchFilter('');
|
||||||
|
if (relationRecords.length > 0) {
|
||||||
|
setSingleRecordPickerSelectedId(relationRecords[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isToManyObjects) {
|
||||||
|
setMultipleRecordPickerSearchableObjectMetadataItems([
|
||||||
|
relationObjectMetadataItem,
|
||||||
|
]);
|
||||||
|
setMultipleRecordPickerSearchFilter('');
|
||||||
|
setMultipleRecordPickerPickableMorphItems(
|
||||||
|
relationRecords.map((record) => ({
|
||||||
|
recordId: record.id,
|
||||||
|
objectMetadataId: relationObjectMetadataItem.id,
|
||||||
|
isSelected: true,
|
||||||
|
isMatchingSearchFilter: true,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
multipleRecordPickerPerformSearch({
|
||||||
|
multipleRecordPickerInstanceId: dropdownId,
|
||||||
|
forceSearchFilter: '',
|
||||||
|
forceSearchableObjectMetadataItems: [relationObjectMetadataItem],
|
||||||
|
forcePickableMorphItems: relationRecords.map((record) => ({
|
||||||
|
recordId: record.id,
|
||||||
|
objectMetadataId: relationObjectMetadataItem.id,
|
||||||
|
isSelected: true,
|
||||||
|
isMatchingSearchFilter: true,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownScope dropdownScopeId={dropdownId}>
|
||||||
|
<StyledAddDropdown
|
||||||
|
dropdownId={dropdownId}
|
||||||
|
dropdownPlacement="left-start"
|
||||||
|
onClose={handleCloseRelationPickerDropdown}
|
||||||
|
onOpen={handleOpenRelationPickerDropdown}
|
||||||
|
clickableComponent={
|
||||||
|
<LightIconButton
|
||||||
|
className="displayOnHover"
|
||||||
|
Icon={isToOneObject ? IconPencil : IconPlus}
|
||||||
|
accent="tertiary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||||
|
dropdownComponents={
|
||||||
|
isToOneObject ? (
|
||||||
|
<SingleRecordPicker
|
||||||
|
componentInstanceId={dropdownId}
|
||||||
|
EmptyIcon={IconForbid}
|
||||||
|
onRecordSelected={handleRelationPickerEntitySelected}
|
||||||
|
objectNameSingular={relationObjectMetadataNameSingular}
|
||||||
|
recordPickerInstanceId={dropdownId}
|
||||||
|
onCreate={createNewRecordAndOpenRightDrawer}
|
||||||
|
onCancel={closeDropdown}
|
||||||
|
layoutDirection={
|
||||||
|
dropdownPlacement?.includes('end')
|
||||||
|
? 'search-bar-on-bottom'
|
||||||
|
: 'search-bar-on-top'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MultipleRecordPicker
|
||||||
|
componentInstanceId={dropdownId}
|
||||||
|
onCreate={() => {
|
||||||
|
closeDropdown();
|
||||||
|
createNewRecordAndOpenRightDrawer?.();
|
||||||
|
}}
|
||||||
|
onChange={updateRelation}
|
||||||
|
onSubmit={closeDropdown}
|
||||||
|
onClickOutside={closeDropdown}
|
||||||
|
layoutDirection={
|
||||||
|
dropdownPlacement?.includes('end')
|
||||||
|
? 'search-bar-on-bottom'
|
||||||
|
: 'search-bar-on-top'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DropdownScope>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user