mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-22 03:17:40 +03:00
fix: soft deleted records are read only (#8198)
TODO: - [ ] It should not be possible to add tasks, notes, files, etc. Fix for https://github.com/twentyhq/twenty/issues/7172 Note for reviewer: - With these changes, `deletedAt` is now always included in `recordGqlFields`. --- Edit from Charles -- In this PR: 1) As mentionned by @pau-not-paul, we are adding deletedAt to our recordGqlFields for board and table 2) I'm removing cellReadOnly logic, it is now fully computed using useIsFieldValueReadonly like it's done in other places, there is no need to maintain two read only systems 3) refactoring useIsFieldValueReadonly to take all the business logics into one place together. It's now a function of the 5 factors (isRemote, isDeleted, objectName, fieldName, etc...). Later it's likely to get back to a function of Pick<FieldMetadata>, Pick<ObjectMetadata>, record.isDeleted but we are not there yet Note: as all cells are listening to the record (for isDeleted), updating a field will trigger a re-rendering of the row which is not an issue right now. It will be if we introduce bulk edition. In this case we would need to use a selector on fields on top of record store --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
parent
3c5eb539bb
commit
ae4fb7d113
@ -108,5 +108,6 @@ export const actorFieldDefinition: FieldDefinition<FieldActorMetadata> = {
|
||||
defaultValue: { source: 'MANUAL', name: '' },
|
||||
metadata: {
|
||||
fieldName: 'actor',
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
};
|
||||
|
@ -1,50 +0,0 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import {
|
||||
actorFieldDefinition,
|
||||
phonesFieldDefinition,
|
||||
} from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
const recordId = 'recordId';
|
||||
|
||||
const getWrapper =
|
||||
(fieldDefinition: FieldDefinition<FieldMetadata>) =>
|
||||
({ children }: { children: ReactNode }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
hotkeyScope: 'hotkeyScope',
|
||||
isLabelIdentifier: false,
|
||||
}}
|
||||
>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
|
||||
const ActorWrapper = getWrapper(actorFieldDefinition);
|
||||
const PhoneWrapper = getWrapper(phonesFieldDefinition);
|
||||
|
||||
describe('useIsFieldReadOnly', () => {
|
||||
it('should return true', () => {
|
||||
const { result } = renderHook(() => useIsFieldReadOnly(), {
|
||||
wrapper: ActorWrapper,
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false', () => {
|
||||
const { result } = renderHook(() => useIsFieldReadOnly(), {
|
||||
wrapper: PhoneWrapper,
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
});
|
@ -0,0 +1,74 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import {
|
||||
actorFieldDefinition,
|
||||
phonesFieldDefinition,
|
||||
} from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
||||
import { JestRecordStoreSetter } from '~/testing/jest/JestRecordStoreSetter';
|
||||
|
||||
import { useIsFieldValueReadOnly } from '../useIsFieldValueReadOnly';
|
||||
|
||||
const recordId = 'recordId';
|
||||
|
||||
const getWrapper =
|
||||
(fieldDefinition: FieldDefinition<FieldMetadata>, isRecordDeleted: boolean) =>
|
||||
({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<JestObjectMetadataItemSetter>
|
||||
<JestRecordStoreSetter
|
||||
records={[
|
||||
{
|
||||
id: recordId,
|
||||
deletedAt: isRecordDeleted ? new Date().toISOString() : null,
|
||||
__typename: 'standardObject',
|
||||
} as ObjectRecord,
|
||||
]}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition,
|
||||
recordId,
|
||||
hotkeyScope: 'hotkeyScope',
|
||||
isLabelIdentifier: false,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FieldContext.Provider>
|
||||
</JestRecordStoreSetter>
|
||||
</JestObjectMetadataItemSetter>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
||||
|
||||
describe('useIsFieldValueReadOnly', () => {
|
||||
it('should take fieldDefinition into account', () => {
|
||||
const { result } = renderHook(() => useIsFieldValueReadOnly(), {
|
||||
wrapper: getWrapper(phonesFieldDefinition, false),
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
|
||||
const { result: result2 } = renderHook(() => useIsFieldValueReadOnly(), {
|
||||
wrapper: getWrapper(actorFieldDefinition, false),
|
||||
});
|
||||
|
||||
expect(result2.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should take isRecordDeleted into account', () => {
|
||||
const { result } = renderHook(() => useIsFieldValueReadOnly(), {
|
||||
wrapper: getWrapper(phonesFieldDefinition, true),
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
});
|
@ -1,18 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { isFieldMetadataReadOnly } from '../utils/isFieldMetadataReadOnly';
|
||||
|
||||
export const useIsFieldReadOnly = () => {
|
||||
const { fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const { metadata } = fieldDefinition;
|
||||
|
||||
return (
|
||||
isFieldActor(fieldDefinition) ||
|
||||
isFieldRichText(fieldDefinition) ||
|
||||
isFieldMetadataReadOnly(metadata)
|
||||
);
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly';
|
||||
|
||||
export const useIsFieldValueReadOnly = () => {
|
||||
const { fieldDefinition, recordId } = useContext(FieldContext);
|
||||
|
||||
const { metadata, type } = fieldDefinition;
|
||||
|
||||
const recordFromStore = useRecoilValue<ObjectRecord | null>(
|
||||
recordStoreFamilyState(recordId),
|
||||
);
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: metadata.objectMetadataNameSingular ?? '',
|
||||
});
|
||||
|
||||
return isFieldValueReadOnly({
|
||||
objectNameSingular: metadata.objectMetadataNameSingular,
|
||||
fieldName: metadata.fieldName,
|
||||
fieldType: type,
|
||||
isObjectRemote: objectMetadataItem.isRemote,
|
||||
isRecordDeleted: recordFromStore?.deletedAt,
|
||||
});
|
||||
};
|
@ -0,0 +1,113 @@
|
||||
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
describe('isFieldValueReadOnly', () => {
|
||||
it('should return true if fieldName is noteTargets or taskTargets', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
fieldName: 'noteTargets',
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
|
||||
const result2 = isFieldValueReadOnly({
|
||||
fieldName: 'taskTargets',
|
||||
});
|
||||
|
||||
expect(result2).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if fieldName is not noteTargets or taskTargets', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
fieldName: 'test',
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if isObjectRemote is true', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
isObjectRemote: true,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if isObjectRemote is false', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
isObjectRemote: false,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if isRecordDeleted is true', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
isRecordDeleted: true,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if isRecordDeleted is false', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
isRecordDeleted: false,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if objectNameSingular is Workflow and fieldName is not name', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
objectNameSingular: 'workflow',
|
||||
fieldName: 'test',
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if objectNameSingular is Workflow and fieldName is name', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
objectNameSingular: 'Workflow',
|
||||
fieldName: 'name',
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if isWorkflowSubObjectMetadata is true', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
objectNameSingular: 'workflowVersion',
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if fieldType is FieldMetadataType.Actor', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
fieldType: FieldMetadataType.Actor,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if fieldType is FieldMetadataType.RichText', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
fieldType: FieldMetadataType.RichText,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if fieldType is not FieldMetadataType.Actor or FieldMetadataType.RichText', () => {
|
||||
const result = isFieldValueReadOnly({
|
||||
fieldType: FieldMetadataType.Text,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if none of the conditions are met', () => {
|
||||
const result = isFieldValueReadOnly({});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
@ -1,19 +0,0 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
export const isFieldMetadataReadOnly = (fieldMetadata: FieldMetadata) => {
|
||||
if (
|
||||
fieldMetadata.fieldName === 'noteTargets' ||
|
||||
fieldMetadata.fieldName === 'taskTargets'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
isWorkflowSubObjectMetadata(fieldMetadata.objectMetadataNameSingular) ||
|
||||
(fieldMetadata.objectMetadataNameSingular ===
|
||||
CoreObjectNameSingular.Workflow &&
|
||||
fieldMetadata.fieldName !== 'name')
|
||||
);
|
||||
};
|
@ -0,0 +1,54 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
||||
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
type isFieldValueReadOnlyParams = {
|
||||
objectNameSingular?: string;
|
||||
fieldName?: string;
|
||||
fieldType?: FieldMetadataType;
|
||||
isObjectRemote?: boolean;
|
||||
isRecordDeleted?: boolean;
|
||||
};
|
||||
|
||||
export const isFieldValueReadOnly = ({
|
||||
objectNameSingular,
|
||||
fieldName,
|
||||
fieldType,
|
||||
isObjectRemote = false,
|
||||
isRecordDeleted = false,
|
||||
}: isFieldValueReadOnlyParams) => {
|
||||
if (fieldName === 'noteTargets' || fieldName === 'taskTargets') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isObjectRemote) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isRecordDeleted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isWorkflowSubObjectMetadata(objectNameSingular)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
objectNameSingular === CoreObjectNameSingular.Workflow &&
|
||||
fieldName !== 'name'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(fieldType) &&
|
||||
(isFieldActor({ type: fieldType }) || isFieldRichText({ type: fieldType }))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
@ -36,6 +36,7 @@ export const useRecordBoardRecordGqlFields = ({
|
||||
|
||||
const recordGqlFields: Record<string, any> = {
|
||||
id: true,
|
||||
deletedAt: true,
|
||||
...Object.fromEntries(
|
||||
visibleFieldDefinitions.map((visibleFieldDefinition) => [
|
||||
visibleFieldDefinition.metadata.fieldName,
|
||||
|
@ -41,6 +41,7 @@ export const useRecordTableRecordGqlFields = ({
|
||||
|
||||
const recordGqlFields: Record<string, any> = {
|
||||
id: true,
|
||||
deletedAt: true,
|
||||
...Object.fromEntries(
|
||||
visibleTableColumns.map((column) => [column.metadata.fieldName, true]),
|
||||
),
|
||||
|
@ -13,7 +13,7 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types
|
||||
|
||||
import { useInlineCell } from '../hooks/useInlineCell';
|
||||
|
||||
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { RecordInlineCellContainer } from './RecordInlineCellContainer';
|
||||
import {
|
||||
@ -26,21 +26,16 @@ type RecordInlineCellProps = {
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
export const RecordInlineCell = ({
|
||||
readonly,
|
||||
loading,
|
||||
}: RecordInlineCellProps) => {
|
||||
export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
||||
const { fieldDefinition, recordId, isCentered } = useContext(FieldContext);
|
||||
const buttonIcon = useGetButtonIcon();
|
||||
|
||||
const isFieldInputOnly = useIsFieldInputOnly();
|
||||
|
||||
const isFieldReadOnly = useIsFieldReadOnly();
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
||||
|
||||
const { closeInlineCell } = useInlineCell();
|
||||
|
||||
const cellIsReadOnly = readonly || isFieldReadOnly;
|
||||
|
||||
const handleEnter: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
closeInlineCell();
|
||||
@ -77,7 +72,7 @@ export const RecordInlineCell = ({
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const RecordInlineCellContextValue: RecordInlineCellContextProps = {
|
||||
readonly: cellIsReadOnly,
|
||||
readonly: isFieldReadOnly,
|
||||
buttonIcon: buttonIcon,
|
||||
customEditHotkeyScope: isFieldRelation(fieldDefinition)
|
||||
? { scope: RelationPickerHotkeyScope.RelationPicker }
|
||||
@ -102,7 +97,7 @@ export const RecordInlineCell = ({
|
||||
onTab={handleTab}
|
||||
onShiftTab={handleShiftTab}
|
||||
onClickOutside={handleClickOutside}
|
||||
isReadOnly={cellIsReadOnly}
|
||||
isReadOnly={isFieldReadOnly}
|
||||
/>
|
||||
),
|
||||
displayModeContent: <FieldDisplay />,
|
||||
|
@ -84,7 +84,6 @@ export const FieldsCard = ({
|
||||
fieldMetadataItem.name === 'taskTargets')
|
||||
),
|
||||
);
|
||||
const isReadOnly = objectMetadataItem.isRemote;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -149,10 +148,7 @@ export const FieldsCard = ({
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell
|
||||
loading={recordLoading}
|
||||
readonly={isReadOnly}
|
||||
/>
|
||||
<RecordInlineCell loading={recordLoading} />
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
</>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { RightDrawerTitleRecordInlineCell } from '@/object-record/record-right-drawer/components/RightDrawerTitleRecordInlineCell';
|
||||
@ -29,7 +30,6 @@ export const SummaryCard = ({
|
||||
const {
|
||||
recordFromStore,
|
||||
recordLoading,
|
||||
objectMetadataItem,
|
||||
labelIdentifierFieldMetadataItem,
|
||||
isPrefetchLoading,
|
||||
recordIdentifier,
|
||||
@ -47,7 +47,11 @@ export const SummaryCard = ({
|
||||
|
||||
const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular);
|
||||
const isMobile = useIsMobile() || isInRightDrawer;
|
||||
const isReadOnly = objectMetadataItem.isRemote;
|
||||
|
||||
const isReadOnly = isFieldValueReadOnly({
|
||||
objectNameSingular,
|
||||
isRecordDeleted: recordFromStore?.isDeleted,
|
||||
});
|
||||
|
||||
if (isNewRightDrawerItemLoading || !isDefined(recordFromStore)) {
|
||||
return <ShowPageSummaryCardSkeletonLoader />;
|
||||
|
@ -25,9 +25,9 @@ import {
|
||||
RecordUpdateHook,
|
||||
RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { isFieldMetadataReadOnly } from '@/object-record/record-field/utils/isFieldMetadataReadOnly';
|
||||
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';
|
||||
@ -189,7 +189,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
[isExpanded],
|
||||
);
|
||||
|
||||
const canEdit = !isFieldMetadataReadOnly(fieldDefinition.metadata);
|
||||
const isReadOnly = useIsFieldValueReadOnly();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -206,7 +206,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
||||
accent="tertiary"
|
||||
/>
|
||||
</StyledClickableZone>
|
||||
{canEdit && (
|
||||
{!isReadOnly && (
|
||||
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||
<Dropdown
|
||||
dropdownId={dropdownScopeId}
|
||||
|
@ -7,11 +7,11 @@ import { IconForbid, IconPencil, IconPlus, LightIconButton } from 'twenty-ui';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||
import { RelationFromManyFieldInputMultiRecordsEffect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect';
|
||||
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
|
||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { isFieldMetadataReadOnly } from '@/object-record/record-field/utils/isFieldMetadataReadOnly';
|
||||
import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList';
|
||||
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
|
||||
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
||||
@ -34,7 +34,6 @@ import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams
|
||||
import { View } from '@/views/types/View';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||
|
||||
type RecordDetailRelationSectionProps = {
|
||||
loading: boolean;
|
||||
};
|
||||
@ -158,7 +157,7 @@ export const RecordDetailRelationSection = ({
|
||||
recordId,
|
||||
});
|
||||
|
||||
const canEdit = !isFieldMetadataReadOnly(fieldDefinition.metadata);
|
||||
const isReadOnly = useIsFieldValueReadOnly();
|
||||
|
||||
if (loading) return null;
|
||||
|
||||
@ -180,7 +179,7 @@ export const RecordDetailRelationSection = ({
|
||||
hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile}
|
||||
areRecordsAvailable={relationRecords.length > 0}
|
||||
rightAdornment={
|
||||
canEdit && (
|
||||
!isReadOnly && (
|
||||
<DropdownScope dropdownScopeId={dropdownId}>
|
||||
<StyledAddDropdown
|
||||
dropdownId={dropdownId}
|
||||
|
@ -94,7 +94,6 @@ const meta: Meta = {
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
}) + mockPerformance.recordId,
|
||||
isSelected: false,
|
||||
isReadOnly: false,
|
||||
isDragging: false,
|
||||
dragHandleProps: null,
|
||||
inView: true,
|
||||
|
@ -7,7 +7,6 @@ export type RecordTableRowContextProps = {
|
||||
recordId: string;
|
||||
rowIndex: number;
|
||||
isSelected: boolean;
|
||||
isReadOnly: boolean;
|
||||
isPendingRow?: boolean;
|
||||
isDragging: boolean;
|
||||
dragHandleProps: DraggableProvidedDragHandleProps | null;
|
||||
|
@ -2,7 +2,7 @@ import { useContext } from 'react';
|
||||
|
||||
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
@ -21,7 +21,7 @@ export const RecordTableCellFieldInput = () => {
|
||||
useContext(RecordTableContext);
|
||||
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
const isFieldReadOnly = useIsFieldReadOnly();
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
||||
|
||||
const handleEnter: FieldInputEvent = (persistField) => {
|
||||
onUpsertRecord({
|
||||
|
@ -10,7 +10,6 @@ import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEm
|
||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||
import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
|
||||
import { RecordTableCellButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellButton';
|
||||
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
|
||||
@ -22,7 +21,7 @@ import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
|
||||
|
||||
type RecordTableCellSoftFocusModeProps = {
|
||||
@ -36,11 +35,8 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
}: RecordTableCellSoftFocusModeProps) => {
|
||||
const { columnIndex } = useContext(RecordTableCellContext);
|
||||
const closeCurrentTableCell = useCloseCurrentTableCellInEditMode();
|
||||
const { isReadOnly: isRowReadOnly } = useContext(RecordTableRowContext);
|
||||
|
||||
const isFieldReadOnly = useIsFieldReadOnly();
|
||||
|
||||
const isCellReadOnly = isFieldReadOnly || isRowReadOnly;
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
||||
|
||||
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||
|
||||
@ -78,7 +74,7 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
() => {
|
||||
if (isCellReadOnly) {
|
||||
if (isFieldReadOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -95,7 +91,7 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
useScopedHotkeys(
|
||||
'*',
|
||||
(keyboardEvent) => {
|
||||
if (isCellReadOnly) {
|
||||
if (isFieldReadOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -124,7 +120,7 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
);
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isFieldInputOnly && !isCellReadOnly) {
|
||||
if (!isFieldInputOnly && !isFieldReadOnly) {
|
||||
openTableCell();
|
||||
}
|
||||
};
|
||||
@ -156,9 +152,9 @@ export const RecordTableCellSoftFocusMode = ({
|
||||
isDefined(buttonIcon) &&
|
||||
!editModeContentOnly &&
|
||||
(!isFirstColumn || !isEmpty) &&
|
||||
!isCellReadOnly;
|
||||
!isFieldReadOnly;
|
||||
|
||||
const dontShowContent = isEmpty && isCellReadOnly;
|
||||
const dontShowContent = isEmpty && isFieldReadOnly;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -8,7 +8,6 @@ export const recordTableRow: RecordTableRowContextProps = {
|
||||
recordId: 'recordId',
|
||||
pathToShowPage: '/',
|
||||
objectNameSingular: 'objectNameSingular',
|
||||
isReadOnly: false,
|
||||
dragHandleProps: {} as any,
|
||||
isDragging: false,
|
||||
inView: true,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useIsFieldReadOnly } from '@/object-record/record-field/hooks/useIsFieldReadOnly';
|
||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
|
||||
@ -32,11 +32,10 @@ export const useOpenRecordTableCellFromCell = () => {
|
||||
const cellPosition = useCurrentTableCellPosition();
|
||||
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
|
||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
||||
const { isReadOnly, pathToShowPage, objectNameSingular } = useContext(
|
||||
const { pathToShowPage, objectNameSingular } = useContext(
|
||||
RecordTableRowContext,
|
||||
);
|
||||
const isFieldReadOnly = useIsFieldReadOnly();
|
||||
const cellIsReadOnly = isReadOnly || isFieldReadOnly;
|
||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
||||
|
||||
const openTableCell = (
|
||||
initialValue?: string,
|
||||
@ -47,7 +46,7 @@ export const useOpenRecordTableCellFromCell = () => {
|
||||
customCellHotkeyScope,
|
||||
recordId,
|
||||
fieldDefinition,
|
||||
isReadOnly: cellIsReadOnly,
|
||||
isReadOnly: isFieldReadOnly,
|
||||
pathToShowPage,
|
||||
objectNameSingular,
|
||||
initialValue,
|
||||
|
@ -12,7 +12,7 @@ import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/
|
||||
import { tableCellWidthsComponentState } from '@/object-record/record-table/states/tableCellWidthsComponentState';
|
||||
import { RecordTableWithWrappersScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
export const RecordTableRowWrapper = ({
|
||||
recordId,
|
||||
@ -48,7 +48,7 @@ export const RecordTableRowWrapper = ({
|
||||
rootMargin: '1000px',
|
||||
});
|
||||
|
||||
const [, setTableCellWidths] = useRecoilComponentStateV2(
|
||||
const setTableCellWidths = useSetRecoilComponentStateV2(
|
||||
tableCellWidthsComponentState,
|
||||
);
|
||||
|
||||
@ -108,7 +108,6 @@ export const RecordTableRowWrapper = ({
|
||||
}) + recordId,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
isSelected: currentRowSelected,
|
||||
isReadOnly: objectMetadataItem.isRemote ?? false,
|
||||
isPendingRow,
|
||||
isDragging: draggableSnapshot.isDragging,
|
||||
dragHandleProps: draggableProvided.dragHandleProps,
|
||||
|
@ -0,0 +1,20 @@
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export const JestRecordStoreSetter = ({
|
||||
children,
|
||||
records,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
records: ObjectRecord[];
|
||||
}) => {
|
||||
const { upsertRecords } = useUpsertRecordsInStore();
|
||||
|
||||
useEffect(() => {
|
||||
upsertRecords(records);
|
||||
});
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
Loading…
Reference in New Issue
Block a user