[feat][FE] Stop persisting new empty records (#4853)
## Context Closes [#4773](https://github.com/twentyhq/twenty/issues/4773) Persisting of new records is delayed to cell escape and not performed for empty records. ## How was it tested? Locally tested + jest --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -35,6 +35,7 @@ const meta: Meta = {
|
|||||||
iconName: 'IconCalendarEvent',
|
iconName: 'IconCalendarEvent',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'Date',
|
fieldName: 'Date',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
|||||||
@ -36,6 +36,7 @@ const meta: Meta = {
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'Email',
|
fieldName: 'Email',
|
||||||
placeHolder: 'Email',
|
placeHolder: 'Email',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
|||||||
@ -35,6 +35,7 @@ const meta: Meta = {
|
|||||||
fieldName: 'Number',
|
fieldName: 'Number',
|
||||||
placeHolder: 'Number',
|
placeHolder: 'Number',
|
||||||
isPositive: true,
|
isPositive: true,
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
|||||||
@ -34,6 +34,7 @@ const meta: Meta = {
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'Text',
|
fieldName: 'Text',
|
||||||
placeHolder: 'Text',
|
placeHolder: 'Text',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
|||||||
@ -58,6 +58,7 @@ const AddressInputWithContext = ({
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'Address',
|
fieldName: 'Address',
|
||||||
placeHolder: 'Enter text',
|
placeHolder: 'Enter text',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -47,6 +47,7 @@ const BooleanFieldInputWithContext = ({
|
|||||||
type: FieldMetadataType.Boolean,
|
type: FieldMetadataType.Boolean,
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'Boolean',
|
fieldName: 'Boolean',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -49,6 +49,7 @@ const DateFieldInputWithContext = ({
|
|||||||
iconName: 'IconCalendarEvent',
|
iconName: 'IconCalendarEvent',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'Date',
|
fieldName: 'Date',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ const EmailFieldInputWithContext = ({
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'email',
|
fieldName: 'email',
|
||||||
placeHolder: 'username@email.com',
|
placeHolder: 'username@email.com',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ const NumberFieldInputWithContext = ({
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'number',
|
fieldName: 'number',
|
||||||
placeHolder: 'Enter number',
|
placeHolder: 'Enter number',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ const RatingFieldInputWithContext = ({
|
|||||||
iconName: 'Icon123',
|
iconName: 'Icon123',
|
||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'Rating',
|
fieldName: 'Rating',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -73,6 +73,7 @@ const RelationFieldInputWithContext = ({
|
|||||||
relationObjectMetadataNamePlural: 'workspaceMembers',
|
relationObjectMetadataNamePlural: 'workspaceMembers',
|
||||||
relationObjectMetadataNameSingular:
|
relationObjectMetadataNameSingular:
|
||||||
CoreObjectNameSingular.WorkspaceMember,
|
CoreObjectNameSingular.WorkspaceMember,
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ const TextFieldInputWithContext = ({
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName: 'Text',
|
fieldName: 'Text',
|
||||||
placeHolder: 'Enter text',
|
placeHolder: 'Enter text',
|
||||||
|
objectMetadataNameSingular: 'person',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
entityId={entityId}
|
entityId={entityId}
|
||||||
|
|||||||
@ -153,6 +153,7 @@ export const RecordShowContainer = ({
|
|||||||
metadata: {
|
metadata: {
|
||||||
fieldName:
|
fieldName:
|
||||||
labelIdentifierFieldMetadataItem?.name || '',
|
labelIdentifierFieldMetadataItem?.name || '',
|
||||||
|
objectMetadataNameSingular: objectNameSingular,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/components/RecordTableBodyFetchMoreLoader';
|
import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/components/RecordTableBodyFetchMoreLoader';
|
||||||
|
import { RecordTablePendingRow } from '@/object-record/record-table/components/RecordTablePendingRow';
|
||||||
import { RecordTableRow } from '@/object-record/record-table/components/RecordTableRow';
|
import { RecordTableRow } from '@/object-record/record-table/components/RecordTableRow';
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ export const RecordTableBody = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<RecordTablePendingRow />
|
||||||
{tableRowIds.map((recordId, rowIndex) => (
|
{tableRowIds.map((recordId, rowIndex) => (
|
||||||
<RecordTableRow
|
<RecordTableRow
|
||||||
key={recordId}
|
key={recordId}
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { RecordTableRow } from '@/object-record/record-table/components/RecordTableRow';
|
||||||
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
|
|
||||||
|
export const RecordTablePendingRow = () => {
|
||||||
|
const { pendingRecordIdState } = useRecordTableStates();
|
||||||
|
const pendingRecordId = useRecoilValue(pendingRecordIdState);
|
||||||
|
|
||||||
|
if (!pendingRecordId) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordTableRow
|
||||||
|
key={pendingRecordId}
|
||||||
|
recordId={pendingRecordId}
|
||||||
|
rowIndex={-1}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -12,6 +12,7 @@ import { onColumnsChangeComponentState } from '@/object-record/record-table/stat
|
|||||||
import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState';
|
import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState';
|
||||||
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
|
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
|
||||||
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
||||||
|
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||||
import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector';
|
import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector';
|
||||||
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
|
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
|
||||||
@ -132,5 +133,9 @@ export const useRecordTableStates = (recordTableId?: string) => {
|
|||||||
visibleTableColumnsComponentSelector,
|
visibleTableColumnsComponentSelector,
|
||||||
scopeId,
|
scopeId,
|
||||||
),
|
),
|
||||||
|
pendingRecordIdState: extractComponentState(
|
||||||
|
recordTablePendingRecordIdComponentState,
|
||||||
|
scopeId,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
selectedRowIdsSelector,
|
selectedRowIdsSelector,
|
||||||
onToggleColumnFilterState,
|
onToggleColumnFilterState,
|
||||||
onToggleColumnSortState,
|
onToggleColumnSortState,
|
||||||
|
pendingRecordIdState,
|
||||||
} = useRecordTableStates(recordTableId);
|
} = useRecordTableStates(recordTableId);
|
||||||
|
|
||||||
const setAvailableTableColumns = useRecoilCallback(
|
const setAvailableTableColumns = useRecoilCallback(
|
||||||
@ -194,6 +195,8 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
const isSomeCellInEditModeState =
|
const isSomeCellInEditModeState =
|
||||||
useGetIsSomeCellInEditModeState(recordTableId);
|
useGetIsSomeCellInEditModeState(recordTableId);
|
||||||
|
|
||||||
|
const setPendingRecordId = useSetRecoilState(pendingRecordIdState);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scopeId,
|
scopeId,
|
||||||
onColumnsChange,
|
onColumnsChange,
|
||||||
@ -222,5 +225,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
setHasUserSelectedAllRows,
|
setHasUserSelectedAllRows,
|
||||||
setOnToggleColumnFilter,
|
setOnToggleColumnFilter,
|
||||||
setOnToggleColumnSort,
|
setOnToggleColumnSort,
|
||||||
|
setPendingRecordId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEv
|
|||||||
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
||||||
import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer';
|
import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer';
|
||||||
import { useCloseRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCell';
|
import { useCloseRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCell';
|
||||||
|
import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord';
|
||||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
export const RecordTableCell = ({
|
export const RecordTableCell = ({
|
||||||
@ -15,19 +16,21 @@ export const RecordTableCell = ({
|
|||||||
customHotkeyScope: HotkeyScope;
|
customHotkeyScope: HotkeyScope;
|
||||||
}) => {
|
}) => {
|
||||||
const { closeTableCell } = useCloseRecordTableCell();
|
const { closeTableCell } = useCloseRecordTableCell();
|
||||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
const { upsertRecord } = useUpsertRecord();
|
||||||
|
|
||||||
const { moveLeft, moveRight, moveDown } = useRecordTableMoveFocus();
|
const { moveLeft, moveRight, moveDown } = useRecordTableMoveFocus();
|
||||||
|
|
||||||
|
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
const handleEnter: FieldInputEvent = (persistField) => {
|
const handleEnter: FieldInputEvent = (persistField) => {
|
||||||
persistField();
|
upsertRecord(persistField);
|
||||||
|
|
||||||
closeTableCell();
|
closeTableCell();
|
||||||
moveDown();
|
moveDown();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit: FieldInputEvent = (persistField) => {
|
const handleSubmit: FieldInputEvent = (persistField) => {
|
||||||
persistField();
|
upsertRecord(persistField);
|
||||||
|
|
||||||
closeTableCell();
|
closeTableCell();
|
||||||
};
|
};
|
||||||
@ -37,26 +40,26 @@ export const RecordTableCell = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClickOutside: FieldInputEvent = (persistField) => {
|
const handleClickOutside: FieldInputEvent = (persistField) => {
|
||||||
persistField();
|
upsertRecord(persistField);
|
||||||
|
|
||||||
closeTableCell();
|
closeTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEscape: FieldInputEvent = (persistField) => {
|
const handleEscape: FieldInputEvent = (persistField) => {
|
||||||
persistField();
|
upsertRecord(persistField);
|
||||||
|
|
||||||
closeTableCell();
|
closeTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTab: FieldInputEvent = (persistField) => {
|
const handleTab: FieldInputEvent = (persistField) => {
|
||||||
persistField();
|
upsertRecord(persistField);
|
||||||
|
|
||||||
closeTableCell();
|
closeTableCell();
|
||||||
moveRight();
|
moveRight();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShiftTab: FieldInputEvent = (persistField) => {
|
const handleShiftTab: FieldInputEvent = (persistField) => {
|
||||||
persistField();
|
upsertRecord(persistField);
|
||||||
|
|
||||||
closeTableCell();
|
closeTableCell();
|
||||||
moveLeft();
|
moveLeft();
|
||||||
|
|||||||
@ -0,0 +1,152 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { act, renderHook } from '@testing-library/react';
|
||||||
|
import { RecoilRoot } from 'recoil';
|
||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
|
import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord';
|
||||||
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
|
|
||||||
|
const pendingRecordId = 'a7286b9a-c039-4a89-9567-2dfa7953cda9';
|
||||||
|
const draftValue = 'updated Name';
|
||||||
|
|
||||||
|
jest.mock('@/object-record/hooks/useCreateOneRecord', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
useCreateOneRecord: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const draftValueState = createState<string | null>({
|
||||||
|
key: 'draftValueState',
|
||||||
|
defaultValue: null,
|
||||||
|
});
|
||||||
|
jest.mock(
|
||||||
|
'@/object-record/record-field/hooks/internal/useRecordFieldInputStates',
|
||||||
|
() => ({
|
||||||
|
__esModule: true,
|
||||||
|
useRecordFieldInputStates: jest.fn(() => ({
|
||||||
|
getDraftValueSelector: () => draftValueState,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const pendingRecordIdState = createState<string | null>({
|
||||||
|
key: 'pendingRecordIdState',
|
||||||
|
defaultValue: null,
|
||||||
|
});
|
||||||
|
jest.mock(
|
||||||
|
'@/object-record/record-table/hooks/internal/useRecordTableStates',
|
||||||
|
() => ({
|
||||||
|
__esModule: true,
|
||||||
|
useRecordTableStates: jest.fn(() => ({
|
||||||
|
pendingRecordIdState: pendingRecordIdState,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const createOneRecordMock = jest.fn();
|
||||||
|
const updateOneRecordMock = jest.fn();
|
||||||
|
(useCreateOneRecord as jest.Mock).mockReturnValue({
|
||||||
|
createOneRecord: createOneRecordMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Wrapper = ({
|
||||||
|
children,
|
||||||
|
pendingRecordIdMockedValue,
|
||||||
|
draftValueMockedValue,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
pendingRecordIdMockedValue: string | null;
|
||||||
|
draftValueMockedValue: string | null;
|
||||||
|
}) => (
|
||||||
|
<RecoilRoot
|
||||||
|
initializeState={(snapshot) => {
|
||||||
|
snapshot.set(pendingRecordIdState, pendingRecordIdMockedValue);
|
||||||
|
snapshot.set(draftValueState, draftValueMockedValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FieldContext.Provider
|
||||||
|
value={{
|
||||||
|
entityId: 'entityId',
|
||||||
|
fieldDefinition: {
|
||||||
|
...textfieldDefinition,
|
||||||
|
metadata: {
|
||||||
|
...textfieldDefinition.metadata,
|
||||||
|
objectMetadataNameSingular: CoreObjectNameSingular.Person,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hotkeyScope: TableHotkeyScope.Table,
|
||||||
|
isLabelIdentifier: false,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</FieldContext.Provider>
|
||||||
|
</RecoilRoot>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('useUpsertRecord', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
createOneRecordMock.mockClear();
|
||||||
|
updateOneRecordMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls update record if there is no pending record', async () => {
|
||||||
|
const { result } = renderHook(() => useUpsertRecord(), {
|
||||||
|
wrapper: ({ children }) =>
|
||||||
|
Wrapper({
|
||||||
|
pendingRecordIdMockedValue: null,
|
||||||
|
draftValueMockedValue: null,
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.upsertRecord(updateOneRecordMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createOneRecordMock).not.toHaveBeenCalled();
|
||||||
|
expect(updateOneRecordMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls update record if pending record is empty', async () => {
|
||||||
|
const { result } = renderHook(() => useUpsertRecord(), {
|
||||||
|
wrapper: ({ children }) =>
|
||||||
|
Wrapper({
|
||||||
|
pendingRecordIdMockedValue: null,
|
||||||
|
draftValueMockedValue: draftValue,
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.upsertRecord(updateOneRecordMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createOneRecordMock).not.toHaveBeenCalled();
|
||||||
|
expect(updateOneRecordMock).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls create record if pending record is not empty', async () => {
|
||||||
|
const { result } = renderHook(() => useUpsertRecord(), {
|
||||||
|
wrapper: ({ children }) =>
|
||||||
|
Wrapper({
|
||||||
|
pendingRecordIdMockedValue: pendingRecordId,
|
||||||
|
draftValueMockedValue: draftValue,
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.upsertRecord(updateOneRecordMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createOneRecordMock).toHaveBeenCalledWith({
|
||||||
|
id: pendingRecordId,
|
||||||
|
name: draftValue,
|
||||||
|
position: 'first',
|
||||||
|
});
|
||||||
|
expect(updateOneRecordMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
import { useResetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
|
||||||
@ -7,13 +10,17 @@ import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
|||||||
export const useCloseRecordTableCell = () => {
|
export const useCloseRecordTableCell = () => {
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
const { setDragSelectionStartEnabled } = useDragSelect();
|
const { setDragSelectionStartEnabled } = useDragSelect();
|
||||||
|
const { pendingRecordIdState } = useRecordTableStates();
|
||||||
|
|
||||||
const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode();
|
const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode();
|
||||||
|
const resetRecordTablePendingRecordId =
|
||||||
|
useResetRecoilState(pendingRecordIdState);
|
||||||
|
|
||||||
const closeTableCell = async () => {
|
const closeTableCell = async () => {
|
||||||
setDragSelectionStartEnabled(true);
|
setDragSelectionStartEnabled(true);
|
||||||
closeCurrentTableCellInEditMode();
|
closeCurrentTableCellInEditMode();
|
||||||
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
||||||
|
resetRecordTablePendingRecordId();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useRecordFieldInputStates } from '@/object-record/record-field/hooks/internal/useRecordFieldInputStates';
|
||||||
|
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||||
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
export const useUpsertRecord = () => {
|
||||||
|
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
|
const { pendingRecordIdState } = useRecordTableStates();
|
||||||
|
|
||||||
|
const pendingRecordId = useRecoilValue(pendingRecordIdState);
|
||||||
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
|
const { getDraftValueSelector } = useRecordFieldInputStates(
|
||||||
|
`${entityId}-${fieldName}`,
|
||||||
|
);
|
||||||
|
const draftValue = useRecoilValue(getDraftValueSelector());
|
||||||
|
|
||||||
|
const objectNameSingular =
|
||||||
|
fieldDefinition.metadata.objectMetadataNameSingular ?? '';
|
||||||
|
const { createOneRecord } = useCreateOneRecord({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const upsertRecord = (persistField: () => void) => {
|
||||||
|
if (isDefined(pendingRecordId) && isDefined(draftValue)) {
|
||||||
|
createOneRecord({
|
||||||
|
id: pendingRecordId,
|
||||||
|
name: draftValue,
|
||||||
|
position: 'first',
|
||||||
|
});
|
||||||
|
} else if (!pendingRecordId) {
|
||||||
|
persistField();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { upsertRecord };
|
||||||
|
};
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||||
|
|
||||||
|
export const recordTablePendingRecordIdComponentState = createComponentState<
|
||||||
|
string | null
|
||||||
|
>({
|
||||||
|
key: 'recordTablePendingRecordIdState',
|
||||||
|
defaultValue: null,
|
||||||
|
});
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
|
||||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
|
||||||
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
|
import { RecordIndexContainer } from '@/object-record/record-index/components/RecordIndexContainer';
|
||||||
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
|
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
|
||||||
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
|
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
|
||||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||||
@ -20,14 +20,6 @@ const StyledIndexContainer = styled.div`
|
|||||||
export const RecordIndexPage = () => {
|
export const RecordIndexPage = () => {
|
||||||
const objectNamePlural = useParams().objectNamePlural ?? '';
|
const objectNamePlural = useParams().objectNamePlural ?? '';
|
||||||
|
|
||||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
|
||||||
objectNamePlural,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { createOneRecord: createOneObject } = useCreateOneRecord({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recordIndexId = objectNamePlural ?? '';
|
const recordIndexId = objectNamePlural ?? '';
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
@ -35,12 +27,14 @@ export const RecordIndexPage = () => {
|
|||||||
scopeId: recordIndexId,
|
scopeId: recordIndexId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAddButtonClick = async () => {
|
const { setPendingRecordId } = useRecordTable({
|
||||||
await createOneObject?.({
|
recordTableId: recordIndexId,
|
||||||
position: 'first',
|
});
|
||||||
});
|
|
||||||
|
|
||||||
setSelectedTableCellEditMode(0, 0);
|
const handleAddButtonClick = async () => {
|
||||||
|
setPendingRecordId(v4());
|
||||||
|
|
||||||
|
setSelectedTableCellEditMode(-1, 0);
|
||||||
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
|
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user