feat: expand relation record cards on click in Record Show page (#4570)
Closes #3126
This commit is contained in:
@ -0,0 +1,41 @@
|
|||||||
|
import { useLazyQuery } from '@apollo/client';
|
||||||
|
|
||||||
|
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||||
|
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||||
|
import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerateFindOneRecordQuery';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
|
||||||
|
type UseLazyFindOneRecordParams = ObjectMetadataItemIdentifier & {
|
||||||
|
depth?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FindOneRecordParams<T extends ObjectRecord> = {
|
||||||
|
objectRecordId: string | undefined;
|
||||||
|
onCompleted?: (data: T) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLazyFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
||||||
|
objectNameSingular,
|
||||||
|
depth,
|
||||||
|
}: UseLazyFindOneRecordParams) => {
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItemOnly({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
const findOneRecordQuery = useGenerateFindOneRecordQuery();
|
||||||
|
|
||||||
|
const [findOneRecord, { loading, error, data, called }] = useLazyQuery(
|
||||||
|
findOneRecordQuery({ objectMetadataItem, depth }),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
findOneRecord: ({ objectRecordId, onCompleted }: FindOneRecordParams<T>) =>
|
||||||
|
findOneRecord({
|
||||||
|
variables: { objectRecordId },
|
||||||
|
onCompleted: (data) => onCompleted?.(data[objectNameSingular]),
|
||||||
|
}),
|
||||||
|
called,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
record: data?.[objectNameSingular] || undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,8 +1,8 @@
|
|||||||
|
import groupBy from 'lodash.groupby';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||||
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import {
|
import {
|
||||||
FieldContext,
|
FieldContext,
|
||||||
@ -17,7 +17,7 @@ import { RecordDetailRelationSection } from '@/object-record/record-show/record-
|
|||||||
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
|
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
|
||||||
import { isFieldMetadataItemAvailable } from '@/object-record/utils/isFieldMetadataItemAvailable';
|
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||||
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
|
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
|
||||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||||
@ -89,13 +89,7 @@ export const RecordShowContainer = ({
|
|||||||
|
|
||||||
const avatarUrl = result?.data?.uploadImage;
|
const avatarUrl = result?.data?.uploadImage;
|
||||||
|
|
||||||
if (!avatarUrl) {
|
if (!avatarUrl || isUndefinedOrNull(updateOneRecord) || !recordFromStore) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isUndefinedOrNull(updateOneRecord)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!recordFromStore) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,21 +104,19 @@ export const RecordShowContainer = ({
|
|||||||
const availableFieldMetadataItems = objectMetadataItem.fields
|
const availableFieldMetadataItems = objectMetadataItem.fields
|
||||||
.filter(
|
.filter(
|
||||||
(fieldMetadataItem) =>
|
(fieldMetadataItem) =>
|
||||||
isFieldMetadataItemAvailable(fieldMetadataItem) &&
|
isFieldCellSupported(fieldMetadataItem) &&
|
||||||
fieldMetadataItem.id !== labelIdentifierFieldMetadata?.id,
|
fieldMetadataItem.id !== labelIdentifierFieldMetadata?.id,
|
||||||
)
|
)
|
||||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||||
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
const inlineFieldMetadataItems = availableFieldMetadataItems.filter(
|
const { inlineFieldMetadataItems, relationFieldMetadataItems } = groupBy(
|
||||||
|
availableFieldMetadataItems,
|
||||||
(fieldMetadataItem) =>
|
(fieldMetadataItem) =>
|
||||||
fieldMetadataItem.type !== FieldMetadataType.Relation,
|
fieldMetadataItem.type === FieldMetadataType.Relation
|
||||||
);
|
? 'relationFieldMetadataItems'
|
||||||
|
: 'inlineFieldMetadataItems',
|
||||||
const relationFieldMetadataItems = availableFieldMetadataItems.filter(
|
|
||||||
(fieldMetadataItem) =>
|
|
||||||
fieldMetadataItem.type === FieldMetadataType.Relation,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -197,40 +189,25 @@ export const RecordShowContainer = ({
|
|||||||
objectRecordId={objectRecordId}
|
objectRecordId={objectRecordId}
|
||||||
objectNameSingular={objectNameSingular}
|
objectNameSingular={objectNameSingular}
|
||||||
/>
|
/>
|
||||||
{relationFieldMetadataItems
|
{relationFieldMetadataItems.map((fieldMetadataItem, index) => (
|
||||||
.filter((item) => {
|
<FieldContext.Provider
|
||||||
const relationObjectMetadataItem = item.toRelationMetadata
|
key={objectRecordId + fieldMetadataItem.id}
|
||||||
? item.toRelationMetadata.fromObjectMetadata
|
value={{
|
||||||
: item.fromRelationMetadata?.toObjectMetadata;
|
entityId: objectRecordId,
|
||||||
|
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||||
if (!relationObjectMetadataItem) {
|
isLabelIdentifier: false,
|
||||||
return false;
|
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||||
}
|
field: fieldMetadataItem,
|
||||||
|
position: index,
|
||||||
return isObjectMetadataAvailableForRelation(
|
objectMetadataItem,
|
||||||
relationObjectMetadataItem,
|
}),
|
||||||
);
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
})
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
.map((fieldMetadataItem, index) => (
|
}}
|
||||||
<FieldContext.Provider
|
>
|
||||||
key={objectRecordId + fieldMetadataItem.id}
|
<RecordDetailRelationSection />
|
||||||
value={{
|
</FieldContext.Provider>
|
||||||
entityId: objectRecordId,
|
))}
|
||||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
|
||||||
isLabelIdentifier: false,
|
|
||||||
fieldDefinition:
|
|
||||||
formatFieldMetadataItemAsColumnDefinition({
|
|
||||||
field: fieldMetadataItem,
|
|
||||||
position: index,
|
|
||||||
objectMetadataItem,
|
|
||||||
}),
|
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RecordDetailRelationSection />
|
|
||||||
</FieldContext.Provider>
|
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ShowPageLeftContainer>
|
</ShowPageLeftContainer>
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
const StyledRecordsList = styled.div`
|
const StyledRecordsList = styled.div`
|
||||||
color: ${({ theme }) => theme.font.color.secondary};
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
overflow: hidden;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledRecordsList as RecordDetailRecordsList };
|
export { StyledRecordsList as RecordDetailRecordsList };
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { RecordDetailRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsList';
|
import { RecordDetailRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsList';
|
||||||
import { RecordDetailRelationRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem';
|
import { RecordDetailRelationRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
@ -8,13 +10,22 @@ type RecordDetailRelationRecordsListProps = {
|
|||||||
|
|
||||||
export const RecordDetailRelationRecordsList = ({
|
export const RecordDetailRelationRecordsList = ({
|
||||||
relationRecords,
|
relationRecords,
|
||||||
}: RecordDetailRelationRecordsListProps) => (
|
}: RecordDetailRelationRecordsListProps) => {
|
||||||
<RecordDetailRecordsList>
|
const [expandedItem, setExpandedItem] = useState('');
|
||||||
{relationRecords.slice(0, 5).map((relationRecord) => (
|
|
||||||
<RecordDetailRelationRecordsListItem
|
const handleItemClick = (recordId: string) =>
|
||||||
key={relationRecord.id}
|
setExpandedItem(recordId === expandedItem ? '' : recordId);
|
||||||
relationRecord={relationRecord}
|
|
||||||
/>
|
return (
|
||||||
))}
|
<RecordDetailRecordsList>
|
||||||
</RecordDetailRecordsList>
|
{relationRecords.slice(0, 5).map((relationRecord) => (
|
||||||
);
|
<RecordDetailRelationRecordsListItem
|
||||||
|
key={relationRecord.id}
|
||||||
|
isExpanded={expandedItem === relationRecord.id}
|
||||||
|
onClick={handleItemClick}
|
||||||
|
relationRecord={relationRecord}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RecordDetailRecordsList>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -1,23 +1,42 @@
|
|||||||
import { useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
import { LightIconButton, MenuItem } from 'tsup.ui.index';
|
import { LightIconButton, MenuItem } from 'tsup.ui.index';
|
||||||
import { IconDotsVertical, IconTrash, IconUnlink } from 'twenty-ui';
|
import {
|
||||||
|
IconChevronDown,
|
||||||
|
IconDotsVertical,
|
||||||
|
IconTrash,
|
||||||
|
IconUnlink,
|
||||||
|
} from 'twenty-ui';
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord.ts';
|
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord.ts';
|
||||||
|
import { useLazyFindOneRecord } from '@/object-record/hooks/useLazyFindOneRecord';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import {
|
||||||
|
FieldContext,
|
||||||
|
RecordUpdateHook,
|
||||||
|
RecordUpdateHookParams,
|
||||||
|
} from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
|
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||||
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { RecordDetailRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsListItem';
|
import { RecordDetailRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsListItem';
|
||||||
|
import { useSetRecordInStore } from '@/object-record/record-store/hooks/useSetRecordInStore';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||||
|
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
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 { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
|
import { AnimatedEaseInOut } from '@/ui/utilities/animation/components/AnimatedEaseInOut';
|
||||||
|
|
||||||
const StyledListItem = styled(RecordDetailRecordsListItem)<{
|
const StyledListItem = styled(RecordDetailRecordsListItem)<{
|
||||||
isDropdownOpen?: boolean;
|
isDropdownOpen?: boolean;
|
||||||
@ -40,11 +59,26 @@ const StyledListItem = styled(RecordDetailRecordsListItem)<{
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledClickableZone = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MotionIconChevronDown = motion(IconChevronDown);
|
||||||
|
|
||||||
type RecordDetailRelationRecordsListItemProps = {
|
type RecordDetailRelationRecordsListItemProps = {
|
||||||
|
isExpanded: boolean;
|
||||||
|
onClick: (relationRecordId: string) => void;
|
||||||
relationRecord: ObjectRecord;
|
relationRecord: ObjectRecord;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordDetailRelationRecordsListItem = ({
|
export const RecordDetailRelationRecordsListItem = ({
|
||||||
|
isExpanded,
|
||||||
|
onClick,
|
||||||
relationRecord,
|
relationRecord,
|
||||||
}: RecordDetailRelationRecordsListItemProps) => {
|
}: RecordDetailRelationRecordsListItemProps) => {
|
||||||
const { fieldDefinition } = useContext(FieldContext);
|
const { fieldDefinition } = useContext(FieldContext);
|
||||||
@ -57,18 +91,39 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
|
|
||||||
const isToOneObject = relationType === 'TO_ONE_OBJECT';
|
const isToOneObject = relationType === 'TO_ONE_OBJECT';
|
||||||
const { objectMetadataItem: relationObjectMetadataItem } =
|
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||||
useObjectMetadataItem({
|
useObjectMetadataItemOnly({
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const persistField = usePersistField();
|
const persistField = usePersistField();
|
||||||
|
|
||||||
|
const {
|
||||||
|
called: hasFetchedRelationRecord,
|
||||||
|
findOneRecord: findOneRelationRecord,
|
||||||
|
} = useLazyFindOneRecord({
|
||||||
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
|
});
|
||||||
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
const { updateOneRecord: updateOneRelationRecord } = useUpdateOneRecord({
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { deleteOneRecord: deleteOneRelationRecord } = useDeleteOneRecord({
|
const { deleteOneRecord: deleteOneRelationRecord } = useDeleteOneRecord({
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isAccountOwnerRelation =
|
||||||
|
relationObjectMetadataNameSingular ===
|
||||||
|
CoreObjectNameSingular.WorkspaceMember;
|
||||||
|
|
||||||
|
const availableRelationFieldMetadataItems = relationObjectMetadataItem.fields
|
||||||
|
.filter(
|
||||||
|
(fieldMetadataItem) =>
|
||||||
|
isFieldCellSupported(fieldMetadataItem) &&
|
||||||
|
fieldMetadataItem.id !==
|
||||||
|
relationObjectMetadataItem.labelIdentifierFieldMetadataId &&
|
||||||
|
fieldMetadataItem.id !== relationFieldMetadataId,
|
||||||
|
)
|
||||||
|
.sort();
|
||||||
|
|
||||||
const dropdownScopeId = `record-field-card-menu-${relationRecord.id}`;
|
const dropdownScopeId = `record-field-card-menu-${relationRecord.id}`;
|
||||||
|
|
||||||
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownScopeId);
|
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownScopeId);
|
||||||
@ -100,17 +155,58 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
await deleteOneRelationRecord(relationRecord.id);
|
await deleteOneRelationRecord(relationRecord.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAccountOwnerRelation =
|
const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
|
||||||
relationObjectMetadataNameSingular ===
|
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||||
CoreObjectNameSingular.WorkspaceMember;
|
updateOneRelationRecord?.({
|
||||||
|
idToUpdate: variables.where.id as string,
|
||||||
|
updateOneRecordInput: variables.updateOneRecordInput,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return [updateEntity, { loading: false }];
|
||||||
|
};
|
||||||
|
|
||||||
|
const { setRecords } = useSetRecordInStore();
|
||||||
|
|
||||||
|
const handleClick = () => onClick(relationRecord.id);
|
||||||
|
|
||||||
|
const AnimatedIconChevronDown = useCallback<IconComponent>(
|
||||||
|
(props) => (
|
||||||
|
<MotionIconChevronDown
|
||||||
|
className={props.className}
|
||||||
|
color={props.color}
|
||||||
|
size={props.size}
|
||||||
|
stroke={props.stroke}
|
||||||
|
initial={{ rotate: isExpanded ? 0 : -180 }}
|
||||||
|
animate={{ rotate: isExpanded ? -180 : 0 }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[isExpanded],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledListItem isDropdownOpen={isDropdownOpen}>
|
<>
|
||||||
<RecordChip
|
<StyledListItem isDropdownOpen={isDropdownOpen}>
|
||||||
record={relationRecord}
|
<RecordChip
|
||||||
objectNameSingular={relationObjectMetadataItem.nameSingular}
|
record={relationRecord}
|
||||||
/>
|
objectNameSingular={relationObjectMetadataItem.nameSingular}
|
||||||
{
|
/>
|
||||||
|
<StyledClickableZone
|
||||||
|
onClick={handleClick}
|
||||||
|
onMouseOver={() =>
|
||||||
|
!hasFetchedRelationRecord &&
|
||||||
|
findOneRelationRecord({
|
||||||
|
objectRecordId: relationRecord.id,
|
||||||
|
onCompleted: (record) => setRecords([record]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<LightIconButton
|
||||||
|
className="displayOnHover"
|
||||||
|
Icon={AnimatedIconChevronDown}
|
||||||
|
accent="tertiary"
|
||||||
|
/>
|
||||||
|
</StyledClickableZone>
|
||||||
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={dropdownScopeId}
|
dropdownId={dropdownScopeId}
|
||||||
@ -139,12 +235,38 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
)}
|
)}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
}
|
}
|
||||||
dropdownHotkeyScope={{
|
dropdownHotkeyScope={{ scope: dropdownScopeId }}
|
||||||
scope: dropdownScopeId,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</DropdownScope>
|
</DropdownScope>
|
||||||
}
|
</StyledListItem>
|
||||||
</StyledListItem>
|
<AnimatedEaseInOut isOpen={isExpanded}>
|
||||||
|
<PropertyBox>
|
||||||
|
{availableRelationFieldMetadataItems.map(
|
||||||
|
(fieldMetadataItem, index) => (
|
||||||
|
<FieldContext.Provider
|
||||||
|
key={fieldMetadataItem.id}
|
||||||
|
value={{
|
||||||
|
entityId: relationRecord.id,
|
||||||
|
maxWidth: 200,
|
||||||
|
recoilScopeId: `${relationRecord.id}-${fieldMetadataItem.id}`,
|
||||||
|
isLabelIdentifier: false,
|
||||||
|
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||||
|
field: fieldMetadataItem,
|
||||||
|
position: index,
|
||||||
|
objectMetadataItem: relationObjectMetadataItem,
|
||||||
|
showLabel: true,
|
||||||
|
labelWidth: 90,
|
||||||
|
}),
|
||||||
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecordInlineCell />
|
||||||
|
</FieldContext.Provider>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</PropertyBox>
|
||||||
|
</AnimatedEaseInOut>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import qs from 'qs';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { IconForbid, IconPencil, IconPlus } from 'twenty-ui';
|
import { IconForbid, IconPencil, IconPlus } from 'twenty-ui';
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
@ -42,7 +42,7 @@ export const RecordDetailRelationSection = () => {
|
|||||||
const record = useRecoilValue(recordStoreFamilyState(entityId));
|
const record = useRecoilValue(recordStoreFamilyState(entityId));
|
||||||
|
|
||||||
const { objectMetadataItem: relationObjectMetadataItem } =
|
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||||
useObjectMetadataItem({
|
useObjectMetadataItemOnly({
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
|
||||||
|
import {
|
||||||
|
FieldMetadataType,
|
||||||
|
RelationMetadataType,
|
||||||
|
} from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
export const isFieldCellSupported = (fieldMetadataItem: FieldMetadataItem) => {
|
||||||
|
if (
|
||||||
|
[FieldMetadataType.Uuid, FieldMetadataType.Position].includes(
|
||||||
|
fieldMetadataItem.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldMetadataItem.type === FieldMetadataType.Relation) {
|
||||||
|
const relationMetadata =
|
||||||
|
fieldMetadataItem.fromRelationMetadata ??
|
||||||
|
fieldMetadataItem.toRelationMetadata;
|
||||||
|
const relationObjectMetadataItem =
|
||||||
|
fieldMetadataItem.fromRelationMetadata?.toObjectMetadata ??
|
||||||
|
fieldMetadataItem.toRelationMetadata?.fromObjectMetadata;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!relationMetadata ||
|
||||||
|
// TODO: Many to many relations are not supported yet.
|
||||||
|
relationMetadata.relationType === RelationMetadataType.ManyToMany ||
|
||||||
|
!relationObjectMetadataItem ||
|
||||||
|
!isObjectMetadataAvailableForRelation(relationObjectMetadataItem)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !fieldMetadataItem.isSystem && !!fieldMetadataItem.isActive;
|
||||||
|
};
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
|
||||||
import { RelationMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
export const isFieldMetadataItemAvailable = (
|
|
||||||
fieldMetadataItem: FieldMetadataItem,
|
|
||||||
) =>
|
|
||||||
fieldMetadataItem.type !== 'UUID' &&
|
|
||||||
// TODO: Many to many relations are not supported yet.
|
|
||||||
!(
|
|
||||||
fieldMetadataItem.type === 'RELATION' &&
|
|
||||||
(
|
|
||||||
fieldMetadataItem.fromRelationMetadata ??
|
|
||||||
fieldMetadataItem.toRelationMetadata
|
|
||||||
)?.relationType === RelationMetadataType.ManyToMany
|
|
||||||
) &&
|
|
||||||
!fieldMetadataItem.isSystem &&
|
|
||||||
!!fieldMetadataItem.isActive;
|
|
||||||
Reference in New Issue
Block a user