Improve Data Importer Select Matching - Post Merge Updates (#6750)
This PR was created by [GitStart](https://gitstart.com/) to address the requirements from this ticket: [TWNTY-6135-1](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-6135-1). --- ### Description This [PR](https://github.com/twentyhq/twenty/pull/6338) was merged, but the reviewr asked for some changes in another PR https://github.com/twentyhq/twenty/pull/6338#pullrequestreview-2255006727 ### Refs https://github.com/twentyhq/twenty/pull/6338 ### Demo <https://jam.dev/c/80336c7a-4536-4a58-b531-981bac81cb26> Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
0531eb5015
commit
7c894fe870
@ -89,6 +89,7 @@ export const SelectFieldInput = ({
|
||||
fieldDefinition.metadata.isNullable ? handleClearField : undefined
|
||||
}
|
||||
clearLabel={fieldDefinition.label}
|
||||
hotkeyScope={hotkeyScope}
|
||||
/>
|
||||
</SelectableList>
|
||||
</div>
|
||||
|
||||
@ -21,8 +21,8 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
|
||||
import { initialComputedColumnsSelector } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/states/initialComputedColumnsState';
|
||||
import { UnmatchColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn';
|
||||
import { initialComputedColumnsState } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/states/initialComputedColumnsState';
|
||||
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
|
||||
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
|
||||
import { useRecoilState } from 'recoil';
|
||||
@ -63,7 +63,7 @@ export type MatchColumnsStepProps = {
|
||||
setPreviousStepState: (currentStepState: SpreadsheetImportStep) => void;
|
||||
currentStepState: SpreadsheetImportStep;
|
||||
nextStep: () => void;
|
||||
errorToast: (message: string) => void;
|
||||
onError: (message: string) => void;
|
||||
};
|
||||
|
||||
export enum ColumnType {
|
||||
@ -136,7 +136,7 @@ export const MatchColumnsStep = <T extends string>({
|
||||
setPreviousStepState,
|
||||
currentStepState,
|
||||
nextStep,
|
||||
errorToast,
|
||||
onError,
|
||||
}: MatchColumnsStepProps) => {
|
||||
const { enqueueDialog } = useDialogManager();
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
@ -145,7 +145,7 @@ export const MatchColumnsStep = <T extends string>({
|
||||
useSpreadsheetImportInternal<T>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [columns, setColumns] = useRecoilState(
|
||||
initialComputedColumnsState(headerValues),
|
||||
initialComputedColumnsSelector(headerValues),
|
||||
);
|
||||
|
||||
const { matchColumnsStepHook } = useSpreadsheetImportInternal();
|
||||
@ -215,7 +215,7 @@ export const MatchColumnsStep = <T extends string>({
|
||||
],
|
||||
);
|
||||
|
||||
const onContinue = useCallback(
|
||||
const handleContinue = useCallback(
|
||||
async (
|
||||
values: ImportedStructuredRow<string>[],
|
||||
rawData: ImportedRow[],
|
||||
@ -231,11 +231,11 @@ export const MatchColumnsStep = <T extends string>({
|
||||
setPreviousStepState(currentStepState);
|
||||
nextStep();
|
||||
} catch (e) {
|
||||
errorToast((e as Error).message);
|
||||
onError((e as Error).message);
|
||||
}
|
||||
},
|
||||
[
|
||||
errorToast,
|
||||
onError,
|
||||
matchColumnsStepHook,
|
||||
nextStep,
|
||||
setPreviousStepState,
|
||||
@ -263,9 +263,13 @@ export const MatchColumnsStep = <T extends string>({
|
||||
|
||||
const handleAlertOnContinue = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
await onContinue(normalizeTableData(columns, data, fields), data, columns);
|
||||
await handleContinue(
|
||||
normalizeTableData(columns, data, fields),
|
||||
data,
|
||||
columns,
|
||||
);
|
||||
setIsLoading(false);
|
||||
}, [onContinue, columns, data, fields]);
|
||||
}, [handleContinue, columns, data, fields]);
|
||||
|
||||
const handleOnContinue = useCallback(async () => {
|
||||
if (unmatchedRequiredFields.length > 0) {
|
||||
@ -293,7 +297,7 @@ export const MatchColumnsStep = <T extends string>({
|
||||
});
|
||||
} else {
|
||||
setIsLoading(true);
|
||||
await onContinue(
|
||||
await handleContinue(
|
||||
normalizeTableData(columns, data, fields),
|
||||
data,
|
||||
columns,
|
||||
@ -304,7 +308,7 @@ export const MatchColumnsStep = <T extends string>({
|
||||
unmatchedRequiredFields,
|
||||
enqueueDialog,
|
||||
handleAlertOnContinue,
|
||||
onContinue,
|
||||
handleContinue,
|
||||
columns,
|
||||
data,
|
||||
fields,
|
||||
|
||||
@ -6,8 +6,10 @@ import { SelectOption } from '@/spreadsheet-import/types';
|
||||
|
||||
import { getFieldOptions } from '@/spreadsheet-import/utils/getFieldOptions';
|
||||
|
||||
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
|
||||
import { SelectInput } from '@/ui/input/components/SelectInput';
|
||||
import { useState } from 'react';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { IconChevronDown, Tag, TagColor } from 'twenty-ui';
|
||||
import {
|
||||
MatchedOptions,
|
||||
@ -83,6 +85,12 @@ export const SubMatchingSelect = <T extends string>({
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
useEffect(() => {
|
||||
setHotkeyScope(SelectFieldHotkeyScope.SelectField);
|
||||
}, [setHotkeyScope]);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledControlContainer cursor="default">
|
||||
@ -113,6 +121,7 @@ export const SubMatchingSelect = <T extends string>({
|
||||
options={options}
|
||||
onOptionSelected={handleSelect}
|
||||
onCancel={() => setIsOpen(false)}
|
||||
hotkeyScope={SelectFieldHotkeyScope.SelectField}
|
||||
/>
|
||||
)}
|
||||
</StyledControlContainer>
|
||||
|
||||
@ -10,11 +10,11 @@ export const matchColumnsState = atom({
|
||||
default: [] as Columns<string>,
|
||||
});
|
||||
|
||||
export const initialComputedColumnsState = selectorFamily<
|
||||
export const initialComputedColumnsSelector = selectorFamily<
|
||||
Columns<string>,
|
||||
ImportedRow
|
||||
>({
|
||||
key: 'InitialComputedColumnsState',
|
||||
key: 'initialComputedColumnsSelector',
|
||||
get:
|
||||
(headerValues: ImportedRow) =>
|
||||
({ get }) => {
|
||||
|
||||
@ -27,7 +27,7 @@ type SelectHeaderStepProps = {
|
||||
setCurrentStepState: (currentStepState: SpreadsheetImportStep) => void;
|
||||
nextStep: () => void;
|
||||
setPreviousStepState: (currentStepState: SpreadsheetImportStep) => void;
|
||||
errorToast: (message: string) => void;
|
||||
onError: (message: string) => void;
|
||||
onBack: () => void;
|
||||
currentStepState: SpreadsheetImportStep;
|
||||
};
|
||||
@ -37,7 +37,7 @@ export const SelectHeaderStep = ({
|
||||
setCurrentStepState,
|
||||
nextStep,
|
||||
setPreviousStepState,
|
||||
errorToast,
|
||||
onError,
|
||||
onBack,
|
||||
currentStepState,
|
||||
}: SelectHeaderStepProps) => {
|
||||
@ -49,7 +49,7 @@ export const SelectHeaderStep = ({
|
||||
|
||||
const { selectHeaderStepHook } = useSpreadsheetImportInternal();
|
||||
|
||||
const onContinue = useCallback(
|
||||
const handleContinue = useCallback(
|
||||
async (...args: Parameters<typeof selectHeaderStepHook>) => {
|
||||
try {
|
||||
const { importedRows: data, headerRow: headerValues } =
|
||||
@ -62,11 +62,11 @@ export const SelectHeaderStep = ({
|
||||
setPreviousStepState(currentStepState);
|
||||
nextStep();
|
||||
} catch (e) {
|
||||
errorToast((e as Error).message);
|
||||
onError((e as Error).message);
|
||||
}
|
||||
},
|
||||
[
|
||||
errorToast,
|
||||
onError,
|
||||
nextStep,
|
||||
selectHeaderStepHook,
|
||||
setPreviousStepState,
|
||||
@ -75,17 +75,17 @@ export const SelectHeaderStep = ({
|
||||
],
|
||||
);
|
||||
|
||||
const handleContinue = useCallback(async () => {
|
||||
const handleOnContinue = useCallback(async () => {
|
||||
const [selectedRowIndex] = Array.from(new Set(selectedRowIndexes));
|
||||
// We consider data above header to be redundant
|
||||
const trimmedData = importedRows.slice(selectedRowIndex + 1);
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
await onContinue(importedRows[selectedRowIndex], trimmedData);
|
||||
await handleContinue(importedRows[selectedRowIndex], trimmedData);
|
||||
|
||||
setIsLoading(false);
|
||||
}, [onContinue, importedRows, selectedRowIndexes]);
|
||||
}, [handleContinue, importedRows, selectedRowIndexes]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -100,7 +100,7 @@ export const SelectHeaderStep = ({
|
||||
</StyledTableContainer>
|
||||
</Modal.Content>
|
||||
<StepNavigationButton
|
||||
onClick={handleContinue}
|
||||
onClick={handleOnContinue}
|
||||
onBack={onBack}
|
||||
title="Continue"
|
||||
isLoading={isLoading}
|
||||
|
||||
@ -35,7 +35,7 @@ type SelectSheetStepProps = {
|
||||
sheetNames: string[];
|
||||
onBack: () => void;
|
||||
setCurrentStepState: (data: SpreadsheetImportStep) => void;
|
||||
errorToast: (message: string) => void;
|
||||
onError: (message: string) => void;
|
||||
setPreviousStepState: (data: SpreadsheetImportStep) => void;
|
||||
currentStepState: {
|
||||
type: SpreadsheetImportStepType.selectSheet;
|
||||
@ -46,7 +46,7 @@ type SelectSheetStepProps = {
|
||||
export const SelectSheetStep = ({
|
||||
sheetNames,
|
||||
setCurrentStepState,
|
||||
errorToast,
|
||||
onError,
|
||||
setPreviousStepState,
|
||||
onBack,
|
||||
currentStepState,
|
||||
@ -57,7 +57,7 @@ export const SelectSheetStep = ({
|
||||
|
||||
const { maxRecords, uploadStepHook } = useSpreadsheetImportInternal();
|
||||
|
||||
const onContinue = useCallback(
|
||||
const handleContinue = useCallback(
|
||||
async (sheetName: string) => {
|
||||
if (
|
||||
maxRecords > 0 &&
|
||||
@ -66,7 +66,7 @@ export const SelectSheetStep = ({
|
||||
maxRecords,
|
||||
)
|
||||
) {
|
||||
errorToast(`Too many records. Up to ${maxRecords.toString()} allowed`);
|
||||
onError(`Too many records. Up to ${maxRecords.toString()} allowed`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -79,11 +79,11 @@ export const SelectSheetStep = ({
|
||||
});
|
||||
setPreviousStepState(currentStepState);
|
||||
} catch (e) {
|
||||
errorToast((e as Error).message);
|
||||
onError((e as Error).message);
|
||||
}
|
||||
},
|
||||
[
|
||||
errorToast,
|
||||
onError,
|
||||
maxRecords,
|
||||
currentStepState,
|
||||
setPreviousStepState,
|
||||
@ -95,10 +95,10 @@ export const SelectSheetStep = ({
|
||||
const handleOnContinue = useCallback(
|
||||
async (data: typeof value) => {
|
||||
setIsLoading(true);
|
||||
await onContinue(data);
|
||||
await handleContinue(data);
|
||||
setIsLoading(false);
|
||||
},
|
||||
[onContinue],
|
||||
[handleContinue],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -48,7 +48,7 @@ export const SpreadsheetImportStepper = ({
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const errorToast = useCallback(
|
||||
const handleError = useCallback(
|
||||
(description: string) => {
|
||||
enqueueSnackBar(description, {
|
||||
title: 'Error',
|
||||
@ -58,7 +58,7 @@ export const SpreadsheetImportStepper = ({
|
||||
[enqueueSnackBar],
|
||||
);
|
||||
|
||||
const onBack = useCallback(() => {
|
||||
const handleBack = useCallback(() => {
|
||||
setCurrentStepState(previousStepState);
|
||||
prevStep();
|
||||
}, [prevStep, previousStepState]);
|
||||
@ -71,7 +71,7 @@ export const SpreadsheetImportStepper = ({
|
||||
currentStepState={currentStepState}
|
||||
setPreviousStepState={setPreviousStepState}
|
||||
setCurrentStepState={setCurrentStepState}
|
||||
errorToast={errorToast}
|
||||
onError={handleError}
|
||||
nextStep={nextStep}
|
||||
/>
|
||||
);
|
||||
@ -81,9 +81,9 @@ export const SpreadsheetImportStepper = ({
|
||||
sheetNames={currentStepState.workbook.SheetNames}
|
||||
setCurrentStepState={setCurrentStepState}
|
||||
currentStepState={currentStepState}
|
||||
errorToast={errorToast}
|
||||
onError={handleError}
|
||||
setPreviousStepState={setPreviousStepState}
|
||||
onBack={onBack}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
case SpreadsheetImportStepType.selectHeader:
|
||||
@ -93,8 +93,8 @@ export const SpreadsheetImportStepper = ({
|
||||
setCurrentStepState={setCurrentStepState}
|
||||
nextStep={nextStep}
|
||||
setPreviousStepState={setPreviousStepState}
|
||||
errorToast={errorToast}
|
||||
onBack={onBack}
|
||||
onError={handleError}
|
||||
onBack={handleBack}
|
||||
currentStepState={currentStepState}
|
||||
/>
|
||||
);
|
||||
@ -107,10 +107,8 @@ export const SpreadsheetImportStepper = ({
|
||||
setPreviousStepState={setPreviousStepState}
|
||||
currentStepState={currentStepState}
|
||||
nextStep={nextStep}
|
||||
onBack={() => {
|
||||
onBack();
|
||||
}}
|
||||
errorToast={errorToast}
|
||||
onBack={handleBack}
|
||||
onError={handleError}
|
||||
/>
|
||||
);
|
||||
case SpreadsheetImportStepType.validateData:
|
||||
@ -124,7 +122,7 @@ export const SpreadsheetImportStepper = ({
|
||||
file={uploadedFile}
|
||||
setCurrentStepState={setCurrentStepState}
|
||||
onBack={() => {
|
||||
onBack();
|
||||
handleBack();
|
||||
setPreviousStepState(
|
||||
initialStepState || { type: SpreadsheetImportStepType.upload },
|
||||
);
|
||||
|
||||
@ -18,7 +18,7 @@ const StyledContent = styled(Modal.Content)`
|
||||
type UploadStepProps = {
|
||||
setUploadedFile: (file: File) => void;
|
||||
setCurrentStepState: (data: any) => void;
|
||||
errorToast: (message: string) => void;
|
||||
onError: (message: string) => void;
|
||||
nextStep: () => void;
|
||||
setPreviousStepState: (data: any) => void;
|
||||
currentStepState: SpreadsheetImportStep;
|
||||
@ -27,7 +27,7 @@ type UploadStepProps = {
|
||||
export const UploadStep = ({
|
||||
setUploadedFile,
|
||||
setCurrentStepState,
|
||||
errorToast,
|
||||
onError,
|
||||
nextStep,
|
||||
setPreviousStepState,
|
||||
currentStepState,
|
||||
@ -36,7 +36,7 @@ export const UploadStep = ({
|
||||
const { maxRecords, uploadStepHook, selectHeaderStepHook, selectHeader } =
|
||||
useSpreadsheetImportInternal();
|
||||
|
||||
const onContinue = useCallback(
|
||||
const handleContinue = useCallback(
|
||||
async (workbook: WorkBook, file: File) => {
|
||||
setUploadedFile(file);
|
||||
const isSingleSheet = workbook.SheetNames.length === 1;
|
||||
@ -45,9 +45,7 @@ export const UploadStep = ({
|
||||
maxRecords > 0 &&
|
||||
exceedsMaxRecords(workbook.Sheets[workbook.SheetNames[0]], maxRecords)
|
||||
) {
|
||||
errorToast(
|
||||
`Too many records. Up to ${maxRecords.toString()} allowed`,
|
||||
);
|
||||
onError(`Too many records. Up to ${maxRecords.toString()} allowed`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -72,7 +70,7 @@ export const UploadStep = ({
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
errorToast((e as Error).message);
|
||||
onError((e as Error).message);
|
||||
}
|
||||
} else {
|
||||
setCurrentStepState({
|
||||
@ -84,7 +82,7 @@ export const UploadStep = ({
|
||||
nextStep();
|
||||
},
|
||||
[
|
||||
errorToast,
|
||||
onError,
|
||||
maxRecords,
|
||||
nextStep,
|
||||
selectHeader,
|
||||
@ -100,10 +98,10 @@ export const UploadStep = ({
|
||||
const handleOnContinue = useCallback(
|
||||
async (data: WorkBook, file: File) => {
|
||||
setIsLoading(true);
|
||||
await onContinue(data, file);
|
||||
await handleContinue(data, file);
|
||||
setIsLoading(false);
|
||||
},
|
||||
[onContinue],
|
||||
[handleContinue],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -72,7 +72,7 @@ export const Default = () => (
|
||||
setPreviousStepState={() => null}
|
||||
currentStepState={{} as SpreadsheetImportStep}
|
||||
nextStep={() => null}
|
||||
errorToast={() => null}
|
||||
onError={() => null}
|
||||
/>
|
||||
</ModalWrapper>
|
||||
</ReactSpreadsheetImportContextProvider>
|
||||
|
||||
@ -29,7 +29,7 @@ export const Default = () => (
|
||||
setCurrentStepState={() => null}
|
||||
nextStep={() => Promise.resolve()}
|
||||
setPreviousStepState={() => null}
|
||||
errorToast={() => null}
|
||||
onError={() => null}
|
||||
onBack={() => Promise.resolve()}
|
||||
currentStepState={{
|
||||
type: SpreadsheetImportStepType.selectHeader,
|
||||
|
||||
@ -50,7 +50,7 @@ export const Default = () => (
|
||||
},
|
||||
},
|
||||
}}
|
||||
errorToast={() => null}
|
||||
onError={() => null}
|
||||
onBack={() => Promise.resolve()}
|
||||
/>
|
||||
</ModalWrapper>
|
||||
|
||||
@ -26,7 +26,7 @@ export const Default = () => (
|
||||
<UploadStep
|
||||
setUploadedFile={() => null}
|
||||
setCurrentStepState={() => null}
|
||||
errorToast={() => null}
|
||||
onError={() => null}
|
||||
nextStep={() => null}
|
||||
setPreviousStepState={() => null}
|
||||
currentStepState={{
|
||||
|
||||
@ -2,14 +2,12 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { SelectOption } from '@/spreadsheet-import/types';
|
||||
|
||||
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { MenuItemSelectTag } from '@/ui/navigation/menu-item/components/MenuItemSelectTag';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import {
|
||||
@ -40,6 +38,7 @@ interface SelectInputProps {
|
||||
onFilterChange?: (filteredOptions: SelectOption[]) => void;
|
||||
onClear?: () => void;
|
||||
clearLabel?: string;
|
||||
hotkeyScope: string;
|
||||
}
|
||||
|
||||
export const SelectInput = ({
|
||||
@ -51,6 +50,7 @@ export const SelectInput = ({
|
||||
defaultOption,
|
||||
parentRef,
|
||||
onFilterChange,
|
||||
hotkeyScope,
|
||||
}: SelectInputProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -97,12 +97,6 @@ export const SelectInput = ({
|
||||
placement: 'bottom-start',
|
||||
});
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
useEffect(() => {
|
||||
setHotkeyScope(SelectFieldHotkeyScope.SelectField);
|
||||
}, [setHotkeyScope]);
|
||||
|
||||
useEffect(() => {
|
||||
onFilterChange?.(optionsInDropDown);
|
||||
}, [onFilterChange, optionsInDropDown]);
|
||||
@ -132,7 +126,7 @@ export const SelectInput = ({
|
||||
handleOptionChange(selectedOption);
|
||||
}
|
||||
},
|
||||
SelectFieldHotkeyScope.SelectField,
|
||||
hotkeyScope,
|
||||
[searchFilter, optionsInDropDown],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user