From a2ccb643ffc00b660c82c19eec3e73f90d50ed66 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Tue, 25 Jul 2023 20:00:15 +0200 Subject: [PATCH] Optimize table loading (#866) * wip * wip * Ok * Deleted unused code * Fixed lint * Minor fixes * Minor fixes * Minor Fixes * Minor merge fixes * Ok * Fix storybook tests * Removed console.log * Fix login * asd * Fixed storybook * Added await * Fixed await * Added sleep for failing test * Fix sleep * Fix test * Fix tests --------- Co-authored-by: Charles Bochet --- front/.storybook/preview.ts | 1 + front/package.json | 1 + front/src/hooks/usePrevious.ts | 11 -- front/src/index.tsx | 34 ++--- .../analytics/hooks/useTrackPageView.ts | 26 ---- .../components/CompanyAccountOwnerPicker.tsx | 20 ++- .../components/CompanyEditableNameCell.tsx | 15 +- .../components/CompanyEntityTableData.tsx | 22 --- .../CompanyEntityTableDataMocked.tsx | 24 +-- .../table/components/CompanyTable.tsx | 6 - .../table/components/CompanyTableMockMode.tsx | 6 - .../EditableCompanyDomainNameCell.tsx | 15 +- .../table/hooks/useSetCompanyEntityTable.ts | 43 +++++- .../components/EditablePeopleFullName.tsx | 12 +- .../people/components/PeopleCompanyPicker.tsx | 27 ++-- .../PeopleCompanyEditableFieldEditMode.tsx | 25 +-- .../people/hooks/useSetPeopleEntityTable.ts | 40 +++++ .../components/EditablePeopleCityCell.tsx | 15 +- .../components/EditablePeopleFullNameCell.tsx | 27 +--- .../components/EditablePeopleJobTitleCell.tsx | 15 +- .../EditablePeopleLinkedinUrlCell.tsx | 15 +- .../components/EditablePeoplePhoneCell.tsx | 15 +- .../people/table/components/PeopleTable.tsx | 6 - .../PipelineProgressPointOfContactPicker.tsx | 8 +- .../settings/components/SettingsNavbar.tsx | 7 +- .../components/BoardCardEditableFieldText.tsx | 4 +- .../ui/debug/components/TimingProfiler.tsx | 42 ++++++ .../components/FilterDropdownButton.tsx | 12 -- .../FilterDropdownEntitySearchSelect.tsx | 10 +- .../src/modules/ui/hooks/useIsPageLoading.ts | 12 ++ .../hooks/internal/useHotkeyScopeAutoSync.ts | 30 ---- .../hotkey/hooks/useScopedHotkeyCallback.ts | 40 +++++ .../ui/hotkey/hooks/useScopedHotkeys.ts | 35 +++-- .../hotkey/hooks/useSequenceScopedHotkeys.ts | 60 ++++++-- .../ui/hotkey/hooks/useSetHotkeyScope.ts | 21 ++- .../components/InplaceInputDoubleText.tsx | 6 +- .../components/InplaceInputTextEditMode.tsx | 51 ++++++- .../ui/layout/components/DefaultLayout.tsx | 5 +- .../layout/hooks/useAutoNavigateOnboarding.ts | 52 ------- .../components/SingleEntitySelect.tsx | 8 +- .../components/SingleEntitySelectBase.tsx | 2 +- .../ui/states/currentPageLocationState.ts | 6 + .../ui/table/components/EntityTable.tsx | 10 ++ .../ui/table/components/EntityTableBody.tsx | 22 +-- .../ui/table/components/EntityTableCell.tsx | 42 ++---- .../ui/table/components/EntityTableRow.tsx | 58 +------ .../ui/table/components/HooksEntityTable.tsx | 25 --- .../editable-cell/components/EditableCell.tsx | 55 ++++--- .../components/EditableCellEditMode.tsx | 15 +- .../__stories__/EditableCellText.stories.tsx | 14 +- .../hooks/useCurrentCellPosition.ts | 21 +-- .../editable-cell/hooks/useEditableCell.ts | 16 +- .../hooks/useRegisterCloseCellHandlers.ts | 2 + .../hooks/useRegisterEditableCell.ts | 23 --- .../hooks/useSetSoftFocusOnCurrentCell.ts | 31 +--- .../customCellHotkeyScopeScopedState.ts | 11 -- .../types/EditableCellDateEditMode.tsx | 17 ++- .../types/EditableCellDoubleText.tsx | 26 +--- .../types/EditableCellDoubleTextEditMode.tsx | 56 +++++-- .../editable-cell/types/EditableCellPhone.tsx | 33 +--- .../editable-cell/types/EditableCellText.tsx | 25 +-- .../editable-cell/types/EditableCellURL.tsx | 29 +--- .../editable-cell/types/EditableChip.tsx | 28 +--- .../ui/table/hooks/useCurrentEntityId.ts | 12 +- .../ui/table/hooks/useCurrentRowSelected.ts | 6 +- .../ui/table/hooks/useLeaveTableFocus.ts | 2 - .../modules/ui/table/states/CellContext.ts | 3 - .../ui/table/states/CellHotkeyScopeContext.ts | 5 + .../ui/table/states/ColumnIndexContext.ts | 3 + .../src/modules/ui/table/states/RowContext.ts | 3 - .../modules/ui/table/states/RowIdContext.ts | 3 + .../ui/table/states/RowIndexContext.ts | 3 + .../__stories__/TableHeader.stories.tsx | 13 +- front/src/pages/auth/Verify.tsx | 1 + front/src/sync-hooks/AnalyticsHook.tsx | 7 - front/src/sync-hooks/AppInternalHooks.tsx | 6 - front/src/sync-hooks/AuthAutoRouter.tsx | 142 ++++++++++++++++++ .../sync-hooks/HotkeyScopeAutoSyncHook.tsx | 7 - .../HotkeyScopeBrowserRouterSync.tsx | 73 --------- .../testing/InitializeHotkeyStorybookHook.tsx | 14 ++ .../decorators/CellPositionDecorator.tsx | 13 +- .../src/testing/decorators/PageDecorator.tsx | 25 ++- .../src/testing/decorators/RootDecorator.tsx | 2 + front/src/utils/measureTotalFrameLoad.ts | 11 ++ front/yarn.lock | 5 + 85 files changed, 846 insertions(+), 904 deletions(-) delete mode 100644 front/src/hooks/usePrevious.ts delete mode 100644 front/src/modules/analytics/hooks/useTrackPageView.ts create mode 100644 front/src/modules/ui/debug/components/TimingProfiler.tsx create mode 100644 front/src/modules/ui/hooks/useIsPageLoading.ts delete mode 100644 front/src/modules/ui/hotkey/hooks/internal/useHotkeyScopeAutoSync.ts create mode 100644 front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts delete mode 100644 front/src/modules/ui/layout/hooks/useAutoNavigateOnboarding.ts create mode 100644 front/src/modules/ui/states/currentPageLocationState.ts delete mode 100644 front/src/modules/ui/table/components/HooksEntityTable.tsx delete mode 100644 front/src/modules/ui/table/editable-cell/hooks/useRegisterEditableCell.ts delete mode 100644 front/src/modules/ui/table/editable-cell/states/customCellHotkeyScopeScopedState.ts delete mode 100644 front/src/modules/ui/table/states/CellContext.ts create mode 100644 front/src/modules/ui/table/states/CellHotkeyScopeContext.ts create mode 100644 front/src/modules/ui/table/states/ColumnIndexContext.ts delete mode 100644 front/src/modules/ui/table/states/RowContext.ts create mode 100644 front/src/modules/ui/table/states/RowIdContext.ts create mode 100644 front/src/modules/ui/table/states/RowIndexContext.ts delete mode 100644 front/src/sync-hooks/AnalyticsHook.tsx create mode 100644 front/src/sync-hooks/AuthAutoRouter.tsx delete mode 100644 front/src/sync-hooks/HotkeyScopeAutoSyncHook.tsx delete mode 100644 front/src/sync-hooks/HotkeyScopeBrowserRouterSync.tsx create mode 100644 front/src/testing/InitializeHotkeyStorybookHook.tsx create mode 100644 front/src/utils/measureTotalFrameLoad.ts diff --git a/front/.storybook/preview.ts b/front/.storybook/preview.ts index 77ee7cdd0..70e33a8a6 100644 --- a/front/.storybook/preview.ts +++ b/front/.storybook/preview.ts @@ -6,6 +6,7 @@ import { lightTheme, darkTheme } from '../src/modules/ui/themes/themes'; import { RootDecorator } from '../src/testing/decorators/RootDecorator'; import 'react-loading-skeleton/dist/skeleton.css'; import { mockedUserJWT } from '../src/testing/mock-data/jwt'; + initialize(); const preview: Preview = { diff --git a/front/package.json b/front/package.json index 2101ad308..891ab1062 100644 --- a/front/package.json +++ b/front/package.json @@ -17,6 +17,7 @@ "@types/react": "^18.0.25", "@types/react-dom": "^18.0.9", "@types/react-modal": "^3.16.0", + "afterframe": "^1.0.2", "apollo-link-rest": "^0.9.0", "apollo-upload-client": "^17.0.0", "cmdk": "^0.2.0", diff --git a/front/src/hooks/usePrevious.ts b/front/src/hooks/usePrevious.ts deleted file mode 100644 index a5233144c..000000000 --- a/front/src/hooks/usePrevious.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useEffect, useRef } from 'react'; - -export default function usePrevious(state: T): T | undefined { - const ref = useRef(); - - useEffect(() => { - ref.current = state; - }); - - return ref.current; -} diff --git a/front/src/index.tsx b/front/src/index.tsx index 7371d3200..0afba335f 100644 --- a/front/src/index.tsx +++ b/front/src/index.tsx @@ -1,12 +1,10 @@ import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; -import { HotkeysProvider } from 'react-hotkeys-hook'; import { BrowserRouter } from 'react-router-dom'; import { RecoilRoot } from 'recoil'; import { ApolloProvider } from '@/apollo/components/ApolloProvider'; import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider'; -import { INITIAL_HOTKEYS_SCOPES } from '@/ui/hotkey/constants'; import { SnackBarProvider } from '@/ui/snack-bar/components/SnackBarProvider'; import { AppThemeProvider } from '@/ui/themes/components/AppThemeProvider'; import { ThemeType } from '@/ui/themes/themes'; @@ -14,6 +12,7 @@ import { UserProvider } from '@/users/components/UserProvider'; import '@emotion/react'; +import { AuthAutoRouter } from './sync-hooks/AuthAutoRouter'; import { App } from './App'; import './index.css'; @@ -26,23 +25,20 @@ const root = ReactDOM.createRoot( root.render( - - - - - - - - - - - - - - - + + + + + + + + + + + + + + , ); diff --git a/front/src/modules/analytics/hooks/useTrackPageView.ts b/front/src/modules/analytics/hooks/useTrackPageView.ts deleted file mode 100644 index 05bae16a6..000000000 --- a/front/src/modules/analytics/hooks/useTrackPageView.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useEffect } from 'react'; -import { useLocation } from 'react-router-dom'; - -import usePrevious from '~/hooks/usePrevious'; - -import { useEventTracker } from './useEventTracker'; - -export function useTrackPageView() { - const location = useLocation(); - const previousLocation = usePrevious(location); - const eventTracker = useEventTracker(); - - useEffect(() => { - // Avoid lot of pageview events enven if the location is the same - if ( - !previousLocation?.pathname || - previousLocation?.pathname !== location.pathname - ) { - eventTracker('pageview', { - location: { - pathname: location.pathname, - }, - }); - } - }, [location, eventTracker, previousLocation?.pathname]); -} diff --git a/front/src/modules/companies/components/CompanyAccountOwnerPicker.tsx b/front/src/modules/companies/components/CompanyAccountOwnerPicker.tsx index 66ee92cbd..45bad5e05 100644 --- a/front/src/modules/companies/components/CompanyAccountOwnerPicker.tsx +++ b/front/src/modules/companies/components/CompanyAccountOwnerPicker.tsx @@ -48,15 +48,19 @@ export function CompanyAccountOwnerPicker({ searchOnFields: ['firstName', 'lastName'], }); - async function handleEntitySelected(selectedUser: UserForSelect) { - await updateCompany({ - variables: { - where: { id: company.id }, - data: { - accountOwner: { connect: { id: selectedUser.id } }, + async function handleEntitySelected( + selectedUser: UserForSelect | null | undefined, + ) { + if (selectedUser) { + await updateCompany({ + variables: { + where: { id: company.id }, + data: { + accountOwner: { connect: { id: selectedUser.id } }, + }, }, - }, - }); + }); + } onSubmit?.(); } diff --git a/front/src/modules/companies/components/CompanyEditableNameCell.tsx b/front/src/modules/companies/components/CompanyEditableNameCell.tsx index 704e647b8..fbea17c39 100644 --- a/front/src/modules/companies/components/CompanyEditableNameCell.tsx +++ b/front/src/modules/companies/components/CompanyEditableNameCell.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from 'react'; import { getOperationName } from '@apollo/client/utilities'; import { EditableCellChip } from '@/ui/table/editable-cell/types/EditableChip'; @@ -22,17 +21,10 @@ type OwnProps = { export function CompanyEditableNameChipCell({ company }: OwnProps) { const [updateCompany] = useUpdateOneCompanyMutation(); - const [internalValue, setInternalValue] = useState(company.name ?? ''); - - useEffect(() => { - setInternalValue(company.name ?? ''); - }, [company.name]); - return ( } - onSubmit={() => + onSubmit={(newName) => updateCompany({ variables: { where: { id: company.id }, data: { - name: internalValue, + name: newName, }, }, refetchQueries: [getOperationName(GET_COMPANY) ?? ''], }) } - onCancel={() => setInternalValue(company.name ?? '')} /> ); } diff --git a/front/src/modules/companies/table/components/CompanyEntityTableData.tsx b/front/src/modules/companies/table/components/CompanyEntityTableData.tsx index 313452590..1f92eff82 100644 --- a/front/src/modules/companies/table/components/CompanyEntityTableData.tsx +++ b/front/src/modules/companies/table/components/CompanyEntityTableData.tsx @@ -1,8 +1,4 @@ -import { useRecoilState } from 'recoil'; - import { defaultOrderBy } from '@/companies/queries'; -import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState'; -import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { PersonOrderByWithRelationInput, useGetCompaniesQuery, @@ -17,12 +13,6 @@ export function CompanyEntityTableData({ orderBy?: PersonOrderByWithRelationInput[]; whereFilters?: any; }) { - const [, setTableRowIds] = useRecoilState(tableRowIdsState); - - const [, setIsFetchingEntityTableData] = useRecoilState( - isFetchingEntityTableDataState, - ); - const setCompanyEntityTable = useSetCompanyEntityTable(); useGetCompaniesQuery({ @@ -30,19 +20,7 @@ export function CompanyEntityTableData({ onCompleted: (data) => { const companies = data.companies ?? []; - const companyIds = companies.map((company) => company.id); - - setTableRowIds((currentRowIds) => { - if (JSON.stringify(currentRowIds) !== JSON.stringify(companyIds)) { - return companyIds; - } - - return currentRowIds; - }); - setCompanyEntityTable(companies); - - setIsFetchingEntityTableData(false); }, }); diff --git a/front/src/modules/companies/table/components/CompanyEntityTableDataMocked.tsx b/front/src/modules/companies/table/components/CompanyEntityTableDataMocked.tsx index 9078abe44..d8d750169 100644 --- a/front/src/modules/companies/table/components/CompanyEntityTableDataMocked.tsx +++ b/front/src/modules/companies/table/components/CompanyEntityTableDataMocked.tsx @@ -1,37 +1,15 @@ import { useEffect } from 'react'; -import { useRecoilState } from 'recoil'; - -import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState'; -import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { useSetCompanyEntityTable } from '../hooks/useSetCompanyEntityTable'; import { mockedCompaniesData } from './companies-mock-data'; export function CompanyEntityTableDataMocked() { - const [, setTableRowIds] = useRecoilState(tableRowIdsState); - - const [, setIsFetchingEntityTableData] = useRecoilState( - isFetchingEntityTableDataState, - ); - const setCompanyEntityTable = useSetCompanyEntityTable(); useEffect(() => { - const companyIds = mockedCompaniesData.map((company) => company.id); - - setTableRowIds((currentRowIds) => { - if (JSON.stringify(currentRowIds) !== JSON.stringify(companyIds)) { - return companyIds; - } - - return currentRowIds; - }); - setCompanyEntityTable(mockedCompaniesData); - - setIsFetchingEntityTableData(false); - }, [setCompanyEntityTable, setIsFetchingEntityTableData, setTableRowIds]); + }, [setCompanyEntityTable]); return <>; } diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx index 34c0e9f28..89aa55772 100644 --- a/front/src/modules/companies/table/components/CompanyTable.tsx +++ b/front/src/modules/companies/table/components/CompanyTable.tsx @@ -9,10 +9,8 @@ import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIn import { IconList } from '@/ui/icon'; import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue'; import { EntityTable } from '@/ui/table/components/EntityTable'; -import { HooksEntityTable } from '@/ui/table/components/HooksEntityTable'; import { TableContext } from '@/ui/table/states/TableContext'; import { CompanyOrderByWithRelationInput } from '~/generated/graphql'; -import { companiesFilters } from '~/pages/companies/companies-filters'; import { availableSorts } from '~/pages/companies/companies-sorts'; export function CompanyTable() { @@ -32,10 +30,6 @@ export function CompanyTable() { return ( <> - - { - setInternalValue(name ?? ''); - }, [name]); return ( + url={domainName ?? ''} + onSubmit={(newURL) => updateCompany({ variables: { where: { id: currentRowEntityId, }, data: { - domainName: internalValue, + domainName: newURL, }, }, }) } - onCancel={() => setInternalValue(name ?? '')} /> ); } diff --git a/front/src/modules/companies/table/hooks/useSetCompanyEntityTable.ts b/front/src/modules/companies/table/hooks/useSetCompanyEntityTable.ts index b75e3921a..8c04826cc 100644 --- a/front/src/modules/companies/table/hooks/useSetCompanyEntityTable.ts +++ b/front/src/modules/companies/table/hooks/useSetCompanyEntityTable.ts @@ -1,3 +1,4 @@ +import { useLocation } from 'react-router-dom'; import { useRecoilCallback } from 'recoil'; import { companyAccountOwnerFamilyState } from '@/companies/states/companyAccountOwnerFamilyState'; @@ -10,7 +11,24 @@ import { companyLinkedinUrlFamilyState } from '@/companies/states/companyLinkedi import { companyNameFamilyState } from '@/companies/states/companyNameFamilyState'; import { GetCompaniesQuery } from '~/generated/graphql'; +import { companiesFilters } from '../../../../pages/companies/companies-filters'; +import { availableFiltersScopedState } from '../../../ui/filter-n-sort/states/availableFiltersScopedState'; +import { useContextScopeId } from '../../../ui/recoil-scope/hooks/useContextScopeId'; +import { currentPageLocationState } from '../../../ui/states/currentPageLocationState'; +import { useResetTableRowSelection } from '../../../ui/table/hooks/useResetTableRowSelection'; +import { entityTableDimensionsState } from '../../../ui/table/states/entityTableDimensionsState'; +import { isFetchingEntityTableDataState } from '../../../ui/table/states/isFetchingEntityTableDataState'; +import { TableContext } from '../../../ui/table/states/TableContext'; +import { tableRowIdsState } from '../../../ui/table/states/tableRowIdsState'; +import { companyColumns } from '../components/companyColumns'; + export function useSetCompanyEntityTable() { + const resetTableRowSelection = useResetTableRowSelection(); + + const tableContextScopeId = useContextScopeId(TableContext); + + const currentLocation = useLocation().pathname; + return useRecoilCallback( ({ set, snapshot }) => (newCompanyArray: GetCompaniesQuery['companies']) => { @@ -94,7 +112,30 @@ export function useSetCompanyEntityTable() { set(companyCreatedAtFamilyState(company.id), company.createdAt); } } + + const companyIds = newCompanyArray.map((company) => company.id); + + set(tableRowIdsState, (currentRowIds) => { + if (JSON.stringify(currentRowIds) !== JSON.stringify(companyIds)) { + return companyIds; + } + + return currentRowIds; + }); + + resetTableRowSelection(); + + set(entityTableDimensionsState, { + numberOfColumns: companyColumns.length, + numberOfRows: companyIds.length, + }); + + set(availableFiltersScopedState(tableContextScopeId), companiesFilters); + + set(currentPageLocationState, currentLocation); + + set(isFetchingEntityTableDataState, false); }, - [], + [resetTableRowSelection, tableContextScopeId, currentLocation], ); } diff --git a/front/src/modules/people/components/EditablePeopleFullName.tsx b/front/src/modules/people/components/EditablePeopleFullName.tsx index fd8822e09..7a8b63bc4 100644 --- a/front/src/modules/people/components/EditablePeopleFullName.tsx +++ b/front/src/modules/people/components/EditablePeopleFullName.tsx @@ -19,8 +19,8 @@ type OwnProps = { > | null | undefined; - onChange: (firstName: string, lastName: string) => void; - onSubmit?: () => void; + onChange?: (firstName: string, lastName: string) => void; + onSubmit?: (firstName: string, lastName: string) => void; onCancel?: () => void; }; @@ -37,20 +37,12 @@ export function EditablePeopleFullName({ onSubmit, onCancel, }: OwnProps) { - function handleDoubleTextChange( - firstValue: string, - secondValue: string, - ): void { - onChange(firstValue, secondValue); - } - return ( & { company?: Pick | null }; }; @@ -37,17 +39,21 @@ export function PeopleCompanyPicker({ people }: OwnProps) { selectedIds: people.company?.id ? [people.company.id] : [], }); - async function handleEntitySelected(entity: any) { - await updatePerson({ - variables: { - where: { - id: people.id, + async function handleEntitySelected( + entity: EntityForSelect | null | undefined, + ) { + if (entity) { + await updatePerson({ + variables: { + where: { + id: people.id, + }, + data: { + company: { connect: { id: entity.id } }, + }, }, - data: { - company: { connect: { id: entity.id } }, - }, - }, - }); + }); + } closeEditableCell(); } @@ -67,6 +73,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) { return ( closeEditableCell()} onEntitySelected={handleEntitySelected} entities={{ entitiesToSelect: companies.entitiesToSelect, diff --git a/front/src/modules/people/editable-field/components/PeopleCompanyEditableFieldEditMode.tsx b/front/src/modules/people/editable-field/components/PeopleCompanyEditableFieldEditMode.tsx index 97fb219c6..7e944cb41 100644 --- a/front/src/modules/people/editable-field/components/PeopleCompanyEditableFieldEditMode.tsx +++ b/front/src/modules/people/editable-field/components/PeopleCompanyEditableFieldEditMode.tsx @@ -27,17 +27,22 @@ export function PeopleCompanyEditableFieldEditMode({ people }: OwnProps) { selectedIds: people.company?.id ? [people.company.id] : [], }); - async function handleEntitySelected(entity: EntityForSelect) { - await updatePerson({ - variables: { - where: { - id: people.id, + async function handleEntitySelected( + entity: EntityForSelect | null | undefined, + ) { + if (entity) { + await updatePerson({ + variables: { + where: { + id: people.id, + }, + data: { + company: { connect: { id: entity.id } }, + }, }, - data: { - company: { connect: { id: entity.id } }, - }, - }, - }); + }); + } + closeEditableField(); } diff --git a/front/src/modules/people/hooks/useSetPeopleEntityTable.ts b/front/src/modules/people/hooks/useSetPeopleEntityTable.ts index 2d22069b7..c6f215983 100644 --- a/front/src/modules/people/hooks/useSetPeopleEntityTable.ts +++ b/front/src/modules/people/hooks/useSetPeopleEntityTable.ts @@ -1,7 +1,17 @@ +import { useLocation } from 'react-router-dom'; import { useRecoilCallback } from 'recoil'; import { GetPeopleQuery } from '~/generated/graphql'; +import { peopleFilters } from '../../../pages/people/people-filters'; +import { availableFiltersScopedState } from '../../ui/filter-n-sort/states/availableFiltersScopedState'; +import { useContextScopeId } from '../../ui/recoil-scope/hooks/useContextScopeId'; +import { currentPageLocationState } from '../../ui/states/currentPageLocationState'; +import { useResetTableRowSelection } from '../../ui/table/hooks/useResetTableRowSelection'; +import { entityTableDimensionsState } from '../../ui/table/states/entityTableDimensionsState'; +import { isFetchingEntityTableDataState } from '../../ui/table/states/isFetchingEntityTableDataState'; +import { TableContext } from '../../ui/table/states/TableContext'; +import { tableRowIdsState } from '../../ui/table/states/tableRowIdsState'; import { peopleCityFamilyState } from '../states/peopleCityFamilyState'; import { peopleCompanyFamilyState } from '../states/peopleCompanyFamilyState'; import { peopleCreatedAtFamilyState } from '../states/peopleCreatedAtFamilyState'; @@ -10,8 +20,15 @@ import { peopleJobTitleFamilyState } from '../states/peopleJobTitleFamilyState'; import { peopleLinkedinUrlFamilyState } from '../states/peopleLinkedinUrlFamilyState'; import { peopleNameCellFamilyState } from '../states/peopleNamesFamilyState'; import { peoplePhoneFamilyState } from '../states/peoplePhoneFamilyState'; +import { peopleColumns } from '../table/components/peopleColumns'; export function useSetPeopleEntityTable() { + const resetTableRowSelection = useResetTableRowSelection(); + + const tableContextScopeId = useContextScopeId(TableContext); + + const currentLocation = useLocation().pathname; + return useRecoilCallback( ({ set, snapshot }) => (newPeopleArray: GetPeopleQuery['people']) => { @@ -94,6 +111,29 @@ export function useSetPeopleEntityTable() { }); } } + + const peopleIds = newPeopleArray.map((people) => people.id); + + set(tableRowIdsState, (currentRowIds) => { + if (JSON.stringify(currentRowIds) !== JSON.stringify(peopleIds)) { + return peopleIds; + } + + return currentRowIds; + }); + + resetTableRowSelection(); + + set(entityTableDimensionsState, { + numberOfColumns: peopleColumns.length, + numberOfRows: peopleIds.length, + }); + + set(availableFiltersScopedState(tableContextScopeId), peopleFilters); + + set(currentPageLocationState, currentLocation); + + set(isFetchingEntityTableDataState, false); }, [], ); diff --git a/front/src/modules/people/table/components/EditablePeopleCityCell.tsx b/front/src/modules/people/table/components/EditablePeopleCityCell.tsx index ea81ff170..db86ca44e 100644 --- a/front/src/modules/people/table/components/EditablePeopleCityCell.tsx +++ b/front/src/modules/people/table/components/EditablePeopleCityCell.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { peopleCityFamilyState } from '@/people/states/peopleCityFamilyState'; @@ -13,29 +12,21 @@ export function EditablePeopleCityCell() { const city = useRecoilValue(peopleCityFamilyState(currentRowEntityId ?? '')); - const [internalValue, setInternalValue] = useState(city ?? ''); - - useEffect(() => { - setInternalValue(city ?? ''); - }, [city]); - return ( + value={city ?? ''} + onSubmit={(newText) => updatePerson({ variables: { where: { id: currentRowEntityId, }, data: { - city: internalValue, + city: newText, }, }, }) } - onCancel={() => setInternalValue(city ?? '')} /> ); } diff --git a/front/src/modules/people/table/components/EditablePeopleFullNameCell.tsx b/front/src/modules/people/table/components/EditablePeopleFullNameCell.tsx index 2ef4280d4..5730bc9f1 100644 --- a/front/src/modules/people/table/components/EditablePeopleFullNameCell.tsx +++ b/front/src/modules/people/table/components/EditablePeopleFullNameCell.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from 'react'; import { getOperationName } from '@apollo/client/utilities'; import { useRecoilValue } from 'recoil'; @@ -18,45 +17,29 @@ export function EditablePeopleFullNameCell() { peopleNameCellFamilyState(currentRowEntityId ?? ''), ); - const [internalFirstName, setInternalFirstName] = useState(firstName ?? ''); - const [internalLastName, setInternalLastName] = useState(lastName ?? ''); - - useEffect(() => { - setInternalFirstName(firstName ?? ''); - setInternalLastName(lastName ?? ''); - }, [firstName, lastName]); - return ( { - setInternalFirstName(firstName); - setInternalLastName(lastName); - }} - onSubmit={() => + onSubmit={(newFirstValue, newSecondValue) => updatePerson({ variables: { where: { id: currentRowEntityId, }, data: { - firstName: internalFirstName, - lastName: internalLastName, + firstName: newFirstValue, + lastName: newSecondValue, }, }, refetchQueries: [getOperationName(GET_PERSON) ?? ''], }) } - onCancel={() => { - setInternalFirstName(firstName ?? ''); - setInternalLastName(lastName ?? ''); - }} /> ); } diff --git a/front/src/modules/people/table/components/EditablePeopleJobTitleCell.tsx b/front/src/modules/people/table/components/EditablePeopleJobTitleCell.tsx index 16ab33086..61e29a493 100644 --- a/front/src/modules/people/table/components/EditablePeopleJobTitleCell.tsx +++ b/front/src/modules/people/table/components/EditablePeopleJobTitleCell.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { peopleJobTitleFamilyState } from '@/people/states/peopleJobTitleFamilyState'; @@ -15,29 +14,21 @@ export function EditablePeopleJobTitleCell() { peopleJobTitleFamilyState(currentRowEntityId ?? ''), ); - const [internalValue, setInternalValue] = useState(jobTitle ?? ''); - - useEffect(() => { - setInternalValue(jobTitle ?? ''); - }, [jobTitle]); - return ( + value={jobTitle ?? ''} + onSubmit={(newText) => updatePerson({ variables: { where: { id: currentRowEntityId, }, data: { - jobTitle: internalValue, + jobTitle: newText, }, }, }) } - onCancel={() => setInternalValue(jobTitle ?? '')} /> ); } diff --git a/front/src/modules/people/table/components/EditablePeopleLinkedinUrlCell.tsx b/front/src/modules/people/table/components/EditablePeopleLinkedinUrlCell.tsx index eee873bc9..1842da2b6 100644 --- a/front/src/modules/people/table/components/EditablePeopleLinkedinUrlCell.tsx +++ b/front/src/modules/people/table/components/EditablePeopleLinkedinUrlCell.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { peopleLinkedinUrlFamilyState } from '@/people/states/peopleLinkedinUrlFamilyState'; @@ -16,29 +15,21 @@ export function EditablePeopleLinkedinUrlCell() { peopleLinkedinUrlFamilyState(currentRowEntityId ?? ''), ); - const [internalValue, setInternalValue] = useState(linkedinUrl ?? ''); - - useEffect(() => { - setInternalValue(linkedinUrl ?? ''); - }, [linkedinUrl]); - return ( + url={linkedinUrl ?? ''} + onSubmit={(newURL) => updatePerson({ variables: { where: { id: currentRowEntityId, }, data: { - linkedinUrl: internalValue, + linkedinUrl: newURL, }, }, }) } - onCancel={() => setInternalValue(linkedinUrl ?? '')} /> ); } diff --git a/front/src/modules/people/table/components/EditablePeoplePhoneCell.tsx b/front/src/modules/people/table/components/EditablePeoplePhoneCell.tsx index a5dea6dbe..92263ddb1 100644 --- a/front/src/modules/people/table/components/EditablePeoplePhoneCell.tsx +++ b/front/src/modules/people/table/components/EditablePeoplePhoneCell.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { peoplePhoneFamilyState } from '@/people/states/peoplePhoneFamilyState'; @@ -15,29 +14,21 @@ export function EditablePeoplePhoneCell() { peoplePhoneFamilyState(currentRowEntityId ?? ''), ); - const [internalValue, setInternalValue] = useState(phone ?? ''); - - useEffect(() => { - setInternalValue(phone ?? ''); - }, [phone]); - return ( + value={phone?.toString() ?? ''} + onSubmit={(newPhone) => updatePerson({ variables: { where: { id: currentRowEntityId, }, data: { - phone: internalValue, + phone: newPhone, }, }, }) } - onCancel={() => setInternalValue(phone ?? '')} /> ); } diff --git a/front/src/modules/people/table/components/PeopleTable.tsx b/front/src/modules/people/table/components/PeopleTable.tsx index f60e7dc79..da072cf85 100644 --- a/front/src/modules/people/table/components/PeopleTable.tsx +++ b/front/src/modules/people/table/components/PeopleTable.tsx @@ -10,10 +10,8 @@ import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIn import { IconList } from '@/ui/icon'; import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue'; import { EntityTable } from '@/ui/table/components/EntityTable'; -import { HooksEntityTable } from '@/ui/table/components/HooksEntityTable'; import { TableContext } from '@/ui/table/states/TableContext'; import { PersonOrderByWithRelationInput } from '~/generated/graphql'; -import { peopleFilters } from '~/pages/people/people-filters'; import { availableSorts } from '~/pages/people/people-sorts'; export function PeopleTable() { @@ -33,10 +31,6 @@ export function PeopleTable() { return ( <> - { signOut(); - }, [signOut]); + navigate(AppPath.SignIn); + }, [signOut, navigate]); return ( diff --git a/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldText.tsx b/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldText.tsx index 5739a7cac..11e37b1e9 100644 --- a/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldText.tsx +++ b/front/src/modules/ui/board/card-field/components/BoardCardEditableFieldText.tsx @@ -1,7 +1,7 @@ import { ChangeEvent, useMemo, useState } from 'react'; import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode'; -import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; +import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; import { debounce } from '~/utils/debounce'; import { BoardCardEditableField } from './BoardCardEditableField'; @@ -29,7 +29,7 @@ export function BoardCardEditableFieldText({ , + ) { + console.debug( + 'TimingProfiler', + JSON.stringify( + { + id, + phase, + actualDuration, + baseDuration, + startTime, + commitTime, + interactions, + }, + null, + 2, + ), + ); + } + + return ( + + {children} + + ); +} diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownButton.tsx index 9b83367c7..d4e473b1f 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownButton.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownButton.tsx @@ -1,5 +1,4 @@ import { Context, useCallback, useState } from 'react'; -import { Key } from 'ts-key-enum'; import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; import { filterDefinitionUsedInDropdownScopedState } from '@/ui/filter-n-sort/states/filterDefinitionUsedInDropdownScopedState'; @@ -7,10 +6,8 @@ import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/ import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/ui/filter-n-sort/states/isFilterDropdownOperandSelectUnfoldedScopedState'; import { selectedOperandInDropdownScopedState } from '@/ui/filter-n-sort/states/selectedOperandInDropdownScopedState'; -import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys'; import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope'; import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; -import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope'; import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; @@ -83,15 +80,6 @@ export function FilterDropdownButton({ } } - useScopedHotkeys( - [Key.Escape], - () => { - handleIsUnfoldedChange(false); - }, - RelationPickerHotkeyScope.RelationPicker, - [handleIsUnfoldedChange], - ); - return ( { - const scopesToSet: string[] = []; - - if (currentHotkeyScope.customScopes?.commandMenu) { - scopesToSet.push(AppHotkeyScope.CommandMenu); - } - - if (currentHotkeyScope?.customScopes?.goto) { - scopesToSet.push(AppHotkeyScope.Goto); - } - - scopesToSet.push(currentHotkeyScope.scope); - - setHotkeyScopes(scopesToSet); - }, [setHotkeyScopes, currentHotkeyScope]); -} diff --git a/front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts b/front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts new file mode 100644 index 000000000..656903121 --- /dev/null +++ b/front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts @@ -0,0 +1,40 @@ +import { Hotkey } from 'react-hotkeys-hook/dist/types'; +import { useRecoilCallback } from 'recoil'; + +import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState'; + +export function useScopedHotkeyCallback() { + return useRecoilCallback( + ({ snapshot }) => + ({ + callback, + hotkeysEvent, + keyboardEvent, + scope, + preventDefault = true, + }: { + keyboardEvent: KeyboardEvent; + hotkeysEvent: Hotkey; + callback: (keyboardEvent: KeyboardEvent, hotkeysEvent: Hotkey) => void; + scope: string; + preventDefault?: boolean; + }) => { + const currentHotkeyScopes = snapshot + .getLoadable(internalHotkeysEnabledScopesState) + .valueOrThrow(); + + if (!currentHotkeyScopes.includes(scope)) { + return; + } + + if (preventDefault) { + keyboardEvent.stopPropagation(); + keyboardEvent.preventDefault(); + keyboardEvent.stopImmediatePropagation(); + } + + return callback(keyboardEvent, hotkeysEvent); + }, + [], + ); +} diff --git a/front/src/modules/ui/hotkey/hooks/useScopedHotkeys.ts b/front/src/modules/ui/hotkey/hooks/useScopedHotkeys.ts index c238cb2d6..6167b0b38 100644 --- a/front/src/modules/ui/hotkey/hooks/useScopedHotkeys.ts +++ b/front/src/modules/ui/hotkey/hooks/useScopedHotkeys.ts @@ -1,6 +1,5 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { - Hotkey, HotkeyCallback, Keys, Options, @@ -10,6 +9,8 @@ import { useRecoilState } from 'recoil'; import { pendingHotkeyState } from '../states/internal/pendingHotkeysState'; +import { useScopedHotkeyCallback } from './useScopedHotkeyCallback'; + export function useScopedHotkeys( keys: Keys, callback: HotkeyCallback, @@ -23,21 +24,29 @@ export function useScopedHotkeys( ) { const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState); - function callbackIfDirectKey( - keyboardEvent: KeyboardEvent, - hotkeysEvent: Hotkey, - ) { - if (!pendingHotkey) { - callback(keyboardEvent, hotkeysEvent); - return; - } - setPendingHotkey(null); - } + const callScopedHotkeyCallback = useScopedHotkeyCallback(); return useHotkeys( keys, - callbackIfDirectKey, - { ...options, scopes: [scope] }, + (keyboardEvent, hotkeysEvent) => { + callScopedHotkeyCallback({ + keyboardEvent, + hotkeysEvent, + callback: () => { + if (!pendingHotkey) { + callback(keyboardEvent, hotkeysEvent); + return; + } + setPendingHotkey(null); + }, + scope, + preventDefault: !!options.preventDefault, + }); + }, + { + enableOnContentEditable: options.enableOnContentEditable, + enableOnFormTags: options.enableOnFormTags, + }, dependencies, ); } diff --git a/front/src/modules/ui/hotkey/hooks/useSequenceScopedHotkeys.ts b/front/src/modules/ui/hotkey/hooks/useSequenceScopedHotkeys.ts index f8eb838fa..f1a50f9dd 100644 --- a/front/src/modules/ui/hotkey/hooks/useSequenceScopedHotkeys.ts +++ b/front/src/modules/ui/hotkey/hooks/useSequenceScopedHotkeys.ts @@ -4,10 +4,12 @@ import { useRecoilState } from 'recoil'; import { pendingHotkeyState } from '../states/internal/pendingHotkeysState'; +import { useScopedHotkeyCallback } from './useScopedHotkeyCallback'; + export function useSequenceHotkeys( firstKey: Keys, secondKey: Keys, - callback: () => void, + sequenceCallback: () => void, scope: string, options: Options = { enableOnContentEditable: true, @@ -18,25 +20,57 @@ export function useSequenceHotkeys( ) { const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState); + const callScopedHotkeyCallback = useScopedHotkeyCallback(); + useHotkeys( firstKey, - () => { - setPendingHotkey(firstKey); + (keyboardEvent, hotkeysEvent) => { + callScopedHotkeyCallback({ + keyboardEvent, + hotkeysEvent, + callback: () => { + setPendingHotkey(firstKey); + }, + scope, + preventDefault: !!options.preventDefault, + }); }, - { ...options, scopes: [scope] }, - [setPendingHotkey], + { + enableOnContentEditable: options.enableOnContentEditable, + enableOnFormTags: options.enableOnFormTags, + }, + [setPendingHotkey, scope], ); useHotkeys( secondKey, - () => { - if (pendingHotkey !== firstKey) { - return; - } - setPendingHotkey(null); - callback(); + (keyboardEvent, hotkeysEvent) => { + callScopedHotkeyCallback({ + keyboardEvent, + hotkeysEvent, + callback: () => { + if (pendingHotkey !== firstKey) { + return; + } + + setPendingHotkey(null); + + if (!!options.preventDefault) { + keyboardEvent.stopImmediatePropagation(); + keyboardEvent.stopPropagation(); + keyboardEvent.preventDefault(); + } + + sequenceCallback(); + }, + scope, + preventDefault: false, + }); }, - { ...options, scopes: [scope] }, - [pendingHotkey, setPendingHotkey, ...deps], + { + enableOnContentEditable: options.enableOnContentEditable, + enableOnFormTags: options.enableOnFormTags, + }, + [pendingHotkey, setPendingHotkey, scope, ...deps], ); } diff --git a/front/src/modules/ui/hotkey/hooks/useSetHotkeyScope.ts b/front/src/modules/ui/hotkey/hooks/useSetHotkeyScope.ts index d797b5198..81b6e28ba 100644 --- a/front/src/modules/ui/hotkey/hooks/useSetHotkeyScope.ts +++ b/front/src/modules/ui/hotkey/hooks/useSetHotkeyScope.ts @@ -4,7 +4,10 @@ import { isDefined } from '~/utils/isDefined'; import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants'; import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState'; +import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState'; +import { AppHotkeyScope } from '../types/AppHotkeyScope'; import { CustomHotkeyScopes } from '../types/CustomHotkeyScope'; +import { HotkeyScope } from '../types/HotkeyScope'; function isCustomScopesEqual( customScopesA: CustomHotkeyScopes | undefined, @@ -46,13 +49,27 @@ export function useSetHotkeyScope() { } } - set(currentHotkeyScopeState, { + const newHotkeyScope: HotkeyScope = { scope: hotkeyScopeToSet, customScopes: { commandMenu: customScopes?.commandMenu ?? true, goto: customScopes?.goto ?? false, }, - }); + }; + + const scopesToSet: string[] = []; + + if (newHotkeyScope.customScopes?.commandMenu) { + scopesToSet.push(AppHotkeyScope.CommandMenu); + } + + if (newHotkeyScope?.customScopes?.goto) { + scopesToSet.push(AppHotkeyScope.Goto); + } + + scopesToSet.push(newHotkeyScope.scope); + + set(internalHotkeysEnabledScopesState, scopesToSet); }, [], ); diff --git a/front/src/modules/ui/inplace-input/components/InplaceInputDoubleText.tsx b/front/src/modules/ui/inplace-input/components/InplaceInputDoubleText.tsx index 41d41f971..aabdbf770 100644 --- a/front/src/modules/ui/inplace-input/components/InplaceInputDoubleText.tsx +++ b/front/src/modules/ui/inplace-input/components/InplaceInputDoubleText.tsx @@ -1,7 +1,7 @@ import { ChangeEvent } from 'react'; import styled from '@emotion/styled'; -import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; +import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; type OwnProps = { firstValue: string; @@ -31,7 +31,7 @@ export function InplaceInputDoubleText({ }: OwnProps) { return ( - - ) => { diff --git a/front/src/modules/ui/inplace-input/components/InplaceInputTextEditMode.tsx b/front/src/modules/ui/inplace-input/components/InplaceInputTextEditMode.tsx index d33a0240c..e34a39177 100644 --- a/front/src/modules/ui/inplace-input/components/InplaceInputTextEditMode.tsx +++ b/front/src/modules/ui/inplace-input/components/InplaceInputTextEditMode.tsx @@ -1,9 +1,58 @@ +import { ChangeEvent, useEffect, useRef, useState } from 'react'; import styled from '@emotion/styled'; import { textInputStyle } from '@/ui/themes/effects'; -export const InplaceInputTextEditMode = styled.input` +import { useRegisterCloseCellHandlers } from '../../table/editable-cell/hooks/useRegisterCloseCellHandlers'; + +export const StyledInput = styled.input` margin: 0; width: 100%; ${textInputStyle} `; + +type OwnProps = { + placeholder?: string; + autoFocus?: boolean; + value: string; + onSubmit: (newText: string) => void; +}; + +export function InplaceInputTextEditMode({ + placeholder, + autoFocus, + value, + onSubmit, +}: OwnProps) { + const [internalText, setInternalText] = useState(value); + + const wrapperRef = useRef(null); + + function handleSubmit() { + onSubmit(internalText); + } + + function handleCancel() { + setInternalText(value); + } + + function handleChange(event: ChangeEvent) { + setInternalText(event.target.value); + } + + useEffect(() => { + setInternalText(value); + }, [value]); + + useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel); + + return ( + + ); +} diff --git a/front/src/modules/ui/layout/components/DefaultLayout.tsx b/front/src/modules/ui/layout/components/DefaultLayout.tsx index fc6b7c5ce..ee3f1ce6f 100644 --- a/front/src/modules/ui/layout/components/DefaultLayout.tsx +++ b/front/src/modules/ui/layout/components/DefaultLayout.tsx @@ -11,7 +11,6 @@ import { MOBILE_VIEWPORT } from '@/ui/themes/themes'; import { AppNavbar } from '~/AppNavbar'; import { CompaniesMockMode } from '~/pages/companies/CompaniesMockMode'; -import { useAutoNavigateOnboarding } from '../hooks/useAutoNavigateOnboarding'; import { isNavbarOpenedState } from '../states/isNavbarOpenedState'; const StyledLayout = styled.div` @@ -39,12 +38,10 @@ const MainContainer = styled.div` `; type OwnProps = { - children: JSX.Element; + children: React.ReactNode; }; export function DefaultLayout({ children }: OwnProps) { - useAutoNavigateOnboarding(); - const onboardingStatus = useOnboardingStatus(); return ( diff --git a/front/src/modules/ui/layout/hooks/useAutoNavigateOnboarding.ts b/front/src/modules/ui/layout/hooks/useAutoNavigateOnboarding.ts deleted file mode 100644 index ff92361e8..000000000 --- a/front/src/modules/ui/layout/hooks/useAutoNavigateOnboarding.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { useIsMatchingLocation } from '../../../../hooks/useIsMatchingLocation'; -import { useOnboardingStatus } from '../../../auth/hooks/useOnboardingStatus'; -import { OnboardingStatus } from '../../../auth/utils/getOnboardingStatus'; -import { AppPath } from '../../../types/AppPath'; - -export function useAutoNavigateOnboarding() { - const navigate = useNavigate(); - const isMatchingLocation = useIsMatchingLocation(); - - const onboardingStatus = useOnboardingStatus(); - - useEffect(() => { - const isMachinOngoingUserCreationRoute = - isMatchingLocation(AppPath.SignUp) || - isMatchingLocation(AppPath.SignIn) || - isMatchingLocation(AppPath.Invite) || - isMatchingLocation(AppPath.Verify); - - const isMatchingOnboardingRoute = - isMatchingLocation(AppPath.SignUp) || - isMatchingLocation(AppPath.SignIn) || - isMatchingLocation(AppPath.Invite) || - isMatchingLocation(AppPath.Verify) || - isMatchingLocation(AppPath.CreateWorkspace) || - isMatchingLocation(AppPath.CreateProfile); - - if ( - onboardingStatus === OnboardingStatus.OngoingUserCreation && - !isMachinOngoingUserCreationRoute - ) { - navigate(AppPath.SignIn); - } else if ( - onboardingStatus === OnboardingStatus.OngoingWorkspaceCreation && - !isMatchingLocation(AppPath.CreateWorkspace) - ) { - navigate(AppPath.CreateWorkspace); - } else if ( - onboardingStatus === OnboardingStatus.OngoingProfileCreation && - !isMatchingLocation(AppPath.CreateProfile) - ) { - navigate(AppPath.CreateProfile); - } else if ( - onboardingStatus === OnboardingStatus.Completed && - isMatchingOnboardingRoute - ) { - navigate('/'); - } - }, [onboardingStatus, navigate, isMatchingLocation]); -} diff --git a/front/src/modules/ui/relation-picker/components/SingleEntitySelect.tsx b/front/src/modules/ui/relation-picker/components/SingleEntitySelect.tsx index 3cb5ee184..beca6e7fd 100644 --- a/front/src/modules/ui/relation-picker/components/SingleEntitySelect.tsx +++ b/front/src/modules/ui/relation-picker/components/SingleEntitySelect.tsx @@ -35,7 +35,7 @@ export function SingleEntitySelect< onCancel?: () => void; onCreate?: () => void; entities: EntitiesForSingleEntitySelect; - onEntitySelected: (entity: CustomEntityForSelect) => void; + onEntitySelected: (entity: CustomEntityForSelect | null | undefined) => void; disableBackgroundBlur?: boolean; }) { const containerRef = useRef(null); @@ -48,7 +48,11 @@ export function SingleEntitySelect< useListenClickOutside({ refs: [containerRef], - callback: () => { + callback: (event) => { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + onCancel?.(); }, }); diff --git a/front/src/modules/ui/relation-picker/components/SingleEntitySelectBase.tsx b/front/src/modules/ui/relation-picker/components/SingleEntitySelectBase.tsx index e4039225d..978868276 100644 --- a/front/src/modules/ui/relation-picker/components/SingleEntitySelectBase.tsx +++ b/front/src/modules/ui/relation-picker/components/SingleEntitySelectBase.tsx @@ -32,7 +32,7 @@ export function SingleEntitySelectBase< onCancel, }: { entities: EntitiesForSingleEntitySelect; - onEntitySelected: (entity: CustomEntityForSelect) => void; + onEntitySelected: (entity: CustomEntityForSelect | null | undefined) => void; onCancel?: () => void; }) { const containerRef = useRef(null); diff --git a/front/src/modules/ui/states/currentPageLocationState.ts b/front/src/modules/ui/states/currentPageLocationState.ts new file mode 100644 index 000000000..0d96571b3 --- /dev/null +++ b/front/src/modules/ui/states/currentPageLocationState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const currentPageLocationState = atom({ + key: 'currentPageLocationState', + default: '', +}); diff --git a/front/src/modules/ui/table/components/EntityTable.tsx b/front/src/modules/ui/table/components/EntityTable.tsx index 57b65d202..116a912d8 100644 --- a/front/src/modules/ui/table/components/EntityTable.tsx +++ b/front/src/modules/ui/table/components/EntityTable.tsx @@ -5,7 +5,9 @@ import { TableColumn } from '@/people/table/components/peopleColumns'; import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface'; import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside'; +import { useIsPageLoading } from '../../hooks/useIsPageLoading'; import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus'; +import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus'; import { TableHeader } from '../table-header/components/TableHeader'; import { EntityTableBody } from './EntityTableBody'; @@ -88,6 +90,8 @@ export function EntityTable({ }: OwnProps) { const tableBodyRef = React.useRef(null); + useMapKeyboardToSoftFocus(); + const leaveTableFocus = useLeaveTableFocus(); useListenClickOutside({ @@ -97,6 +101,12 @@ export function EntityTable({ }, }); + const isPageLoading = useIsPageLoading(); + + if (isPageLoading) { + return null; + } + return ( }) { isFetchingEntityTableDataState, ); + if (isFetchingEntityTableData || isNavbarSwitchingSize) { + return null; + } + return ( - {!isFetchingEntityTableData && !isNavbarSwitchingSize - ? rowIds.map((rowId, index) => ( - - - - )) - : null} + {rowIds.map((rowId, index) => ( + + + + + + ))} ); } diff --git a/front/src/modules/ui/table/components/EntityTableCell.tsx b/front/src/modules/ui/table/components/EntityTableCell.tsx index 9cd9516f0..69b480caf 100644 --- a/front/src/modules/ui/table/components/EntityTableCell.tsx +++ b/front/src/modules/ui/table/components/EntityTableCell.tsx @@ -1,33 +1,19 @@ -import { useEffect } from 'react'; import { useSetRecoilState } from 'recoil'; -import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; - +import { RecoilScope } from '../../recoil-scope/components/RecoilScope'; import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected'; -import { CellContext } from '../states/CellContext'; +import { ColumnIndexContext } from '../states/ColumnIndexContext'; import { contextMenuPositionState } from '../states/contextMenuPositionState'; -import { currentColumnNumberScopedState } from '../states/currentColumnNumberScopedState'; export function EntityTableCell({ - rowId, cellIndex, children, size, }: { size: number; - rowId: string; cellIndex: number; children: React.ReactNode; }) { - const [, setCurrentColumnNumber] = useRecoilScopedState( - currentColumnNumberScopedState, - CellContext, - ); - - useEffect(() => { - setCurrentColumnNumber(cellIndex); - }, [cellIndex, setCurrentColumnNumber]); - const setContextMenuPosition = useSetRecoilState(contextMenuPositionState); const { setCurrentRowSelected } = useCurrentRowSelected(); @@ -44,15 +30,19 @@ export function EntityTableCell({ } return ( - handleContextMenu(event)} - style={{ - width: size, - minWidth: size, - maxWidth: size, - }} - > - {children} - + + + handleContextMenu(event)} + style={{ + width: size, + minWidth: size, + maxWidth: size, + }} + > + {children} + + + ); } diff --git a/front/src/modules/ui/table/components/EntityTableRow.tsx b/front/src/modules/ui/table/components/EntityTableRow.tsx index 425e8dec8..68b235cb0 100644 --- a/front/src/modules/ui/table/components/EntityTableRow.tsx +++ b/front/src/modules/ui/table/components/EntityTableRow.tsx @@ -1,16 +1,6 @@ -import { useEffect } from 'react'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; import { TableColumn } from '@/people/table/components/peopleColumns'; -import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; -import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; - -import { CellContext } from '../states/CellContext'; -import { currentRowEntityIdScopedState } from '../states/currentRowEntityIdScopedState'; -import { currentRowNumberScopedState } from '../states/currentRowNumberScopedState'; -import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState'; -import { RowContext } from '../states/RowContext'; import { CheckboxCell } from './CheckboxCell'; import { EntityTableCell } from './EntityTableCell'; @@ -23,56 +13,24 @@ const StyledRow = styled.tr<{ selected: boolean }>` export function EntityTableRow({ columns, rowId, - index, }: { columns: TableColumn[]; rowId: string; - index: number; }) { - const [currentRowEntityId, setCurrentRowEntityId] = useRecoilScopedState( - currentRowEntityIdScopedState, - RowContext, - ); - - const isCurrentRowSelected = useRecoilValue(isRowSelectedFamilyState(rowId)); - - const [, setCurrentRowNumber] = useRecoilScopedState( - currentRowNumberScopedState, - RowContext, - ); - - useEffect(() => { - if (currentRowEntityId !== rowId) { - setCurrentRowEntityId(rowId); - } - }, [rowId, setCurrentRowEntityId, currentRowEntityId]); - - useEffect(() => { - setCurrentRowNumber(index); - }, [index, setCurrentRowNumber]); - return ( - + {columns.map((column, columnIndex) => { return ( - - - - {column.cellComponent} - - - + + {column.cellComponent} + ); })} diff --git a/front/src/modules/ui/table/components/HooksEntityTable.tsx b/front/src/modules/ui/table/components/HooksEntityTable.tsx deleted file mode 100644 index 347b735a9..000000000 --- a/front/src/modules/ui/table/components/HooksEntityTable.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition'; - -import { useInitializeEntityTable } from '../hooks/useInitializeEntityTable'; -import { useInitializeEntityTableFilters } from '../hooks/useInitializeEntityTableFilters'; -import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus'; - -export function HooksEntityTable({ - numberOfColumns, - availableFilters, -}: { - numberOfColumns: number; - availableFilters: FilterDefinition[]; -}) { - useMapKeyboardToSoftFocus(); - - useInitializeEntityTable({ - numberOfColumns, - }); - - useInitializeEntityTableFilters({ - availableFilters, - }); - - return <>; -} diff --git a/front/src/modules/ui/table/editable-cell/components/EditableCell.tsx b/front/src/modules/ui/table/editable-cell/components/EditableCell.tsx index 3d3ad45ff..394d80c47 100644 --- a/front/src/modules/ui/table/editable-cell/components/EditableCell.tsx +++ b/front/src/modules/ui/table/editable-cell/components/EditableCell.tsx @@ -3,9 +3,10 @@ import styled from '@emotion/styled'; import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope'; +import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext'; +import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode'; import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell'; -import { useRegisterEditableCell } from '../hooks/useRegisterEditableCell'; import { EditableCellDisplayMode } from './EditableCellDisplayMode'; import { EditableCellEditMode } from './EditableCellEditMode'; @@ -34,6 +35,10 @@ type OwnProps = { onCancel?: () => void; }; +const DEFAULT_CELL_SCOPE: HotkeyScope = { + scope: TableHotkeyScope.CellEditMode, +}; + export function EditableCell({ editModeHorizontalAlign = 'left', editModeVerticalPosition = 'over', @@ -42,35 +47,35 @@ export function EditableCell({ editHotkeyScope, transparent = false, maxContentWidth, - onSubmit, - onCancel, }: OwnProps) { const { isCurrentCellInEditMode } = useCurrentCellEditMode(); const hasSoftFocus = useIsSoftFocusOnCurrentCell(); - useRegisterEditableCell(editHotkeyScope); - return ( - - {isCurrentCellInEditMode ? ( - - {editModeContent} - - ) : hasSoftFocus ? ( - - {nonEditModeContent} - - ) : ( - {nonEditModeContent} - )} - + + + {isCurrentCellInEditMode ? ( + + {editModeContent} + + ) : hasSoftFocus ? ( + + {nonEditModeContent} + + ) : ( + + {nonEditModeContent} + + )} + + ); } diff --git a/front/src/modules/ui/table/editable-cell/components/EditableCellEditMode.tsx b/front/src/modules/ui/table/editable-cell/components/EditableCellEditMode.tsx index fe003f9ff..f61a4b992 100644 --- a/front/src/modules/ui/table/editable-cell/components/EditableCellEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/components/EditableCellEditMode.tsx @@ -1,10 +1,8 @@ -import { ReactElement, useRef } from 'react'; +import { ReactElement } from 'react'; import styled from '@emotion/styled'; import { overlayBackground } from '@/ui/themes/effects'; -import { useRegisterCloseCellHandlers } from '../hooks/useRegisterCloseCellHandlers'; - export const EditableCellEditModeContainer = styled.div` align-items: center; border: ${({ transparent, theme }) => @@ -36,30 +34,21 @@ type OwnProps = { maxContentWidth?: number; editModeHorizontalAlign?: 'left' | 'right'; editModeVerticalPosition?: 'over' | 'below'; - onOutsideClick?: () => void; - onCancel?: () => void; - onSubmit?: () => void; + initialValue?: string; }; export function EditableCellEditMode({ editModeHorizontalAlign, editModeVerticalPosition, children, - onCancel, - onSubmit, transparent = false, maxContentWidth, }: OwnProps) { - const wrapperRef = useRef(null); - - useRegisterCloseCellHandlers(wrapperRef, onSubmit, onCancel); - return ( diff --git a/front/src/modules/ui/table/editable-cell/components/__stories__/EditableCellText.stories.tsx b/front/src/modules/ui/table/editable-cell/components/__stories__/EditableCellText.stories.tsx index 747aa8742..450c7cf1b 100644 --- a/front/src/modules/ui/table/editable-cell/components/__stories__/EditableCellText.stories.tsx +++ b/front/src/modules/ui/table/editable-cell/components/__stories__/EditableCellText.stories.tsx @@ -4,6 +4,7 @@ import { userEvent, within } from '@storybook/testing-library'; import { CellPositionDecorator } from '~/testing/decorators/CellPositionDecorator'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; +import { sleep } from '~/testing/sleep'; import { EditableCellText } from '../../types/EditableCellText'; @@ -28,13 +29,12 @@ export const SoftFocusMode: Story = { play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); - await step('Click once', () => - userEvent.click(canvas.getByText('Content')), - ); + const content = await canvas.findByText('Content'); - await step('Escape', () => { - userEvent.keyboard('{esc}'); - }); + await userEvent.click(content); + await userEvent.keyboard('{esc}'); + + await sleep(10); await step('Has soft focus mode', () => { expect(canvas.getByTestId('editable-cell-soft-focus-mode')).toBeDefined(); @@ -47,7 +47,7 @@ export const EditMode: Story = { play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); - const click = async () => userEvent.click(canvas.getByText('Content')); + const click = () => userEvent.click(canvas.getByText('Content')); await step('Click once', click); diff --git a/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellPosition.ts b/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellPosition.ts index 9a3bcddb5..2ac9cb3f9 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellPosition.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useCurrentCellPosition.ts @@ -1,23 +1,12 @@ -import { useMemo } from 'react'; +import { useContext, useMemo } from 'react'; -import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; - -import { CellContext } from '../../states/CellContext'; -import { currentColumnNumberScopedState } from '../../states/currentColumnNumberScopedState'; -import { currentRowNumberScopedState } from '../../states/currentRowNumberScopedState'; -import { RowContext } from '../../states/RowContext'; +import { ColumnIndexContext } from '../../states/ColumnIndexContext'; +import { RowIndexContext } from '../../states/RowIndexContext'; import { CellPosition } from '../../types/CellPosition'; export function useCurrentCellPosition() { - const [currentRowNumber] = useRecoilScopedState( - currentRowNumberScopedState, - RowContext, - ); - - const [currentColumnNumber] = useRecoilScopedState( - currentColumnNumberScopedState, - CellContext, - ); + const currentRowNumber = useContext(RowIndexContext); + const currentColumnNumber = useContext(ColumnIndexContext); const currentCellPosition: CellPosition = useMemo( () => ({ diff --git a/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts index 4974b44e1..5a2347ddb 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts @@ -1,15 +1,13 @@ +import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope'; import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope'; -import { useContextScopeId } from '../../../recoil-scope/hooks/useContextScopeId'; -import { getSnapshotScopedState } from '../../../recoil-scope/utils/getSnapshotScopedState'; import { useCloseCurrentCellInEditMode } from '../../hooks/useClearCellInEditMode'; -import { CellContext } from '../../states/CellContext'; +import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext'; import { isSomeInputInEditModeState } from '../../states/isSomeInputInEditModeState'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; -import { customCellHotkeyScopeScopedState } from '../states/customCellHotkeyScopeScopedState'; import { useCurrentCellEditMode } from './useCurrentCellEditMode'; @@ -24,7 +22,7 @@ export function useEditableCell() { const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode(); - const cellContextId = useContextScopeId(CellContext); + const customCellHotkeyScope = useContext(CellHotkeyScopeContext); function closeEditableCell() { closeCurrentCellInEditMode(); @@ -38,12 +36,6 @@ export function useEditableCell() { .getLoadable(isSomeInputInEditModeState) .valueOrThrow(); - const customCellHotkeyScope = getSnapshotScopedState({ - snapshot, - state: customCellHotkeyScopeScopedState, - contextScopeId: cellContextId, - }); - if (!isSomeInputInEditMode) { set(isSomeInputInEditModeState, true); @@ -62,7 +54,7 @@ export function useEditableCell() { } } }, - [setCurrentCellInEditMode, setHotkeyScope, cellContextId], + [setCurrentCellInEditMode, setHotkeyScope, customCellHotkeyScope], ); return { diff --git a/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts b/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts index 9d509b3da..134ec8e67 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useRegisterCloseCellHandlers.ts @@ -14,6 +14,7 @@ export function useRegisterCloseCellHandlers( ) { const { closeEditableCell } = useEditableCell(); const { isCurrentCellInEditMode } = useCurrentCellEditMode(); + useListenClickOutside({ refs: [wrapperRef], callback: (event) => { @@ -26,6 +27,7 @@ export function useRegisterCloseCellHandlers( } }, }); + const { moveRight, moveLeft, moveDown } = useMoveSoftFocus(); useScopedHotkeys( diff --git a/front/src/modules/ui/table/editable-cell/hooks/useRegisterEditableCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useRegisterEditableCell.ts deleted file mode 100644 index 4988eb5f6..000000000 --- a/front/src/modules/ui/table/editable-cell/hooks/useRegisterEditableCell.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useEffect } from 'react'; - -import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope'; - -import { useRecoilScopedState } from '../../../recoil-scope/hooks/useRecoilScopedState'; -import { CellContext } from '../../states/CellContext'; -import { TableHotkeyScope } from '../../types/TableHotkeyScope'; -import { customCellHotkeyScopeScopedState } from '../states/customCellHotkeyScopeScopedState'; - -const DEFAULT_CELL_SCOPE: HotkeyScope = { - scope: TableHotkeyScope.CellEditMode, -}; - -export function useRegisterEditableCell(cellHotkeyScope?: HotkeyScope) { - const [, setCustomCellHotkeyScope] = useRecoilScopedState( - customCellHotkeyScopeScopedState, - CellContext, - ); - - useEffect(() => { - setCustomCellHotkeyScope(cellHotkeyScope ?? DEFAULT_CELL_SCOPE); - }, [cellHotkeyScope, setCustomCellHotkeyScope]); -} diff --git a/front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentCell.ts index a80093bd8..4abd2372b 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentCell.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useSetSoftFocusOnCurrentCell.ts @@ -1,50 +1,29 @@ -import { useMemo } from 'react'; import { useRecoilCallback } from 'recoil'; import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope'; -import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState'; import { useSetSoftFocusPosition } from '../../hooks/useSetSoftFocusPosition'; -import { CellContext } from '../../states/CellContext'; -import { currentColumnNumberScopedState } from '../../states/currentColumnNumberScopedState'; -import { currentRowNumberScopedState } from '../../states/currentRowNumberScopedState'; import { isSoftFocusActiveState } from '../../states/isSoftFocusActiveState'; -import { RowContext } from '../../states/RowContext'; -import { CellPosition } from '../../types/CellPosition'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; +import { useCurrentCellPosition } from './useCurrentCellPosition'; + export function useSetSoftFocusOnCurrentCell() { const setSoftFocusPosition = useSetSoftFocusPosition(); - const [currentRowNumber] = useRecoilScopedState( - currentRowNumberScopedState, - RowContext, - ); - - const [currentColumnNumber] = useRecoilScopedState( - currentColumnNumberScopedState, - CellContext, - ); - - const currentTablePosition: CellPosition = useMemo( - () => ({ - column: currentColumnNumber, - row: currentRowNumber, - }), - [currentColumnNumber, currentRowNumber], - ); + const currentCellPosition = useCurrentCellPosition(); const setHotkeyScope = useSetHotkeyScope(); return useRecoilCallback( ({ set }) => () => { - setSoftFocusPosition(currentTablePosition); + setSoftFocusPosition(currentCellPosition); set(isSoftFocusActiveState, true); setHotkeyScope(TableHotkeyScope.TableSoftFocus); }, - [setHotkeyScope, currentTablePosition, setSoftFocusPosition], + [setHotkeyScope, currentCellPosition, setSoftFocusPosition], ); } diff --git a/front/src/modules/ui/table/editable-cell/states/customCellHotkeyScopeScopedState.ts b/front/src/modules/ui/table/editable-cell/states/customCellHotkeyScopeScopedState.ts deleted file mode 100644 index 4f0d9a224..000000000 --- a/front/src/modules/ui/table/editable-cell/states/customCellHotkeyScopeScopedState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { atomFamily } from 'recoil'; - -import { HotkeyScope } from '../../../hotkey/types/HotkeyScope'; - -export const customCellHotkeyScopeScopedState = atomFamily< - HotkeyScope | null, - string ->({ - key: 'customCellHotkeyScopeScopedState', - default: null, -}); diff --git a/front/src/modules/ui/table/editable-cell/types/EditableCellDateEditMode.tsx b/front/src/modules/ui/table/editable-cell/types/EditableCellDateEditMode.tsx index 8df4535e4..914c221bd 100644 --- a/front/src/modules/ui/table/editable-cell/types/EditableCellDateEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/types/EditableCellDateEditMode.tsx @@ -1,9 +1,11 @@ +import { useRef } from 'react'; import styled from '@emotion/styled'; import { Key } from 'ts-key-enum'; import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys'; import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate'; +import { useListenClickOutside } from '../../../hooks/useListenClickOutside'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { useEditableCell } from '../hooks/useEditableCell'; @@ -38,8 +40,21 @@ export function EditableCellDateEditMode({ [closeEditableCell], ); + const containerRef = useRef(null); + + useListenClickOutside({ + refs: [containerRef], + callback: (event) => { + event.stopImmediatePropagation(); + event.stopPropagation(); + event.preventDefault(); + + closeEditableCell(); + }, + }); + return ( - + ); diff --git a/front/src/modules/ui/table/editable-cell/types/EditableCellDoubleText.tsx b/front/src/modules/ui/table/editable-cell/types/EditableCellDoubleText.tsx index 6c96fd796..3f347f558 100644 --- a/front/src/modules/ui/table/editable-cell/types/EditableCellDoubleText.tsx +++ b/front/src/modules/ui/table/editable-cell/types/EditableCellDoubleText.tsx @@ -1,4 +1,4 @@ -import { ReactElement, useEffect, useState } from 'react'; +import { ReactElement } from 'react'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { CellSkeleton } from '../components/CellSkeleton'; @@ -12,8 +12,7 @@ type OwnProps = { firstValuePlaceholder: string; secondValuePlaceholder: string; nonEditModeContent: ReactElement; - onChange: (firstValue: string, secondValue: string) => void; - onSubmit?: () => void; + onSubmit?: (firstValue: string, secondValue: string) => void; onCancel?: () => void; loading?: boolean; }; @@ -23,36 +22,21 @@ export function EditableCellDoubleText({ secondValue, firstValuePlaceholder, secondValuePlaceholder, - onChange, + onSubmit, onCancel, nonEditModeContent, loading, }: OwnProps) { - const [firstInternalValue, setFirstInternalValue] = useState(firstValue); - const [secondInternalValue, setSecondInternalValue] = useState(secondValue); - - useEffect(() => { - setFirstInternalValue(firstValue); - setSecondInternalValue(secondValue); - }, [firstValue, secondValue]); - - function handleOnChange(firstValue: string, secondValue: string): void { - setFirstInternalValue(firstValue); - setSecondInternalValue(secondValue); - onChange(firstValue, secondValue); - } - return ( diff --git a/front/src/modules/ui/table/editable-cell/types/EditableCellDoubleTextEditMode.tsx b/front/src/modules/ui/table/editable-cell/types/EditableCellDoubleTextEditMode.tsx index ccd3ba307..a5f969b44 100644 --- a/front/src/modules/ui/table/editable-cell/types/EditableCellDoubleTextEditMode.tsx +++ b/front/src/modules/ui/table/editable-cell/types/EditableCellDoubleTextEditMode.tsx @@ -1,21 +1,22 @@ -import { ChangeEvent, useRef, useState } from 'react'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; import styled from '@emotion/styled'; import { Key } from 'ts-key-enum'; import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys'; -import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; +import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus'; import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { useEditableCell } from '../hooks/useEditableCell'; +import { useRegisterCloseCellHandlers } from '../hooks/useRegisterCloseCellHandlers'; type OwnProps = { firstValue: string; secondValue: string; firstValuePlaceholder: string; secondValuePlaceholder: string; - onChange: (firstValue: string, secondValue: string) => void; - onSubmit?: () => void; + onChange?: (firstValue: string, secondValue: string) => void; + onSubmit?: (firstValue: string, secondValue: string) => void; onCancel?: () => void; }; @@ -39,6 +40,19 @@ export function EditableCellDoubleTextEditMode({ onSubmit, onCancel, }: OwnProps) { + const [firstInternalValue, setFirstInternalValue] = useState(firstValue); + const [secondInternalValue, setSecondInternalValue] = useState(secondValue); + + useEffect(() => { + setFirstInternalValue(firstValue); + setSecondInternalValue(secondValue); + }, [firstValue, secondValue]); + + function handleOnChange(firstValue: string, secondValue: string): void { + setFirstInternalValue(firstValue); + setSecondInternalValue(secondValue); + } + const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left'); const firstValueInputRef = useRef(null); @@ -52,12 +66,23 @@ export function EditableCellDoubleTextEditMode({ closeEditableCell(); } + function handleCancel() { + setFirstInternalValue(firstValue); + setSecondInternalValue(secondValue); + + onCancel?.(); + } + + function handleSubmit() { + onSubmit?.(firstInternalValue, secondInternalValue); + } + useScopedHotkeys( Key.Enter, () => { closeCell(); moveDown(); - onSubmit?.(); + handleSubmit(); }, TableHotkeyScope.CellDoubleTextInput, [closeCell], @@ -66,7 +91,7 @@ export function EditableCellDoubleTextEditMode({ useScopedHotkeys( Key.Escape, () => { - onCancel?.(); + handleCancel(); closeCell(); }, TableHotkeyScope.CellDoubleTextInput, @@ -80,7 +105,8 @@ export function EditableCellDoubleTextEditMode({ setFocusPosition('right'); secondValueInputRef.current?.focus(); } else { - onSubmit?.(); + handleSubmit(); + closeCell(); moveRight(); } @@ -96,7 +122,7 @@ export function EditableCellDoubleTextEditMode({ setFocusPosition('left'); firstValueInputRef.current?.focus(); } else { - onSubmit?.(); + handleSubmit(); closeCell(); moveLeft(); } @@ -105,23 +131,27 @@ export function EditableCellDoubleTextEditMode({ [closeCell, moveRight, focusPosition], ); + const wrapperRef = useRef(null); + + useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel); + return ( - - + ) => { - onChange(event.target.value, secondValue); + handleOnChange(event.target.value, secondValue); }} /> - ) => { - onChange(firstValue, event.target.value); + handleOnChange(firstValue, event.target.value); }} /> diff --git a/front/src/modules/ui/table/editable-cell/types/EditableCellPhone.tsx b/front/src/modules/ui/table/editable-cell/types/EditableCellPhone.tsx index 408944980..2f97b8962 100644 --- a/front/src/modules/ui/table/editable-cell/types/EditableCellPhone.tsx +++ b/front/src/modules/ui/table/editable-cell/types/EditableCellPhone.tsx @@ -1,5 +1,3 @@ -import { ChangeEvent, useEffect, useRef, useState } from 'react'; - import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode'; import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; @@ -8,42 +6,21 @@ import { EditableCell } from '../components/EditableCell'; type OwnProps = { placeholder?: string; value: string; - onChange: (updated: string) => void; - onSubmit?: () => void; - onCancel?: () => void; + onSubmit?: (newText: string) => void; }; -export function EditableCellPhone({ - value, - placeholder, - onChange, - onSubmit, - onCancel, -}: OwnProps) { - const inputRef = useRef(null); - const [inputValue, setInputValue] = useState(value); - - useEffect(() => { - setInputValue(value); - }, [value]); - +export function EditableCellPhone({ value, placeholder, onSubmit }: OwnProps) { return ( ) => { - setInputValue(event.target.value); - onChange(event.target.value); - }} + value={value} + onSubmit={(newText) => onSubmit?.(newText)} /> } - nonEditModeContent={} - onSubmit={onSubmit} - onCancel={onCancel} + nonEditModeContent={} /> ); } diff --git a/front/src/modules/ui/table/editable-cell/types/EditableCellText.tsx b/front/src/modules/ui/table/editable-cell/types/EditableCellText.tsx index da1e60c1a..d20f20137 100644 --- a/front/src/modules/ui/table/editable-cell/types/EditableCellText.tsx +++ b/front/src/modules/ui/table/editable-cell/types/EditableCellText.tsx @@ -1,5 +1,3 @@ -import { ChangeEvent, useEffect, useState } from 'react'; - import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode'; import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; @@ -9,10 +7,10 @@ import { EditableCell } from '../components/EditableCell'; type OwnProps = { placeholder?: string; value: string; - onChange: (newValue: string) => void; + onChange?: (newValue: string) => void; editModeHorizontalAlign?: 'left' | 'right'; loading?: boolean; - onSubmit?: () => void; + onSubmit?: (newText: string) => void; onCancel?: () => void; }; @@ -25,12 +23,6 @@ export function EditableCellText({ onCancel, onSubmit, }: OwnProps) { - const [internalValue, setInternalValue] = useState(value); - - useEffect(() => { - setInternalValue(value); - }, [value]); - return ( ) => { - setInternalValue(event.target.value); - onChange(event.target.value); - }} + value={value} + onSubmit={(newText) => onSubmit?.(newText)} /> } - onSubmit={onSubmit} - onCancel={onCancel} nonEditModeContent={ loading ? ( ) : ( - - {internalValue} - + {value} ) } > diff --git a/front/src/modules/ui/table/editable-cell/types/EditableCellURL.tsx b/front/src/modules/ui/table/editable-cell/types/EditableCellURL.tsx index 0390d8f77..56babdc20 100644 --- a/front/src/modules/ui/table/editable-cell/types/EditableCellURL.tsx +++ b/front/src/modules/ui/table/editable-cell/types/EditableCellURL.tsx @@ -1,5 +1,3 @@ -import { ChangeEvent, useEffect, useState } from 'react'; - import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode'; import { RawLink } from '../../../link/components/RawLink'; @@ -9,53 +7,40 @@ import { EditableCell } from '../components/EditableCell'; type OwnProps = { placeholder?: string; url: string; - onChange: (newURL: string) => void; + onChange?: (newURL: string) => void; editModeHorizontalAlign?: 'left' | 'right'; loading?: boolean; - onSubmit?: () => void; + onSubmit?: (newURL: string) => void; onCancel?: () => void; }; export function EditableCellURL({ url, placeholder, - onChange, editModeHorizontalAlign, loading, - onCancel, onSubmit, }: OwnProps) { - const [internalValue, setInternalValue] = useState(url); - - useEffect(() => { - setInternalValue(url); - }, [url]); - return ( ) => { - setInternalValue(event.target.value); - onChange(event.target.value); - }} + value={url} + onSubmit={(newURL) => onSubmit?.(newURL)} /> } - onSubmit={onSubmit} - onCancel={onCancel} nonEditModeContent={ loading ? ( ) : ( e.stopPropagation()} - href={internalValue ? 'https://' + internalValue : ''} + href={url ? 'https://' + url : ''} > - {internalValue} + {url} ) } diff --git a/front/src/modules/ui/table/editable-cell/types/EditableChip.tsx b/front/src/modules/ui/table/editable-cell/types/EditableChip.tsx index 1208a5391..62e4816c8 100644 --- a/front/src/modules/ui/table/editable-cell/types/EditableChip.tsx +++ b/front/src/modules/ui/table/editable-cell/types/EditableChip.tsx @@ -1,30 +1,21 @@ -import { ChangeEvent, ReactNode, useEffect, useRef, useState } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import styled from '@emotion/styled'; -import { textInputStyle } from '@/ui/themes/effects'; - +import { InplaceInputTextEditMode } from '../../../inplace-input/components/InplaceInputTextEditMode'; import { EditableCell } from '../components/EditableCell'; export type EditableChipProps = { placeholder?: string; value: string; - changeHandler: (updated: string) => void; editModeHorizontalAlign?: 'left' | 'right'; ChipComponent: React.ReactNode; commentThreadCount?: number; onCommentClick?: (event: React.MouseEvent) => void; rightEndContents?: ReactNode[]; - onSubmit?: () => void; + onSubmit?: (newValue: string) => void; onCancel?: () => void; }; -// TODO: refactor -const StyledInplaceInput = styled.input` - width: 100%; - - ${textInputStyle} -`; - const NoEditModeContainer = styled.div` align-items: center; display: flex; @@ -40,14 +31,11 @@ const RightContainer = styled.div` export function EditableCellChip({ value, placeholder, - changeHandler, editModeHorizontalAlign, ChipComponent, rightEndContents, onSubmit, - onCancel, }: EditableChipProps) { - const inputRef = useRef(null); const [inputValue, setInputValue] = useState(value); useEffect(() => { @@ -64,19 +52,13 @@ export function EditableCellChip({ ) => { - setInputValue(event.target.value); - changeHandler(event.target.value); - }} + onSubmit={(newValue) => onSubmit?.(newValue)} /> } - onSubmit={onSubmit} - onCancel={onCancel} nonEditModeContent={ {ChipComponent} diff --git a/front/src/modules/ui/table/hooks/useCurrentEntityId.ts b/front/src/modules/ui/table/hooks/useCurrentEntityId.ts index 3499f70dd..05040206d 100644 --- a/front/src/modules/ui/table/hooks/useCurrentEntityId.ts +++ b/front/src/modules/ui/table/hooks/useCurrentEntityId.ts @@ -1,7 +1,6 @@ -import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue'; +import { useContext } from 'react'; -import { currentRowEntityIdScopedState } from '../states/currentRowEntityIdScopedState'; -import { RowContext } from '../states/RowContext'; +import { RowIdContext } from '../states/RowIdContext'; export type TableDimensions = { numberOfColumns: number; @@ -9,10 +8,7 @@ export type TableDimensions = { }; export function useCurrentRowEntityId() { - const currentRowEntityIdScoped = useRecoilScopedValue( - currentRowEntityIdScopedState, - RowContext, - ); + const currentEntityId = useContext(RowIdContext); - return currentRowEntityIdScoped; + return currentEntityId; } diff --git a/front/src/modules/ui/table/hooks/useCurrentRowSelected.ts b/front/src/modules/ui/table/hooks/useCurrentRowSelected.ts index 46ec08fbd..0f5ea5665 100644 --- a/front/src/modules/ui/table/hooks/useCurrentRowSelected.ts +++ b/front/src/modules/ui/table/hooks/useCurrentRowSelected.ts @@ -1,11 +1,11 @@ +import { useContext } from 'react'; import { useRecoilCallback, useRecoilState } from 'recoil'; import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState'; - -import { useCurrentRowEntityId } from './useCurrentEntityId'; +import { RowIdContext } from '../states/RowIdContext'; export function useCurrentRowSelected() { - const currentRowId = useCurrentRowEntityId(); + const currentRowId = useContext(RowIdContext); const [isRowSelected] = useRecoilState( isRowSelectedFamilyState(currentRowId ?? ''), diff --git a/front/src/modules/ui/table/hooks/useLeaveTableFocus.ts b/front/src/modules/ui/table/hooks/useLeaveTableFocus.ts index b1a184bee..e858f2c1f 100644 --- a/front/src/modules/ui/table/hooks/useLeaveTableFocus.ts +++ b/front/src/modules/ui/table/hooks/useLeaveTableFocus.ts @@ -45,8 +45,6 @@ export function useLeaveTableFocus() { closeCurrentCellInEditMode(); disableSoftFocus(); - - setHotkeyScope(TableHotkeyScope.Table, { goto: true }); }, [setHotkeyScope, closeCurrentCellInEditMode, disableSoftFocus], ); diff --git a/front/src/modules/ui/table/states/CellContext.ts b/front/src/modules/ui/table/states/CellContext.ts deleted file mode 100644 index 10724b0bf..000000000 --- a/front/src/modules/ui/table/states/CellContext.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createContext } from 'react'; - -export const CellContext = createContext(null); diff --git a/front/src/modules/ui/table/states/CellHotkeyScopeContext.ts b/front/src/modules/ui/table/states/CellHotkeyScopeContext.ts new file mode 100644 index 000000000..434fb72e7 --- /dev/null +++ b/front/src/modules/ui/table/states/CellHotkeyScopeContext.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +import { HotkeyScope } from '../../hotkey/types/HotkeyScope'; + +export const CellHotkeyScopeContext = createContext(null); diff --git a/front/src/modules/ui/table/states/ColumnIndexContext.ts b/front/src/modules/ui/table/states/ColumnIndexContext.ts new file mode 100644 index 000000000..536c684e1 --- /dev/null +++ b/front/src/modules/ui/table/states/ColumnIndexContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const ColumnIndexContext = createContext(0); diff --git a/front/src/modules/ui/table/states/RowContext.ts b/front/src/modules/ui/table/states/RowContext.ts deleted file mode 100644 index 8b6ad6acc..000000000 --- a/front/src/modules/ui/table/states/RowContext.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createContext } from 'react'; - -export const RowContext = createContext(null); diff --git a/front/src/modules/ui/table/states/RowIdContext.ts b/front/src/modules/ui/table/states/RowIdContext.ts new file mode 100644 index 000000000..a0459158e --- /dev/null +++ b/front/src/modules/ui/table/states/RowIdContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const RowIdContext = createContext(null); diff --git a/front/src/modules/ui/table/states/RowIndexContext.ts b/front/src/modules/ui/table/states/RowIndexContext.ts new file mode 100644 index 000000000..03d028bb2 --- /dev/null +++ b/front/src/modules/ui/table/states/RowIndexContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const RowIndexContext = createContext(0); diff --git a/front/src/modules/ui/table/table-header/components/__stories__/TableHeader.stories.tsx b/front/src/modules/ui/table/table-header/components/__stories__/TableHeader.stories.tsx index c511d8466..776c0301d 100644 --- a/front/src/modules/ui/table/table-header/components/__stories__/TableHeader.stories.tsx +++ b/front/src/modules/ui/table/table-header/components/__stories__/TableHeader.stories.tsx @@ -3,11 +3,10 @@ import { userEvent, within } from '@storybook/testing-library'; import { IconList } from '@/ui/icon/index'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; -import { companiesFilters } from '~/pages/companies/companies-filters'; import { availableSorts } from '~/pages/companies/companies-sorts'; -import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; -import { HooksEntityTable } from '../../../components/HooksEntityTable'; +import { ComponentWithRouterDecorator } from '../../../../../../testing/decorators/ComponentWithRouterDecorator'; +import { CompanyEntityTableDataMocked } from '../../../../../companies/table/components/CompanyEntityTableDataMocked'; import { TableContext } from '../../../states/TableContext'; import { TableHeader } from '../TableHeader'; @@ -17,15 +16,11 @@ const meta: Meta = { decorators: [ (Story) => ( - {/* TODO: add company mocked loader + ), - ComponentDecorator, + ComponentWithRouterDecorator, ], argTypes: { viewIcon: { control: false } }, args: { diff --git a/front/src/pages/auth/Verify.tsx b/front/src/pages/auth/Verify.tsx index 9c8e0d0a1..cbf21c3d8 100644 --- a/front/src/pages/auth/Verify.tsx +++ b/front/src/pages/auth/Verify.tsx @@ -21,6 +21,7 @@ export function Verify() { navigate(AppPath.SignIn); } else { await verify(loginToken); + navigate(AppPath.CompaniesPage); } } diff --git a/front/src/sync-hooks/AnalyticsHook.tsx b/front/src/sync-hooks/AnalyticsHook.tsx deleted file mode 100644 index 3f3088815..000000000 --- a/front/src/sync-hooks/AnalyticsHook.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useTrackPageView } from '@/analytics/hooks/useTrackPageView'; - -export function AnalyticsHook() { - useTrackPageView(); - - return <>; -} diff --git a/front/src/sync-hooks/AppInternalHooks.tsx b/front/src/sync-hooks/AppInternalHooks.tsx index a3443b2b6..91340c881 100644 --- a/front/src/sync-hooks/AppInternalHooks.tsx +++ b/front/src/sync-hooks/AppInternalHooks.tsx @@ -1,15 +1,9 @@ -import { AnalyticsHook } from './AnalyticsHook'; import { GotoHotkeysHooks } from './GotoHotkeysHooks'; -import { HotkeyScopeAutoSyncHook } from './HotkeyScopeAutoSyncHook'; -import { HotkeyScopeBrowserRouterSync } from './HotkeyScopeBrowserRouterSync'; export function AppInternalHooks() { return ( <> - - - ); } diff --git a/front/src/sync-hooks/AuthAutoRouter.tsx b/front/src/sync-hooks/AuthAutoRouter.tsx new file mode 100644 index 000000000..8cd32a7be --- /dev/null +++ b/front/src/sync-hooks/AuthAutoRouter.tsx @@ -0,0 +1,142 @@ +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +import { useIsMatchingLocation } from '../hooks/useIsMatchingLocation'; +import { useEventTracker } from '../modules/analytics/hooks/useEventTracker'; +import { useOnboardingStatus } from '../modules/auth/hooks/useOnboardingStatus'; +import { OnboardingStatus } from '../modules/auth/utils/getOnboardingStatus'; +import { AppBasePath } from '../modules/types/AppBasePath'; +import { AppPath } from '../modules/types/AppPath'; +import { PageHotkeyScope } from '../modules/types/PageHotkeyScope'; +import { SettingsPath } from '../modules/types/SettingsPath'; +import { useSetHotkeyScope } from '../modules/ui/hotkey/hooks/useSetHotkeyScope'; +import { TableHotkeyScope } from '../modules/ui/table/types/TableHotkeyScope'; + +export function AuthAutoRouter() { + const navigate = useNavigate(); + const isMatchingLocation = useIsMatchingLocation(); + + const [previousLocation, setPreviousLocation] = useState(''); + + const onboardingStatus = useOnboardingStatus(); + + const setHotkeyScope = useSetHotkeyScope(); + + const location = useLocation(); + + const eventTracker = useEventTracker(); + + useEffect(() => { + if (!previousLocation || previousLocation !== location.pathname) { + setPreviousLocation(location.pathname); + } else { + return; + } + + const isMachinOngoingUserCreationRoute = + isMatchingLocation(AppPath.SignUp) || + isMatchingLocation(AppPath.SignIn) || + isMatchingLocation(AppPath.Invite) || + isMatchingLocation(AppPath.Verify); + + const isMatchingOnboardingRoute = + isMatchingLocation(AppPath.SignUp) || + isMatchingLocation(AppPath.SignIn) || + isMatchingLocation(AppPath.Invite) || + isMatchingLocation(AppPath.Verify) || + isMatchingLocation(AppPath.CreateWorkspace) || + isMatchingLocation(AppPath.CreateProfile); + + if ( + onboardingStatus === OnboardingStatus.OngoingUserCreation && + !isMachinOngoingUserCreationRoute + ) { + navigate(AppPath.SignIn); + } else if ( + onboardingStatus === OnboardingStatus.OngoingWorkspaceCreation && + !isMatchingLocation(AppPath.CreateWorkspace) + ) { + navigate(AppPath.CreateWorkspace); + } else if ( + onboardingStatus === OnboardingStatus.OngoingProfileCreation && + !isMatchingLocation(AppPath.CreateProfile) + ) { + navigate(AppPath.CreateProfile); + } else if ( + onboardingStatus === OnboardingStatus.Completed && + isMatchingOnboardingRoute + ) { + navigate('/'); + } + + switch (true) { + case isMatchingLocation(AppPath.CompaniesPage): { + setHotkeyScope(TableHotkeyScope.Table, { goto: true }); + break; + } + case isMatchingLocation(AppPath.PeoplePage): { + setHotkeyScope(TableHotkeyScope.Table, { goto: true }); + break; + } + case isMatchingLocation(AppPath.CompanyShowPage): { + setHotkeyScope(PageHotkeyScope.CompanyShowPage, { goto: true }); + break; + } + case isMatchingLocation(AppPath.PersonShowPage): { + setHotkeyScope(PageHotkeyScope.PersonShowPage, { goto: true }); + break; + } + case isMatchingLocation(AppPath.OpportunitiesPage): { + setHotkeyScope(PageHotkeyScope.OpportunitiesPage, { goto: true }); + break; + } + case isMatchingLocation(AppPath.SignIn): { + setHotkeyScope(PageHotkeyScope.SignInUp); + break; + } + case isMatchingLocation(AppPath.SignUp): { + setHotkeyScope(PageHotkeyScope.SignInUp); + break; + } + case isMatchingLocation(AppPath.Invite): { + setHotkeyScope(PageHotkeyScope.SignInUp); + break; + } + case isMatchingLocation(AppPath.CreateProfile): { + setHotkeyScope(PageHotkeyScope.CreateProfile); + break; + } + case isMatchingLocation(AppPath.CreateWorkspace): { + setHotkeyScope(PageHotkeyScope.CreateWokspace); + break; + } + case isMatchingLocation(SettingsPath.ProfilePage, AppBasePath.Settings): { + setHotkeyScope(PageHotkeyScope.ProfilePage, { goto: true }); + break; + } + case isMatchingLocation( + SettingsPath.WorkspaceMembersPage, + AppBasePath.Settings, + ): { + setHotkeyScope(PageHotkeyScope.WorkspaceMemberPage, { goto: true }); + break; + } + } + + eventTracker('pageview', { + location: { + pathname: location.pathname, + }, + }); + }, [ + onboardingStatus, + navigate, + isMatchingLocation, + setHotkeyScope, + location, + previousLocation, + eventTracker, + ]); + + return <>; +} diff --git a/front/src/sync-hooks/HotkeyScopeAutoSyncHook.tsx b/front/src/sync-hooks/HotkeyScopeAutoSyncHook.tsx deleted file mode 100644 index 5786e61c7..000000000 --- a/front/src/sync-hooks/HotkeyScopeAutoSyncHook.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { useHotkeyScopeAutoSync } from '@/ui/hotkey/hooks/internal/useHotkeyScopeAutoSync'; - -export function HotkeyScopeAutoSyncHook() { - useHotkeyScopeAutoSync(); - - return <>; -} diff --git a/front/src/sync-hooks/HotkeyScopeBrowserRouterSync.tsx b/front/src/sync-hooks/HotkeyScopeBrowserRouterSync.tsx deleted file mode 100644 index f20915154..000000000 --- a/front/src/sync-hooks/HotkeyScopeBrowserRouterSync.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useEffect } from 'react'; - -import { AppBasePath } from '@/types/AppBasePath'; -import { AppPath } from '@/types/AppPath'; -import { PageHotkeyScope } from '@/types/PageHotkeyScope'; -import { SettingsPath } from '@/types/SettingsPath'; -import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope'; -import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope'; -import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation'; - -export function HotkeyScopeBrowserRouterSync() { - const isMatchingLocation = useIsMatchingLocation(); - - const setHotkeyScope = useSetHotkeyScope(); - - useEffect(() => { - switch (true) { - case isMatchingLocation(AppPath.CompaniesPage): { - setHotkeyScope(TableHotkeyScope.Table, { goto: true }); - break; - } - case isMatchingLocation(AppPath.PeoplePage): { - setHotkeyScope(TableHotkeyScope.Table, { goto: true }); - break; - } - case isMatchingLocation(AppPath.CompanyShowPage): { - setHotkeyScope(PageHotkeyScope.CompanyShowPage, { goto: true }); - break; - } - case isMatchingLocation(AppPath.PersonShowPage): { - setHotkeyScope(PageHotkeyScope.PersonShowPage, { goto: true }); - break; - } - case isMatchingLocation(AppPath.OpportunitiesPage): { - setHotkeyScope(PageHotkeyScope.OpportunitiesPage, { goto: true }); - break; - } - case isMatchingLocation(AppPath.SignIn): { - setHotkeyScope(PageHotkeyScope.SignInUp); - break; - } - case isMatchingLocation(AppPath.SignUp): { - setHotkeyScope(PageHotkeyScope.SignInUp); - break; - } - case isMatchingLocation(AppPath.Invite): { - setHotkeyScope(PageHotkeyScope.SignInUp); - break; - } - case isMatchingLocation(AppPath.CreateProfile): { - setHotkeyScope(PageHotkeyScope.CreateProfile); - break; - } - case isMatchingLocation(AppPath.CreateWorkspace): { - setHotkeyScope(PageHotkeyScope.CreateWokspace); - break; - } - case isMatchingLocation(SettingsPath.ProfilePage, AppBasePath.Settings): { - setHotkeyScope(PageHotkeyScope.ProfilePage, { goto: true }); - break; - } - case isMatchingLocation( - SettingsPath.WorkspaceMembersPage, - AppBasePath.Settings, - ): { - setHotkeyScope(PageHotkeyScope.WorkspaceMemberPage, { goto: true }); - break; - } - } - }, [isMatchingLocation, setHotkeyScope]); - - return <>; -} diff --git a/front/src/testing/InitializeHotkeyStorybookHook.tsx b/front/src/testing/InitializeHotkeyStorybookHook.tsx new file mode 100644 index 000000000..767cddc86 --- /dev/null +++ b/front/src/testing/InitializeHotkeyStorybookHook.tsx @@ -0,0 +1,14 @@ +import { useEffect } from 'react'; + +import { useSetHotkeyScope } from '../modules/ui/hotkey/hooks/useSetHotkeyScope'; +import { AppHotkeyScope } from '../modules/ui/hotkey/types/AppHotkeyScope'; + +export function InitializeHotkeyStorybookHook() { + const setHotkeyScope = useSetHotkeyScope(); + + useEffect(() => { + setHotkeyScope(AppHotkeyScope.App, { commandMenu: true, goto: false }); + }, [setHotkeyScope]); + + return <>; +} diff --git a/front/src/testing/decorators/CellPositionDecorator.tsx b/front/src/testing/decorators/CellPositionDecorator.tsx index 04240c616..951b47335 100644 --- a/front/src/testing/decorators/CellPositionDecorator.tsx +++ b/front/src/testing/decorators/CellPositionDecorator.tsx @@ -1,13 +1,12 @@ import { Decorator } from '@storybook/react'; -import { RecoilScope } from '../../modules/ui/recoil-scope/components/RecoilScope'; -import { CellContext } from '../../modules/ui/table/states/CellContext'; -import { RowContext } from '../../modules/ui/table/states/RowContext'; +import { ColumnIndexContext } from '../../modules/ui/table/states/ColumnIndexContext'; +import { RowIndexContext } from '../../modules/ui/table/states/RowIndexContext'; export const CellPositionDecorator: Decorator = (Story) => ( - - + + - - + + ); diff --git a/front/src/testing/decorators/PageDecorator.tsx b/front/src/testing/decorators/PageDecorator.tsx index 4d9117172..628ef34b5 100644 --- a/front/src/testing/decorators/PageDecorator.tsx +++ b/front/src/testing/decorators/PageDecorator.tsx @@ -1,11 +1,10 @@ -import { HotkeysProvider } from 'react-hotkeys-hook'; import { MemoryRouter } from 'react-router-dom'; import { Decorator } from '@storybook/react'; -import { ClientConfigProvider } from '../../modules/client-config/components/ClientConfigProvider'; -import { INITIAL_HOTKEYS_SCOPES } from '../../modules/ui/hotkey/constants'; -import { DefaultLayout } from '../../modules/ui/layout/components/DefaultLayout'; -import { UserProvider } from '../../modules/users/components/UserProvider'; +import { ClientConfigProvider } from '~/modules/client-config/components/ClientConfigProvider'; +import { DefaultLayout } from '~/modules/ui/layout/components/DefaultLayout'; +import { UserProvider } from '~/modules/users/components/UserProvider'; + import { FullHeightStorybookLayout } from '../FullHeightStorybookLayout'; export type PageDecoratorArgs = { currentPath: string }; @@ -16,15 +15,13 @@ export const PageDecorator: Decorator<{ currentPath: string }> = ( ) => ( - - - - - - - - - + + + + + + + ); diff --git a/front/src/testing/decorators/RootDecorator.tsx b/front/src/testing/decorators/RootDecorator.tsx index e2d63c247..9b62eb013 100644 --- a/front/src/testing/decorators/RootDecorator.tsx +++ b/front/src/testing/decorators/RootDecorator.tsx @@ -2,11 +2,13 @@ import { ApolloProvider } from '@apollo/client'; import { Decorator } from '@storybook/react'; import { RecoilRoot } from 'recoil'; +import { InitializeHotkeyStorybookHook } from '../InitializeHotkeyStorybookHook'; import { mockedClient } from '../mockedClient'; export const RootDecorator: Decorator = (Story) => ( + diff --git a/front/src/utils/measureTotalFrameLoad.ts b/front/src/utils/measureTotalFrameLoad.ts new file mode 100644 index 000000000..f999ae136 --- /dev/null +++ b/front/src/utils/measureTotalFrameLoad.ts @@ -0,0 +1,11 @@ +import afterFrame from 'afterframe'; + +export function measureTotalFrameLoad(id: string) { + const timerId = `Total loading time for : ${id}`; + + console.time(timerId); + + afterFrame(() => { + console.timeEnd(timerId); + }); +} diff --git a/front/yarn.lock b/front/yarn.lock index 262be2270..2158f9df9 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -5877,6 +5877,11 @@ adjust-sourcemap-loader@^4.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" +afterframe@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/afterframe/-/afterframe-1.0.2.tgz#c63e17cdb29e4e60be2e618a315caf5ab5ade0c0" + integrity sha512-0JeMZI7dIfVs5guqLgidQNV7c6jBC2HO0QNSekAUB82Hr7PdU9QXNAF3kpFkvATvHYDDTGto7FPsRu1ey+aKJQ== + agent-base@5: version "5.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"