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:
Thaïs
2023-12-20 18:52:02 +01:00
committed by GitHub
parent b1841d0e2f
commit a5f28b4395
10 changed files with 151 additions and 89 deletions

View File

@ -112,14 +112,14 @@ export const useObjectMetadataItem = (
objectMetadataItem, objectMetadataItem,
}); });
const labelIdentifierFieldMetadataId = objectMetadataItem.fields.find( const labelIdentifierFieldMetadata = objectMetadataItem.fields.find(
({ name }) => name === 'name', ({ name }) => name === 'name',
)?.id; );
const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`; const basePathToShowPage = `/object/${objectMetadataItem.nameSingular}/`;
return { return {
labelIdentifierFieldMetadataId, labelIdentifierFieldMetadata,
basePathToShowPage, basePathToShowPage,
objectMetadataItem, objectMetadataItem,
getRecordFromCache, getRecordFromCache,

View File

@ -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
);
};

View File

@ -5,6 +5,7 @@ import { CompanyTeam } from '@/companies/components/CompanyTeam';
import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useFavorites } from '@/favorites/hooks/useFavorites';
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 { parseFieldType } from '@/object-metadata/utils/parseFieldType';
import { FieldContext } from '@/object-record/field/contexts/FieldContext'; import { FieldContext } from '@/object-record/field/contexts/FieldContext';
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState'; import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; 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 { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
import { PageTitle } from '@/ui/utilities/page-title/PageTitle'; import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; 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 { getLogoUrlFromDomainName } from '~/utils';
import { useFindOneRecord } from '../hooks/useFindOneRecord'; import { useFindOneRecord } from '../hooks/useFindOneRecord';
@ -41,9 +46,10 @@ export const RecordShowPage = () => {
throw new Error(`Object name is not defined`); throw new Error(`Object name is not defined`);
} }
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem, labelIdentifierFieldMetadata } =
objectNameSingular, useObjectMetadataItem({
}); objectNameSingular,
});
const { identifiersMapper } = useRelationPicker(); 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 ( return (
<PageContainer> <PageContainer>
<PageTitle title={pageName} /> <PageTitle title={pageName} />
@ -204,9 +220,37 @@ export const RecordShowPage = () => {
<ShowPageSummaryCard <ShowPageSummaryCard
id={record.id} id={record.id}
logoOrAvatar={recordIdentifiers?.avatarUrl} logoOrAvatar={recordIdentifiers?.avatarUrl}
title={recordIdentifiers?.name ?? 'No name'} avatarPlaceholder={recordIdentifiers?.name ?? ''}
date={record.createdAt ?? ''} 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'} avatarType={recordIdentifiers?.avatarType ?? 'rounded'}
onUploadPicture={ onUploadPicture={
objectNameSingular === 'person' objectNameSingular === 'person'
@ -215,35 +259,29 @@ export const RecordShowPage = () => {
} }
/> />
<PropertyBox extraPadding={true}> <PropertyBox extraPadding={true}>
{objectMetadataItem && {fieldMetadataItemsToShow.map(
[...objectMetadataItem.fields] (fieldMetadataItem, index) => (
.sort((a, b) => <FieldContext.Provider
a.name === 'name' ? -1 : a.name.localeCompare(b.name), key={record.id + fieldMetadataItem.id}
) value={{
.filter(isFieldMetadataItemAvailable) entityId: record.id,
.map((metadataField, index) => { recoilScopeId: record.id + fieldMetadataItem.id,
return ( isLabelIdentifier: false,
<FieldContext.Provider fieldDefinition:
key={record.id + metadataField.id} formatFieldMetadataItemAsColumnDefinition({
value={{ field: fieldMetadataItem,
entityId: record.id, position: index,
recoilScopeId: record.id + metadataField.id, objectMetadataItem,
isLabelIdentifier: false, }),
fieldDefinition: useUpdateEntityMutation:
formatFieldMetadataItemAsColumnDefinition({ useUpdateOneObjectRecordMutation,
field: metadataField, hotkeyScope: InlineCellHotkeyScope.InlineCell,
position: index, }}
objectMetadataItem, >
}), <RecordInlineCell />
useUpdateEntityMutation: </FieldContext.Provider>
useUpdateOneObjectRecordMutation, ),
hotkeyScope: InlineCellHotkeyScope.InlineCell, )}
}}
>
<RecordInlineCell />
</FieldContext.Provider>
);
})}
</PropertyBox> </PropertyBox>
{objectNameSingular === 'company' ? ( {objectNameSingular === 'company' ? (
<> <>

View File

@ -31,7 +31,7 @@ export const RecordTableEffect = ({
const { const {
objectMetadataItem, objectMetadataItem,
basePathToShowPage, basePathToShowPage,
labelIdentifierFieldMetadataId, labelIdentifierFieldMetadata,
} = useObjectMetadataItem({ } = useObjectMetadataItem({
objectNameSingular, objectNameSingular,
}); });
@ -49,16 +49,16 @@ export const RecordTableEffect = ({
} = useViewBar({ viewBarId }); } = useViewBar({ viewBarId });
useEffect(() => { useEffect(() => {
if (basePathToShowPage && labelIdentifierFieldMetadataId) { if (basePathToShowPage && labelIdentifierFieldMetadata) {
setObjectMetadataConfig?.({ setObjectMetadataConfig?.({
basePathToShowPage, basePathToShowPage,
labelIdentifierFieldMetadataId, labelIdentifierFieldMetadataId: labelIdentifierFieldMetadata.id,
}); });
} }
}, [ }, [
basePathToShowPage, basePathToShowPage,
objectMetadataItem, objectMetadataItem,
labelIdentifierFieldMetadataId, labelIdentifierFieldMetadata,
setObjectMetadataConfig, setObjectMetadataConfig,
]); ]);

View File

@ -1,8 +1,8 @@
import { useFullNameField } from '@/object-record/field/meta-types/hooks/useFullNameField'; import { useFullNameField } from '@/object-record/field/meta-types/hooks/useFullNameField';
import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText'; import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText';
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput'; 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 { usePersistField } from '../../../hooks/usePersistField';
import { FieldInputEvent } from './DateFieldInput'; import { FieldInputEvent } from './DateFieldInput';

View File

@ -71,7 +71,9 @@ export const RecordInlineCell = () => {
} }
: undefined : undefined
} }
IconLabel={getIcon(fieldDefinition.iconName)} IconLabel={
fieldDefinition.iconName ? getIcon(fieldDefinition.iconName) : undefined
}
editModeContent={ editModeContent={
<FieldInput <FieldInput
onEnter={handleEnter} onEnter={handleEnter}

View File

@ -36,7 +36,6 @@ const StyledLabelAndIconContainer = styled.div`
const StyledValueContainer = styled.div` const StyledValueContainer = styled.div`
display: flex; display: flex;
max-width: calc(100% - ${({ theme }) => theme.spacing(4)});
`; `;
const StyledLabel = styled.div< const StyledLabel = styled.div<
@ -70,8 +69,6 @@ const StyledInlineCellBaseContainer = styled.div`
position: relative; position: relative;
user-select: none; user-select: none;
width: 100%;
`; `;
type RecordInlineCellContainerProps = { type RecordInlineCellContainerProps = {
@ -129,16 +126,18 @@ export const RecordInlineCellContainer = ({
onMouseEnter={handleContainerMouseEnter} onMouseEnter={handleContainerMouseEnter}
onMouseLeave={handleContainerMouseLeave} onMouseLeave={handleContainerMouseLeave}
> >
<StyledLabelAndIconContainer> {(!!IconLabel || !!label) && (
{IconLabel && ( <StyledLabelAndIconContainer>
<StyledIconContainer> {IconLabel && (
<IconLabel stroke={theme.icon.stroke.sm} /> <StyledIconContainer>
</StyledIconContainer> <IconLabel stroke={theme.icon.stroke.sm} />
)} </StyledIconContainer>
{label && ( )}
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel> {label && (
)} <StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
</StyledLabelAndIconContainer> )}
</StyledLabelAndIconContainer>
)}
<StyledValueContainer> <StyledValueContainer>
{isInlineCellInEditMode ? ( {isInlineCellInEditMode ? (
<RecordInlineCellEditMode>{editModeContent}</RecordInlineCellEditMode> <RecordInlineCellEditMode>{editModeContent}</RecordInlineCellEditMode>

View File

@ -14,7 +14,7 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
type SettingsObjectFieldActiveActionDropdownProps = { type SettingsObjectFieldActiveActionDropdownProps = {
isCustomField?: boolean; isCustomField?: boolean;
onDisable: () => void; onDisable?: () => void;
onEdit: () => void; onEdit: () => void;
scopeKey: string; scopeKey: string;
}; };
@ -35,7 +35,7 @@ export const SettingsObjectFieldActiveActionDropdown = ({
}; };
const handleDisable = () => { const handleDisable = () => {
onDisable(); onDisable?.();
closeDropdown(); closeDropdown();
}; };
@ -53,11 +53,13 @@ export const SettingsObjectFieldActiveActionDropdown = ({
LeftIcon={isCustomField ? IconPencil : IconEye} LeftIcon={isCustomField ? IconPencil : IconEye}
onClick={handleEdit} onClick={handleEdit}
/> />
<MenuItem {!!onDisable && (
text="Disable" <MenuItem
LeftIcon={IconArchive} text="Disable"
onClick={handleDisable} LeftIcon={IconArchive}
/> onClick={handleDisable}
/>
)}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>
</DropdownMenu> </DropdownMenu>
} }

View File

@ -1,4 +1,4 @@
import { ChangeEvent, useRef } from 'react'; import { ChangeEvent, ReactNode, useRef } from 'react';
import { Tooltip } from 'react-tooltip'; import { Tooltip } from 'react-tooltip';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { v4 as uuidV4 } from 'uuid'; import { v4 as uuidV4 } from 'uuid';
@ -9,16 +9,14 @@ import {
beautifyPastDateRelativeToNow, beautifyPastDateRelativeToNow,
} from '~/utils/date-utils'; } from '~/utils/date-utils';
import { OverflowingTextWithTooltip } from '../../../display/tooltip/OverflowingTextWithTooltip';
type ShowPageSummaryCardProps = { type ShowPageSummaryCardProps = {
avatarPlaceholder: string;
avatarType: AvatarType;
date: string;
id?: string; id?: string;
logoOrAvatar?: string; logoOrAvatar?: string;
title: string;
date: string;
renderTitleEditComponent?: () => JSX.Element;
onUploadPicture?: (file: File) => void; onUploadPicture?: (file: File) => void;
avatarType: AvatarType; title: ReactNode;
}; };
const StyledShowPageSummaryCard = styled.div` const StyledShowPageSummaryCard = styled.div`
@ -47,7 +45,6 @@ const StyledDate = styled.div`
const StyledTitle = styled.div` const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary}; color: ${({ theme }) => theme.font.color.primary};
display: flex; display: flex;
flex-direction: row;
font-size: ${({ theme }) => theme.font.size.xl}; font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold}; font-weight: ${({ theme }) => theme.font.weight.semiBold};
justify-content: center; justify-content: center;
@ -74,13 +71,13 @@ const StyledFileInput = styled.input`
`; `;
export const ShowPageSummaryCard = ({ export const ShowPageSummaryCard = ({
avatarPlaceholder,
avatarType,
date,
id, id,
logoOrAvatar, logoOrAvatar,
title,
date,
avatarType,
renderTitleEditComponent,
onUploadPicture, onUploadPicture,
title,
}: ShowPageSummaryCardProps) => { }: ShowPageSummaryCardProps) => {
const beautifiedCreatedAt = const beautifiedCreatedAt =
date !== '' ? beautifyPastDateRelativeToNow(date) : ''; date !== '' ? beautifyPastDateRelativeToNow(date) : '';
@ -104,7 +101,7 @@ export const ShowPageSummaryCard = ({
onClick={onUploadPicture ? handleAvatarClick : undefined} onClick={onUploadPicture ? handleAvatarClick : undefined}
size="xl" size="xl"
colorId={id} colorId={id}
placeholder={title} placeholder={avatarPlaceholder}
type={avatarType} type={avatarType}
/> />
<StyledFileInput <StyledFileInput
@ -113,15 +110,8 @@ export const ShowPageSummaryCard = ({
type="file" type="file"
/> />
</StyledAvatarWrapper> </StyledAvatarWrapper>
<StyledInfoContainer> <StyledInfoContainer>
<StyledTitle> <StyledTitle>{title}</StyledTitle>
{renderTitleEditComponent ? (
renderTitleEditComponent()
) : (
<OverflowingTextWithTooltip text={title} />
)}
</StyledTitle>
<StyledDate id={dateElementId}>Added {beautifiedCreatedAt}</StyledDate> <StyledDate id={dateElementId}>Added {beautifiedCreatedAt}</StyledDate>
<StyledTooltip <StyledTooltip
anchorSelect={`#${dateElementId}`} anchorSelect={`#${dateElementId}`}

View File

@ -4,7 +4,9 @@ import styled from '@emotion/styled';
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getFieldSlug } from '@/object-metadata/utils/getFieldSlug'; import { getFieldSlug } from '@/object-metadata/utils/getFieldSlug';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsAboutSection } from '@/settings/data-model/object-details/components/SettingsObjectAboutSection'; import { SettingsAboutSection } from '@/settings/data-model/object-details/components/SettingsObjectAboutSection';
import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown'; import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldActiveActionDropdown';
@ -56,11 +58,17 @@ export const SettingsObjectDetail = () => {
(metadataField) => !metadataField.isActive && !metadataField.isSystem, (metadataField) => !metadataField.isActive && !metadataField.isSystem,
); );
const handleDisable = async () => { const handleDisableObject = async () => {
await disableObjectMetadataItem(activeObjectMetadataItem); await disableObjectMetadataItem(activeObjectMetadataItem);
navigate('/settings/objects'); navigate('/settings/objects');
}; };
const handleDisableField = async (
activeFieldMetadatItem: FieldMetadataItem,
) => {
disableMetadataField(activeFieldMetadatItem);
};
return ( return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings"> <SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer> <SettingsPageContainer>
@ -74,7 +82,7 @@ export const SettingsObjectDetail = () => {
iconKey={activeObjectMetadataItem.icon ?? undefined} iconKey={activeObjectMetadataItem.icon ?? undefined}
name={activeObjectMetadataItem.labelPlural || ''} name={activeObjectMetadataItem.labelPlural || ''}
isCustom={activeObjectMetadataItem.isCustom} isCustom={activeObjectMetadataItem.isCustom}
onDisable={handleDisable} onDisable={handleDisableObject}
onEdit={() => navigate('./edit')} onEdit={() => navigate('./edit')}
/> />
<Section> <Section>
@ -102,8 +110,13 @@ export const SettingsObjectDetail = () => {
onEdit={() => onEdit={() =>
navigate(`./${getFieldSlug(activeMetadataField)}`) navigate(`./${getFieldSlug(activeMetadataField)}`)
} }
onDisable={() => onDisable={
disableMetadataField(activeMetadataField) isLabelIdentifierField({
fieldMetadataItem: activeMetadataField,
objectMetadataItem: activeObjectMetadataItem,
})
? undefined
: () => handleDisableField(activeMetadataField)
} }
/> />
} }