mirror of
https://github.com/twentyhq/twenty.git
synced 2024-10-04 21:07:21 +03:00
fix: display label identifier field input in Show Page (#3063)
* fix: display label identifier field input in Show Page Fixes #3003 * Cleaned a bit after comments --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
parent
b1841d0e2f
commit
a5f28b4395
@ -112,14 +112,14 @@ export const useObjectMetadataItem = (
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const labelIdentifierFieldMetadataId = objectMetadataItem.fields.find(
|
||||
const labelIdentifierFieldMetadata = objectMetadataItem.fields.find(
|
||||
({ name }) => name === 'name',
|
||||
)?.id;
|
||||
);
|
||||
|
||||
const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
|
||||
|
||||
return {
|
||||
labelIdentifierFieldMetadataId,
|
||||
labelIdentifierFieldMetadata,
|
||||
basePathToShowPage,
|
||||
objectMetadataItem,
|
||||
getRecordFromCache,
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const DEFAULT_LABEL_IDENTIFIER_FIELD_NAME = 'name';
|
||||
|
||||
export const isLabelIdentifierField = ({
|
||||
fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
fieldMetadataItem: FieldMetadataItem;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
return (
|
||||
fieldMetadataItem.id ===
|
||||
objectMetadataItem.labelIdentifierFieldMetadataId ||
|
||||
fieldMetadataItem.name === DEFAULT_LABEL_IDENTIFIER_FIELD_NAME
|
||||
);
|
||||
};
|
@ -5,6 +5,7 @@ import { CompanyTeam } from '@/companies/components/CompanyTeam';
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { parseFieldType } from '@/object-metadata/utils/parseFieldType';
|
||||
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
|
||||
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
@ -25,7 +26,11 @@ import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSu
|
||||
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import { FileFolder, useUploadImageMutation } from '~/generated/graphql';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
FileFolder,
|
||||
useUploadImageMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
import { useFindOneRecord } from '../hooks/useFindOneRecord';
|
||||
@ -41,9 +46,10 @@ export const RecordShowPage = () => {
|
||||
throw new Error(`Object name is not defined`);
|
||||
}
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
const { objectMetadataItem, labelIdentifierFieldMetadata } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { identifiersMapper } = useRelationPicker();
|
||||
|
||||
@ -171,6 +177,16 @@ export const RecordShowPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const fieldMetadataItemsToShow = [...objectMetadataItem.fields]
|
||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
||||
)
|
||||
.filter(isFieldMetadataItemAvailable)
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id !== labelIdentifierFieldMetadata?.id,
|
||||
);
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageTitle title={pageName} />
|
||||
@ -204,9 +220,37 @@ export const RecordShowPage = () => {
|
||||
<ShowPageSummaryCard
|
||||
id={record.id}
|
||||
logoOrAvatar={recordIdentifiers?.avatarUrl}
|
||||
title={recordIdentifiers?.name ?? 'No name'}
|
||||
avatarPlaceholder={recordIdentifiers?.name ?? ''}
|
||||
date={record.createdAt ?? ''}
|
||||
renderTitleEditComponent={() => <></>}
|
||||
title={
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: record.id,
|
||||
recoilScopeId:
|
||||
record.id + labelIdentifierFieldMetadata?.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
type: parseFieldType(
|
||||
labelIdentifierFieldMetadata?.type ||
|
||||
FieldMetadataType.Text,
|
||||
),
|
||||
iconName: '',
|
||||
fieldMetadataId:
|
||||
labelIdentifierFieldMetadata?.id ?? '',
|
||||
label: labelIdentifierFieldMetadata?.label || '',
|
||||
metadata: {
|
||||
fieldName:
|
||||
labelIdentifierFieldMetadata?.name || '',
|
||||
},
|
||||
},
|
||||
useUpdateEntityMutation:
|
||||
useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
}
|
||||
avatarType={recordIdentifiers?.avatarType ?? 'rounded'}
|
||||
onUploadPicture={
|
||||
objectNameSingular === 'person'
|
||||
@ -215,35 +259,29 @@ export const RecordShowPage = () => {
|
||||
}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
{objectMetadataItem &&
|
||||
[...objectMetadataItem.fields]
|
||||
.sort((a, b) =>
|
||||
a.name === 'name' ? -1 : a.name.localeCompare(b.name),
|
||||
)
|
||||
.filter(isFieldMetadataItemAvailable)
|
||||
.map((metadataField, index) => {
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
key={record.id + metadataField.id}
|
||||
value={{
|
||||
entityId: record.id,
|
||||
recoilScopeId: record.id + metadataField.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition:
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: metadataField,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
}),
|
||||
useUpdateEntityMutation:
|
||||
useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
})}
|
||||
{fieldMetadataItemsToShow.map(
|
||||
(fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={record.id + fieldMetadataItem.id}
|
||||
value={{
|
||||
entityId: record.id,
|
||||
recoilScopeId: record.id + fieldMetadataItem.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition:
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
}),
|
||||
useUpdateEntityMutation:
|
||||
useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
)}
|
||||
</PropertyBox>
|
||||
{objectNameSingular === 'company' ? (
|
||||
<>
|
||||
|
@ -31,7 +31,7 @@ export const RecordTableEffect = ({
|
||||
const {
|
||||
objectMetadataItem,
|
||||
basePathToShowPage,
|
||||
labelIdentifierFieldMetadataId,
|
||||
labelIdentifierFieldMetadata,
|
||||
} = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
@ -49,16 +49,16 @@ export const RecordTableEffect = ({
|
||||
} = useViewBar({ viewBarId });
|
||||
|
||||
useEffect(() => {
|
||||
if (basePathToShowPage && labelIdentifierFieldMetadataId) {
|
||||
if (basePathToShowPage && labelIdentifierFieldMetadata) {
|
||||
setObjectMetadataConfig?.({
|
||||
basePathToShowPage,
|
||||
labelIdentifierFieldMetadataId,
|
||||
labelIdentifierFieldMetadataId: labelIdentifierFieldMetadata.id,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
basePathToShowPage,
|
||||
objectMetadataItem,
|
||||
labelIdentifierFieldMetadataId,
|
||||
labelIdentifierFieldMetadata,
|
||||
setObjectMetadataConfig,
|
||||
]);
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useFullNameField } from '@/object-record/field/meta-types/hooks/useFullNameField';
|
||||
import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText';
|
||||
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
|
||||
import { FieldInputOverlay } from '@/ui/field/input/components/FieldInputOverlay';
|
||||
|
||||
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
|
||||
import { usePersistField } from '../../../hooks/usePersistField';
|
||||
|
||||
import { FieldInputEvent } from './DateFieldInput';
|
||||
|
@ -71,7 +71,9 @@ export const RecordInlineCell = () => {
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
IconLabel={getIcon(fieldDefinition.iconName)}
|
||||
IconLabel={
|
||||
fieldDefinition.iconName ? getIcon(fieldDefinition.iconName) : undefined
|
||||
}
|
||||
editModeContent={
|
||||
<FieldInput
|
||||
onEnter={handleEnter}
|
||||
|
@ -36,7 +36,6 @@ const StyledLabelAndIconContainer = styled.div`
|
||||
|
||||
const StyledValueContainer = styled.div`
|
||||
display: flex;
|
||||
max-width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.div<
|
||||
@ -70,8 +69,6 @@ const StyledInlineCellBaseContainer = styled.div`
|
||||
|
||||
position: relative;
|
||||
user-select: none;
|
||||
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type RecordInlineCellContainerProps = {
|
||||
@ -129,16 +126,18 @@ export const RecordInlineCellContainer = ({
|
||||
onMouseEnter={handleContainerMouseEnter}
|
||||
onMouseLeave={handleContainerMouseLeave}
|
||||
>
|
||||
<StyledLabelAndIconContainer>
|
||||
{IconLabel && (
|
||||
<StyledIconContainer>
|
||||
<IconLabel stroke={theme.icon.stroke.sm} />
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
{label && (
|
||||
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
|
||||
)}
|
||||
</StyledLabelAndIconContainer>
|
||||
{(!!IconLabel || !!label) && (
|
||||
<StyledLabelAndIconContainer>
|
||||
{IconLabel && (
|
||||
<StyledIconContainer>
|
||||
<IconLabel stroke={theme.icon.stroke.sm} />
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
{label && (
|
||||
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
|
||||
)}
|
||||
</StyledLabelAndIconContainer>
|
||||
)}
|
||||
<StyledValueContainer>
|
||||
{isInlineCellInEditMode ? (
|
||||
<RecordInlineCellEditMode>{editModeContent}</RecordInlineCellEditMode>
|
||||
|
@ -14,7 +14,7 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
|
||||
type SettingsObjectFieldActiveActionDropdownProps = {
|
||||
isCustomField?: boolean;
|
||||
onDisable: () => void;
|
||||
onDisable?: () => void;
|
||||
onEdit: () => void;
|
||||
scopeKey: string;
|
||||
};
|
||||
@ -35,7 +35,7 @@ export const SettingsObjectFieldActiveActionDropdown = ({
|
||||
};
|
||||
|
||||
const handleDisable = () => {
|
||||
onDisable();
|
||||
onDisable?.();
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
@ -53,11 +53,13 @@ export const SettingsObjectFieldActiveActionDropdown = ({
|
||||
LeftIcon={isCustomField ? IconPencil : IconEye}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Disable"
|
||||
LeftIcon={IconArchive}
|
||||
onClick={handleDisable}
|
||||
/>
|
||||
{!!onDisable && (
|
||||
<MenuItem
|
||||
text="Disable"
|
||||
LeftIcon={IconArchive}
|
||||
onClick={handleDisable}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChangeEvent, useRef } from 'react';
|
||||
import { ChangeEvent, ReactNode, useRef } from 'react';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import styled from '@emotion/styled';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
@ -9,16 +9,14 @@ import {
|
||||
beautifyPastDateRelativeToNow,
|
||||
} from '~/utils/date-utils';
|
||||
|
||||
import { OverflowingTextWithTooltip } from '../../../display/tooltip/OverflowingTextWithTooltip';
|
||||
|
||||
type ShowPageSummaryCardProps = {
|
||||
avatarPlaceholder: string;
|
||||
avatarType: AvatarType;
|
||||
date: string;
|
||||
id?: string;
|
||||
logoOrAvatar?: string;
|
||||
title: string;
|
||||
date: string;
|
||||
renderTitleEditComponent?: () => JSX.Element;
|
||||
onUploadPicture?: (file: File) => void;
|
||||
avatarType: AvatarType;
|
||||
title: ReactNode;
|
||||
};
|
||||
|
||||
const StyledShowPageSummaryCard = styled.div`
|
||||
@ -47,7 +45,6 @@ const StyledDate = styled.div`
|
||||
const StyledTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
justify-content: center;
|
||||
@ -74,13 +71,13 @@ const StyledFileInput = styled.input`
|
||||
`;
|
||||
|
||||
export const ShowPageSummaryCard = ({
|
||||
avatarPlaceholder,
|
||||
avatarType,
|
||||
date,
|
||||
id,
|
||||
logoOrAvatar,
|
||||
title,
|
||||
date,
|
||||
avatarType,
|
||||
renderTitleEditComponent,
|
||||
onUploadPicture,
|
||||
title,
|
||||
}: ShowPageSummaryCardProps) => {
|
||||
const beautifiedCreatedAt =
|
||||
date !== '' ? beautifyPastDateRelativeToNow(date) : '';
|
||||
@ -104,7 +101,7 @@ export const ShowPageSummaryCard = ({
|
||||
onClick={onUploadPicture ? handleAvatarClick : undefined}
|
||||
size="xl"
|
||||
colorId={id}
|
||||
placeholder={title}
|
||||
placeholder={avatarPlaceholder}
|
||||
type={avatarType}
|
||||
/>
|
||||
<StyledFileInput
|
||||
@ -113,15 +110,8 @@ export const ShowPageSummaryCard = ({
|
||||
type="file"
|
||||
/>
|
||||
</StyledAvatarWrapper>
|
||||
|
||||
<StyledInfoContainer>
|
||||
<StyledTitle>
|
||||
{renderTitleEditComponent ? (
|
||||
renderTitleEditComponent()
|
||||
) : (
|
||||
<OverflowingTextWithTooltip text={title} />
|
||||
)}
|
||||
</StyledTitle>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<StyledDate id={dateElementId}>Added {beautifiedCreatedAt}</StyledDate>
|
||||
<StyledTooltip
|
||||
anchorSelect={`#${dateElementId}`}
|
||||
|
@ -4,7 +4,9 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
|
||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getFieldSlug } from '@/object-metadata/utils/getFieldSlug';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsAboutSection } from '@/settings/data-model/object-details/components/SettingsObjectAboutSection';
|
||||
import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown';
|
||||
@ -56,11 +58,17 @@ export const SettingsObjectDetail = () => {
|
||||
(metadataField) => !metadataField.isActive && !metadataField.isSystem,
|
||||
);
|
||||
|
||||
const handleDisable = async () => {
|
||||
const handleDisableObject = async () => {
|
||||
await disableObjectMetadataItem(activeObjectMetadataItem);
|
||||
navigate('/settings/objects');
|
||||
};
|
||||
|
||||
const handleDisableField = async (
|
||||
activeFieldMetadatItem: FieldMetadataItem,
|
||||
) => {
|
||||
disableMetadataField(activeFieldMetadatItem);
|
||||
};
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
@ -74,7 +82,7 @@ export const SettingsObjectDetail = () => {
|
||||
iconKey={activeObjectMetadataItem.icon ?? undefined}
|
||||
name={activeObjectMetadataItem.labelPlural || ''}
|
||||
isCustom={activeObjectMetadataItem.isCustom}
|
||||
onDisable={handleDisable}
|
||||
onDisable={handleDisableObject}
|
||||
onEdit={() => navigate('./edit')}
|
||||
/>
|
||||
<Section>
|
||||
@ -102,8 +110,13 @@ export const SettingsObjectDetail = () => {
|
||||
onEdit={() =>
|
||||
navigate(`./${getFieldSlug(activeMetadataField)}`)
|
||||
}
|
||||
onDisable={() =>
|
||||
disableMetadataField(activeMetadataField)
|
||||
onDisable={
|
||||
isLabelIdentifierField({
|
||||
fieldMetadataItem: activeMetadataField,
|
||||
objectMetadataItem: activeObjectMetadataItem,
|
||||
})
|
||||
? undefined
|
||||
: () => handleDisableField(activeMetadataField)
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user