Introduce new board feature flag (#3602)

This commit is contained in:
Charles Bochet
2024-01-24 14:24:02 +01:00
committed by GitHub
parent b991790f62
commit afc36c7329
11 changed files with 389 additions and 341 deletions

View File

@ -1,11 +1,10 @@
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { RecordShowPage } from '@/object-record/components/RecordShowPage';
import { RecordTablePage } from '@/object-record/components/RecordTablePage';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { DefaultLayout } from '@/ui/layout/page/DefaultLayout';
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect';
import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect';
import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
@ -16,6 +15,8 @@ import { SignInUp } from '~/pages/auth/SignInUp';
import { VerifyEffect } from '~/pages/auth/VerifyEffect';
import { ImpersonateEffect } from '~/pages/impersonate/ImpersonateEffect';
import { NotFound } from '~/pages/not-found/NotFound';
import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
import { Opportunities } from '~/pages/opportunities/Opportunities';
import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts';
import { SettingsAccountsEmails } from '~/pages/settings/accounts/SettingsAccountsEmails';
@ -43,6 +44,9 @@ export const App = () => {
const { defaultHomePagePath } = useDefaultHomePagePath();
const pageTitle = getPageTitleFromPath(pathname);
const isNewRecordBoardEnabled = useIsFeatureEnabled(
'IS_NEW_RECORD_BOARD_ENABLED',
);
return (
<>
@ -62,8 +66,13 @@ export const App = () => {
<Route path={AppPath.TasksPage} element={<Tasks />} />
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
<Route path={AppPath.OpportunitiesPage} element={<Opportunities />} />
<Route path={AppPath.RecordTablePage} element={<RecordTablePage />} />
{!isNewRecordBoardEnabled && (
<Route
path={AppPath.OpportunitiesPage}
element={<Opportunities />}
/>
)}
<Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} />
<Route path={AppPath.RecordShowPage} element={<RecordShowPage />} />
<Route

View File

@ -134,7 +134,7 @@ export const PageChangeEffect = () => {
useEffect(() => {
switch (true) {
case isMatchingLocation(AppPath.RecordTablePage): {
case isMatchingLocation(AppPath.RecordIndexPage): {
setHotkeyScope(TableHotkeyScope.Table, {
goto: true,
keyboardShortcutMenu: true,

View File

@ -1,327 +0,0 @@
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
import { parseFieldType } from '@/object-metadata/utils/parseFieldType';
import {
FieldContext,
RecordUpdateHook,
RecordUpdateHookParams,
} from '@/object-record/field/contexts/FieldContext';
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
import { RecordRelationFieldCardSection } from '@/object-record/record-relation-card/components/RecordRelationFieldCardSection';
import { isFieldMetadataItemAvailable } from '@/object-record/utils/isFieldMetadataItemAvailable';
import { IconBuildingSkyscraper } from '@/ui/display/icon';
import { PageBody } from '@/ui/layout/page/PageBody';
import { PageContainer } from '@/ui/layout/page/PageContainer';
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
import { PageHeader } from '@/ui/layout/page/PageHeader';
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton';
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
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 {
FieldMetadataType,
FileFolder,
useUploadImageMutation,
} from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
import { useFindOneRecord } from '../hooks/useFindOneRecord';
import { useUpdateOneRecord } from '../hooks/useUpdateOneRecord';
export const RecordShowPage = () => {
const { objectNameSingular, objectRecordId } = useParams<{
objectNameSingular: string;
objectRecordId: string;
}>();
if (!objectNameSingular) {
throw new Error(`Object name is not defined`);
}
const {
objectMetadataItem,
labelIdentifierFieldMetadata,
mapToObjectRecordIdentifier,
} = useObjectMetadataItem({
objectNameSingular,
});
const { favorites, createFavorite, deleteFavorite } = useFavorites();
const setEntityFields = useSetRecoilState(
entityFieldsFamilyState(objectRecordId ?? ''),
);
const { record, loading } = useFindOneRecord({
objectRecordId,
objectNameSingular,
});
useEffect(() => {
if (!record) return;
setEntityFields(record);
}, [record, setEntityFields]);
const [uploadImage] = useUploadImageMutation();
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
updateOneRecord?.({
idToUpdate: variables.where.id as string,
updateOneRecordInput: variables.updateOneRecordInput,
});
};
return [updateEntity, { loading: false }];
};
const correspondingFavorite = favorites.find(
(favorite) => favorite.recordId === objectRecordId,
);
const isFavorite = isDefined(correspondingFavorite);
const handleFavoriteButtonClick = async () => {
if (!objectNameSingular || !record) return;
if (isFavorite && record) {
deleteFavorite(correspondingFavorite.id);
} else {
createFavorite(record, objectNameSingular);
}
};
const pageName =
objectNameSingular === 'person'
? record?.name.firstName + ' ' + record?.name.lastName
: record?.name;
const onUploadPicture = async (file: File) => {
if (objectNameSingular !== 'person') {
return;
}
const result = await uploadImage({
variables: {
file,
fileFolder: FileFolder.PersonPicture,
},
});
const avatarUrl = result?.data?.uploadImage;
if (!avatarUrl) {
return;
}
if (!updateOneRecord) {
return;
}
if (!record) {
return;
}
await updateOneRecord({
idToUpdate: record.id,
updateOneRecordInput: {
avatarUrl,
},
});
};
const availableFieldMetadataItems = objectMetadataItem.fields
.filter(
(fieldMetadataItem) =>
isFieldMetadataItemAvailable(fieldMetadataItem) &&
fieldMetadataItem.id !== labelIdentifierFieldMetadata?.id,
)
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
);
const inlineFieldMetadataItems = availableFieldMetadataItems.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type !== FieldMetadataType.Relation,
);
const relationFieldMetadataItems = availableFieldMetadataItems.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type === FieldMetadataType.Relation,
);
return (
<PageContainer>
<PageTitle title={pageName} />
<PageHeader
title={pageName ?? ''}
hasBackButton
Icon={IconBuildingSkyscraper}
>
{record && (
<>
<PageFavoriteButton
isFavorite={isFavorite}
onClick={handleFavoriteButtonClick}
/>
<ShowPageAddButton
key="add"
entity={{
id: record.id,
targetObjectNameSingular: objectMetadataItem?.nameSingular,
}}
/>
<ShowPageMoreButton
key="more"
recordId={record.id}
objectNameSingular={objectNameSingular}
/>
</>
)}
</PageHeader>
<PageBody>
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
<ShowPageContainer>
<ShowPageLeftContainer>
{!loading && !!record && (
<>
<ShowPageSummaryCard
id={record.id}
logoOrAvatar={
mapToObjectRecordIdentifier(record).avatarUrl ?? ''
}
avatarPlaceholder={
mapToObjectRecordIdentifier(record).name ?? ''
}
date={record.createdAt ?? ''}
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 || '',
},
},
useUpdateRecord: useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell />
</FieldContext.Provider>
}
avatarType={
mapToObjectRecordIdentifier(record).avatarType ??
'rounded'
}
onUploadPicture={
objectNameSingular === 'person'
? onUploadPicture
: undefined
}
/>
<PropertyBox extraPadding={true}>
{inlineFieldMetadataItems.map(
(fieldMetadataItem, index) => (
<FieldContext.Provider
key={record.id + fieldMetadataItem.id}
value={{
entityId: record.id,
maxWidth: 200,
recoilScopeId: record.id + fieldMetadataItem.id,
isLabelIdentifier: false,
fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({
field: fieldMetadataItem,
position: index,
objectMetadataItem,
showLabel: true,
labelWidth: 90,
}),
useUpdateRecord: useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell />
</FieldContext.Provider>
),
)}
</PropertyBox>
{relationFieldMetadataItems
.filter((item) => {
const relationObjectMetadataItem = item.toRelationMetadata
? item.toRelationMetadata.fromObjectMetadata
: item.fromRelationMetadata?.toObjectMetadata;
if (!relationObjectMetadataItem) {
return false;
}
return isObjectMetadataAvailableForRelation(
relationObjectMetadataItem,
);
})
.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,
}),
useUpdateRecord: useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordRelationFieldCardSection />
</FieldContext.Provider>
))}
</>
)}
</ShowPageLeftContainer>
<ShowPageRightContainer
targetableObject={{
id: record?.id ?? '',
targetObjectNameSingular: objectMetadataItem?.nameSingular,
}}
timeline
tasks
notes
emails
/>
</ShowPageContainer>
</RecoilScope>
</PageBody>
</PageContainer>
);
};

View File

@ -6,6 +6,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { RecordUpdateHookParams } from '@/object-record/field/contexts/FieldContext';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordTableEffect } from '@/object-record/record-index/components/RecordTableEffect';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { TableOptionsDropdownId } from '@/object-record/record-table/constants/TableOptionsDropdownId';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
@ -17,8 +18,6 @@ import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToC
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { RecordTableEffect } from './RecordTableEffect';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
@ -27,7 +26,7 @@ const StyledContainer = styled.div`
padding-left: ${({ theme }) => theme.table.horizontalCellPadding};
`;
export const RecordTableContainer = ({
export const RecordIndexContainer = ({
recordTableId,
objectNamePlural,
createRecord,

View File

@ -0,0 +1,253 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
import { parseFieldType } from '@/object-metadata/utils/parseFieldType';
import {
FieldContext,
RecordUpdateHook,
RecordUpdateHookParams,
} from '@/object-record/field/contexts/FieldContext';
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
import { RecordRelationFieldCardSection } from '@/object-record/record-relation-card/components/RecordRelationFieldCardSection';
import { isFieldMetadataItemAvailable } from '@/object-record/utils/isFieldMetadataItemAvailable';
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import {
FieldMetadataType,
FileFolder,
useUploadImageMutation,
} from '~/generated/graphql';
type RecordShowContainerProps = {
objectNameSingular: string;
objectRecordId: string;
};
export const RecordShowContainer = ({
objectNameSingular,
objectRecordId,
}: RecordShowContainerProps) => {
const {
objectMetadataItem,
labelIdentifierFieldMetadata,
mapToObjectRecordIdentifier,
} = useObjectMetadataItem({
objectNameSingular,
});
const setEntityFields = useSetRecoilState(
entityFieldsFamilyState(objectRecordId ?? ''),
);
const { record, loading } = useFindOneRecord({
objectRecordId,
objectNameSingular,
});
useEffect(() => {
if (!record) return;
setEntityFields(record);
}, [record, setEntityFields]);
const [uploadImage] = useUploadImageMutation();
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
updateOneRecord?.({
idToUpdate: variables.where.id as string,
updateOneRecordInput: variables.updateOneRecordInput,
});
};
return [updateEntity, { loading: false }];
};
const onUploadPicture = async (file: File) => {
if (objectNameSingular !== 'person') {
return;
}
const result = await uploadImage({
variables: {
file,
fileFolder: FileFolder.PersonPicture,
},
});
const avatarUrl = result?.data?.uploadImage;
if (!avatarUrl) {
return;
}
if (!updateOneRecord) {
return;
}
if (!record) {
return;
}
await updateOneRecord({
idToUpdate: record.id,
updateOneRecordInput: {
avatarUrl,
},
});
};
const availableFieldMetadataItems = objectMetadataItem.fields
.filter(
(fieldMetadataItem) =>
isFieldMetadataItemAvailable(fieldMetadataItem) &&
fieldMetadataItem.id !== labelIdentifierFieldMetadata?.id,
)
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
);
const inlineFieldMetadataItems = availableFieldMetadataItems.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type !== FieldMetadataType.Relation,
);
const relationFieldMetadataItems = availableFieldMetadataItems.filter(
(fieldMetadataItem) =>
fieldMetadataItem.type === FieldMetadataType.Relation,
);
return (
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
<ShowPageContainer>
<ShowPageLeftContainer>
{!loading && !!record && (
<>
<ShowPageSummaryCard
id={record.id}
logoOrAvatar={
mapToObjectRecordIdentifier(record).avatarUrl ?? ''
}
avatarPlaceholder={
mapToObjectRecordIdentifier(record).name ?? ''
}
date={record.createdAt ?? ''}
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 || '',
},
},
useUpdateRecord: useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell />
</FieldContext.Provider>
}
avatarType={
mapToObjectRecordIdentifier(record).avatarType ?? 'rounded'
}
onUploadPicture={
objectNameSingular === 'person' ? onUploadPicture : undefined
}
/>
<PropertyBox extraPadding={true}>
{inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
<FieldContext.Provider
key={record.id + fieldMetadataItem.id}
value={{
entityId: record.id,
maxWidth: 200,
recoilScopeId: record.id + fieldMetadataItem.id,
isLabelIdentifier: false,
fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({
field: fieldMetadataItem,
position: index,
objectMetadataItem,
showLabel: true,
labelWidth: 90,
}),
useUpdateRecord: useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell />
</FieldContext.Provider>
))}
</PropertyBox>
{relationFieldMetadataItems
.filter((item) => {
const relationObjectMetadataItem = item.toRelationMetadata
? item.toRelationMetadata.fromObjectMetadata
: item.fromRelationMetadata?.toObjectMetadata;
if (!relationObjectMetadataItem) {
return false;
}
return isObjectMetadataAvailableForRelation(
relationObjectMetadataItem,
);
})
.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,
}),
useUpdateRecord: useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordRelationFieldCardSection />
</FieldContext.Provider>
))}
</>
)}
</ShowPageLeftContainer>
<ShowPageRightContainer
targetableObject={{
id: record?.id ?? '',
targetObjectNameSingular: objectMetadataItem?.nameSingular,
}}
timeline
tasks
notes
emails
/>
</ShowPageContainer>
</RecoilScope>
);
};

View File

@ -14,8 +14,8 @@ export enum AppPath {
Index = '/',
TasksPage = '/tasks',
OpportunitiesPage = '/objects/opportunities',
RecordTablePage = '/objects/:objectNamePlural',
RecordIndexPage = '/objects/:objectNamePlural',
RecordShowPage = '/object/:objectNameSingular/:objectRecordId',
SettingsCatchAll = `/settings/*`,

View File

@ -1,4 +1,5 @@
export type FeatureFlagKey =
| 'IS_MESSAGING_ENABLED'
| 'IS_QUICK_ACTIONS_ENABLED'
| 'IS_RATING_FIELD_TYPE_ENABLED';
| 'IS_RATING_FIELD_TYPE_ENABLED'
| 'IS_NEW_RECORD_BOARD_ENABLED';

View File

@ -8,6 +8,7 @@ import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar';
import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu';
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
@ -20,15 +21,13 @@ import { PageHeader } from '@/ui/layout/page/PageHeader';
import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { RecordTableContainer } from './RecordTableContainer';
const StyledTableContainer = styled.div`
display: flex;
height: 100%;
width: 100%;
`;
export const RecordTablePage = () => {
export const RecordIndexPage = () => {
const objectNamePlural = useParams().objectNamePlural ?? '';
const { objectNameSingular } = useObjectNameSingularFromPlural({
@ -87,7 +86,7 @@ export const RecordTablePage = () => {
</PageHeader>
<PageBody>
<StyledTableContainer>
<RecordTableContainer
<RecordIndexContainer
recordTableId={recordTableId}
objectNamePlural={objectNamePlural}
createRecord={handleAddButtonClick}

View File

@ -0,0 +1,113 @@
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
import { IconBuildingSkyscraper } from '@/ui/display/icon';
import { PageBody } from '@/ui/layout/page/PageBody';
import { PageContainer } from '@/ui/layout/page/PageContainer';
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
import { PageHeader } from '@/ui/layout/page/PageHeader';
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
import { ShowPageMoreButton } from '@/ui/layout/show-page/components/ShowPageMoreButton';
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
import { isDefined } from '~/utils/isDefined';
import { useFindOneRecord } from '../../modules/object-record/hooks/useFindOneRecord';
export const RecordShowPage = () => {
const { objectNameSingular, objectRecordId } = useParams<{
objectNameSingular: string;
objectRecordId: string;
}>();
if (!objectNameSingular) {
throw new Error(`Object name is not defined`);
}
if (!objectRecordId) {
throw new Error(`Record id is not defined`);
}
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { favorites, createFavorite, deleteFavorite } = useFavorites();
const setEntityFields = useSetRecoilState(
entityFieldsFamilyState(objectRecordId ?? ''),
);
const { record } = useFindOneRecord({
objectRecordId,
objectNameSingular,
});
useEffect(() => {
if (!record) return;
setEntityFields(record);
}, [record, setEntityFields]);
const correspondingFavorite = favorites.find(
(favorite) => favorite.recordId === objectRecordId,
);
const isFavorite = isDefined(correspondingFavorite);
const handleFavoriteButtonClick = async () => {
if (!objectNameSingular || !record) return;
if (isFavorite && record) {
deleteFavorite(correspondingFavorite.id);
} else {
createFavorite(record, objectNameSingular);
}
};
const pageName =
objectNameSingular === 'person'
? record?.name.firstName + ' ' + record?.name.lastName
: record?.name;
return (
<PageContainer>
<PageTitle title={pageName} />
<PageHeader
title={pageName ?? ''}
hasBackButton
Icon={IconBuildingSkyscraper}
>
{record && (
<>
<PageFavoriteButton
isFavorite={isFavorite}
onClick={handleFavoriteButtonClick}
/>
<ShowPageAddButton
key="add"
entity={{
id: record.id,
targetObjectNameSingular: objectMetadataItem?.nameSingular,
}}
/>
<ShowPageMoreButton
key="more"
recordId={record.id}
objectNameSingular={objectNameSingular}
/>
</>
)}
</PageHeader>
<PageBody>
<RecordShowContainer
objectNameSingular={objectNameSingular}
objectRecordId={objectRecordId}
/>
</PageBody>
</PageContainer>
);
};

View File

@ -17,6 +17,7 @@ export enum FeatureFlagKeys {
IsMessagingEnabled = 'IS_MESSAGING_ENABLED',
IsRatingFieldTypeEnabled = 'IS_RATING_FIELD_TYPE_ENABLED',
IsWorkspaceCleanable = 'IS_WORKSPACE_CLEANABLE',
IsNewRecordBoardEnabled = 'IS_NEW_RECORD_BOARD_ENABLED',
}
@Entity({ name: 'featureFlag', schema: 'core' })