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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 151 additions and 89 deletions

View File

@ -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,

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 { 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' ? (
<>

View File

@ -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,
]);

View File

@ -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';

View File

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

View File

@ -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>

View File

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

View File

@ -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}`}

View File

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