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 <charles@twenty.com>
This commit is contained in:
@ -6,6 +6,7 @@ import { lightTheme, darkTheme } from '../src/modules/ui/themes/themes';
|
|||||||
import { RootDecorator } from '../src/testing/decorators/RootDecorator';
|
import { RootDecorator } from '../src/testing/decorators/RootDecorator';
|
||||||
import 'react-loading-skeleton/dist/skeleton.css';
|
import 'react-loading-skeleton/dist/skeleton.css';
|
||||||
import { mockedUserJWT } from '../src/testing/mock-data/jwt';
|
import { mockedUserJWT } from '../src/testing/mock-data/jwt';
|
||||||
|
|
||||||
initialize();
|
initialize();
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
"@types/react": "^18.0.25",
|
"@types/react": "^18.0.25",
|
||||||
"@types/react-dom": "^18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
"@types/react-modal": "^3.16.0",
|
"@types/react-modal": "^3.16.0",
|
||||||
|
"afterframe": "^1.0.2",
|
||||||
"apollo-link-rest": "^0.9.0",
|
"apollo-link-rest": "^0.9.0",
|
||||||
"apollo-upload-client": "^17.0.0",
|
"apollo-upload-client": "^17.0.0",
|
||||||
"cmdk": "^0.2.0",
|
"cmdk": "^0.2.0",
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
export default function usePrevious<T>(state: T): T | undefined {
|
|
||||||
const ref = useRef<T>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
ref.current = state;
|
|
||||||
});
|
|
||||||
|
|
||||||
return ref.current;
|
|
||||||
}
|
|
||||||
@ -1,12 +1,10 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { HotkeysProvider } from 'react-hotkeys-hook';
|
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
|
|
||||||
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
|
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
|
||||||
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
|
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
|
||||||
import { INITIAL_HOTKEYS_SCOPES } from '@/ui/hotkey/constants';
|
|
||||||
import { SnackBarProvider } from '@/ui/snack-bar/components/SnackBarProvider';
|
import { SnackBarProvider } from '@/ui/snack-bar/components/SnackBarProvider';
|
||||||
import { AppThemeProvider } from '@/ui/themes/components/AppThemeProvider';
|
import { AppThemeProvider } from '@/ui/themes/components/AppThemeProvider';
|
||||||
import { ThemeType } from '@/ui/themes/themes';
|
import { ThemeType } from '@/ui/themes/themes';
|
||||||
@ -14,6 +12,7 @@ import { UserProvider } from '@/users/components/UserProvider';
|
|||||||
|
|
||||||
import '@emotion/react';
|
import '@emotion/react';
|
||||||
|
|
||||||
|
import { AuthAutoRouter } from './sync-hooks/AuthAutoRouter';
|
||||||
import { App } from './App';
|
import { App } from './App';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
@ -26,23 +25,20 @@ const root = ReactDOM.createRoot(
|
|||||||
root.render(
|
root.render(
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<ApolloProvider>
|
<ApolloProvider>
|
||||||
<AppThemeProvider>
|
<UserProvider>
|
||||||
<StrictMode>
|
<ClientConfigProvider>
|
||||||
<BrowserRouter>
|
<AppThemeProvider>
|
||||||
<UserProvider>
|
<SnackBarProvider>
|
||||||
<SnackBarProvider>
|
<BrowserRouter>
|
||||||
<ClientConfigProvider>
|
<AuthAutoRouter />
|
||||||
<HotkeysProvider
|
<StrictMode>
|
||||||
initiallyActiveScopes={INITIAL_HOTKEYS_SCOPES}
|
<App />
|
||||||
>
|
</StrictMode>
|
||||||
<App />
|
</BrowserRouter>
|
||||||
</HotkeysProvider>
|
</SnackBarProvider>
|
||||||
</ClientConfigProvider>
|
</AppThemeProvider>
|
||||||
</SnackBarProvider>
|
</ClientConfigProvider>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
</BrowserRouter>
|
|
||||||
</StrictMode>
|
|
||||||
</AppThemeProvider>
|
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
</RecoilRoot>,
|
</RecoilRoot>,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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]);
|
|
||||||
}
|
|
||||||
@ -48,15 +48,19 @@ export function CompanyAccountOwnerPicker({
|
|||||||
searchOnFields: ['firstName', 'lastName'],
|
searchOnFields: ['firstName', 'lastName'],
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleEntitySelected(selectedUser: UserForSelect) {
|
async function handleEntitySelected(
|
||||||
await updateCompany({
|
selectedUser: UserForSelect | null | undefined,
|
||||||
variables: {
|
) {
|
||||||
where: { id: company.id },
|
if (selectedUser) {
|
||||||
data: {
|
await updateCompany({
|
||||||
accountOwner: { connect: { id: selectedUser.id } },
|
variables: {
|
||||||
|
where: { id: company.id },
|
||||||
|
data: {
|
||||||
|
accountOwner: { connect: { id: selectedUser.id } },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
onSubmit?.();
|
onSubmit?.();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
|
||||||
import { EditableCellChip } from '@/ui/table/editable-cell/types/EditableChip';
|
import { EditableCellChip } from '@/ui/table/editable-cell/types/EditableChip';
|
||||||
@ -22,17 +21,10 @@ type OwnProps = {
|
|||||||
export function CompanyEditableNameChipCell({ company }: OwnProps) {
|
export function CompanyEditableNameChipCell({ company }: OwnProps) {
|
||||||
const [updateCompany] = useUpdateOneCompanyMutation();
|
const [updateCompany] = useUpdateOneCompanyMutation();
|
||||||
|
|
||||||
const [internalValue, setInternalValue] = useState(company.name ?? '');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalValue(company.name ?? '');
|
|
||||||
}, [company.name]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellChip
|
<EditableCellChip
|
||||||
value={internalValue}
|
value={company.name}
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
changeHandler={setInternalValue}
|
|
||||||
ChipComponent={
|
ChipComponent={
|
||||||
<CompanyChip
|
<CompanyChip
|
||||||
id={company.id}
|
id={company.id}
|
||||||
@ -40,18 +32,17 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) {
|
|||||||
pictureUrl={getLogoUrlFromDomainName(company.domainName)}
|
pictureUrl={getLogoUrlFromDomainName(company.domainName)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onSubmit={() =>
|
onSubmit={(newName) =>
|
||||||
updateCompany({
|
updateCompany({
|
||||||
variables: {
|
variables: {
|
||||||
where: { id: company.id },
|
where: { id: company.id },
|
||||||
data: {
|
data: {
|
||||||
name: internalValue,
|
name: newName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
refetchQueries: [getOperationName(GET_COMPANY) ?? ''],
|
refetchQueries: [getOperationName(GET_COMPANY) ?? ''],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onCancel={() => setInternalValue(company.name ?? '')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { defaultOrderBy } from '@/companies/queries';
|
import { defaultOrderBy } from '@/companies/queries';
|
||||||
import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState';
|
|
||||||
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
|
|
||||||
import {
|
import {
|
||||||
PersonOrderByWithRelationInput,
|
PersonOrderByWithRelationInput,
|
||||||
useGetCompaniesQuery,
|
useGetCompaniesQuery,
|
||||||
@ -17,12 +13,6 @@ export function CompanyEntityTableData({
|
|||||||
orderBy?: PersonOrderByWithRelationInput[];
|
orderBy?: PersonOrderByWithRelationInput[];
|
||||||
whereFilters?: any;
|
whereFilters?: any;
|
||||||
}) {
|
}) {
|
||||||
const [, setTableRowIds] = useRecoilState(tableRowIdsState);
|
|
||||||
|
|
||||||
const [, setIsFetchingEntityTableData] = useRecoilState(
|
|
||||||
isFetchingEntityTableDataState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setCompanyEntityTable = useSetCompanyEntityTable();
|
const setCompanyEntityTable = useSetCompanyEntityTable();
|
||||||
|
|
||||||
useGetCompaniesQuery({
|
useGetCompaniesQuery({
|
||||||
@ -30,19 +20,7 @@ export function CompanyEntityTableData({
|
|||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
const companies = data.companies ?? [];
|
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);
|
setCompanyEntityTable(companies);
|
||||||
|
|
||||||
setIsFetchingEntityTableData(false);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,37 +1,15 @@
|
|||||||
import { useEffect } from 'react';
|
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 { useSetCompanyEntityTable } from '../hooks/useSetCompanyEntityTable';
|
||||||
|
|
||||||
import { mockedCompaniesData } from './companies-mock-data';
|
import { mockedCompaniesData } from './companies-mock-data';
|
||||||
|
|
||||||
export function CompanyEntityTableDataMocked() {
|
export function CompanyEntityTableDataMocked() {
|
||||||
const [, setTableRowIds] = useRecoilState(tableRowIdsState);
|
|
||||||
|
|
||||||
const [, setIsFetchingEntityTableData] = useRecoilState(
|
|
||||||
isFetchingEntityTableDataState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setCompanyEntityTable = useSetCompanyEntityTable();
|
const setCompanyEntityTable = useSetCompanyEntityTable();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const companyIds = mockedCompaniesData.map((company) => company.id);
|
|
||||||
|
|
||||||
setTableRowIds((currentRowIds) => {
|
|
||||||
if (JSON.stringify(currentRowIds) !== JSON.stringify(companyIds)) {
|
|
||||||
return companyIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentRowIds;
|
|
||||||
});
|
|
||||||
|
|
||||||
setCompanyEntityTable(mockedCompaniesData);
|
setCompanyEntityTable(mockedCompaniesData);
|
||||||
|
}, [setCompanyEntityTable]);
|
||||||
setIsFetchingEntityTableData(false);
|
|
||||||
}, [setCompanyEntityTable, setIsFetchingEntityTableData, setTableRowIds]);
|
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,10 +9,8 @@ import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIn
|
|||||||
import { IconList } from '@/ui/icon';
|
import { IconList } from '@/ui/icon';
|
||||||
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
|
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||||
import { HooksEntityTable } from '@/ui/table/components/HooksEntityTable';
|
|
||||||
import { TableContext } from '@/ui/table/states/TableContext';
|
import { TableContext } from '@/ui/table/states/TableContext';
|
||||||
import { CompanyOrderByWithRelationInput } from '~/generated/graphql';
|
import { CompanyOrderByWithRelationInput } from '~/generated/graphql';
|
||||||
import { companiesFilters } from '~/pages/companies/companies-filters';
|
|
||||||
import { availableSorts } from '~/pages/companies/companies-sorts';
|
import { availableSorts } from '~/pages/companies/companies-sorts';
|
||||||
|
|
||||||
export function CompanyTable() {
|
export function CompanyTable() {
|
||||||
@ -32,10 +30,6 @@ export function CompanyTable() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CompanyEntityTableData orderBy={orderBy} whereFilters={whereFilters} />
|
<CompanyEntityTableData orderBy={orderBy} whereFilters={whereFilters} />
|
||||||
<HooksEntityTable
|
|
||||||
numberOfColumns={companyColumns.length}
|
|
||||||
availableFilters={companiesFilters}
|
|
||||||
/>
|
|
||||||
<EntityTable
|
<EntityTable
|
||||||
columns={companyColumns}
|
columns={companyColumns}
|
||||||
viewName="All Companies"
|
viewName="All Companies"
|
||||||
|
|||||||
@ -2,18 +2,12 @@ import { companyColumns } from '@/companies/table/components/companyColumns';
|
|||||||
import { CompanyEntityTableDataMocked } from '@/companies/table/components/CompanyEntityTableDataMocked';
|
import { CompanyEntityTableDataMocked } from '@/companies/table/components/CompanyEntityTableDataMocked';
|
||||||
import { IconList } from '@/ui/icon';
|
import { IconList } from '@/ui/icon';
|
||||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||||
import { HooksEntityTable } from '@/ui/table/components/HooksEntityTable';
|
|
||||||
import { companiesFilters } from '~/pages/companies/companies-filters';
|
|
||||||
import { availableSorts } from '~/pages/companies/companies-sorts';
|
import { availableSorts } from '~/pages/companies/companies-sorts';
|
||||||
|
|
||||||
export function CompanyTableMockMode() {
|
export function CompanyTableMockMode() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CompanyEntityTableDataMocked />
|
<CompanyEntityTableDataMocked />
|
||||||
<HooksEntityTable
|
|
||||||
numberOfColumns={companyColumns.length}
|
|
||||||
availableFilters={companiesFilters}
|
|
||||||
/>
|
|
||||||
<EntityTable
|
<EntityTable
|
||||||
columns={companyColumns}
|
columns={companyColumns}
|
||||||
viewName="All Companies"
|
viewName="All Companies"
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { companyDomainNameFamilyState } from '@/companies/states/companyDomainNameFamilyState';
|
import { companyDomainNameFamilyState } from '@/companies/states/companyDomainNameFamilyState';
|
||||||
@ -12,31 +11,25 @@ export function EditableCompanyDomainNameCell() {
|
|||||||
|
|
||||||
const [updateCompany] = useUpdateOneCompanyMutation();
|
const [updateCompany] = useUpdateOneCompanyMutation();
|
||||||
|
|
||||||
const name = useRecoilValue(
|
const domainName = useRecoilValue(
|
||||||
companyDomainNameFamilyState(currentRowEntityId ?? ''),
|
companyDomainNameFamilyState(currentRowEntityId ?? ''),
|
||||||
);
|
);
|
||||||
const [internalValue, setInternalValue] = useState(name ?? '');
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalValue(name ?? '');
|
|
||||||
}, [name]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellURL
|
<EditableCellURL
|
||||||
url={internalValue}
|
url={domainName ?? ''}
|
||||||
onChange={setInternalValue}
|
onSubmit={(newURL) =>
|
||||||
onSubmit={() =>
|
|
||||||
updateCompany({
|
updateCompany({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
id: currentRowEntityId,
|
id: currentRowEntityId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
domainName: internalValue,
|
domainName: newURL,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onCancel={() => setInternalValue(name ?? '')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { companyAccountOwnerFamilyState } from '@/companies/states/companyAccountOwnerFamilyState';
|
import { companyAccountOwnerFamilyState } from '@/companies/states/companyAccountOwnerFamilyState';
|
||||||
@ -10,7 +11,24 @@ import { companyLinkedinUrlFamilyState } from '@/companies/states/companyLinkedi
|
|||||||
import { companyNameFamilyState } from '@/companies/states/companyNameFamilyState';
|
import { companyNameFamilyState } from '@/companies/states/companyNameFamilyState';
|
||||||
import { GetCompaniesQuery } from '~/generated/graphql';
|
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() {
|
export function useSetCompanyEntityTable() {
|
||||||
|
const resetTableRowSelection = useResetTableRowSelection();
|
||||||
|
|
||||||
|
const tableContextScopeId = useContextScopeId(TableContext);
|
||||||
|
|
||||||
|
const currentLocation = useLocation().pathname;
|
||||||
|
|
||||||
return useRecoilCallback(
|
return useRecoilCallback(
|
||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
(newCompanyArray: GetCompaniesQuery['companies']) => {
|
(newCompanyArray: GetCompaniesQuery['companies']) => {
|
||||||
@ -94,7 +112,30 @@ export function useSetCompanyEntityTable() {
|
|||||||
set(companyCreatedAtFamilyState(company.id), company.createdAt);
|
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],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,8 @@ type OwnProps = {
|
|||||||
>
|
>
|
||||||
| null
|
| null
|
||||||
| undefined;
|
| undefined;
|
||||||
onChange: (firstName: string, lastName: string) => void;
|
onChange?: (firstName: string, lastName: string) => void;
|
||||||
onSubmit?: () => void;
|
onSubmit?: (firstName: string, lastName: string) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,20 +37,12 @@ export function EditablePeopleFullName({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
function handleDoubleTextChange(
|
|
||||||
firstValue: string,
|
|
||||||
secondValue: string,
|
|
||||||
): void {
|
|
||||||
onChange(firstValue, secondValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellDoubleText
|
<EditableCellDoubleText
|
||||||
firstValue={person?.firstName ?? ''}
|
firstValue={person?.firstName ?? ''}
|
||||||
secondValue={person?.lastName ?? ''}
|
secondValue={person?.lastName ?? ''}
|
||||||
firstValuePlaceholder="First name"
|
firstValuePlaceholder="First name"
|
||||||
secondValuePlaceholder="Last name"
|
secondValuePlaceholder="Last name"
|
||||||
onChange={handleDoubleTextChange}
|
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
nonEditModeContent={
|
nonEditModeContent={
|
||||||
|
|||||||
@ -16,6 +16,8 @@ import {
|
|||||||
useUpdateOnePersonMutation,
|
useUpdateOnePersonMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { EntityForSelect } from '../../ui/relation-picker/types/EntityForSelect';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
people: Pick<Person, 'id'> & { company?: Pick<Company, 'id'> | null };
|
people: Pick<Person, 'id'> & { company?: Pick<Company, 'id'> | null };
|
||||||
};
|
};
|
||||||
@ -37,17 +39,21 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
|
|||||||
selectedIds: people.company?.id ? [people.company.id] : [],
|
selectedIds: people.company?.id ? [people.company.id] : [],
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleEntitySelected(entity: any) {
|
async function handleEntitySelected(
|
||||||
await updatePerson({
|
entity: EntityForSelect | null | undefined,
|
||||||
variables: {
|
) {
|
||||||
where: {
|
if (entity) {
|
||||||
id: people.id,
|
await updatePerson({
|
||||||
|
variables: {
|
||||||
|
where: {
|
||||||
|
id: people.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
company: { connect: { id: entity.id } },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data: {
|
});
|
||||||
company: { connect: { id: entity.id } },
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
closeEditableCell();
|
closeEditableCell();
|
||||||
}
|
}
|
||||||
@ -67,6 +73,7 @@ export function PeopleCompanyPicker({ people }: OwnProps) {
|
|||||||
return (
|
return (
|
||||||
<SingleEntitySelect
|
<SingleEntitySelect
|
||||||
onCreate={handleCreate}
|
onCreate={handleCreate}
|
||||||
|
onCancel={() => closeEditableCell()}
|
||||||
onEntitySelected={handleEntitySelected}
|
onEntitySelected={handleEntitySelected}
|
||||||
entities={{
|
entities={{
|
||||||
entitiesToSelect: companies.entitiesToSelect,
|
entitiesToSelect: companies.entitiesToSelect,
|
||||||
|
|||||||
@ -27,17 +27,22 @@ export function PeopleCompanyEditableFieldEditMode({ people }: OwnProps) {
|
|||||||
selectedIds: people.company?.id ? [people.company.id] : [],
|
selectedIds: people.company?.id ? [people.company.id] : [],
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleEntitySelected(entity: EntityForSelect) {
|
async function handleEntitySelected(
|
||||||
await updatePerson({
|
entity: EntityForSelect | null | undefined,
|
||||||
variables: {
|
) {
|
||||||
where: {
|
if (entity) {
|
||||||
id: people.id,
|
await updatePerson({
|
||||||
|
variables: {
|
||||||
|
where: {
|
||||||
|
id: people.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
company: { connect: { id: entity.id } },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data: {
|
});
|
||||||
company: { connect: { id: entity.id } },
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
closeEditableField();
|
closeEditableField();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,17 @@
|
|||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { GetPeopleQuery } from '~/generated/graphql';
|
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 { peopleCityFamilyState } from '../states/peopleCityFamilyState';
|
||||||
import { peopleCompanyFamilyState } from '../states/peopleCompanyFamilyState';
|
import { peopleCompanyFamilyState } from '../states/peopleCompanyFamilyState';
|
||||||
import { peopleCreatedAtFamilyState } from '../states/peopleCreatedAtFamilyState';
|
import { peopleCreatedAtFamilyState } from '../states/peopleCreatedAtFamilyState';
|
||||||
@ -10,8 +20,15 @@ import { peopleJobTitleFamilyState } from '../states/peopleJobTitleFamilyState';
|
|||||||
import { peopleLinkedinUrlFamilyState } from '../states/peopleLinkedinUrlFamilyState';
|
import { peopleLinkedinUrlFamilyState } from '../states/peopleLinkedinUrlFamilyState';
|
||||||
import { peopleNameCellFamilyState } from '../states/peopleNamesFamilyState';
|
import { peopleNameCellFamilyState } from '../states/peopleNamesFamilyState';
|
||||||
import { peoplePhoneFamilyState } from '../states/peoplePhoneFamilyState';
|
import { peoplePhoneFamilyState } from '../states/peoplePhoneFamilyState';
|
||||||
|
import { peopleColumns } from '../table/components/peopleColumns';
|
||||||
|
|
||||||
export function useSetPeopleEntityTable() {
|
export function useSetPeopleEntityTable() {
|
||||||
|
const resetTableRowSelection = useResetTableRowSelection();
|
||||||
|
|
||||||
|
const tableContextScopeId = useContextScopeId(TableContext);
|
||||||
|
|
||||||
|
const currentLocation = useLocation().pathname;
|
||||||
|
|
||||||
return useRecoilCallback(
|
return useRecoilCallback(
|
||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
(newPeopleArray: GetPeopleQuery['people']) => {
|
(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);
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { peopleCityFamilyState } from '@/people/states/peopleCityFamilyState';
|
import { peopleCityFamilyState } from '@/people/states/peopleCityFamilyState';
|
||||||
@ -13,29 +12,21 @@ export function EditablePeopleCityCell() {
|
|||||||
|
|
||||||
const city = useRecoilValue(peopleCityFamilyState(currentRowEntityId ?? ''));
|
const city = useRecoilValue(peopleCityFamilyState(currentRowEntityId ?? ''));
|
||||||
|
|
||||||
const [internalValue, setInternalValue] = useState(city ?? '');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalValue(city ?? '');
|
|
||||||
}, [city]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellText
|
<EditableCellText
|
||||||
value={internalValue}
|
value={city ?? ''}
|
||||||
onChange={setInternalValue}
|
onSubmit={(newText) =>
|
||||||
onSubmit={() =>
|
|
||||||
updatePerson({
|
updatePerson({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
id: currentRowEntityId,
|
id: currentRowEntityId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
city: internalValue,
|
city: newText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onCancel={() => setInternalValue(city ?? '')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
@ -18,45 +17,29 @@ export function EditablePeopleFullNameCell() {
|
|||||||
peopleNameCellFamilyState(currentRowEntityId ?? ''),
|
peopleNameCellFamilyState(currentRowEntityId ?? ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [internalFirstName, setInternalFirstName] = useState(firstName ?? '');
|
|
||||||
const [internalLastName, setInternalLastName] = useState(lastName ?? '');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalFirstName(firstName ?? '');
|
|
||||||
setInternalLastName(lastName ?? '');
|
|
||||||
}, [firstName, lastName]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditablePeopleFullName
|
<EditablePeopleFullName
|
||||||
person={{
|
person={{
|
||||||
id: currentRowEntityId ?? undefined,
|
id: currentRowEntityId ?? undefined,
|
||||||
_commentThreadCount: commentCount ?? undefined,
|
_commentThreadCount: commentCount ?? undefined,
|
||||||
firstName: internalFirstName,
|
firstName,
|
||||||
lastName: internalLastName,
|
lastName,
|
||||||
displayName: displayName ?? undefined,
|
displayName: displayName ?? undefined,
|
||||||
}}
|
}}
|
||||||
onChange={(firstName, lastName) => {
|
onSubmit={(newFirstValue, newSecondValue) =>
|
||||||
setInternalFirstName(firstName);
|
|
||||||
setInternalLastName(lastName);
|
|
||||||
}}
|
|
||||||
onSubmit={() =>
|
|
||||||
updatePerson({
|
updatePerson({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
id: currentRowEntityId,
|
id: currentRowEntityId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
firstName: internalFirstName,
|
firstName: newFirstValue,
|
||||||
lastName: internalLastName,
|
lastName: newSecondValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
refetchQueries: [getOperationName(GET_PERSON) ?? ''],
|
refetchQueries: [getOperationName(GET_PERSON) ?? ''],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onCancel={() => {
|
|
||||||
setInternalFirstName(firstName ?? '');
|
|
||||||
setInternalLastName(lastName ?? '');
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { peopleJobTitleFamilyState } from '@/people/states/peopleJobTitleFamilyState';
|
import { peopleJobTitleFamilyState } from '@/people/states/peopleJobTitleFamilyState';
|
||||||
@ -15,29 +14,21 @@ export function EditablePeopleJobTitleCell() {
|
|||||||
peopleJobTitleFamilyState(currentRowEntityId ?? ''),
|
peopleJobTitleFamilyState(currentRowEntityId ?? ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [internalValue, setInternalValue] = useState(jobTitle ?? '');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalValue(jobTitle ?? '');
|
|
||||||
}, [jobTitle]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellText
|
<EditableCellText
|
||||||
value={internalValue}
|
value={jobTitle ?? ''}
|
||||||
onChange={setInternalValue}
|
onSubmit={(newText) =>
|
||||||
onSubmit={() =>
|
|
||||||
updatePerson({
|
updatePerson({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
id: currentRowEntityId,
|
id: currentRowEntityId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
jobTitle: internalValue,
|
jobTitle: newText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onCancel={() => setInternalValue(jobTitle ?? '')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { peopleLinkedinUrlFamilyState } from '@/people/states/peopleLinkedinUrlFamilyState';
|
import { peopleLinkedinUrlFamilyState } from '@/people/states/peopleLinkedinUrlFamilyState';
|
||||||
@ -16,29 +15,21 @@ export function EditablePeopleLinkedinUrlCell() {
|
|||||||
peopleLinkedinUrlFamilyState(currentRowEntityId ?? ''),
|
peopleLinkedinUrlFamilyState(currentRowEntityId ?? ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [internalValue, setInternalValue] = useState(linkedinUrl ?? '');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalValue(linkedinUrl ?? '');
|
|
||||||
}, [linkedinUrl]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellURL
|
<EditableCellURL
|
||||||
url={internalValue}
|
url={linkedinUrl ?? ''}
|
||||||
onChange={setInternalValue}
|
onSubmit={(newURL) =>
|
||||||
onSubmit={() =>
|
|
||||||
updatePerson({
|
updatePerson({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
id: currentRowEntityId,
|
id: currentRowEntityId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
linkedinUrl: internalValue,
|
linkedinUrl: newURL,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onCancel={() => setInternalValue(linkedinUrl ?? '')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { peoplePhoneFamilyState } from '@/people/states/peoplePhoneFamilyState';
|
import { peoplePhoneFamilyState } from '@/people/states/peoplePhoneFamilyState';
|
||||||
@ -15,29 +14,21 @@ export function EditablePeoplePhoneCell() {
|
|||||||
peoplePhoneFamilyState(currentRowEntityId ?? ''),
|
peoplePhoneFamilyState(currentRowEntityId ?? ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [internalValue, setInternalValue] = useState(phone ?? '');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalValue(phone ?? '');
|
|
||||||
}, [phone]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellPhone
|
<EditableCellPhone
|
||||||
value={internalValue}
|
value={phone?.toString() ?? ''}
|
||||||
onChange={setInternalValue}
|
onSubmit={(newPhone) =>
|
||||||
onSubmit={() =>
|
|
||||||
updatePerson({
|
updatePerson({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
id: currentRowEntityId,
|
id: currentRowEntityId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
phone: internalValue,
|
phone: newPhone,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onCancel={() => setInternalValue(phone ?? '')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,8 @@ import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIn
|
|||||||
import { IconList } from '@/ui/icon';
|
import { IconList } from '@/ui/icon';
|
||||||
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
|
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
|
||||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||||
import { HooksEntityTable } from '@/ui/table/components/HooksEntityTable';
|
|
||||||
import { TableContext } from '@/ui/table/states/TableContext';
|
import { TableContext } from '@/ui/table/states/TableContext';
|
||||||
import { PersonOrderByWithRelationInput } from '~/generated/graphql';
|
import { PersonOrderByWithRelationInput } from '~/generated/graphql';
|
||||||
import { peopleFilters } from '~/pages/people/people-filters';
|
|
||||||
import { availableSorts } from '~/pages/people/people-sorts';
|
import { availableSorts } from '~/pages/people/people-sorts';
|
||||||
|
|
||||||
export function PeopleTable() {
|
export function PeopleTable() {
|
||||||
@ -33,10 +31,6 @@ export function PeopleTable() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PeopleEntityTableData orderBy={orderBy} whereFilters={whereFilters} />
|
<PeopleEntityTableData orderBy={orderBy} whereFilters={whereFilters} />
|
||||||
<HooksEntityTable
|
|
||||||
numberOfColumns={peopleColumns.length}
|
|
||||||
availableFilters={peopleFilters}
|
|
||||||
/>
|
|
||||||
<EntityTable
|
<EntityTable
|
||||||
columns={peopleColumns}
|
columns={peopleColumns}
|
||||||
viewName="All People"
|
viewName="All People"
|
||||||
|
|||||||
@ -44,7 +44,13 @@ export function PipelineProgressPointOfContactPicker({
|
|||||||
: [],
|
: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handleEntitySelected(entity: EntityForSelect) {
|
async function handleEntitySelected(
|
||||||
|
entity: EntityForSelect | null | undefined,
|
||||||
|
) {
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await updatePipelineProgress({
|
await updatePipelineProgress({
|
||||||
variables: {
|
variables: {
|
||||||
...pipelineProgress,
|
...pipelineProgress,
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useMatch, useResolvedPath } from 'react-router-dom';
|
import { useMatch, useNavigate, useResolvedPath } from 'react-router-dom';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
import { useAuth } from '@/auth/hooks/useAuth';
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
|
import { AppPath } from '@/types/AppPath';
|
||||||
import {
|
import {
|
||||||
IconColorSwatch,
|
IconColorSwatch,
|
||||||
IconLogout,
|
IconLogout,
|
||||||
@ -16,12 +17,14 @@ import SubMenuNavbar from '@/ui/navbar/components/SubMenuNavbar';
|
|||||||
|
|
||||||
export function SettingsNavbar() {
|
export function SettingsNavbar() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { signOut } = useAuth();
|
const { signOut } = useAuth();
|
||||||
|
|
||||||
const handleLogout = useCallback(() => {
|
const handleLogout = useCallback(() => {
|
||||||
signOut();
|
signOut();
|
||||||
}, [signOut]);
|
navigate(AppPath.SignIn);
|
||||||
|
}, [signOut, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuNavbar backButtonTitle="Settings">
|
<SubMenuNavbar backButtonTitle="Settings">
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { ChangeEvent, useMemo, useState } from 'react';
|
import { ChangeEvent, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
|
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 { debounce } from '~/utils/debounce';
|
||||||
|
|
||||||
import { BoardCardEditableField } from './BoardCardEditableField';
|
import { BoardCardEditableField } from './BoardCardEditableField';
|
||||||
@ -29,7 +29,7 @@ export function BoardCardEditableFieldText({
|
|||||||
<BoardCardEditableField
|
<BoardCardEditableField
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<InplaceInputTextEditMode
|
<StyledInput
|
||||||
placeholder={placeholder || ''}
|
placeholder={placeholder || ''}
|
||||||
autoFocus
|
autoFocus
|
||||||
value={internalValue}
|
value={internalValue}
|
||||||
|
|||||||
42
front/src/modules/ui/debug/components/TimingProfiler.tsx
Normal file
42
front/src/modules/ui/debug/components/TimingProfiler.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Profiler } from 'react';
|
||||||
|
import { Interaction } from 'scheduler/tracing';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
id: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function TimingProfiler({ id, children }: OwnProps) {
|
||||||
|
function handleRender(
|
||||||
|
id: string,
|
||||||
|
phase: 'mount' | 'update',
|
||||||
|
actualDuration: number,
|
||||||
|
baseDuration: number,
|
||||||
|
startTime: number,
|
||||||
|
commitTime: number,
|
||||||
|
interactions: Set<Interaction>,
|
||||||
|
) {
|
||||||
|
console.debug(
|
||||||
|
'TimingProfiler',
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
phase,
|
||||||
|
actualDuration,
|
||||||
|
baseDuration,
|
||||||
|
startTime,
|
||||||
|
commitTime,
|
||||||
|
interactions,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Profiler id={id} onRender={handleRender}>
|
||||||
|
{children}
|
||||||
|
</Profiler>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { Context, useCallback, useState } from 'react';
|
import { Context, useCallback, useState } from 'react';
|
||||||
import { Key } from 'ts-key-enum';
|
|
||||||
|
|
||||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||||
import { filterDefinitionUsedInDropdownScopedState } from '@/ui/filter-n-sort/states/filterDefinitionUsedInDropdownScopedState';
|
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 { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||||
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/ui/filter-n-sort/states/isFilterDropdownOperandSelectUnfoldedScopedState';
|
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/ui/filter-n-sort/states/isFilterDropdownOperandSelectUnfoldedScopedState';
|
||||||
import { selectedOperandInDropdownScopedState } from '@/ui/filter-n-sort/states/selectedOperandInDropdownScopedState';
|
import { selectedOperandInDropdownScopedState } from '@/ui/filter-n-sort/states/selectedOperandInDropdownScopedState';
|
||||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
|
||||||
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
|
|
||||||
|
|
||||||
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
||||||
|
|
||||||
@ -83,15 +80,6 @@ export function FilterDropdownButton({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
handleIsUnfoldedChange(false);
|
|
||||||
},
|
|
||||||
RelationPickerHotkeyScope.RelationPicker,
|
|
||||||
[handleIsUnfoldedChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
label="Filter"
|
label="Filter"
|
||||||
|
|||||||
@ -36,8 +36,14 @@ export function FilterDropdownEntitySearchSelect({
|
|||||||
|
|
||||||
const filterCurrentlyEdited = useFilterCurrentlyEdited(context);
|
const filterCurrentlyEdited = useFilterCurrentlyEdited(context);
|
||||||
|
|
||||||
function handleUserSelected(selectedEntity: EntityForSelect) {
|
function handleUserSelected(
|
||||||
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) {
|
selectedEntity: EntityForSelect | null | undefined,
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!filterDefinitionUsedInDropdown ||
|
||||||
|
!selectedOperandInDropdown ||
|
||||||
|
!selectedEntity
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
front/src/modules/ui/hooks/useIsPageLoading.ts
Normal file
12
front/src/modules/ui/hooks/useIsPageLoading.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { currentPageLocationState } from '../states/currentPageLocationState';
|
||||||
|
|
||||||
|
export function useIsPageLoading() {
|
||||||
|
const currentLocation = useLocation().pathname;
|
||||||
|
|
||||||
|
const currentPageLocation = useRecoilValue(currentPageLocationState);
|
||||||
|
|
||||||
|
return currentLocation !== currentPageLocation;
|
||||||
|
}
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
import { currentHotkeyScopeState } from '@/ui/hotkey/states/internal/currentHotkeyScopeState';
|
|
||||||
|
|
||||||
import { AppHotkeyScope } from '../../types/AppHotkeyScope';
|
|
||||||
|
|
||||||
import { useHotkeyScopes } from './useHotkeyScopes';
|
|
||||||
|
|
||||||
export function useHotkeyScopeAutoSync() {
|
|
||||||
const { setHotkeyScopes } = useHotkeyScopes();
|
|
||||||
|
|
||||||
const currentHotkeyScope = useRecoilValue(currentHotkeyScopeState);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
40
front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts
Normal file
40
front/src/modules/ui/hotkey/hooks/useScopedHotkeyCallback.ts
Normal file
@ -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);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import {
|
import {
|
||||||
Hotkey,
|
|
||||||
HotkeyCallback,
|
HotkeyCallback,
|
||||||
Keys,
|
Keys,
|
||||||
Options,
|
Options,
|
||||||
@ -10,6 +9,8 @@ import { useRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
|
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
|
||||||
|
|
||||||
|
import { useScopedHotkeyCallback } from './useScopedHotkeyCallback';
|
||||||
|
|
||||||
export function useScopedHotkeys(
|
export function useScopedHotkeys(
|
||||||
keys: Keys,
|
keys: Keys,
|
||||||
callback: HotkeyCallback,
|
callback: HotkeyCallback,
|
||||||
@ -23,21 +24,29 @@ export function useScopedHotkeys(
|
|||||||
) {
|
) {
|
||||||
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
|
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
|
||||||
|
|
||||||
function callbackIfDirectKey(
|
const callScopedHotkeyCallback = useScopedHotkeyCallback();
|
||||||
keyboardEvent: KeyboardEvent,
|
|
||||||
hotkeysEvent: Hotkey,
|
|
||||||
) {
|
|
||||||
if (!pendingHotkey) {
|
|
||||||
callback(keyboardEvent, hotkeysEvent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPendingHotkey(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return useHotkeys(
|
return useHotkeys(
|
||||||
keys,
|
keys,
|
||||||
callbackIfDirectKey,
|
(keyboardEvent, hotkeysEvent) => {
|
||||||
{ ...options, scopes: [scope] },
|
callScopedHotkeyCallback({
|
||||||
|
keyboardEvent,
|
||||||
|
hotkeysEvent,
|
||||||
|
callback: () => {
|
||||||
|
if (!pendingHotkey) {
|
||||||
|
callback(keyboardEvent, hotkeysEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPendingHotkey(null);
|
||||||
|
},
|
||||||
|
scope,
|
||||||
|
preventDefault: !!options.preventDefault,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enableOnContentEditable: options.enableOnContentEditable,
|
||||||
|
enableOnFormTags: options.enableOnFormTags,
|
||||||
|
},
|
||||||
dependencies,
|
dependencies,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,12 @@ import { useRecoilState } from 'recoil';
|
|||||||
|
|
||||||
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
|
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
|
||||||
|
|
||||||
|
import { useScopedHotkeyCallback } from './useScopedHotkeyCallback';
|
||||||
|
|
||||||
export function useSequenceHotkeys(
|
export function useSequenceHotkeys(
|
||||||
firstKey: Keys,
|
firstKey: Keys,
|
||||||
secondKey: Keys,
|
secondKey: Keys,
|
||||||
callback: () => void,
|
sequenceCallback: () => void,
|
||||||
scope: string,
|
scope: string,
|
||||||
options: Options = {
|
options: Options = {
|
||||||
enableOnContentEditable: true,
|
enableOnContentEditable: true,
|
||||||
@ -18,25 +20,57 @@ export function useSequenceHotkeys(
|
|||||||
) {
|
) {
|
||||||
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
|
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
|
||||||
|
|
||||||
|
const callScopedHotkeyCallback = useScopedHotkeyCallback();
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
firstKey,
|
firstKey,
|
||||||
() => {
|
(keyboardEvent, hotkeysEvent) => {
|
||||||
setPendingHotkey(firstKey);
|
callScopedHotkeyCallback({
|
||||||
|
keyboardEvent,
|
||||||
|
hotkeysEvent,
|
||||||
|
callback: () => {
|
||||||
|
setPendingHotkey(firstKey);
|
||||||
|
},
|
||||||
|
scope,
|
||||||
|
preventDefault: !!options.preventDefault,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{ ...options, scopes: [scope] },
|
{
|
||||||
[setPendingHotkey],
|
enableOnContentEditable: options.enableOnContentEditable,
|
||||||
|
enableOnFormTags: options.enableOnFormTags,
|
||||||
|
},
|
||||||
|
[setPendingHotkey, scope],
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
secondKey,
|
secondKey,
|
||||||
() => {
|
(keyboardEvent, hotkeysEvent) => {
|
||||||
if (pendingHotkey !== firstKey) {
|
callScopedHotkeyCallback({
|
||||||
return;
|
keyboardEvent,
|
||||||
}
|
hotkeysEvent,
|
||||||
setPendingHotkey(null);
|
callback: () => {
|
||||||
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],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import { isDefined } from '~/utils/isDefined';
|
|||||||
|
|
||||||
import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants';
|
import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants';
|
||||||
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
|
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
|
||||||
|
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
|
||||||
|
import { AppHotkeyScope } from '../types/AppHotkeyScope';
|
||||||
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
|
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
|
||||||
|
import { HotkeyScope } from '../types/HotkeyScope';
|
||||||
|
|
||||||
function isCustomScopesEqual(
|
function isCustomScopesEqual(
|
||||||
customScopesA: CustomHotkeyScopes | undefined,
|
customScopesA: CustomHotkeyScopes | undefined,
|
||||||
@ -46,13 +49,27 @@ export function useSetHotkeyScope() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set(currentHotkeyScopeState, {
|
const newHotkeyScope: HotkeyScope = {
|
||||||
scope: hotkeyScopeToSet,
|
scope: hotkeyScopeToSet,
|
||||||
customScopes: {
|
customScopes: {
|
||||||
commandMenu: customScopes?.commandMenu ?? true,
|
commandMenu: customScopes?.commandMenu ?? true,
|
||||||
goto: customScopes?.goto ?? false,
|
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);
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
import { StyledInput } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
firstValue: string;
|
firstValue: string;
|
||||||
@ -31,7 +31,7 @@ export function InplaceInputDoubleText({
|
|||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<InplaceInputTextEditMode
|
<StyledInput
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder={firstValuePlaceholder}
|
placeholder={firstValuePlaceholder}
|
||||||
value={firstValue}
|
value={firstValue}
|
||||||
@ -39,7 +39,7 @@ export function InplaceInputDoubleText({
|
|||||||
onChange(event.target.value, secondValue);
|
onChange(event.target.value, secondValue);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<InplaceInputTextEditMode
|
<StyledInput
|
||||||
placeholder={secondValuePlaceholder}
|
placeholder={secondValuePlaceholder}
|
||||||
value={secondValue}
|
value={secondValue}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
|||||||
@ -1,9 +1,58 @@
|
|||||||
|
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { textInputStyle } from '@/ui/themes/effects';
|
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;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
${textInputStyle}
|
${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<HTMLInputElement>) {
|
||||||
|
setInternalText(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInternalText(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledInput
|
||||||
|
ref={wrapperRef}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={handleChange}
|
||||||
|
autoFocus={autoFocus}
|
||||||
|
value={internalText}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { MOBILE_VIEWPORT } from '@/ui/themes/themes';
|
|||||||
import { AppNavbar } from '~/AppNavbar';
|
import { AppNavbar } from '~/AppNavbar';
|
||||||
import { CompaniesMockMode } from '~/pages/companies/CompaniesMockMode';
|
import { CompaniesMockMode } from '~/pages/companies/CompaniesMockMode';
|
||||||
|
|
||||||
import { useAutoNavigateOnboarding } from '../hooks/useAutoNavigateOnboarding';
|
|
||||||
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
|
import { isNavbarOpenedState } from '../states/isNavbarOpenedState';
|
||||||
|
|
||||||
const StyledLayout = styled.div`
|
const StyledLayout = styled.div`
|
||||||
@ -39,12 +38,10 @@ const MainContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
children: JSX.Element;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DefaultLayout({ children }: OwnProps) {
|
export function DefaultLayout({ children }: OwnProps) {
|
||||||
useAutoNavigateOnboarding();
|
|
||||||
|
|
||||||
const onboardingStatus = useOnboardingStatus();
|
const onboardingStatus = useOnboardingStatus();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -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]);
|
|
||||||
}
|
|
||||||
@ -35,7 +35,7 @@ export function SingleEntitySelect<
|
|||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
onCreate?: () => void;
|
onCreate?: () => void;
|
||||||
entities: EntitiesForSingleEntitySelect<CustomEntityForSelect>;
|
entities: EntitiesForSingleEntitySelect<CustomEntityForSelect>;
|
||||||
onEntitySelected: (entity: CustomEntityForSelect) => void;
|
onEntitySelected: (entity: CustomEntityForSelect | null | undefined) => void;
|
||||||
disableBackgroundBlur?: boolean;
|
disableBackgroundBlur?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -48,7 +48,11 @@ export function SingleEntitySelect<
|
|||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
refs: [containerRef],
|
refs: [containerRef],
|
||||||
callback: () => {
|
callback: (event) => {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export function SingleEntitySelectBase<
|
|||||||
onCancel,
|
onCancel,
|
||||||
}: {
|
}: {
|
||||||
entities: EntitiesForSingleEntitySelect<CustomEntityForSelect>;
|
entities: EntitiesForSingleEntitySelect<CustomEntityForSelect>;
|
||||||
onEntitySelected: (entity: CustomEntityForSelect) => void;
|
onEntitySelected: (entity: CustomEntityForSelect | null | undefined) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
6
front/src/modules/ui/states/currentPageLocationState.ts
Normal file
6
front/src/modules/ui/states/currentPageLocationState.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const currentPageLocationState = atom<string>({
|
||||||
|
key: 'currentPageLocationState',
|
||||||
|
default: '',
|
||||||
|
});
|
||||||
@ -5,7 +5,9 @@ import { TableColumn } from '@/people/table/components/peopleColumns';
|
|||||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||||
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/hooks/useListenClickOutside';
|
||||||
|
|
||||||
|
import { useIsPageLoading } from '../../hooks/useIsPageLoading';
|
||||||
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||||
|
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
||||||
import { TableHeader } from '../table-header/components/TableHeader';
|
import { TableHeader } from '../table-header/components/TableHeader';
|
||||||
|
|
||||||
import { EntityTableBody } from './EntityTableBody';
|
import { EntityTableBody } from './EntityTableBody';
|
||||||
@ -88,6 +90,8 @@ export function EntityTable<SortField>({
|
|||||||
}: OwnProps<SortField>) {
|
}: OwnProps<SortField>) {
|
||||||
const tableBodyRef = React.useRef<HTMLDivElement>(null);
|
const tableBodyRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useMapKeyboardToSoftFocus();
|
||||||
|
|
||||||
const leaveTableFocus = useLeaveTableFocus();
|
const leaveTableFocus = useLeaveTableFocus();
|
||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
@ -97,6 +101,12 @@ export function EntityTable<SortField>({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isPageLoading = useIsPageLoading();
|
||||||
|
|
||||||
|
if (isPageLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTableWithHeader>
|
<StyledTableWithHeader>
|
||||||
<TableHeader
|
<TableHeader
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import { useRecoilValue } from 'recoil';
|
|||||||
|
|
||||||
import { TableColumn } from '@/people/table/components/peopleColumns';
|
import { TableColumn } from '@/people/table/components/peopleColumns';
|
||||||
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
|
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
|
||||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
|
||||||
|
|
||||||
import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
|
import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
|
||||||
import { RowContext } from '../states/RowContext';
|
import { RowIdContext } from '../states/RowIdContext';
|
||||||
|
import { RowIndexContext } from '../states/RowIndexContext';
|
||||||
import { tableRowIdsState } from '../states/tableRowIdsState';
|
import { tableRowIdsState } from '../states/tableRowIdsState';
|
||||||
|
|
||||||
import { EntityTableRow } from './EntityTableRow';
|
import { EntityTableRow } from './EntityTableRow';
|
||||||
@ -19,15 +19,19 @@ export function EntityTableBody({ columns }: { columns: Array<TableColumn> }) {
|
|||||||
isFetchingEntityTableDataState,
|
isFetchingEntityTableDataState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isFetchingEntityTableData || isNavbarSwitchingSize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tbody>
|
<tbody>
|
||||||
{!isFetchingEntityTableData && !isNavbarSwitchingSize
|
{rowIds.map((rowId, index) => (
|
||||||
? rowIds.map((rowId, index) => (
|
<RowIdContext.Provider value={rowId} key={rowId}>
|
||||||
<RecoilScope SpecificContext={RowContext} key={rowId}>
|
<RowIndexContext.Provider value={index}>
|
||||||
<EntityTableRow columns={columns} rowId={rowId} index={index} />
|
<EntityTableRow columns={columns} rowId={rowId} />
|
||||||
</RecoilScope>
|
</RowIndexContext.Provider>
|
||||||
))
|
</RowIdContext.Provider>
|
||||||
: null}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,19 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useSetRecoilState } from 'recoil';
|
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 { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
|
||||||
import { CellContext } from '../states/CellContext';
|
import { ColumnIndexContext } from '../states/ColumnIndexContext';
|
||||||
import { contextMenuPositionState } from '../states/contextMenuPositionState';
|
import { contextMenuPositionState } from '../states/contextMenuPositionState';
|
||||||
import { currentColumnNumberScopedState } from '../states/currentColumnNumberScopedState';
|
|
||||||
|
|
||||||
export function EntityTableCell({
|
export function EntityTableCell({
|
||||||
rowId,
|
|
||||||
cellIndex,
|
cellIndex,
|
||||||
children,
|
children,
|
||||||
size,
|
size,
|
||||||
}: {
|
}: {
|
||||||
size: number;
|
size: number;
|
||||||
rowId: string;
|
|
||||||
cellIndex: number;
|
cellIndex: number;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [, setCurrentColumnNumber] = useRecoilScopedState(
|
|
||||||
currentColumnNumberScopedState,
|
|
||||||
CellContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentColumnNumber(cellIndex);
|
|
||||||
}, [cellIndex, setCurrentColumnNumber]);
|
|
||||||
|
|
||||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||||
|
|
||||||
const { setCurrentRowSelected } = useCurrentRowSelected();
|
const { setCurrentRowSelected } = useCurrentRowSelected();
|
||||||
@ -44,15 +30,19 @@ export function EntityTableCell({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<td
|
<RecoilScope>
|
||||||
onContextMenu={(event) => handleContextMenu(event)}
|
<ColumnIndexContext.Provider value={cellIndex}>
|
||||||
style={{
|
<td
|
||||||
width: size,
|
onContextMenu={(event) => handleContextMenu(event)}
|
||||||
minWidth: size,
|
style={{
|
||||||
maxWidth: size,
|
width: size,
|
||||||
}}
|
minWidth: size,
|
||||||
>
|
maxWidth: size,
|
||||||
{children}
|
}}
|
||||||
</td>
|
>
|
||||||
|
{children}
|
||||||
|
</td>
|
||||||
|
</ColumnIndexContext.Provider>
|
||||||
|
</RecoilScope>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
import { TableColumn } from '@/people/table/components/peopleColumns';
|
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 { CheckboxCell } from './CheckboxCell';
|
||||||
import { EntityTableCell } from './EntityTableCell';
|
import { EntityTableCell } from './EntityTableCell';
|
||||||
@ -23,56 +13,24 @@ const StyledRow = styled.tr<{ selected: boolean }>`
|
|||||||
export function EntityTableRow({
|
export function EntityTableRow({
|
||||||
columns,
|
columns,
|
||||||
rowId,
|
rowId,
|
||||||
index,
|
|
||||||
}: {
|
}: {
|
||||||
columns: TableColumn[];
|
columns: TableColumn[];
|
||||||
rowId: string;
|
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 (
|
return (
|
||||||
<StyledRow
|
<StyledRow data-testid={`row-id-${rowId}`} selected={false}>
|
||||||
key={rowId}
|
|
||||||
data-testid={`row-id-${rowId}`}
|
|
||||||
selected={isCurrentRowSelected}
|
|
||||||
>
|
|
||||||
<td>
|
<td>
|
||||||
<CheckboxCell />
|
<CheckboxCell />
|
||||||
</td>
|
</td>
|
||||||
{columns.map((column, columnIndex) => {
|
{columns.map((column, columnIndex) => {
|
||||||
return (
|
return (
|
||||||
<RecoilScope SpecificContext={CellContext} key={column.id.toString()}>
|
<EntityTableCell
|
||||||
<RecoilScope>
|
key={column.id}
|
||||||
<EntityTableCell
|
size={column.size}
|
||||||
rowId={rowId}
|
cellIndex={columnIndex}
|
||||||
size={column.size}
|
>
|
||||||
cellIndex={columnIndex}
|
{column.cellComponent}
|
||||||
>
|
</EntityTableCell>
|
||||||
{column.cellComponent}
|
|
||||||
</EntityTableCell>
|
|
||||||
</RecoilScope>
|
|
||||||
</RecoilScope>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|||||||
@ -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 <></>;
|
|
||||||
}
|
|
||||||
@ -3,9 +3,10 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
|
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
|
||||||
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
|
import { useCurrentCellEditMode } from '../hooks/useCurrentCellEditMode';
|
||||||
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
|
import { useIsSoftFocusOnCurrentCell } from '../hooks/useIsSoftFocusOnCurrentCell';
|
||||||
import { useRegisterEditableCell } from '../hooks/useRegisterEditableCell';
|
|
||||||
|
|
||||||
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
import { EditableCellDisplayMode } from './EditableCellDisplayMode';
|
||||||
import { EditableCellEditMode } from './EditableCellEditMode';
|
import { EditableCellEditMode } from './EditableCellEditMode';
|
||||||
@ -34,6 +35,10 @@ type OwnProps = {
|
|||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
||||||
|
scope: TableHotkeyScope.CellEditMode,
|
||||||
|
};
|
||||||
|
|
||||||
export function EditableCell({
|
export function EditableCell({
|
||||||
editModeHorizontalAlign = 'left',
|
editModeHorizontalAlign = 'left',
|
||||||
editModeVerticalPosition = 'over',
|
editModeVerticalPosition = 'over',
|
||||||
@ -42,35 +47,35 @@ export function EditableCell({
|
|||||||
editHotkeyScope,
|
editHotkeyScope,
|
||||||
transparent = false,
|
transparent = false,
|
||||||
maxContentWidth,
|
maxContentWidth,
|
||||||
onSubmit,
|
|
||||||
onCancel,
|
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||||
|
|
||||||
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
const hasSoftFocus = useIsSoftFocusOnCurrentCell();
|
||||||
|
|
||||||
useRegisterEditableCell(editHotkeyScope);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CellBaseContainer>
|
<CellHotkeyScopeContext.Provider
|
||||||
{isCurrentCellInEditMode ? (
|
value={editHotkeyScope ?? DEFAULT_CELL_SCOPE}
|
||||||
<EditableCellEditMode
|
>
|
||||||
maxContentWidth={maxContentWidth}
|
<CellBaseContainer>
|
||||||
transparent={transparent}
|
{isCurrentCellInEditMode ? (
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
<EditableCellEditMode
|
||||||
editModeVerticalPosition={editModeVerticalPosition}
|
maxContentWidth={maxContentWidth}
|
||||||
onSubmit={onSubmit}
|
transparent={transparent}
|
||||||
onCancel={onCancel}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
>
|
editModeVerticalPosition={editModeVerticalPosition}
|
||||||
{editModeContent}
|
>
|
||||||
</EditableCellEditMode>
|
{editModeContent}
|
||||||
) : hasSoftFocus ? (
|
</EditableCellEditMode>
|
||||||
<EditableCellSoftFocusMode>
|
) : hasSoftFocus ? (
|
||||||
{nonEditModeContent}
|
<EditableCellSoftFocusMode>
|
||||||
</EditableCellSoftFocusMode>
|
{nonEditModeContent}
|
||||||
) : (
|
</EditableCellSoftFocusMode>
|
||||||
<EditableCellDisplayMode>{nonEditModeContent}</EditableCellDisplayMode>
|
) : (
|
||||||
)}
|
<EditableCellDisplayMode>
|
||||||
</CellBaseContainer>
|
{nonEditModeContent}
|
||||||
|
</EditableCellDisplayMode>
|
||||||
|
)}
|
||||||
|
</CellBaseContainer>
|
||||||
|
</CellHotkeyScopeContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { ReactElement, useRef } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { overlayBackground } from '@/ui/themes/effects';
|
import { overlayBackground } from '@/ui/themes/effects';
|
||||||
|
|
||||||
import { useRegisterCloseCellHandlers } from '../hooks/useRegisterCloseCellHandlers';
|
|
||||||
|
|
||||||
export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
export const EditableCellEditModeContainer = styled.div<OwnProps>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: ${({ transparent, theme }) =>
|
border: ${({ transparent, theme }) =>
|
||||||
@ -36,30 +34,21 @@ type OwnProps = {
|
|||||||
maxContentWidth?: number;
|
maxContentWidth?: number;
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
editModeVerticalPosition?: 'over' | 'below';
|
editModeVerticalPosition?: 'over' | 'below';
|
||||||
onOutsideClick?: () => void;
|
initialValue?: string;
|
||||||
onCancel?: () => void;
|
|
||||||
onSubmit?: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableCellEditMode({
|
export function EditableCellEditMode({
|
||||||
editModeHorizontalAlign,
|
editModeHorizontalAlign,
|
||||||
editModeVerticalPosition,
|
editModeVerticalPosition,
|
||||||
children,
|
children,
|
||||||
onCancel,
|
|
||||||
onSubmit,
|
|
||||||
transparent = false,
|
transparent = false,
|
||||||
maxContentWidth,
|
maxContentWidth,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const wrapperRef = useRef(null);
|
|
||||||
|
|
||||||
useRegisterCloseCellHandlers(wrapperRef, onSubmit, onCancel);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellEditModeContainer
|
<EditableCellEditModeContainer
|
||||||
maxContentWidth={maxContentWidth}
|
maxContentWidth={maxContentWidth}
|
||||||
transparent={transparent}
|
transparent={transparent}
|
||||||
data-testid="editable-cell-edit-mode-container"
|
data-testid="editable-cell-edit-mode-container"
|
||||||
ref={wrapperRef}
|
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeVerticalPosition={editModeVerticalPosition}
|
editModeVerticalPosition={editModeVerticalPosition}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { userEvent, within } from '@storybook/testing-library';
|
|||||||
|
|
||||||
import { CellPositionDecorator } from '~/testing/decorators/CellPositionDecorator';
|
import { CellPositionDecorator } from '~/testing/decorators/CellPositionDecorator';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { sleep } from '~/testing/sleep';
|
||||||
|
|
||||||
import { EditableCellText } from '../../types/EditableCellText';
|
import { EditableCellText } from '../../types/EditableCellText';
|
||||||
|
|
||||||
@ -28,13 +29,12 @@ export const SoftFocusMode: Story = {
|
|||||||
play: async ({ canvasElement, step }) => {
|
play: async ({ canvasElement, step }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
await step('Click once', () =>
|
const content = await canvas.findByText('Content');
|
||||||
userEvent.click(canvas.getByText('Content')),
|
|
||||||
);
|
|
||||||
|
|
||||||
await step('Escape', () => {
|
await userEvent.click(content);
|
||||||
userEvent.keyboard('{esc}');
|
await userEvent.keyboard('{esc}');
|
||||||
});
|
|
||||||
|
await sleep(10);
|
||||||
|
|
||||||
await step('Has soft focus mode', () => {
|
await step('Has soft focus mode', () => {
|
||||||
expect(canvas.getByTestId('editable-cell-soft-focus-mode')).toBeDefined();
|
expect(canvas.getByTestId('editable-cell-soft-focus-mode')).toBeDefined();
|
||||||
@ -47,7 +47,7 @@ export const EditMode: Story = {
|
|||||||
play: async ({ canvasElement, step }) => {
|
play: async ({ canvasElement, step }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
const click = async () => userEvent.click(canvas.getByText('Content'));
|
const click = () => userEvent.click(canvas.getByText('Content'));
|
||||||
|
|
||||||
await step('Click once', click);
|
await step('Click once', click);
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,12 @@
|
|||||||
import { useMemo } from 'react';
|
import { useContext, useMemo } from 'react';
|
||||||
|
|
||||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
import { ColumnIndexContext } from '../../states/ColumnIndexContext';
|
||||||
|
import { RowIndexContext } from '../../states/RowIndexContext';
|
||||||
import { CellContext } from '../../states/CellContext';
|
|
||||||
import { currentColumnNumberScopedState } from '../../states/currentColumnNumberScopedState';
|
|
||||||
import { currentRowNumberScopedState } from '../../states/currentRowNumberScopedState';
|
|
||||||
import { RowContext } from '../../states/RowContext';
|
|
||||||
import { CellPosition } from '../../types/CellPosition';
|
import { CellPosition } from '../../types/CellPosition';
|
||||||
|
|
||||||
export function useCurrentCellPosition() {
|
export function useCurrentCellPosition() {
|
||||||
const [currentRowNumber] = useRecoilScopedState(
|
const currentRowNumber = useContext(RowIndexContext);
|
||||||
currentRowNumberScopedState,
|
const currentColumnNumber = useContext(ColumnIndexContext);
|
||||||
RowContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [currentColumnNumber] = useRecoilScopedState(
|
|
||||||
currentColumnNumberScopedState,
|
|
||||||
CellContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentCellPosition: CellPosition = useMemo(
|
const currentCellPosition: CellPosition = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
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 { useCloseCurrentCellInEditMode } from '../../hooks/useClearCellInEditMode';
|
||||||
import { CellContext } from '../../states/CellContext';
|
import { CellHotkeyScopeContext } from '../../states/CellHotkeyScopeContext';
|
||||||
import { isSomeInputInEditModeState } from '../../states/isSomeInputInEditModeState';
|
import { isSomeInputInEditModeState } from '../../states/isSomeInputInEditModeState';
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
import { customCellHotkeyScopeScopedState } from '../states/customCellHotkeyScopeScopedState';
|
|
||||||
|
|
||||||
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
|
import { useCurrentCellEditMode } from './useCurrentCellEditMode';
|
||||||
|
|
||||||
@ -24,7 +22,7 @@ export function useEditableCell() {
|
|||||||
|
|
||||||
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
|
const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode();
|
||||||
|
|
||||||
const cellContextId = useContextScopeId(CellContext);
|
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
|
||||||
|
|
||||||
function closeEditableCell() {
|
function closeEditableCell() {
|
||||||
closeCurrentCellInEditMode();
|
closeCurrentCellInEditMode();
|
||||||
@ -38,12 +36,6 @@ export function useEditableCell() {
|
|||||||
.getLoadable(isSomeInputInEditModeState)
|
.getLoadable(isSomeInputInEditModeState)
|
||||||
.valueOrThrow();
|
.valueOrThrow();
|
||||||
|
|
||||||
const customCellHotkeyScope = getSnapshotScopedState({
|
|
||||||
snapshot,
|
|
||||||
state: customCellHotkeyScopeScopedState,
|
|
||||||
contextScopeId: cellContextId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isSomeInputInEditMode) {
|
if (!isSomeInputInEditMode) {
|
||||||
set(isSomeInputInEditModeState, true);
|
set(isSomeInputInEditModeState, true);
|
||||||
|
|
||||||
@ -62,7 +54,7 @@ export function useEditableCell() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setCurrentCellInEditMode, setHotkeyScope, cellContextId],
|
[setCurrentCellInEditMode, setHotkeyScope, customCellHotkeyScope],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export function useRegisterCloseCellHandlers(
|
|||||||
) {
|
) {
|
||||||
const { closeEditableCell } = useEditableCell();
|
const { closeEditableCell } = useEditableCell();
|
||||||
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
const { isCurrentCellInEditMode } = useCurrentCellEditMode();
|
||||||
|
|
||||||
useListenClickOutside({
|
useListenClickOutside({
|
||||||
refs: [wrapperRef],
|
refs: [wrapperRef],
|
||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
@ -26,6 +27,7 @@ export function useRegisterCloseCellHandlers(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
const { moveRight, moveLeft, moveDown } = useMoveSoftFocus();
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
|
|||||||
@ -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]);
|
|
||||||
}
|
|
||||||
@ -1,50 +1,29 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
|
||||||
|
|
||||||
import { useSetSoftFocusPosition } from '../../hooks/useSetSoftFocusPosition';
|
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 { isSoftFocusActiveState } from '../../states/isSoftFocusActiveState';
|
||||||
import { RowContext } from '../../states/RowContext';
|
|
||||||
import { CellPosition } from '../../types/CellPosition';
|
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
|
import { useCurrentCellPosition } from './useCurrentCellPosition';
|
||||||
|
|
||||||
export function useSetSoftFocusOnCurrentCell() {
|
export function useSetSoftFocusOnCurrentCell() {
|
||||||
const setSoftFocusPosition = useSetSoftFocusPosition();
|
const setSoftFocusPosition = useSetSoftFocusPosition();
|
||||||
|
|
||||||
const [currentRowNumber] = useRecoilScopedState(
|
const currentCellPosition = useCurrentCellPosition();
|
||||||
currentRowNumberScopedState,
|
|
||||||
RowContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [currentColumnNumber] = useRecoilScopedState(
|
|
||||||
currentColumnNumberScopedState,
|
|
||||||
CellContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentTablePosition: CellPosition = useMemo(
|
|
||||||
() => ({
|
|
||||||
column: currentColumnNumber,
|
|
||||||
row: currentRowNumber,
|
|
||||||
}),
|
|
||||||
[currentColumnNumber, currentRowNumber],
|
|
||||||
);
|
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
return useRecoilCallback(
|
return useRecoilCallback(
|
||||||
({ set }) =>
|
({ set }) =>
|
||||||
() => {
|
() => {
|
||||||
setSoftFocusPosition(currentTablePosition);
|
setSoftFocusPosition(currentCellPosition);
|
||||||
|
|
||||||
set(isSoftFocusActiveState, true);
|
set(isSoftFocusActiveState, true);
|
||||||
|
|
||||||
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
||||||
},
|
},
|
||||||
[setHotkeyScope, currentTablePosition, setSoftFocusPosition],
|
[setHotkeyScope, currentCellPosition, setSoftFocusPosition],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
|
||||||
});
|
|
||||||
@ -1,9 +1,11 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||||
import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate';
|
import { InplaceInputDate } from '@/ui/inplace-input/components/InplaceInputDate';
|
||||||
|
|
||||||
|
import { useListenClickOutside } from '../../../hooks/useListenClickOutside';
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
import { useEditableCell } from '../hooks/useEditableCell';
|
import { useEditableCell } from '../hooks/useEditableCell';
|
||||||
|
|
||||||
@ -38,8 +40,21 @@ export function EditableCellDateEditMode({
|
|||||||
[closeEditableCell],
|
[closeEditableCell],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
|
useListenClickOutside({
|
||||||
|
refs: [containerRef],
|
||||||
|
callback: (event) => {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
closeEditableCell();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellDateEditModeContainer>
|
<EditableCellDateEditModeContainer ref={containerRef}>
|
||||||
<InplaceInputDate onChange={handleDateChange} value={value} />
|
<InplaceInputDate onChange={handleDateChange} value={value} />
|
||||||
</EditableCellDateEditModeContainer>
|
</EditableCellDateEditModeContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ReactElement, useEffect, useState } from 'react';
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
import { CellSkeleton } from '../components/CellSkeleton';
|
import { CellSkeleton } from '../components/CellSkeleton';
|
||||||
@ -12,8 +12,7 @@ type OwnProps = {
|
|||||||
firstValuePlaceholder: string;
|
firstValuePlaceholder: string;
|
||||||
secondValuePlaceholder: string;
|
secondValuePlaceholder: string;
|
||||||
nonEditModeContent: ReactElement;
|
nonEditModeContent: ReactElement;
|
||||||
onChange: (firstValue: string, secondValue: string) => void;
|
onSubmit?: (firstValue: string, secondValue: string) => void;
|
||||||
onSubmit?: () => void;
|
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
};
|
};
|
||||||
@ -23,36 +22,21 @@ export function EditableCellDoubleText({
|
|||||||
secondValue,
|
secondValue,
|
||||||
firstValuePlaceholder,
|
firstValuePlaceholder,
|
||||||
secondValuePlaceholder,
|
secondValuePlaceholder,
|
||||||
onChange,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
nonEditModeContent,
|
nonEditModeContent,
|
||||||
loading,
|
loading,
|
||||||
}: OwnProps) {
|
}: 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 (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
editHotkeyScope={{ scope: TableHotkeyScope.CellDoubleTextInput }}
|
editHotkeyScope={{ scope: TableHotkeyScope.CellDoubleTextInput }}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<EditableCellDoubleTextEditMode
|
<EditableCellDoubleTextEditMode
|
||||||
firstValue={firstInternalValue}
|
firstValue={firstValue}
|
||||||
secondValue={secondInternalValue}
|
secondValue={secondValue}
|
||||||
firstValuePlaceholder={firstValuePlaceholder}
|
firstValuePlaceholder={firstValuePlaceholder}
|
||||||
secondValuePlaceholder={secondValuePlaceholder}
|
secondValuePlaceholder={secondValuePlaceholder}
|
||||||
onChange={handleOnChange}
|
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,21 +1,22 @@
|
|||||||
import { ChangeEvent, useRef, useState } from 'react';
|
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
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 { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
import { useEditableCell } from '../hooks/useEditableCell';
|
import { useEditableCell } from '../hooks/useEditableCell';
|
||||||
|
import { useRegisterCloseCellHandlers } from '../hooks/useRegisterCloseCellHandlers';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
firstValue: string;
|
firstValue: string;
|
||||||
secondValue: string;
|
secondValue: string;
|
||||||
firstValuePlaceholder: string;
|
firstValuePlaceholder: string;
|
||||||
secondValuePlaceholder: string;
|
secondValuePlaceholder: string;
|
||||||
onChange: (firstValue: string, secondValue: string) => void;
|
onChange?: (firstValue: string, secondValue: string) => void;
|
||||||
onSubmit?: () => void;
|
onSubmit?: (firstValue: string, secondValue: string) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -39,6 +40,19 @@ export function EditableCellDoubleTextEditMode({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: OwnProps) {
|
}: 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 [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
|
||||||
|
|
||||||
const firstValueInputRef = useRef<HTMLInputElement>(null);
|
const firstValueInputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -52,12 +66,23 @@ export function EditableCellDoubleTextEditMode({
|
|||||||
closeEditableCell();
|
closeEditableCell();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
setFirstInternalValue(firstValue);
|
||||||
|
setSecondInternalValue(secondValue);
|
||||||
|
|
||||||
|
onCancel?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
onSubmit?.(firstInternalValue, secondInternalValue);
|
||||||
|
}
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
Key.Enter,
|
Key.Enter,
|
||||||
() => {
|
() => {
|
||||||
closeCell();
|
closeCell();
|
||||||
moveDown();
|
moveDown();
|
||||||
onSubmit?.();
|
handleSubmit();
|
||||||
},
|
},
|
||||||
TableHotkeyScope.CellDoubleTextInput,
|
TableHotkeyScope.CellDoubleTextInput,
|
||||||
[closeCell],
|
[closeCell],
|
||||||
@ -66,7 +91,7 @@ export function EditableCellDoubleTextEditMode({
|
|||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
Key.Escape,
|
Key.Escape,
|
||||||
() => {
|
() => {
|
||||||
onCancel?.();
|
handleCancel();
|
||||||
closeCell();
|
closeCell();
|
||||||
},
|
},
|
||||||
TableHotkeyScope.CellDoubleTextInput,
|
TableHotkeyScope.CellDoubleTextInput,
|
||||||
@ -80,7 +105,8 @@ export function EditableCellDoubleTextEditMode({
|
|||||||
setFocusPosition('right');
|
setFocusPosition('right');
|
||||||
secondValueInputRef.current?.focus();
|
secondValueInputRef.current?.focus();
|
||||||
} else {
|
} else {
|
||||||
onSubmit?.();
|
handleSubmit();
|
||||||
|
|
||||||
closeCell();
|
closeCell();
|
||||||
moveRight();
|
moveRight();
|
||||||
}
|
}
|
||||||
@ -96,7 +122,7 @@ export function EditableCellDoubleTextEditMode({
|
|||||||
setFocusPosition('left');
|
setFocusPosition('left');
|
||||||
firstValueInputRef.current?.focus();
|
firstValueInputRef.current?.focus();
|
||||||
} else {
|
} else {
|
||||||
onSubmit?.();
|
handleSubmit();
|
||||||
closeCell();
|
closeCell();
|
||||||
moveLeft();
|
moveLeft();
|
||||||
}
|
}
|
||||||
@ -105,23 +131,27 @@ export function EditableCellDoubleTextEditMode({
|
|||||||
[closeCell, moveRight, focusPosition],
|
[closeCell, moveRight, focusPosition],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const wrapperRef = useRef(null);
|
||||||
|
|
||||||
|
useRegisterCloseCellHandlers(wrapperRef, handleSubmit, handleCancel);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer ref={wrapperRef}>
|
||||||
<InplaceInputTextEditMode
|
<StyledInput
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder={firstValuePlaceholder}
|
placeholder={firstValuePlaceholder}
|
||||||
ref={firstValueInputRef}
|
ref={firstValueInputRef}
|
||||||
value={firstValue}
|
value={firstValue}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
onChange(event.target.value, secondValue);
|
handleOnChange(event.target.value, secondValue);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<InplaceInputTextEditMode
|
<StyledInput
|
||||||
placeholder={secondValuePlaceholder}
|
placeholder={secondValuePlaceholder}
|
||||||
ref={secondValueInputRef}
|
ref={secondValueInputRef}
|
||||||
value={secondValue}
|
value={secondValue}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
onChange(firstValue, event.target.value);
|
handleOnChange(firstValue, event.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode';
|
import { InplaceInputPhoneDisplayMode } from '@/ui/display/component/InplaceInputPhoneDisplayMode';
|
||||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||||
|
|
||||||
@ -8,42 +6,21 @@ import { EditableCell } from '../components/EditableCell';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (updated: string) => void;
|
onSubmit?: (newText: string) => void;
|
||||||
onSubmit?: () => void;
|
|
||||||
onCancel?: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableCellPhone({
|
export function EditableCellPhone({ value, placeholder, onSubmit }: OwnProps) {
|
||||||
value,
|
|
||||||
placeholder,
|
|
||||||
onChange,
|
|
||||||
onSubmit,
|
|
||||||
onCancel,
|
|
||||||
}: OwnProps) {
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const [inputValue, setInputValue] = useState(value);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInputValue(value);
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<InplaceInputTextEditMode
|
<InplaceInputTextEditMode
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder={placeholder || ''}
|
placeholder={placeholder || ''}
|
||||||
ref={inputRef}
|
value={value}
|
||||||
value={inputValue}
|
onSubmit={(newText) => onSubmit?.(newText)}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setInputValue(event.target.value);
|
|
||||||
onChange(event.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
nonEditModeContent={<InplaceInputPhoneDisplayMode value={inputValue} />}
|
nonEditModeContent={<InplaceInputPhoneDisplayMode value={value} />}
|
||||||
onSubmit={onSubmit}
|
|
||||||
onCancel={onCancel}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { ChangeEvent, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
|
import { InplaceInputTextDisplayMode } from '@/ui/display/component/InplaceInputTextDisplayMode';
|
||||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||||
|
|
||||||
@ -9,10 +7,10 @@ import { EditableCell } from '../components/EditableCell';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (newValue: string) => void;
|
onChange?: (newValue: string) => void;
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
onSubmit?: () => void;
|
onSubmit?: (newText: string) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,12 +23,6 @@ export function EditableCellText({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [internalValue, setInternalValue] = useState(value);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalValue(value);
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
@ -38,22 +30,15 @@ export function EditableCellText({
|
|||||||
<InplaceInputTextEditMode
|
<InplaceInputTextEditMode
|
||||||
placeholder={placeholder || ''}
|
placeholder={placeholder || ''}
|
||||||
autoFocus
|
autoFocus
|
||||||
value={internalValue}
|
value={value}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onSubmit={(newText) => onSubmit?.(newText)}
|
||||||
setInternalValue(event.target.value);
|
|
||||||
onChange(event.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onSubmit={onSubmit}
|
|
||||||
onCancel={onCancel}
|
|
||||||
nonEditModeContent={
|
nonEditModeContent={
|
||||||
loading ? (
|
loading ? (
|
||||||
<CellSkeleton />
|
<CellSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<InplaceInputTextDisplayMode>
|
<InplaceInputTextDisplayMode>{value}</InplaceInputTextDisplayMode>
|
||||||
{internalValue}
|
|
||||||
</InplaceInputTextDisplayMode>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
></EditableCell>
|
></EditableCell>
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { ChangeEvent, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
import { InplaceInputTextEditMode } from '@/ui/inplace-input/components/InplaceInputTextEditMode';
|
||||||
|
|
||||||
import { RawLink } from '../../../link/components/RawLink';
|
import { RawLink } from '../../../link/components/RawLink';
|
||||||
@ -9,53 +7,40 @@ import { EditableCell } from '../components/EditableCell';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
url: string;
|
url: string;
|
||||||
onChange: (newURL: string) => void;
|
onChange?: (newURL: string) => void;
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
onSubmit?: () => void;
|
onSubmit?: (newURL: string) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableCellURL({
|
export function EditableCellURL({
|
||||||
url,
|
url,
|
||||||
placeholder,
|
placeholder,
|
||||||
onChange,
|
|
||||||
editModeHorizontalAlign,
|
editModeHorizontalAlign,
|
||||||
loading,
|
loading,
|
||||||
onCancel,
|
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [internalValue, setInternalValue] = useState(url);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInternalValue(url);
|
|
||||||
}, [url]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<InplaceInputTextEditMode
|
<InplaceInputTextEditMode
|
||||||
placeholder={placeholder || ''}
|
placeholder={placeholder}
|
||||||
autoFocus
|
autoFocus
|
||||||
value={internalValue}
|
value={url}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onSubmit={(newURL) => onSubmit?.(newURL)}
|
||||||
setInternalValue(event.target.value);
|
|
||||||
onChange(event.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onSubmit={onSubmit}
|
|
||||||
onCancel={onCancel}
|
|
||||||
nonEditModeContent={
|
nonEditModeContent={
|
||||||
loading ? (
|
loading ? (
|
||||||
<CellSkeleton />
|
<CellSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<RawLink
|
<RawLink
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
href={internalValue ? 'https://' + internalValue : ''}
|
href={url ? 'https://' + url : ''}
|
||||||
>
|
>
|
||||||
{internalValue}
|
{url}
|
||||||
</RawLink>
|
</RawLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,21 @@
|
|||||||
import { ChangeEvent, ReactNode, useEffect, useRef, useState } from 'react';
|
import { ReactNode, useEffect, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { textInputStyle } from '@/ui/themes/effects';
|
import { InplaceInputTextEditMode } from '../../../inplace-input/components/InplaceInputTextEditMode';
|
||||||
|
|
||||||
import { EditableCell } from '../components/EditableCell';
|
import { EditableCell } from '../components/EditableCell';
|
||||||
|
|
||||||
export type EditableChipProps = {
|
export type EditableChipProps = {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value: string;
|
value: string;
|
||||||
changeHandler: (updated: string) => void;
|
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
ChipComponent: React.ReactNode;
|
ChipComponent: React.ReactNode;
|
||||||
commentThreadCount?: number;
|
commentThreadCount?: number;
|
||||||
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
rightEndContents?: ReactNode[];
|
rightEndContents?: ReactNode[];
|
||||||
onSubmit?: () => void;
|
onSubmit?: (newValue: string) => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: refactor
|
|
||||||
const StyledInplaceInput = styled.input`
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
${textInputStyle}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const NoEditModeContainer = styled.div`
|
const NoEditModeContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -40,14 +31,11 @@ const RightContainer = styled.div`
|
|||||||
export function EditableCellChip({
|
export function EditableCellChip({
|
||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
changeHandler,
|
|
||||||
editModeHorizontalAlign,
|
editModeHorizontalAlign,
|
||||||
ChipComponent,
|
ChipComponent,
|
||||||
rightEndContents,
|
rightEndContents,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
|
||||||
}: EditableChipProps) {
|
}: EditableChipProps) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const [inputValue, setInputValue] = useState(value);
|
const [inputValue, setInputValue] = useState(value);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -64,19 +52,13 @@ export function EditableCellChip({
|
|||||||
<EditableCell
|
<EditableCell
|
||||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<StyledInplaceInput
|
<InplaceInputTextEditMode
|
||||||
placeholder={placeholder || ''}
|
placeholder={placeholder || ''}
|
||||||
autoFocus
|
autoFocus
|
||||||
ref={inputRef}
|
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onSubmit={(newValue) => onSubmit?.(newValue)}
|
||||||
setInputValue(event.target.value);
|
|
||||||
changeHandler(event.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onSubmit={onSubmit}
|
|
||||||
onCancel={onCancel}
|
|
||||||
nonEditModeContent={
|
nonEditModeContent={
|
||||||
<NoEditModeContainer>
|
<NoEditModeContainer>
|
||||||
{ChipComponent}
|
{ChipComponent}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import { currentRowEntityIdScopedState } from '../states/currentRowEntityIdScopedState';
|
import { RowIdContext } from '../states/RowIdContext';
|
||||||
import { RowContext } from '../states/RowContext';
|
|
||||||
|
|
||||||
export type TableDimensions = {
|
export type TableDimensions = {
|
||||||
numberOfColumns: number;
|
numberOfColumns: number;
|
||||||
@ -9,10 +8,7 @@ export type TableDimensions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function useCurrentRowEntityId() {
|
export function useCurrentRowEntityId() {
|
||||||
const currentRowEntityIdScoped = useRecoilScopedValue(
|
const currentEntityId = useContext(RowIdContext);
|
||||||
currentRowEntityIdScopedState,
|
|
||||||
RowContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
return currentRowEntityIdScoped;
|
return currentEntityId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
|
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
|
||||||
|
import { RowIdContext } from '../states/RowIdContext';
|
||||||
import { useCurrentRowEntityId } from './useCurrentEntityId';
|
|
||||||
|
|
||||||
export function useCurrentRowSelected() {
|
export function useCurrentRowSelected() {
|
||||||
const currentRowId = useCurrentRowEntityId();
|
const currentRowId = useContext(RowIdContext);
|
||||||
|
|
||||||
const [isRowSelected] = useRecoilState(
|
const [isRowSelected] = useRecoilState(
|
||||||
isRowSelectedFamilyState(currentRowId ?? ''),
|
isRowSelectedFamilyState(currentRowId ?? ''),
|
||||||
|
|||||||
@ -45,8 +45,6 @@ export function useLeaveTableFocus() {
|
|||||||
|
|
||||||
closeCurrentCellInEditMode();
|
closeCurrentCellInEditMode();
|
||||||
disableSoftFocus();
|
disableSoftFocus();
|
||||||
|
|
||||||
setHotkeyScope(TableHotkeyScope.Table, { goto: true });
|
|
||||||
},
|
},
|
||||||
[setHotkeyScope, closeCurrentCellInEditMode, disableSoftFocus],
|
[setHotkeyScope, closeCurrentCellInEditMode, disableSoftFocus],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
export const CellContext = createContext<string | null>(null);
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
import { HotkeyScope } from '../../hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
|
export const CellHotkeyScopeContext = createContext<HotkeyScope | null>(null);
|
||||||
3
front/src/modules/ui/table/states/ColumnIndexContext.ts
Normal file
3
front/src/modules/ui/table/states/ColumnIndexContext.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export const ColumnIndexContext = createContext<number>(0);
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
export const RowContext = createContext<string | null>(null);
|
|
||||||
3
front/src/modules/ui/table/states/RowIdContext.ts
Normal file
3
front/src/modules/ui/table/states/RowIdContext.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export const RowIdContext = createContext<string | null>(null);
|
||||||
3
front/src/modules/ui/table/states/RowIndexContext.ts
Normal file
3
front/src/modules/ui/table/states/RowIndexContext.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export const RowIndexContext = createContext<number>(0);
|
||||||
@ -3,11 +3,10 @@ import { userEvent, within } from '@storybook/testing-library';
|
|||||||
|
|
||||||
import { IconList } from '@/ui/icon/index';
|
import { IconList } from '@/ui/icon/index';
|
||||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||||
import { companiesFilters } from '~/pages/companies/companies-filters';
|
|
||||||
import { availableSorts } from '~/pages/companies/companies-sorts';
|
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 { TableContext } from '../../../states/TableContext';
|
||||||
import { TableHeader } from '../TableHeader';
|
import { TableHeader } from '../TableHeader';
|
||||||
|
|
||||||
@ -17,15 +16,11 @@ const meta: Meta<typeof TableHeader> = {
|
|||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<RecoilScope SpecificContext={TableContext}>
|
<RecoilScope SpecificContext={TableContext}>
|
||||||
{/* TODO: add company mocked loader <CompanyEntityTableData */}
|
<CompanyEntityTableDataMocked />
|
||||||
<HooksEntityTable
|
|
||||||
availableFilters={companiesFilters}
|
|
||||||
numberOfColumns={5}
|
|
||||||
/>
|
|
||||||
<Story />
|
<Story />
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
),
|
),
|
||||||
ComponentDecorator,
|
ComponentWithRouterDecorator,
|
||||||
],
|
],
|
||||||
argTypes: { viewIcon: { control: false } },
|
argTypes: { viewIcon: { control: false } },
|
||||||
args: {
|
args: {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export function Verify() {
|
|||||||
navigate(AppPath.SignIn);
|
navigate(AppPath.SignIn);
|
||||||
} else {
|
} else {
|
||||||
await verify(loginToken);
|
await verify(loginToken);
|
||||||
|
navigate(AppPath.CompaniesPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
import { useTrackPageView } from '@/analytics/hooks/useTrackPageView';
|
|
||||||
|
|
||||||
export function AnalyticsHook() {
|
|
||||||
useTrackPageView();
|
|
||||||
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
@ -1,15 +1,9 @@
|
|||||||
import { AnalyticsHook } from './AnalyticsHook';
|
|
||||||
import { GotoHotkeysHooks } from './GotoHotkeysHooks';
|
import { GotoHotkeysHooks } from './GotoHotkeysHooks';
|
||||||
import { HotkeyScopeAutoSyncHook } from './HotkeyScopeAutoSyncHook';
|
|
||||||
import { HotkeyScopeBrowserRouterSync } from './HotkeyScopeBrowserRouterSync';
|
|
||||||
|
|
||||||
export function AppInternalHooks() {
|
export function AppInternalHooks() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AnalyticsHook />
|
|
||||||
<GotoHotkeysHooks />
|
<GotoHotkeysHooks />
|
||||||
<HotkeyScopeAutoSyncHook />
|
|
||||||
<HotkeyScopeBrowserRouterSync />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
142
front/src/sync-hooks/AuthAutoRouter.tsx
Normal file
142
front/src/sync-hooks/AuthAutoRouter.tsx
Normal file
@ -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 <></>;
|
||||||
|
}
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { useHotkeyScopeAutoSync } from '@/ui/hotkey/hooks/internal/useHotkeyScopeAutoSync';
|
|
||||||
|
|
||||||
export function HotkeyScopeAutoSyncHook() {
|
|
||||||
useHotkeyScopeAutoSync();
|
|
||||||
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
@ -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 <></>;
|
|
||||||
}
|
|
||||||
14
front/src/testing/InitializeHotkeyStorybookHook.tsx
Normal file
14
front/src/testing/InitializeHotkeyStorybookHook.tsx
Normal file
@ -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 <></>;
|
||||||
|
}
|
||||||
@ -1,13 +1,12 @@
|
|||||||
import { Decorator } from '@storybook/react';
|
import { Decorator } from '@storybook/react';
|
||||||
|
|
||||||
import { RecoilScope } from '../../modules/ui/recoil-scope/components/RecoilScope';
|
import { ColumnIndexContext } from '../../modules/ui/table/states/ColumnIndexContext';
|
||||||
import { CellContext } from '../../modules/ui/table/states/CellContext';
|
import { RowIndexContext } from '../../modules/ui/table/states/RowIndexContext';
|
||||||
import { RowContext } from '../../modules/ui/table/states/RowContext';
|
|
||||||
|
|
||||||
export const CellPositionDecorator: Decorator = (Story) => (
|
export const CellPositionDecorator: Decorator = (Story) => (
|
||||||
<RecoilScope SpecificContext={RowContext}>
|
<RowIndexContext.Provider value={1}>
|
||||||
<RecoilScope SpecificContext={CellContext}>
|
<ColumnIndexContext.Provider value={1}>
|
||||||
<Story />
|
<Story />
|
||||||
</RecoilScope>
|
</ColumnIndexContext.Provider>
|
||||||
</RecoilScope>
|
</RowIndexContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { HotkeysProvider } from 'react-hotkeys-hook';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Decorator } from '@storybook/react';
|
import { Decorator } from '@storybook/react';
|
||||||
|
|
||||||
import { ClientConfigProvider } from '../../modules/client-config/components/ClientConfigProvider';
|
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 { DefaultLayout } from '../../modules/ui/layout/components/DefaultLayout';
|
import { UserProvider } from '~/modules/users/components/UserProvider';
|
||||||
import { UserProvider } from '../../modules/users/components/UserProvider';
|
|
||||||
import { FullHeightStorybookLayout } from '../FullHeightStorybookLayout';
|
import { FullHeightStorybookLayout } from '../FullHeightStorybookLayout';
|
||||||
|
|
||||||
export type PageDecoratorArgs = { currentPath: string };
|
export type PageDecoratorArgs = { currentPath: string };
|
||||||
@ -16,15 +15,13 @@ export const PageDecorator: Decorator<{ currentPath: string }> = (
|
|||||||
) => (
|
) => (
|
||||||
<UserProvider>
|
<UserProvider>
|
||||||
<ClientConfigProvider>
|
<ClientConfigProvider>
|
||||||
<HotkeysProvider initiallyActiveScopes={INITIAL_HOTKEYS_SCOPES}>
|
<MemoryRouter initialEntries={[args.currentPath]}>
|
||||||
<MemoryRouter initialEntries={[args.currentPath]}>
|
<FullHeightStorybookLayout>
|
||||||
<FullHeightStorybookLayout>
|
<DefaultLayout>
|
||||||
<DefaultLayout>
|
<Story />
|
||||||
<Story />
|
</DefaultLayout>
|
||||||
</DefaultLayout>
|
</FullHeightStorybookLayout>
|
||||||
</FullHeightStorybookLayout>
|
</MemoryRouter>
|
||||||
</MemoryRouter>
|
|
||||||
</HotkeysProvider>
|
|
||||||
</ClientConfigProvider>
|
</ClientConfigProvider>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,11 +2,13 @@ import { ApolloProvider } from '@apollo/client';
|
|||||||
import { Decorator } from '@storybook/react';
|
import { Decorator } from '@storybook/react';
|
||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
|
|
||||||
|
import { InitializeHotkeyStorybookHook } from '../InitializeHotkeyStorybookHook';
|
||||||
import { mockedClient } from '../mockedClient';
|
import { mockedClient } from '../mockedClient';
|
||||||
|
|
||||||
export const RootDecorator: Decorator = (Story) => (
|
export const RootDecorator: Decorator = (Story) => (
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<ApolloProvider client={mockedClient}>
|
<ApolloProvider client={mockedClient}>
|
||||||
|
<InitializeHotkeyStorybookHook />
|
||||||
<Story />
|
<Story />
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
</RecoilRoot>
|
</RecoilRoot>
|
||||||
|
|||||||
11
front/src/utils/measureTotalFrameLoad.ts
Normal file
11
front/src/utils/measureTotalFrameLoad.ts
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -5877,6 +5877,11 @@ adjust-sourcemap-loader@^4.0.0:
|
|||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
regex-parser "^2.2.11"
|
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:
|
agent-base@5:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
||||||
|
|||||||
Reference in New Issue
Block a user