Refactor inline-cell inputs on board (#13921)
Refactor FieldsCard (aka RecordFieldList) and RecordDetailSection so they re-use the same components
This commit is contained in:
+7
-1
@@ -4,8 +4,10 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI
|
||||
import { RecordFieldListCellEditModePortalContent } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortalContent';
|
||||
import { RecordFieldListInputContextProvider } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider';
|
||||
import { useFieldListFieldMetadataFromPosition } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataFromPosition';
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState';
|
||||
import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type RecordFieldListCellEditModePortalProps = {
|
||||
@@ -17,6 +19,10 @@ export const RecordFieldListCellEditModePortal = ({
|
||||
objectMetadataItem,
|
||||
recordId,
|
||||
}: RecordFieldListCellEditModePortalProps) => {
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordFieldListComponentInstanceContext,
|
||||
);
|
||||
|
||||
const editModePosition = useRecoilComponentValue(
|
||||
recordFieldListCellEditModePositionComponentState,
|
||||
);
|
||||
@@ -35,7 +41,7 @@ export const RecordFieldListCellEditModePortal = ({
|
||||
fieldMetadataItem={editedFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
anchorIdPrefix={'record-field-list-inline-cell'}
|
||||
anchorIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListCellEditModePortalContent />
|
||||
|
||||
+7
-1
@@ -4,8 +4,10 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI
|
||||
import { RecordFieldListCellHoveredPortalContent } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortalContent';
|
||||
import { RecordFieldListInputContextProvider } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListInputContextProvider';
|
||||
import { useFieldListFieldMetadataFromPosition } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataFromPosition';
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { RecordInlineCellAnchoredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type RecordFieldListCellHoveredPortalProps = {
|
||||
@@ -17,6 +19,10 @@ export const RecordFieldListCellHoveredPortal = ({
|
||||
objectMetadataItem,
|
||||
recordId,
|
||||
}: RecordFieldListCellHoveredPortalProps) => {
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordFieldListComponentInstanceContext,
|
||||
);
|
||||
|
||||
const hoverPosition = useRecoilComponentValue(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
);
|
||||
@@ -35,7 +41,7 @@ export const RecordFieldListCellHoveredPortal = ({
|
||||
fieldMetadataItem={hoveredFieldMetadataItem}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={recordId}
|
||||
anchorIdPrefix={'record-field-list-inline-cell'}
|
||||
anchorIdPrefix={instanceId}
|
||||
>
|
||||
<RecordFieldListInputContextProvider>
|
||||
<RecordFieldListCellHoveredPortalContent />
|
||||
|
||||
+16
-4
@@ -1,3 +1,4 @@
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay';
|
||||
@@ -8,16 +9,28 @@ import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/c
|
||||
import { RecordInlineCellDisplayMode } from '@/object-record/record-inline-cell/components/RecordInlineCellDisplayMode';
|
||||
import { RecordInlineCellHoveredPortalContent } from '@/object-record/record-inline-cell/components/RecordInlineCellHoveredPortalContent';
|
||||
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const RecordFieldListCellHoveredPortalContent = () => {
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
RecordFieldListComponentInstanceContext,
|
||||
);
|
||||
|
||||
const { editModeContentOnly, isCentered } = useRecordInlineCellContext();
|
||||
|
||||
const { isRecordFieldReadOnly, recordId, fieldDefinition } =
|
||||
useContext(FieldContext);
|
||||
const { openInlineCell } = useInlineCell();
|
||||
const { openInlineCell } = useInlineCell(
|
||||
getRecordFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldDefinition.metadata.fieldName,
|
||||
prefix: instanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
const shouldContainerBeClickable =
|
||||
!isRecordFieldReadOnly && !editModeContentOnly;
|
||||
@@ -38,7 +51,7 @@ export const RecordFieldListCellHoveredPortalContent = () => {
|
||||
openFieldInput({
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
prefix: 'inline-cell',
|
||||
prefix: instanceId,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -51,10 +64,9 @@ export const RecordFieldListCellHoveredPortalContent = () => {
|
||||
<RecordInlineCellHoveredPortalContent
|
||||
readonly={isRecordFieldReadOnly}
|
||||
isCentered={isCentered}
|
||||
onClick={handleClick}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<RecordInlineCellDisplayMode isHovered={true}>
|
||||
<RecordInlineCellDisplayMode isHovered={true} onClick={handleClick}>
|
||||
{editModeContentOnly ? <FieldInput /> : <FieldDisplay />}
|
||||
</RecordInlineCellDisplayMode>
|
||||
</RecordInlineCellHoveredPortalContent>
|
||||
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { RecordFieldListCellEditModePortal } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortal';
|
||||
import { RecordFieldListCellHoveredPortal } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortal';
|
||||
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
|
||||
import { RecordDetailDuplicatesSection } from '@/object-record/record-field-list/record-detail-section/duplicate/components/RecordDetailDuplicatesSection';
|
||||
import { RecordDetailRelationSection } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection';
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useIsRecordReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordReadOnly';
|
||||
import { isRecordFieldReadOnly } from '@/object-record/record-field/ui/hooks/read-only/utils/isRecordFieldReadOnly';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
|
||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
|
||||
type RecordFieldListProps = {
|
||||
instanceId: string;
|
||||
objectNameSingular: string;
|
||||
objectRecordId: string;
|
||||
showDuplicatesSection?: boolean;
|
||||
showRelationSections?: boolean;
|
||||
excludeFieldMetadataIds?: string[];
|
||||
excludeCreatedAtAndUpdatedAt?: boolean;
|
||||
};
|
||||
|
||||
export const RecordFieldList = ({
|
||||
instanceId,
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
showDuplicatesSection = true,
|
||||
showRelationSections = true,
|
||||
excludeFieldMetadataIds = [],
|
||||
excludeCreatedAtAndUpdatedAt = true,
|
||||
}: RecordFieldListProps) => {
|
||||
const { recordLoading, isPrefetchLoading } = useRecordShowContainerData({
|
||||
objectRecordId,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
});
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: objectRecordId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
const setRecordFieldListHoverPosition = useSetRecoilComponentState(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
const handleMouseEnter = (index: number) => {
|
||||
setRecordFieldListHoverPosition(index);
|
||||
};
|
||||
|
||||
const {
|
||||
inlineFieldMetadataItems,
|
||||
inlineRelationFieldMetadataItems,
|
||||
boxedRelationFieldMetadataItems,
|
||||
} = useFieldListFieldMetadataItems({
|
||||
objectNameSingular,
|
||||
excludeFieldMetadataIds,
|
||||
showRelationSections,
|
||||
excludeCreatedAtAndUpdatedAt,
|
||||
});
|
||||
|
||||
return (
|
||||
<RecordFieldListComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId,
|
||||
}}
|
||||
>
|
||||
<PropertyBox>
|
||||
{isPrefetchLoading ? (
|
||||
<PropertyBoxSkeletonLoader />
|
||||
) : (
|
||||
<>
|
||||
{inlineRelationFieldMetadataItems?.map(
|
||||
(fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
onMouseEnter: () => handleMouseEnter(index),
|
||||
anchorId: `${instanceId}-${index}`,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
objectPermissions:
|
||||
getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
}),
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
objectNameSingular,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
fieldType: fieldMetadataItem.type,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<ActivityTargetsInlineCell
|
||||
componentInstanceId={getRecordFieldInputInstanceId({
|
||||
recordId: objectRecordId,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: instanceId,
|
||||
})}
|
||||
activityObjectNameSingular={
|
||||
objectNameSingular as
|
||||
| CoreObjectNameSingular.Note
|
||||
| CoreObjectNameSingular.Task
|
||||
}
|
||||
activityRecordId={objectRecordId}
|
||||
showLabel={true}
|
||||
maxWidth={200}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
)}
|
||||
{inlineFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
objectPermissions:
|
||||
getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
}),
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
objectNameSingular,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
fieldType: fieldMetadataItem.type,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
}),
|
||||
onMouseEnter: () =>
|
||||
handleMouseEnter(
|
||||
index + (inlineRelationFieldMetadataItems?.length ?? 0),
|
||||
),
|
||||
anchorId: `${instanceId}-${
|
||||
index + (inlineRelationFieldMetadataItems?.length ?? 0)
|
||||
}`,
|
||||
}}
|
||||
>
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getRecordFieldInputInstanceId({
|
||||
recordId: objectRecordId,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: instanceId,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell
|
||||
loading={recordLoading}
|
||||
instanceIdPrefix={instanceId}
|
||||
/>
|
||||
</RecordFieldComponentInstanceContext.Provider>
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</PropertyBox>
|
||||
{showDuplicatesSection && (
|
||||
<RecordDetailDuplicatesSection
|
||||
objectRecordId={objectRecordId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
)}
|
||||
{boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
objectPermissions: getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
}),
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
objectNameSingular,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
fieldType: fieldMetadataItem.type,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordDetailRelationSection
|
||||
loading={isPrefetchLoading || recordLoading}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
|
||||
<RecordFieldListCellHoveredPortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={objectRecordId}
|
||||
/>
|
||||
<RecordFieldListCellEditModePortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={objectRecordId}
|
||||
/>
|
||||
</RecordFieldListComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
+13
-1
@@ -11,10 +11,16 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type UseFieldListFieldMetadataItemsProps = {
|
||||
objectNameSingular: string;
|
||||
excludeFieldMetadataIds?: string[];
|
||||
excludeCreatedAtAndUpdatedAt?: boolean;
|
||||
showRelationSections?: boolean;
|
||||
};
|
||||
|
||||
export const useFieldListFieldMetadataItems = ({
|
||||
objectNameSingular,
|
||||
excludeFieldMetadataIds = [],
|
||||
showRelationSections = true,
|
||||
excludeCreatedAtAndUpdatedAt = true,
|
||||
}: UseFieldListFieldMetadataItemsProps) => {
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({
|
||||
@@ -33,7 +39,13 @@ export const useFieldListFieldMetadataItems = ({
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.id !== labelIdentifierFieldMetadataItem?.id,
|
||||
fieldMetadataItem.id !== labelIdentifierFieldMetadataItem?.id &&
|
||||
!excludeFieldMetadataIds.includes(fieldMetadataItem.id) &&
|
||||
(!excludeCreatedAtAndUpdatedAt ||
|
||||
(fieldMetadataItem.name !== 'createdAt' &&
|
||||
fieldMetadataItem.name !== 'deletedAt')) &&
|
||||
(showRelationSections ||
|
||||
fieldMetadataItem.type !== FieldMetadataType.RELATION),
|
||||
)
|
||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@ const StyledRecordsList = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
`;
|
||||
|
||||
export { StyledRecordsList as RecordDetailRecordsList };
|
||||
export { StyledRecordsList as RecordDetailRecordsListContainer };
|
||||
+1
-1
@@ -8,4 +8,4 @@ const StyledListItem = styled.div`
|
||||
height: ${({ theme }) => theme.spacing(10)};
|
||||
`;
|
||||
|
||||
export { StyledListItem as RecordDetailRecordsListItem };
|
||||
export { StyledListItem as RecordDetailRecordsListItemContainer };
|
||||
+29
-17
@@ -1,6 +1,14 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
|
||||
const StyledRecordDetailSectionContainer = styled(Section)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
||||
width: auto;
|
||||
`;
|
||||
|
||||
const StyledHeader = styled.header<{
|
||||
isDropdownOpen?: boolean;
|
||||
@@ -34,7 +42,8 @@ const StyledLink = styled(Link)`
|
||||
}
|
||||
`;
|
||||
|
||||
type RecordDetailSectionHeaderProps = {
|
||||
type RecordDetailSectionContainerProps = {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
link?: { to: string; label: string };
|
||||
rightAdornment?: React.ReactNode;
|
||||
@@ -42,28 +51,31 @@ type RecordDetailSectionHeaderProps = {
|
||||
areRecordsAvailable?: boolean;
|
||||
};
|
||||
|
||||
export const RecordDetailSectionHeader = ({
|
||||
export const RecordDetailSectionContainer = ({
|
||||
children,
|
||||
title,
|
||||
link,
|
||||
rightAdornment,
|
||||
hideRightAdornmentOnMouseLeave = true,
|
||||
areRecordsAvailable = false,
|
||||
}: RecordDetailSectionHeaderProps) => {
|
||||
}: RecordDetailSectionContainerProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<StyledHeader
|
||||
areRecordsAvailable={areRecordsAvailable}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<StyledTitle>
|
||||
<StyledTitleLabel>{title}</StyledTitleLabel>
|
||||
{link && <StyledLink to={link.to}>{link.label}</StyledLink>}
|
||||
</StyledTitle>
|
||||
{hideRightAdornmentOnMouseLeave && !isHovered && areRecordsAvailable
|
||||
? null
|
||||
: rightAdornment}
|
||||
</StyledHeader>
|
||||
<StyledRecordDetailSectionContainer>
|
||||
<StyledHeader
|
||||
areRecordsAvailable={areRecordsAvailable}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<StyledTitle>
|
||||
<StyledTitleLabel>{title}</StyledTitleLabel>
|
||||
{link && <StyledLink to={link.to}>{link.label}</StyledLink>}
|
||||
</StyledTitle>
|
||||
{hideRightAdornmentOnMouseLeave && !isHovered && areRecordsAvailable
|
||||
? null
|
||||
: rightAdornment}
|
||||
</StyledHeader>
|
||||
{children}
|
||||
</StyledRecordDetailSectionContainer>
|
||||
);
|
||||
};
|
||||
+19
-21
@@ -2,10 +2,9 @@ import { useOpenMergeRecordsPageInCommandMenu } from '@/command-menu/hooks/useOp
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
import { useFindDuplicateRecords } from '@/object-record/hooks/useFindDuplicateRecords';
|
||||
import { RecordDetailRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsList';
|
||||
import { RecordDetailRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsListItem';
|
||||
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 { RecordDetailRecordsListContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListContainer';
|
||||
import { RecordDetailRecordsListItemContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListItemContainer';
|
||||
import { RecordDetailSectionContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailSectionContainer';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconArrowMerge } from 'twenty-ui/display';
|
||||
@@ -44,28 +43,27 @@ export const RecordDetailDuplicatesSection = ({
|
||||
return null;
|
||||
|
||||
return (
|
||||
<RecordDetailSection>
|
||||
<RecordDetailSectionHeader
|
||||
title="Duplicates"
|
||||
rightAdornment={
|
||||
<LightIconButton
|
||||
className="displayOnHover"
|
||||
Icon={IconArrowMerge}
|
||||
accent="tertiary"
|
||||
onClick={openMergeRecordsPageInCommandMenu}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<RecordDetailRecordsList>
|
||||
<RecordDetailSectionContainer
|
||||
title="Duplicates"
|
||||
rightAdornment={
|
||||
<LightIconButton
|
||||
className="displayOnHover"
|
||||
Icon={IconArrowMerge}
|
||||
accent="tertiary"
|
||||
onClick={openMergeRecordsPageInCommandMenu}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RecordDetailRecordsListContainer>
|
||||
{queryResults[0].slice(0, 5).map((duplicateRecord) => (
|
||||
<RecordDetailRecordsListItem key={duplicateRecord.id}>
|
||||
<RecordDetailRecordsListItemContainer key={duplicateRecord.id}>
|
||||
<RecordChip
|
||||
record={duplicateRecord}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
</RecordDetailRecordsListItem>
|
||||
</RecordDetailRecordsListItemContainer>
|
||||
))}
|
||||
</RecordDetailRecordsList>
|
||||
</RecordDetailSection>
|
||||
</RecordDetailRecordsListContainer>
|
||||
</RecordDetailSectionContainer>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -7,8 +7,8 @@ import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
|
||||
import { RecordDetailDuplicatesSection } from '../RecordDetailDuplicatesSection';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { RecordDetailDuplicatesSection } from '../../components/RecordDetailDuplicatesSection';
|
||||
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
|
||||
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 { RecordDetailRelationRecordsListItemEffect } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItemEffect';
|
||||
import { RecordDetailRecordsListContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListContainer';
|
||||
import { RecordDetailRelationRecordsListItem } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem';
|
||||
import { RecordDetailRelationRecordsListItemEffect } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItemEffect';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
type RecordDetailRelationRecordsListProps = {
|
||||
@@ -18,7 +18,7 @@ export const RecordDetailRelationRecordsList = ({
|
||||
setExpandedItem(recordId === expandedItem ? '' : recordId);
|
||||
|
||||
return (
|
||||
<RecordDetailRecordsList>
|
||||
<RecordDetailRecordsListContainer>
|
||||
{relationRecords.slice(0, 5).map((relationRecord) => (
|
||||
<Fragment key={relationRecord.id}>
|
||||
<RecordDetailRelationRecordsListItemEffect
|
||||
@@ -33,6 +33,6 @@ export const RecordDetailRelationRecordsList = ({
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</RecordDetailRecordsList>
|
||||
</RecordDetailRecordsListContainer>
|
||||
);
|
||||
};
|
||||
+14
-96
@@ -4,33 +4,21 @@ import { motion } from 'framer-motion';
|
||||
import { useCallback, useContext } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import {
|
||||
FieldContext,
|
||||
type RecordUpdateHook,
|
||||
type RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { RecordFieldList } from '@/object-record/record-field-list/components/RecordFieldList';
|
||||
import { RecordDetailRecordsListItemContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailRecordsListItemContainer';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { FieldInputEventContext } from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
|
||||
import { useIsRecordReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordReadOnly';
|
||||
import { isRecordFieldReadOnly } from '@/object-record/record-field/ui/hooks/read-only/utils/isRecordFieldReadOnly';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { type FieldRelationMetadata } from '@/object-record/record-field/ui/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 { singleRecordPickerSelectedIdComponentState } from '@/object-record/record-picker/single-record-picker/states/singleRecordPickerSelectedIdComponentState';
|
||||
import { RecordDetailRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsListItem';
|
||||
import { getRecordFieldCardRelationPickerDropdownId } from '@/object-record/record-show/utils/getRecordFieldCardRelationPickerDropdownId';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
@@ -38,6 +26,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { useIsInRightDrawerOrThrow } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { createPortal } from 'react-dom';
|
||||
@@ -53,7 +42,7 @@ import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { AnimatedEaseInOut } from 'twenty-ui/utilities';
|
||||
import { RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledListItem = styled(RecordDetailRecordsListItem)<{
|
||||
const StyledListItem = styled(RecordDetailRecordsListItemContainer)<{
|
||||
isDropdownOpen?: boolean;
|
||||
}>`
|
||||
${({ isDropdownOpen, theme }) =>
|
||||
@@ -74,14 +63,6 @@ const StyledListItem = styled(RecordDetailRecordsListItem)<{
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPropertyBox = styled(PropertyBox)`
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
padding-left: ${({ theme }) => theme.spacing(0)};
|
||||
padding-top: ${({ theme }) => theme.spacing(1)};
|
||||
padding-right: ${({ theme }) => theme.spacing(0)};
|
||||
`;
|
||||
|
||||
const StyledClickableZone = styled.div`
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
@@ -133,8 +114,6 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
relationObjectMetadataNameSingular,
|
||||
);
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const relationObjectPermissions = useObjectPermissionsForObject(
|
||||
relationObjectMetadataItem.id,
|
||||
);
|
||||
@@ -150,18 +129,6 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
relationObjectMetadataNameSingular ===
|
||||
CoreObjectNameSingular.WorkspaceMember;
|
||||
|
||||
const availableRelationFieldMetadataItems = relationObjectMetadataItem.fields
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.id !==
|
||||
relationObjectMetadataItem.labelIdentifierFieldMetadataId &&
|
||||
fieldMetadataItem.id !== relationFieldMetadataId &&
|
||||
fieldMetadataItem.name !== 'createdAt' &&
|
||||
fieldMetadataItem.name !== 'deletedAt',
|
||||
)
|
||||
.sort();
|
||||
|
||||
const dropdownInstanceId = `record-field-card-menu-${relationFieldMetadataId}-${relationRecord.id}`;
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
@@ -213,17 +180,6 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
await deleteOneRelationRecord(relationRecord.id);
|
||||
};
|
||||
|
||||
const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRelationRecord?.({
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
const handleClick = () => onClick(relationRecord.id);
|
||||
|
||||
const AnimatedIconChevronDown = useCallback<IconComponent>(
|
||||
@@ -240,10 +196,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
[isExpanded],
|
||||
);
|
||||
|
||||
const isRelationRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: relationRecord.id,
|
||||
objectMetadataId: relationObjectMetadataItem.id,
|
||||
});
|
||||
const { isInRightDrawer } = useIsInRightDrawerOrThrow();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -294,49 +247,14 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
)}
|
||||
</StyledListItem>
|
||||
<AnimatedEaseInOut isOpen={isExpanded}>
|
||||
<StyledPropertyBox>
|
||||
{availableRelationFieldMetadataItems.map(
|
||||
(fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId: relationRecord.id,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem: relationObjectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly: isRelationRecordReadOnly,
|
||||
objectPermissions: relationObjectPermissions,
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
objectNameSingular: relationObjectMetadataNameSingular,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
fieldType: fieldMetadataItem.type,
|
||||
isCustom: relationObjectMetadataItem.isCustom,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getRecordFieldInputInstanceId({
|
||||
recordId: relationRecord.id,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: 'record-detail',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell instanceIdPrefix="record-detail" />
|
||||
</RecordFieldComponentInstanceContext.Provider>
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
)}
|
||||
</StyledPropertyBox>
|
||||
<RecordFieldList
|
||||
instanceId={`record-detail-relation-${relationRecord.id}-${isInRightDrawer ? 'right-drawer' : ''}`}
|
||||
objectNameSingular={relationObjectMetadataNameSingular}
|
||||
objectRecordId={relationRecord.id}
|
||||
showDuplicatesSection={false}
|
||||
excludeCreatedAtAndUpdatedAt={true}
|
||||
excludeFieldMetadataIds={[relationFieldMetadataId]}
|
||||
/>
|
||||
</AnimatedEaseInOut>
|
||||
{createPortal(
|
||||
<ConfirmationModal
|
||||
+23
-25
@@ -5,6 +5,9 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { type RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { RecordDetailSectionContainer } from '@/object-record/record-field-list/record-detail-section/components/RecordDetailSectionContainer';
|
||||
import { RecordDetailRelationRecordsList } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsList';
|
||||
import { RecordDetailRelationSectionDropdown } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdown';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import {
|
||||
FieldInputEventContext,
|
||||
@@ -12,10 +15,6 @@ import {
|
||||
} from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
|
||||
import { usePersistField } from '@/object-record/record-field/ui/hooks/usePersistField';
|
||||
import { type FieldRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
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 { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
||||
import { getRecordFieldCardRelationPickerDropdownId } from '@/object-record/record-show/utils/getRecordFieldCardRelationPickerDropdownId';
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
@@ -157,30 +156,29 @@ export const RecordDetailRelationSection = ({
|
||||
onSubmit: handleSubmit,
|
||||
}}
|
||||
>
|
||||
<RecordDetailSection>
|
||||
<RecordDetailSectionHeader
|
||||
title={fieldDefinition.label}
|
||||
link={
|
||||
isToManyObjects
|
||||
? {
|
||||
to: filterLinkHref,
|
||||
label:
|
||||
relationRecordsCount > 0
|
||||
? t`All (${relationRecordsCount})`
|
||||
: '',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile}
|
||||
areRecordsAvailable={relationRecords.length > 0}
|
||||
rightAdornment={
|
||||
<RecordDetailRelationSectionDropdown loading={loading} />
|
||||
}
|
||||
/>
|
||||
<RecordDetailSectionContainer
|
||||
title={fieldDefinition.label}
|
||||
link={
|
||||
isToManyObjects
|
||||
? {
|
||||
to: filterLinkHref,
|
||||
label:
|
||||
relationRecordsCount > 0
|
||||
? t`All (${relationRecordsCount})`
|
||||
: '',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile}
|
||||
areRecordsAvailable={relationRecords.length > 0}
|
||||
rightAdornment={
|
||||
<RecordDetailRelationSectionDropdown loading={loading} />
|
||||
}
|
||||
>
|
||||
{relationRecords.length > 0 && (
|
||||
<RecordDetailRelationRecordsList relationRecords={relationRecords} />
|
||||
)}
|
||||
</RecordDetailSection>
|
||||
</RecordDetailSectionContainer>
|
||||
</FieldInputEventContext.Provider>
|
||||
);
|
||||
};
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { RecordDetailRelationSectionDropdownToMany } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdownToMany';
|
||||
import { RecordDetailRelationSectionDropdownToOne } from '@/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdownToOne';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useIsRecordReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordReadOnly';
|
||||
import { type FieldRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { RecordDetailRelationSectionDropdownToMany } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToMany';
|
||||
import { RecordDetailRelationSectionDropdownToOne } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionDropdownToOne';
|
||||
import { RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
type RecordDetailRelationSectionDropdownProps = {
|
||||
+2
@@ -12,6 +12,7 @@ import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { RightDrawerDecorator } from '~/testing/decorators/RightDrawerDecorator';
|
||||
import { allMockPersonRecords } from '~/testing/mock-data/people';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
import { RecordDetailRelationSection } from '../RecordDetailRelationSection';
|
||||
@@ -52,6 +53,7 @@ const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||
</FieldContext.Provider>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
),
|
||||
RightDrawerDecorator,
|
||||
ComponentDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
SnackBarDecorator,
|
||||
+6
@@ -18,6 +18,8 @@ export const FieldContextProvider = ({
|
||||
objectRecordId,
|
||||
customUseUpdateOneObjectHook,
|
||||
overridenIsFieldEmpty,
|
||||
onMouseEnter,
|
||||
anchorId,
|
||||
children,
|
||||
}: {
|
||||
clearable?: boolean;
|
||||
@@ -28,6 +30,8 @@ export const FieldContextProvider = ({
|
||||
objectRecordId: string;
|
||||
customUseUpdateOneObjectHook?: RecordUpdateHook;
|
||||
overridenIsFieldEmpty?: boolean;
|
||||
onMouseEnter?: () => void;
|
||||
anchorId?: string;
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
@@ -81,6 +85,8 @@ export const FieldContextProvider = ({
|
||||
clearable,
|
||||
overridenIsFieldEmpty,
|
||||
isRecordFieldReadOnly,
|
||||
onMouseEnter,
|
||||
anchorId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ export const RecordInlineCellAnchoredPortal = ({
|
||||
instanceId: getRecordFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: 'inline-cell',
|
||||
prefix: anchorIdPrefix,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
|
||||
+12
-3
@@ -64,8 +64,12 @@ const StyledEmptyField = styled.div`
|
||||
|
||||
export const RecordInlineCellDisplayMode = ({
|
||||
children,
|
||||
onClick,
|
||||
isHovered,
|
||||
}: React.PropsWithChildren<{ isHovered: boolean }>) => {
|
||||
}: React.PropsWithChildren<{
|
||||
isHovered: boolean;
|
||||
onClick?: () => void;
|
||||
}>) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { editModeContentOnly, showLabel, label, buttonIcon } =
|
||||
@@ -86,7 +90,10 @@ export const RecordInlineCellDisplayMode = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledRecordInlineCellNormalModeOuterContainer isHovered={isHovered}>
|
||||
<StyledRecordInlineCellNormalModeOuterContainer
|
||||
isHovered={isHovered}
|
||||
onClick={onClick}
|
||||
>
|
||||
<StyledRecordInlineCellNormalModeInnerContainer>
|
||||
{(isDisplayModeContentEmpty && !shouldDisplayEditModeOnFocus) ||
|
||||
!children ? (
|
||||
@@ -96,7 +103,9 @@ export const RecordInlineCellDisplayMode = ({
|
||||
)}
|
||||
</StyledRecordInlineCellNormalModeInnerContainer>
|
||||
</StyledRecordInlineCellNormalModeOuterContainer>
|
||||
{showEditButton && <RecordInlineCellButton Icon={buttonIcon} />}
|
||||
{showEditButton && (
|
||||
<RecordInlineCellButton Icon={buttonIcon} onClick={onClick} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+8
-2
@@ -8,10 +8,16 @@ const StyledInlineCellButtonContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const RecordInlineCellButton = ({ Icon }: { Icon: IconComponent }) => {
|
||||
export const RecordInlineCellButton = ({
|
||||
Icon,
|
||||
onClick,
|
||||
}: {
|
||||
Icon: IconComponent;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<AnimatedContainer>
|
||||
<StyledInlineCellButtonContainer>
|
||||
<StyledInlineCellButtonContainer onClick={onClick}>
|
||||
<FloatingIconButton
|
||||
size="small"
|
||||
Icon={Icon}
|
||||
|
||||
-11
@@ -1,5 +1,4 @@
|
||||
import { RecordInlineCellHoveredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellHoveredPortal';
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledRecordTableCellHoveredPortalContent = styled.div<{
|
||||
@@ -16,12 +15,6 @@ const StyledRecordTableCellHoveredPortalContent = styled.div<{
|
||||
`
|
||||
justify-content: center;
|
||||
`};
|
||||
|
||||
${({ readonly }) =>
|
||||
!readonly &&
|
||||
css`
|
||||
cursor: pointer;
|
||||
`};
|
||||
`;
|
||||
|
||||
const StyledInlineCellBaseContainer = styled.div<{ readonly: boolean }>`
|
||||
@@ -32,14 +25,12 @@ const StyledInlineCellBaseContainer = styled.div<{ readonly: boolean }>`
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
cursor: ${({ readonly }) => (readonly ? 'default' : 'pointer')};
|
||||
`;
|
||||
|
||||
type RecordInlineCellHoveredPortalContentProps = {
|
||||
children: React.ReactNode;
|
||||
readonly: boolean;
|
||||
isCentered?: boolean;
|
||||
onClick?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
};
|
||||
|
||||
@@ -47,14 +38,12 @@ export const RecordInlineCellHoveredPortalContent = ({
|
||||
children,
|
||||
isCentered,
|
||||
readonly,
|
||||
onClick,
|
||||
onMouseLeave,
|
||||
}: RecordInlineCellHoveredPortalContentProps) => {
|
||||
return (
|
||||
<RecordInlineCellHoveredPortal>
|
||||
<StyledInlineCellBaseContainer
|
||||
readonly={readonly}
|
||||
onClick={onClick}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<StyledRecordTableCellHoveredPortalContent
|
||||
|
||||
+5
-7
@@ -36,12 +36,10 @@ export const RecordInlineCellValue = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledClickableContainer readonly={readonly} isCentered={isCentered}>
|
||||
<RecordInlineCellDisplayMode isHovered={false}>
|
||||
<FieldDisplay />
|
||||
</RecordInlineCellDisplayMode>
|
||||
</StyledClickableContainer>
|
||||
</>
|
||||
<StyledClickableContainer readonly={readonly} isCentered={isCentered}>
|
||||
<RecordInlineCellDisplayMode isHovered={false}>
|
||||
<FieldDisplay />
|
||||
</RecordInlineCellDisplayMode>
|
||||
</StyledClickableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ export const StyledPropertyBoxContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme }) => theme.spacing(3)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
export const PropertyBox = ({ children, className }: PropertyBoxProps) => (
|
||||
|
||||
+14
-229
@@ -1,28 +1,11 @@
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { RecordFieldListCellEditModePortal } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellEditModePortal';
|
||||
import { RecordFieldListCellHoveredPortal } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortal';
|
||||
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useIsRecordReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordReadOnly';
|
||||
import { isRecordFieldReadOnly } from '@/object-record/record-field/ui/hooks/read-only/utils/isRecordFieldReadOnly';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
|
||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||
import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection';
|
||||
import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
|
||||
import { RecordFieldList } from '@/object-record/record-field-list/components/RecordFieldList';
|
||||
import { useIsInRightDrawerOrThrow } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledPropertyBoxContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(3)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type FieldsCardProps = {
|
||||
objectNameSingular: string;
|
||||
@@ -30,219 +13,21 @@ type FieldsCardProps = {
|
||||
showDuplicatesSection?: boolean;
|
||||
};
|
||||
|
||||
const INPUT_ID_PREFIX = 'fields-card';
|
||||
|
||||
export const FieldsCard = ({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
showDuplicatesSection = true,
|
||||
}: FieldsCardProps) => {
|
||||
const { recordLoading, isPrefetchLoading } = useRecordShowContainerData({
|
||||
objectRecordId,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
});
|
||||
|
||||
const { isInRightDrawer } = useIsInRightDrawerOrThrow();
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: objectRecordId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
const setRecordFieldListHoverPosition = useSetRecoilComponentState(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
`fields-list-${objectRecordId}`,
|
||||
);
|
||||
|
||||
const handleMouseEnter = (index: number) => {
|
||||
setRecordFieldListHoverPosition(index);
|
||||
};
|
||||
|
||||
const {
|
||||
inlineFieldMetadataItems,
|
||||
inlineRelationFieldMetadataItems,
|
||||
boxedRelationFieldMetadataItems,
|
||||
} = useFieldListFieldMetadataItems({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
return (
|
||||
<RecordFieldListComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: `fields-list-${objectRecordId}`,
|
||||
}}
|
||||
>
|
||||
<PropertyBox>
|
||||
{isPrefetchLoading ? (
|
||||
<PropertyBoxSkeletonLoader />
|
||||
) : (
|
||||
<>
|
||||
{inlineRelationFieldMetadataItems?.map(
|
||||
(fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
objectPermissions:
|
||||
getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
}),
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
objectNameSingular,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
fieldType: fieldMetadataItem.type,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<ActivityTargetsInlineCell
|
||||
componentInstanceId={getRecordFieldInputInstanceId({
|
||||
recordId: objectRecordId,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: isInRightDrawer
|
||||
? 'right-drawer-fields-card'
|
||||
: 'fields-card',
|
||||
})}
|
||||
activityObjectNameSingular={
|
||||
objectNameSingular as
|
||||
| CoreObjectNameSingular.Note
|
||||
| CoreObjectNameSingular.Task
|
||||
}
|
||||
activityRecordId={objectRecordId}
|
||||
showLabel={true}
|
||||
maxWidth={200}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
)}
|
||||
{inlineFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
objectPermissions:
|
||||
getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
}),
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
objectNameSingular,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
fieldType: fieldMetadataItem.type,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
}),
|
||||
onMouseEnter: () =>
|
||||
handleMouseEnter(
|
||||
index + (inlineRelationFieldMetadataItems?.length ?? 0),
|
||||
),
|
||||
anchorId: `record-field-list-inline-cell-${
|
||||
index + (inlineRelationFieldMetadataItems?.length ?? 0)
|
||||
}`,
|
||||
}}
|
||||
>
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getRecordFieldInputInstanceId({
|
||||
recordId: objectRecordId,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: INPUT_ID_PREFIX,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell
|
||||
loading={recordLoading}
|
||||
instanceIdPrefix={INPUT_ID_PREFIX}
|
||||
/>
|
||||
</RecordFieldComponentInstanceContext.Provider>
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</PropertyBox>
|
||||
{showDuplicatesSection && (
|
||||
<RecordDetailDuplicatesSection
|
||||
objectRecordId={objectRecordId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
)}
|
||||
{boxedRelationFieldMetadataItems?.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId: objectRecordId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly: isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
objectPermissions: getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
}),
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
objectNameSingular,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
fieldType: fieldMetadataItem.type,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordDetailRelationSection
|
||||
loading={isPrefetchLoading || recordLoading}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
|
||||
<RecordFieldListCellHoveredPortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={objectRecordId}
|
||||
<StyledPropertyBoxContainer>
|
||||
<RecordFieldList
|
||||
instanceId={`fields-card-${objectRecordId}-${isInRightDrawer ? 'right-drawer' : ''}`}
|
||||
objectNameSingular={objectNameSingular}
|
||||
objectRecordId={objectRecordId}
|
||||
showDuplicatesSection={showDuplicatesSection}
|
||||
/>
|
||||
<RecordFieldListCellEditModePortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
recordId={objectRecordId}
|
||||
/>
|
||||
</RecordFieldListComponentInstanceContext.Provider>
|
||||
</StyledPropertyBoxContainer>
|
||||
);
|
||||
};
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
|
||||
const StyledRecordDetailSection = styled(Section)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
padding: ${({ theme }) => theme.spacing(3)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
width: auto;
|
||||
`;
|
||||
|
||||
export { StyledRecordDetailSection as RecordDetailSection };
|
||||
Reference in New Issue
Block a user