diff --git a/packages/twenty-front/src/modules/spreadsheet-import/components/MatchColumnToFieldSelect.tsx b/packages/twenty-front/src/modules/spreadsheet-import/components/MatchColumnToFieldSelect.tsx index b84a09da5..45c34a1c3 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/components/MatchColumnToFieldSelect.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/components/MatchColumnToFieldSelect.tsx @@ -9,7 +9,9 @@ import { MatchColumnSelectSubFieldSelectDropdownContent } from '@/spreadsheet-im import { DO_NOT_IMPORT_OPTION_KEY } from '@/spreadsheet-import/constants/DoNotImportOptionKey'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; +import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; +import { IconChevronDown } from 'twenty-ui/display'; import { SelectOption } from 'twenty-ui/input'; import { MenuItem } from 'twenty-ui/navigation'; @@ -21,6 +23,11 @@ interface MatchColumnToFieldSelectProps { placeholder?: string; } +const StyledMenuItem = styled(MenuItem)` + background-color: ${({ theme }) => theme.background.transparent.lighter}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.sm}; +`; export const MatchColumnToFieldSelect = ({ onChange, value, @@ -112,10 +119,11 @@ export const MatchColumnToFieldSelect = ({ }} dropdownPlacement="bottom-start" clickableComponent={ - } dropdownComponents={ diff --git a/packages/twenty-front/src/modules/spreadsheet-import/components/SpreadSheetImportModalWrapper.tsx b/packages/twenty-front/src/modules/spreadsheet-import/components/SpreadSheetImportModalWrapper.tsx index cf24ca705..067ef5bf3 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/components/SpreadSheetImportModalWrapper.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/components/SpreadSheetImportModalWrapper.tsx @@ -7,12 +7,10 @@ import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; import { SpreadSheetImportModalCloseButton } from './SpreadSheetImportModalCloseButton'; const StyledModal = styled(Modal)` - height: 61%; min-height: 600px; min-width: 800px; padding: 0; position: relative; - width: 63%; @media (max-width: ${MOBILE_VIEWPORT}px) { min-width: auto; min-height: auto; @@ -42,7 +40,7 @@ export const SpreadSheetImportModalWrapper = ({ return ( theme.background.secondary}; + border: none; block-size: 100%; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.md}; width: 100%; .rdg-header-row .rdg-cell { box-shadow: none; color: ${({ theme }) => theme.font.color.tertiary}; + background-color: ${({ theme }) => theme.background.secondary}; font-size: ${({ theme }) => theme.font.size.sm}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; letter-spacing: wider; - text-transform: uppercase; ${({ headerRowHeight }) => { if (headerRowHeight === 0) { return ` @@ -68,7 +67,7 @@ const StyledDataGrid = styled(DataGrid)` } .rdg-cell-error { - background-color: ${({ theme }) => RGBA(theme.color.red, 0.08)}; + background-color: ${({ theme }) => theme.adaptiveColors.yellow1}; } .rdg-cell-warning { @@ -134,7 +133,7 @@ export const SpreadsheetImportTable = ({ return ( theme.border.color.medium}; + box-shadow: ${({ theme }) => theme.boxShadow.strong}; gap: ${({ theme }) => theme.spacing(2.5)}; justify-content: space-between; - padding: ${({ theme }) => theme.spacing(6)} ${({ theme }) => theme.spacing(8)}; + padding: ${({ theme }) => theme.spacing(4)}; + height: auto; `; type StepNavigationButtonProps = { @@ -16,6 +19,7 @@ type StepNavigationButtonProps = { title: string; isLoading?: boolean; onBack?: () => void; + isNextDisabled?: boolean; }; export const StepNavigationButton = ({ @@ -23,6 +27,7 @@ export const StepNavigationButton = ({ title, isLoading, onBack, + isNextDisabled = false, }: StepNavigationButtonProps) => { return ( @@ -39,6 +44,7 @@ export const StepNavigationButton = ({ title={title} onClick={!isLoading ? onClick : undefined} variant="primary" + disabled={isNextDisabled} /> ); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/hooks/__tests__/useSpreadsheetImportInitialStep.test.ts b/packages/twenty-front/src/modules/spreadsheet-import/hooks/__tests__/useSpreadsheetImportInitialStep.test.ts index 551f2eac7..d48443331 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/hooks/__tests__/useSpreadsheetImportInitialStep.test.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/hooks/__tests__/useSpreadsheetImportInitialStep.test.ts @@ -12,7 +12,7 @@ describe('useSpreadsheetImportInitialStep', () => { return { initialStep, setStep }; }); - expect(result.current.initialStep).toBe(-1); + expect(result.current.initialStep).toBe(0); act(() => { result.current.setStep(SpreadsheetImportStepType.upload); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/hooks/useSpreadsheetImportInitialStep.ts b/packages/twenty-front/src/modules/spreadsheet-import/hooks/useSpreadsheetImportInitialStep.ts index e645fac42..77a51c11b 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/hooks/useSpreadsheetImportInitialStep.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/hooks/useSpreadsheetImportInitialStep.ts @@ -19,7 +19,7 @@ export const useSpreadsheetImportInitialStep = ( case SpreadsheetImportStepType.validateData: return 3; default: - return -1; + return 0; } }, [initialStep]); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx index b4aae976c..514cf7f95 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx @@ -1,7 +1,6 @@ import styled from '@emotion/styled'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Heading } from '@/spreadsheet-import/components/Heading'; import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton'; import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal'; import { ImportedRow, ImportedStructuredRow } from '@/spreadsheet-import/types'; @@ -17,7 +16,10 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Modal } from '@/ui/layout/modal/components/Modal'; import { DO_NOT_IMPORT_OPTION_KEY } from '@/spreadsheet-import/constants/DoNotImportOptionKey'; +import { ColumnGrid } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/ColumnGrid'; +import { TemplateColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/TemplateColumn'; import { UnmatchColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn'; +import { UserTableColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/UserTableColumn'; import { initialComputedColumnsSelector } 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'; @@ -29,14 +31,10 @@ import { getMatchedColumnsWithFuse } from '@/spreadsheet-import/utils/getMatched import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { Trans, useLingui } from '@lingui/react/macro'; import { useRecoilState } from 'recoil'; -import { ColumnGrid } from './components/ColumnGrid'; -import { TemplateColumn } from './components/TemplateColumn'; -import { UserTableColumn } from './components/UserTableColumn'; const StyledContent = styled(Modal.Content)` align-items: center; - padding-left: ${({ theme }) => theme.spacing(6)}; - padding-right: ${({ theme }) => theme.spacing(6)}; + padding: 0px; `; const StyledColumnsContainer = styled.div` @@ -274,14 +272,17 @@ export const MatchColumnsStep = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const hasMatchedColumns = columns.some( + (column) => + ![SpreadsheetColumnType.ignored, SpreadsheetColumnType.empty].includes( + column.type, + ), + ); + return ( <> - - - + + ( @@ -307,8 +308,8 @@ export const MatchColumnsStep = ({ /> )} /> - - + + ({ onBack?.(); setColumns([]); }} + isNextDisabled={!hasMatchedColumns} /> ); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/ColumnGrid.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/ColumnGrid.tsx index 2ca5b46e9..fdf4c3cd1 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/ColumnGrid.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/ColumnGrid.tsx @@ -12,12 +12,9 @@ const StyledGridContainer = styled.div` `; const StyledGrid = styled.div` - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.md}; box-sizing: border-box; display: flex; flex-direction: column; - margin-top: ${({ theme }) => theme.spacing(8)}; width: 100%; `; @@ -73,22 +70,16 @@ const StyledGridCell = styled.div` const StyledGridHeader = styled.div` align-items: center; - background-color: ${({ theme }) => theme.background.tertiary}; + background-color: ${({ theme }) => theme.background.secondary}; + border-bottom: 1px solid ${({ theme }) => theme.border.color.medium}; box-sizing: border-box; color: ${({ theme }) => theme.font.color.light}; display: flex; flex: 1; - font-size: ${({ theme }) => theme.font.size.sm}; + font-size: ${({ theme }) => theme.font.size.xs}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; padding-left: ${({ theme }) => theme.spacing(4)}; padding-right: ${({ theme }) => theme.spacing(4)}; - ${({ position, theme }) => { - if (position === 'left') { - return `border-top-left-radius: calc(${theme.border.radius.md} - 1px);`; - } - return `border-top-right-radius: calc(${theme.border.radius.md} - 1px);`; - }}; - text-transform: uppercase; `; type ColumnGridProps = { @@ -117,7 +108,7 @@ export const ColumnGrid = ({ <> - + Imported data Twenty fields diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UserTableColumn.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UserTableColumn.tsx index 36ecc6388..46215c495 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UserTableColumn.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/components/UserTableColumn.tsx @@ -13,7 +13,7 @@ const StyledContainer = styled.div` const StyledValue = styled.span` color: ${({ theme }) => theme.font.color.primary}; - font-size: ${({ theme }) => theme.font.size.sm}; + font-size: ${({ theme }) => theme.font.size.md}; font-weight: ${({ theme }) => theme.font.weight.medium}; overflow: hidden; text-overflow: ellipsis; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx index 81e3b0b22..2964b892d 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx @@ -8,16 +8,15 @@ import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar'; import { Modal } from '@/ui/layout/modal/components/Modal'; import { useLingui } from '@lingui/react/macro'; -import { SpreadsheetImportStepper } from './SpreadsheetImportStepper'; import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; +import { SpreadsheetImportStepper } from './SpreadsheetImportStepper'; const StyledHeader = styled(Modal.Header)` background-color: ${({ theme }) => theme.background.secondary}; border-bottom: 1px solid ${({ theme }) => theme.border.color.medium}; + padding: 0px ${({ theme }) => theme.spacing(30)}; height: 60px; - padding: 0px; - padding-left: ${({ theme }) => theme.spacing(30)}; - padding-right: ${({ theme }) => theme.spacing(30)}; + flex-shrink: 0; @media (max-width: ${MOBILE_VIEWPORT}px) { padding-left: ${({ theme }) => theme.spacing(4)}; padding-right: ${({ theme }) => theme.spacing(4)}; @@ -28,9 +27,9 @@ export const SpreadsheetImportStepperContainer = () => { const { t } = useLingui(); const stepTitles = { - uploadStep: t`Upload file`, - matchColumnsStep: t`Match columns`, - validationStep: t`Validate data`, + uploadStep: t`Upload File`, + matchColumnsStep: t`Match Columns`, + validationStep: t`Validate Data`, }; const { initialStepState } = useSpreadsheetImportInternal(); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx index 0af52d913..78427ccd1 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx @@ -1,4 +1,3 @@ -import { Heading } from '@/spreadsheet-import/components/Heading'; import { SpreadsheetImportTable } from '@/spreadsheet-import/components/SpreadsheetImportTable'; import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton'; import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal'; @@ -31,16 +30,30 @@ import { generateColumns } from './components/columns'; import { ImportedStructuredRowMetadata } from './types'; const StyledContent = styled(Modal.Content)` - padding-left: ${({ theme }) => theme.spacing(6)}; - padding-right: ${({ theme }) => theme.spacing(6)}; + padding: 0px; + position: relative; `; const StyledToolbar = styled.div` + align-items: center; + border-radius: ${({ theme }) => theme.border.radius.md}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + background-color: ${({ theme }) => theme.background.secondary}; + bottom: ${({ theme }) => theme.spacing(3)}; display: flex; flex-direction: row; justify-content: space-between; - margin-bottom: ${({ theme }) => theme.spacing(4)}; - margin-top: ${({ theme }) => theme.spacing(8)}; + left: 50%; + position: absolute; + transform: translateX(-50%); + width: 400px; + padding: ${({ theme }) => theme.spacing(3)}; + z-index: 1; + box-shadow: ${({ theme }) => theme.boxShadow.strong}; +`; + +const StyledButton = styled(Button)` + height: 24px; `; const StyledErrorToggle = styled.div` @@ -50,8 +63,8 @@ const StyledErrorToggle = styled.div` `; const StyledErrorToggleDescription = styled.span` - color: ${({ theme }) => theme.font.color.primary}; - font-size: ${({ theme }) => theme.font.size.sm}; + color: ${({ theme }) => theme.font.color.secondary}; + font-size: ${({ theme }) => theme.font.size.md}; font-weight: ${({ theme }) => theme.font.weight.regular}; margin-left: ${({ theme }) => theme.spacing(2)}; `; @@ -71,6 +84,13 @@ const StyledNoRowsContainer = styled.div` margin-top: ${({ theme }) => theme.spacing(8)}; `; +const StyledNoRowsWithErrorsContainer = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + justify-content: center; + margin: auto 0; +`; + type ValidationStepProps = { initialData: ImportedStructuredRow[]; importedColumns: SpreadsheetColumns; @@ -103,7 +123,6 @@ export const ValidationStep = ({ ReadonlySet >(new Set()); const [filterByErrors, setFilterByErrors] = useState(false); - const [showUnmatchedColumns, setShowUnmatchedColumns] = useState(false); const updateData = useCallback( (rows: typeof data) => { @@ -164,11 +183,11 @@ export const ValidationStep = ({ column.key === 'select-row', ).length > 0; - if (!hasBeenImported && !showUnmatchedColumns) return null; + if (!hasBeenImported) return null; return column; }) .filter(Boolean), - [fields, importedColumns, showUnmatchedColumns], + [fields, importedColumns], ); const tableData = useMemo(() => { @@ -255,56 +274,51 @@ export const ValidationStep = ({ return ( <> - + {filterByErrors && tableData.length === 0 ? ( + + No rows with errors + + ) : ( + + + {filterByErrors + ? t`No data containing errors` + : t`No data found`} + + ), + }} + /> + + )} setFilterByErrors(!filterByErrors)} + toggleSize="small" /> Show only rows with errors - - setShowUnmatchedColumns(!showUnmatchedColumns)} - /> - - Show unmatched columns - - -