Refactor inline-cell-input hover on field-list (#13906)
This is first step toward separating the path between display mode and input mode for inline-cells like we have done for tableCell. The idea is to have 1 floating or anchored field input shared between all cells. This should be done for field-list, board and task/note row + for hover and edit mode. This PR is only about field-list and hover
This commit is contained in:
@@ -24,13 +24,13 @@ import { getActivityAttachmentIdsToDelete } from '@/activities/utils/getActivity
|
||||
import { getActivityAttachmentPathsToRestore } from '@/activities/utils/getActivityAttachmentPathsToRestore';
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
||||
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
|
||||
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { useIsRecordFieldReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordFieldReadOnly';
|
||||
import { isInlineCellInEditModeFamilyState } from '@/object-record/record-inline-cell/states/isInlineCellInEditModeFamilyState';
|
||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||
import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
||||
@@ -363,10 +363,10 @@ export const ActivityRichTextEditor = ({
|
||||
dependencies: [handleAllKeys],
|
||||
});
|
||||
|
||||
const { labelIdentifierFieldMetadataItem } = useRecordShowContainerData({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
objectRecordId: activityId,
|
||||
});
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
});
|
||||
|
||||
const recordTitleCellId = getRecordFieldInputInstanceId({
|
||||
recordId: activityId,
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay';
|
||||
import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useGetButtonIcon } from '@/object-record/record-field/ui/hooks/useGetButtonIcon';
|
||||
import { useIsFieldInputOnly } from '@/object-record/record-field/ui/hooks/useIsFieldInputOnly';
|
||||
import {
|
||||
RecordInlineCellContext,
|
||||
type RecordInlineCellContextProps,
|
||||
} from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
||||
import { useContext, type ReactNode } from 'react';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
|
||||
type RecordFieldListCellAnchorPortalContextProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const RecordFieldListCellAnchorPortalContext = ({
|
||||
children,
|
||||
}: RecordFieldListCellAnchorPortalContextProps) => {
|
||||
const {
|
||||
isRecordFieldReadOnly,
|
||||
fieldDefinition,
|
||||
isDisplayModeFixHeight,
|
||||
onOpenEditMode,
|
||||
onCloseEditMode,
|
||||
isCentered,
|
||||
} = useContext(FieldContext);
|
||||
const buttonIcon = useGetButtonIcon();
|
||||
const { getIcon } = useIcons();
|
||||
const isFieldInputOnly = useIsFieldInputOnly();
|
||||
|
||||
const RecordInlineCellContextValue: RecordInlineCellContextProps = {
|
||||
readonly: isRecordFieldReadOnly,
|
||||
buttonIcon: buttonIcon,
|
||||
IconLabel: fieldDefinition.iconName
|
||||
? getIcon(fieldDefinition.iconName)
|
||||
: undefined,
|
||||
label: fieldDefinition.label,
|
||||
labelWidth: fieldDefinition.labelWidth,
|
||||
showLabel: fieldDefinition.showLabel,
|
||||
isCentered,
|
||||
editModeContent: <FieldInput />,
|
||||
displayModeContent: <FieldDisplay />,
|
||||
isDisplayModeFixHeight: isDisplayModeFixHeight,
|
||||
editModeContentOnly: isFieldInputOnly,
|
||||
loading: false,
|
||||
onOpenEditMode,
|
||||
onCloseEditMode,
|
||||
};
|
||||
|
||||
return (
|
||||
<RecordInlineCellContext.Provider value={RecordInlineCellContextValue}>
|
||||
{children}
|
||||
</RecordInlineCellContext.Provider>
|
||||
);
|
||||
};
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { RecordFieldListCellAnchorPortalContext } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchorPortalContext';
|
||||
import {
|
||||
FieldContext,
|
||||
type RecordUpdateHook,
|
||||
type RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useIsRecordFieldReadOnly } from '@/object-record/record-field/ui/hooks/read-only/useIsRecordFieldReadOnly';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { RecordInlineCellHoveredPortal } from '@/object-record/record-inline-cell/components/RecordInlineCellHoveredPortal';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type RecordFieldListCellAnchoredPortalProps = {
|
||||
position: number;
|
||||
fieldMetadataItem: Pick<
|
||||
FieldMetadataItem,
|
||||
'id' | 'name' | 'type' | 'createdAt' | 'updatedAt' | 'label'
|
||||
>;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const RecordFieldListCellAnchoredPortal = ({
|
||||
position,
|
||||
fieldMetadataItem,
|
||||
children,
|
||||
}: RecordFieldListCellAnchoredPortalProps) => {
|
||||
const anchorElement = document.body.querySelector<HTMLAnchorElement>(
|
||||
`#record-field-list-cell-${position}`,
|
||||
);
|
||||
|
||||
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
|
||||
|
||||
const targetedRecordsRuleFromContextStore = useRecoilComponentValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
);
|
||||
|
||||
let recordId;
|
||||
|
||||
if (targetedRecordsRuleFromContextStore.mode === 'selection') {
|
||||
recordId = targetedRecordsRuleFromContextStore.selectedRecordIds[0];
|
||||
}
|
||||
|
||||
const INPUT_ID_PREFIX = 'fields-card';
|
||||
|
||||
const isRecordFieldReadOnly = useIsRecordFieldReadOnly({
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
recordId: recordId ?? '',
|
||||
});
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
if (!isDefined(anchorElement) || !isDefined(recordId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldContext.Provider
|
||||
key={recordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
recordId,
|
||||
maxWidth: 200,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: 0,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
isDisplayModeFixHeight: true,
|
||||
isRecordFieldReadOnly,
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{createPortal(
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: getRecordFieldInputInstanceId({
|
||||
recordId,
|
||||
fieldName: fieldMetadataItem.name,
|
||||
prefix: INPUT_ID_PREFIX,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordFieldListCellAnchorPortalContext>
|
||||
<RecordInlineCellHoveredPortal>
|
||||
{children}
|
||||
</RecordInlineCellHoveredPortal>
|
||||
</RecordFieldListCellAnchorPortalContext>
|
||||
</RecordFieldComponentInstanceContext.Provider>,
|
||||
anchorElement,
|
||||
)}
|
||||
</>
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { RecordFieldListCellAnchoredPortal } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellAnchoredPortal';
|
||||
import { RecordFieldListCellHoveredPortalContent } from '@/object-record/record-field-list/anchored-portal/components/RecordFieldListCellHoveredPortalContent';
|
||||
import { useFieldListFieldMetadataFromPosition } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataFromPosition';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type RecordFieldListCellHoveredPortalProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const RecordFieldListCellHoveredPortal = ({
|
||||
objectMetadataItem,
|
||||
}: RecordFieldListCellHoveredPortalProps) => {
|
||||
const hoverPosition = useRecoilComponentValue(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
);
|
||||
|
||||
const { hoveredFieldMetadataItem } = useFieldListFieldMetadataFromPosition({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
if (!isDefined(hoverPosition) || !isDefined(hoveredFieldMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordFieldListCellAnchoredPortal
|
||||
position={hoverPosition}
|
||||
fieldMetadataItem={hoveredFieldMetadataItem}
|
||||
>
|
||||
<RecordFieldListCellHoveredPortalContent
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
</RecordFieldListCellAnchoredPortal>
|
||||
);
|
||||
};
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useFieldListFieldMetadataFromPosition } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataFromPosition';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay';
|
||||
import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
||||
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 { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type RecordFieldListCellHoveredPortalContentProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const RecordFieldListCellHoveredPortalContent = ({
|
||||
objectMetadataItem,
|
||||
}: RecordFieldListCellHoveredPortalContentProps) => {
|
||||
const hoverPosition = useRecoilComponentValue(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
);
|
||||
|
||||
const { editModeContentOnly, isCentered } = useRecordInlineCellContext();
|
||||
|
||||
const { hoveredFieldMetadataItem } = useFieldListFieldMetadataFromPosition({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
});
|
||||
|
||||
const { isRecordFieldReadOnly } = useContext(FieldContext);
|
||||
const { openInlineCell } = useInlineCell();
|
||||
|
||||
const shouldContainerBeClickable =
|
||||
!isRecordFieldReadOnly && !editModeContentOnly;
|
||||
|
||||
const setRecordFieldListHoverPosition = useSetRecoilComponentState(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
'fields-card',
|
||||
);
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setRecordFieldListHoverPosition(null);
|
||||
};
|
||||
|
||||
if (!isDefined(hoverPosition) || !isDefined(hoveredFieldMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordInlineCellHoveredPortalContent
|
||||
readonly={isRecordFieldReadOnly}
|
||||
isCentered={isCentered}
|
||||
onClick={shouldContainerBeClickable ? openInlineCell : undefined}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<RecordInlineCellDisplayMode>
|
||||
{editModeContentOnly ? <FieldInput /> : <FieldDisplay />}
|
||||
</RecordInlineCellDisplayMode>
|
||||
</RecordInlineCellHoveredPortalContent>
|
||||
);
|
||||
};
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import { FieldInput } from '@/object-record/record-field/ui/components/FieldInput';
|
||||
|
||||
import { usePersistFieldFromFieldInputContext } from '@/object-record/record-field/ui/hooks/usePersistFieldFromFieldInputContext';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
|
||||
import {
|
||||
FieldInputEventContext,
|
||||
type FieldInputClickOutsideEvent,
|
||||
type FieldInputEvent,
|
||||
} from '@/object-record/record-field/ui/contexts/FieldInputEventContext';
|
||||
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||
import { currentFocusIdSelector } from '@/ui/utilities/focus/states/currentFocusIdSelector';
|
||||
import { useAvailableComponentInstanceId } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceId';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const RecordTableCellFieldInput = () => {
|
||||
const { onMoveFocus, onCloseTableCell } = useRecordTableBodyContextOrThrow();
|
||||
|
||||
const instanceId = useAvailableComponentInstanceId(
|
||||
RecordFieldComponentInstanceContext,
|
||||
);
|
||||
|
||||
const { persistFieldFromFieldInputContext } =
|
||||
usePersistFieldFromFieldInputContext();
|
||||
|
||||
const handleEnter: FieldInputEvent = ({ newValue, skipPersist }) => {
|
||||
if (skipPersist !== true) {
|
||||
persistFieldFromFieldInputContext(newValue);
|
||||
}
|
||||
|
||||
onCloseTableCell();
|
||||
onMoveFocus('down');
|
||||
};
|
||||
|
||||
const handleSubmit: FieldInputEvent = ({ newValue, skipPersist }) => {
|
||||
if (skipPersist !== true) {
|
||||
persistFieldFromFieldInputContext(newValue);
|
||||
}
|
||||
|
||||
onCloseTableCell();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
onCloseTableCell();
|
||||
};
|
||||
|
||||
const handleClickOutside: FieldInputClickOutsideEvent = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
({ newValue, event, skipPersist }) => {
|
||||
const currentFocusId = snapshot
|
||||
.getLoadable(currentFocusIdSelector)
|
||||
.getValue();
|
||||
|
||||
if (currentFocusId !== instanceId) {
|
||||
return;
|
||||
}
|
||||
event?.preventDefault();
|
||||
event?.stopImmediatePropagation();
|
||||
|
||||
if (skipPersist !== true) {
|
||||
persistFieldFromFieldInputContext(newValue);
|
||||
}
|
||||
|
||||
onCloseTableCell();
|
||||
},
|
||||
[onCloseTableCell, instanceId, persistFieldFromFieldInputContext],
|
||||
);
|
||||
|
||||
const handleEscape: FieldInputEvent = ({ newValue, skipPersist }) => {
|
||||
if (skipPersist !== true) {
|
||||
persistFieldFromFieldInputContext(newValue);
|
||||
}
|
||||
|
||||
onCloseTableCell();
|
||||
};
|
||||
|
||||
const handleTab: FieldInputEvent = ({ newValue, skipPersist }) => {
|
||||
if (skipPersist !== true) {
|
||||
persistFieldFromFieldInputContext(newValue);
|
||||
}
|
||||
|
||||
onCloseTableCell();
|
||||
onMoveFocus('right');
|
||||
};
|
||||
|
||||
const handleShiftTab: FieldInputEvent = ({ newValue, skipPersist }) => {
|
||||
if (skipPersist !== true) {
|
||||
persistFieldFromFieldInputContext(newValue);
|
||||
}
|
||||
|
||||
onCloseTableCell();
|
||||
onMoveFocus('left');
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputEventContext.Provider
|
||||
value={{
|
||||
onCancel: handleCancel,
|
||||
onEnter: handleEnter,
|
||||
onEscape: handleEscape,
|
||||
onClickOutside: handleClickOutside,
|
||||
onShiftTab: handleShiftTab,
|
||||
onSubmit: handleSubmit,
|
||||
onTab: handleTab,
|
||||
}}
|
||||
>
|
||||
<FieldInput />
|
||||
</FieldInputEventContext.Provider>
|
||||
);
|
||||
};
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
|
||||
import { recordFieldListCellEditModePositionComponentState } from '@/object-record/record-field-list/states/recordFieldListCellEditModePositionComponentState';
|
||||
import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type UseFieldListFieldMetadataFromPositionProps = {
|
||||
objectNameSingular: string;
|
||||
};
|
||||
|
||||
export const useFieldListFieldMetadataFromPosition = ({
|
||||
objectNameSingular,
|
||||
}: UseFieldListFieldMetadataFromPositionProps) => {
|
||||
const hoverPosition = useRecoilComponentValue(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
);
|
||||
|
||||
const editModePosition = useRecoilComponentValue(
|
||||
recordFieldListCellEditModePositionComponentState,
|
||||
);
|
||||
|
||||
const {
|
||||
inlineFieldMetadataItems,
|
||||
inlineRelationFieldMetadataItems,
|
||||
boxedRelationFieldMetadataItems,
|
||||
} = useFieldListFieldMetadataItems({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const fieldMetadataItems = [
|
||||
...inlineRelationFieldMetadataItems,
|
||||
...inlineFieldMetadataItems,
|
||||
...boxedRelationFieldMetadataItems,
|
||||
];
|
||||
|
||||
const hoveredFieldMetadataItem = isDefined(hoverPosition)
|
||||
? fieldMetadataItems.at(hoverPosition)
|
||||
: undefined;
|
||||
|
||||
const editedFieldMetadataItem = isDefined(editModePosition)
|
||||
? fieldMetadataItems.at(editModePosition)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
hoveredFieldMetadataItem,
|
||||
editedFieldMetadataItem,
|
||||
};
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||
import groupBy from 'lodash.groupby';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type UseFieldListFieldMetadataItemsProps = {
|
||||
objectNameSingular: string;
|
||||
};
|
||||
|
||||
export const useFieldListFieldMetadataItems = ({
|
||||
objectNameSingular,
|
||||
}: UseFieldListFieldMetadataItemsProps) => {
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const availableFieldMetadataItems = objectMetadataItem.readableFields
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.id !== labelIdentifierFieldMetadataItem?.id,
|
||||
)
|
||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
||||
);
|
||||
|
||||
const { inlineFieldMetadataItems, relationFieldMetadataItems } = groupBy(
|
||||
availableFieldMetadataItems
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.name !== 'createdAt' &&
|
||||
fieldMetadataItem.name !== 'deletedAt',
|
||||
)
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.type !== FieldMetadataType.RICH_TEXT_V2,
|
||||
),
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.type === FieldMetadataType.RELATION
|
||||
? 'relationFieldMetadataItems'
|
||||
: 'inlineFieldMetadataItems',
|
||||
);
|
||||
|
||||
const inlineRelationFieldMetadataItems = (
|
||||
relationFieldMetadataItems ?? []
|
||||
).filter(
|
||||
(fieldMetadataItem) =>
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldMetadataItem.name === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldMetadataItem.name === 'taskTargets'),
|
||||
);
|
||||
|
||||
const boxedRelationFieldMetadataItems = (
|
||||
relationFieldMetadataItems ?? []
|
||||
).filter(
|
||||
(fieldMetadataItem) =>
|
||||
!(
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldMetadataItem.name === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldMetadataItem.name === 'taskTargets')
|
||||
) &&
|
||||
isDefined(fieldMetadataItem.relation?.targetObjectMetadata.id) &&
|
||||
getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
fieldMetadataItem.relation?.targetObjectMetadata.id,
|
||||
).canReadObjectRecords,
|
||||
);
|
||||
|
||||
return {
|
||||
inlineFieldMetadataItems,
|
||||
inlineRelationFieldMetadataItems,
|
||||
boxedRelationFieldMetadataItems,
|
||||
};
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||
|
||||
export const RecordFieldListComponentInstanceContext =
|
||||
createComponentInstanceContext();
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const recordFieldListCellEditModePositionComponentState =
|
||||
createComponentState<number | null>({
|
||||
key: 'recordFieldListCellEditModePositionComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: RecordFieldListComponentInstanceContext,
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { RecordFieldListComponentInstanceContext } from '@/object-record/record-field-list/states/contexts/RecordFieldListComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const recordFieldListHoverPositionComponentState = createComponentState<
|
||||
number | null
|
||||
>({
|
||||
key: 'recordFieldListHoverPositionComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: RecordFieldListComponentInstanceContext,
|
||||
});
|
||||
+3
@@ -38,8 +38,11 @@ export type GenericFieldContextType = {
|
||||
onRecordChipClick?: (event: MouseEvent) => void;
|
||||
onOpenEditMode?: () => void;
|
||||
onCloseEditMode?: () => void;
|
||||
onMouseEnter?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
triggerEvent?: TriggerEventType;
|
||||
isForbidden?: boolean;
|
||||
anchorId?: string;
|
||||
};
|
||||
|
||||
export const FieldContext = createContext<GenericFieldContextType>(
|
||||
|
||||
+5
-2
@@ -99,7 +99,8 @@ export const RecordInlineCellContainer = () => {
|
||||
|
||||
const { isInlineCellInEditMode, openInlineCell } = useInlineCell();
|
||||
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
const { recordId, fieldDefinition, onMouseEnter, onMouseLeave, anchorId } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const shouldContainerBeClickable =
|
||||
!readonly && !editModeContentOnly && !isInlineCellInEditMode;
|
||||
@@ -114,12 +115,14 @@ export const RecordInlineCellContainer = () => {
|
||||
if (!readonly) {
|
||||
setIsFocused(true);
|
||||
}
|
||||
onMouseEnter?.();
|
||||
};
|
||||
|
||||
const handleContainerMouseLeave = () => {
|
||||
if (!readonly) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
onMouseLeave?.();
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
@@ -164,7 +167,7 @@ export const RecordInlineCellContainer = () => {
|
||||
{isInlineCellInEditMode && (
|
||||
<RecordInlineCellCloseOnCommandMenuOpeningEffect />
|
||||
)}
|
||||
<StyledValueContainer readonly={readonly ?? false}>
|
||||
<StyledValueContainer readonly={readonly ?? false} id={anchorId}>
|
||||
<RecordInlineCellValue />
|
||||
</StyledValueContainer>
|
||||
</StyledInlineCellBaseContainer>
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledRecordTableCellHoveredPortal = styled.div`
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RecordInlineCellHoveredPortal = StyledRecordTableCellHoveredPortal;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledRecordTableCellHoveredPortalContent = styled.div<{
|
||||
readonly?: boolean;
|
||||
isCentered?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
width: 100%;
|
||||
|
||||
${({ isCentered }) =>
|
||||
isCentered === true &&
|
||||
`
|
||||
justify-content: center;
|
||||
`};
|
||||
|
||||
${({ readonly }) =>
|
||||
!readonly &&
|
||||
css`
|
||||
cursor: pointer;
|
||||
`};
|
||||
`;
|
||||
|
||||
const StyledInlineCellBaseContainer = styled.div<{ readonly: boolean }>`
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: fit-content;
|
||||
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;
|
||||
};
|
||||
|
||||
export const RecordInlineCellHoveredPortalContent = ({
|
||||
children,
|
||||
isCentered,
|
||||
readonly,
|
||||
onClick,
|
||||
onMouseLeave,
|
||||
}: RecordInlineCellHoveredPortalContentProps) => {
|
||||
return (
|
||||
<StyledInlineCellBaseContainer
|
||||
readonly={readonly}
|
||||
onClick={onClick}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<StyledRecordTableCellHoveredPortalContent
|
||||
isCentered={isCentered}
|
||||
readonly={readonly}
|
||||
>
|
||||
{children}
|
||||
</StyledRecordTableCellHoveredPortalContent>
|
||||
</StyledInlineCellBaseContainer>
|
||||
);
|
||||
};
|
||||
+44
-66
@@ -1,12 +1,12 @@
|
||||
import groupBy from 'lodash.groupby';
|
||||
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
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';
|
||||
@@ -19,11 +19,9 @@ import { useRecordShowContainerData } from '@/object-record/record-show/hooks/us
|
||||
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 { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
|
||||
import { useIsInRightDrawerOrThrow } from '@/ui/layout/right-drawer/contexts/RightDrawerContext';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
|
||||
type FieldsCardProps = {
|
||||
objectNameSingular: string;
|
||||
@@ -38,16 +36,14 @@ export const FieldsCard = ({
|
||||
objectRecordId,
|
||||
showDuplicatesSection = true,
|
||||
}: FieldsCardProps) => {
|
||||
const { recordLoading, labelIdentifierFieldMetadataItem, isPrefetchLoading } =
|
||||
useRecordShowContainerData({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
});
|
||||
const { recordLoading, isPrefetchLoading } = useRecordShowContainerData({
|
||||
objectRecordId,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({
|
||||
@@ -57,63 +53,34 @@ export const FieldsCard = ({
|
||||
|
||||
const { isInRightDrawer } = useIsInRightDrawerOrThrow();
|
||||
|
||||
const availableFieldMetadataItems = objectMetadataItem.readableFields
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
isFieldCellSupported(fieldMetadataItem, objectMetadataItems) &&
|
||||
fieldMetadataItem.id !== labelIdentifierFieldMetadataItem?.id,
|
||||
)
|
||||
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
|
||||
fieldMetadataItemA.name.localeCompare(fieldMetadataItemB.name),
|
||||
);
|
||||
|
||||
const { inlineFieldMetadataItems, relationFieldMetadataItems } = groupBy(
|
||||
availableFieldMetadataItems
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.name !== 'createdAt' &&
|
||||
fieldMetadataItem.name !== 'deletedAt',
|
||||
)
|
||||
.filter(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.type !== FieldMetadataType.RICH_TEXT_V2,
|
||||
),
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.type === FieldMetadataType.RELATION
|
||||
? 'relationFieldMetadataItems'
|
||||
: 'inlineFieldMetadataItems',
|
||||
);
|
||||
|
||||
const inlineRelationFieldMetadataItems = relationFieldMetadataItems?.filter(
|
||||
(fieldMetadataItem) =>
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldMetadataItem.name === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldMetadataItem.name === 'taskTargets'),
|
||||
);
|
||||
|
||||
const boxedRelationFieldMetadataItems = relationFieldMetadataItems?.filter(
|
||||
(fieldMetadataItem) =>
|
||||
!(
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldMetadataItem.name === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldMetadataItem.name === 'taskTargets')
|
||||
) &&
|
||||
isDefined(fieldMetadataItem.relation?.targetObjectMetadata.id) &&
|
||||
getObjectPermissionsForObject(
|
||||
objectPermissionsByObjectMetadataId,
|
||||
fieldMetadataItem.relation?.targetObjectMetadata.id,
|
||||
).canReadObjectRecords,
|
||||
);
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: objectRecordId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
const setRecordFieldListHoverPosition = useSetRecoilComponentState(
|
||||
recordFieldListHoverPositionComponentState,
|
||||
'fields-card',
|
||||
);
|
||||
|
||||
const handleMouseEnter = (index: number) => {
|
||||
setRecordFieldListHoverPosition(index);
|
||||
};
|
||||
|
||||
const {
|
||||
inlineFieldMetadataItems,
|
||||
inlineRelationFieldMetadataItems,
|
||||
boxedRelationFieldMetadataItems,
|
||||
} = useFieldListFieldMetadataItems({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<RecordFieldListComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: 'fields-card',
|
||||
}}
|
||||
>
|
||||
<PropertyBox>
|
||||
{isPrefetchLoading ? (
|
||||
<PropertyBoxSkeletonLoader />
|
||||
@@ -200,6 +167,13 @@ export const FieldsCard = ({
|
||||
fieldType: fieldMetadataItem.type,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
}),
|
||||
onMouseEnter: () =>
|
||||
handleMouseEnter(
|
||||
index + (inlineRelationFieldMetadataItems?.length ?? 0),
|
||||
),
|
||||
anchorId: `record-field-list-cell-${
|
||||
index + (inlineRelationFieldMetadataItems?.length ?? 0)
|
||||
}`,
|
||||
}}
|
||||
>
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
@@ -259,6 +233,10 @@ export const FieldsCard = ({
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
</>
|
||||
|
||||
<RecordFieldListCellHoveredPortal
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
</RecordFieldListComponentInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
-1
@@ -36,7 +36,6 @@ export const RecordShowContainer = ({
|
||||
});
|
||||
|
||||
const { isPrefetchLoading, recordLoading } = useRecordShowContainerData({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
});
|
||||
|
||||
|
||||
+9
-5
@@ -1,4 +1,5 @@
|
||||
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
@@ -27,11 +28,9 @@ export const SummaryCard = ({
|
||||
objectRecordId,
|
||||
isInRightDrawer,
|
||||
}: SummaryCardProps) => {
|
||||
const { recordLoading, labelIdentifierFieldMetadataItem, isPrefetchLoading } =
|
||||
useRecordShowContainerData({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
});
|
||||
const { recordLoading, isPrefetchLoading } = useRecordShowContainerData({
|
||||
objectRecordId,
|
||||
});
|
||||
|
||||
const recordCreatedAt = useRecoilValue<string | null>(
|
||||
recordStoreFamilySelector({
|
||||
@@ -60,6 +59,11 @@ export const SummaryCard = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const isTitleReadOnly = useIsRecordFieldReadOnly({
|
||||
recordId: objectRecordId,
|
||||
fieldMetadataId: labelIdentifierFieldMetadataItem?.id ?? '',
|
||||
|
||||
-9
@@ -1,22 +1,14 @@
|
||||
import { useLabelIdentifierFieldMetadataItem } from '@/object-metadata/hooks/useLabelIdentifierFieldMetadataItem';
|
||||
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
type UseRecordShowContainerDataProps = {
|
||||
objectNameSingular: string;
|
||||
objectRecordId: string;
|
||||
};
|
||||
|
||||
export const useRecordShowContainerData = ({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
}: UseRecordShowContainerDataProps) => {
|
||||
const { labelIdentifierFieldMetadataItem } =
|
||||
useLabelIdentifierFieldMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const [recordLoading] = useRecoilState(
|
||||
recordLoadingFamilyState(objectRecordId),
|
||||
);
|
||||
@@ -25,7 +17,6 @@ export const useRecordShowContainerData = ({
|
||||
|
||||
return {
|
||||
recordLoading,
|
||||
labelIdentifierFieldMetadataItem,
|
||||
isPrefetchLoading,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/reco
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { RecordShowContainer } from '@/object-record/record-show/components/RecordShowContainer';
|
||||
import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect';
|
||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||
import { computeRecordShowComponentInstanceId } from '@/object-record/record-show/utils/computeRecordShowComponentInstanceId';
|
||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||
import { PageHeaderToggleCommandMenuButton } from '@/ui/layout/page-header/components/PageHeaderToggleCommandMenuButton';
|
||||
@@ -17,6 +16,7 @@ import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { RecordShowPageHeader } from '~/pages/object-record/RecordShowPageHeader';
|
||||
import { RecordShowPageTitle } from '~/pages/object-record/RecordShowPageTitle';
|
||||
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
|
||||
|
||||
export const RecordShowPage = () => {
|
||||
const parameters = useParams<{
|
||||
|
||||
Reference in New Issue
Block a user