Improve Board performances (#2626)

Improve app performances
This commit is contained in:
Charles Bochet
2023-11-22 09:58:49 +01:00
committed by GitHub
parent ee8f6899fc
commit 10febd9aeb
11 changed files with 88 additions and 122 deletions

View File

@ -1,5 +1,4 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useSetRecoilState } from 'recoil';
import { BoardContext } from '@/companies/states/contexts/BoardContext'; import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { BoardOptionsDropdown } from '@/ui/layout/board/components/BoardOptionsDropdown'; import { BoardOptionsDropdown } from '@/ui/layout/board/components/BoardOptionsDropdown';
@ -11,7 +10,6 @@ import {
import { EntityBoardActionBar } from '@/ui/layout/board/components/EntityBoardActionBar'; import { EntityBoardActionBar } from '@/ui/layout/board/components/EntityBoardActionBar';
import { EntityBoardContextMenu } from '@/ui/layout/board/components/EntityBoardContextMenu'; import { EntityBoardContextMenu } from '@/ui/layout/board/components/EntityBoardContextMenu';
import { ViewBar } from '@/views/components/ViewBar'; import { ViewBar } from '@/views/components/ViewBar';
import { useViewScopedStates } from '@/views/hooks/internal/useViewScopedStates';
import { ViewScope } from '@/views/scopes/ViewScope'; import { ViewScope } from '@/views/scopes/ViewScope';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions'; import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
@ -38,30 +36,12 @@ export const CompanyBoard = ({
}: CompanyBoardProps) => { }: CompanyBoardProps) => {
const viewScopeId = 'company-board-view'; const viewScopeId = 'company-board-view';
const {
currentViewFieldsState,
currentViewFiltersState,
currentViewSortsState,
} = useViewScopedStates({
customViewScopeId: viewScopeId,
});
const setCurrentViewFields = useSetRecoilState(currentViewFieldsState);
const setCurrentViewFilters = useSetRecoilState(currentViewFiltersState);
const setCurrentViewSorts = useSetRecoilState(currentViewSortsState);
return ( return (
<ViewScope <ViewScope
viewScopeId={viewScopeId} viewScopeId={viewScopeId}
onViewFieldsChange={(viewFields) => { onViewFieldsChange={() => {}}
setCurrentViewFields(viewFields); onViewFiltersChange={() => {}}
}} onViewSortsChange={() => {}}
onViewFiltersChange={(viewFilters) => {
setCurrentViewFilters(viewFilters);
}}
onViewSortsChange={(viewSorts) => {
setCurrentViewSorts(viewSorts);
}}
> >
<StyledContainer> <StyledContainer>
<BoardContext.Provider <BoardContext.Provider

View File

@ -127,7 +127,7 @@ const StyledCompactIconContainer = styled.div`
export const CompanyBoardCard = () => { export const CompanyBoardCard = () => {
const { BoardRecoilScopeContext } = useBoardContext(); const { BoardRecoilScopeContext } = useBoardContext();
const { currentCardSelected, setCurrentCardSelected } = const { isCurrentCardSelected, setCurrentCardSelected } =
useCurrentCardSelected(); useCurrentCardSelected();
const boardCardId = useContext(BoardCardIdContext); const boardCardId = useContext(BoardCardIdContext);
@ -200,9 +200,9 @@ export const CompanyBoardCard = () => {
return ( return (
<StyledBoardCardWrapper> <StyledBoardCardWrapper>
<StyledBoardCard <StyledBoardCard
selected={currentCardSelected} selected={isCurrentCardSelected}
onMouseLeave={OnMouseLeaveBoard} onMouseLeave={OnMouseLeaveBoard}
onClick={() => setCurrentCardSelected(!currentCardSelected)} onClick={() => setCurrentCardSelected(!isCurrentCardSelected)}
> >
<StyledBoardCardHeader showCompactView={showCompactView}> <StyledBoardCardHeader showCompactView={showCompactView}>
<CompanyChip <CompanyChip
@ -225,8 +225,8 @@ export const CompanyBoardCard = () => {
)} )}
<StyledCheckboxContainer className="checkbox-container"> <StyledCheckboxContainer className="checkbox-container">
<Checkbox <Checkbox
checked={currentCardSelected} checked={isCurrentCardSelected}
onChange={() => setCurrentCardSelected(!currentCardSelected)} onChange={() => setCurrentCardSelected(!isCurrentCardSelected)}
variant={CheckboxVariant.Secondary} variant={CheckboxVariant.Secondary}
/> />
</StyledCheckboxContainer> </StyledCheckboxContainer>

View File

@ -164,21 +164,19 @@ export const HooksCompanyBoardEffect = () => {
setViewType?.(ViewType.Kanban); setViewType?.(ViewType.Kanban);
}, [objectMetadataItem, setViewObjectMetadataId, setViewType]); }, [objectMetadataItem, setViewObjectMetadataId, setViewType]);
const loading = !companies;
const { setActionBarEntries } = useBoardActionBarEntries(); const { setActionBarEntries } = useBoardActionBarEntries();
const { setContextMenuEntries } = useBoardContextMenuEntries(); const { setContextMenuEntries } = useBoardContextMenuEntries();
useEffect(() => { useEffect(() => {
if (!loading && opportunities && companies) { if (opportunities && companies) {
setActionBarEntries(); setActionBarEntries();
setContextMenuEntries(); setContextMenuEntries();
updateCompanyBoard(pipelineSteps, opportunities, companies); updateCompanyBoard(pipelineSteps, opportunities, companies);
setEntityCountInCurrentView(opportunities.length); setEntityCountInCurrentView(opportunities.length);
} }
}, [ }, [
companies, companies,
loading,
opportunities, opportunities,
pipelineSteps, pipelineSteps,
setActionBarEntries, setActionBarEntries,

View File

@ -105,7 +105,10 @@ export const useUpdateCompanyBoard = () =>
position: pipelineStep.position ?? 0, position: pipelineStep.position ?? 0,
}; };
}); });
if (currentBoardColumns.length === 0) { if (
currentBoardColumns.length === 0 &&
!isDeeplyEqual(newBoardColumns, currentBoardColumns)
) {
set(boardColumnsState, newBoardColumns); set(boardColumnsState, newBoardColumns);
set(savedBoardColumnsState, newBoardColumns); set(savedBoardColumnsState, newBoardColumns);
} }

View File

@ -1,15 +1,12 @@
import { useRecoilValue } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord'; import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord'; import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { PipelineStep } from '@/pipeline/types/PipelineStep'; import { PipelineStep } from '@/pipeline/types/PipelineStep';
import { BoardColumnDefinition } from '@/ui/layout/board/types/BoardColumnDefinition'; import { BoardColumnDefinition } from '@/ui/layout/board/types/BoardColumnDefinition';
import { currentPipelineState } from '../states/currentPipelineState';
export const usePipelineSteps = () => { export const usePipelineSteps = () => {
const currentPipeline = useRecoilValue(currentPipelineState);
const { createOneObject: createOnePipelineStep } = const { createOneObject: createOnePipelineStep } =
useCreateOneObjectRecord<PipelineStep>({ useCreateOneObjectRecord<PipelineStep>({
objectNameSingular: 'pipelineStep', objectNameSingular: 'pipelineStep',
@ -20,28 +17,38 @@ export const usePipelineSteps = () => {
objectNameSingular: 'pipelineStep', objectNameSingular: 'pipelineStep',
}); });
const handlePipelineStepAdd = async (boardColumn: BoardColumnDefinition) => { const handlePipelineStepAdd = useRecoilCallback(
if (!currentPipeline?.id) return; ({ snapshot }) =>
async (boardColumn: BoardColumnDefinition) => {
const currentPipeline = await snapshot.getPromise(currentPipelineState);
if (!currentPipeline?.id) return;
return createOnePipelineStep?.({ return createOnePipelineStep?.({
variables: { variables: {
data: { data: {
color: boardColumn.colorCode ?? 'gray', color: boardColumn.colorCode ?? 'gray',
id: boardColumn.id, id: boardColumn.id,
position: boardColumn.position, position: boardColumn.position,
name: boardColumn.title, name: boardColumn.title,
pipeline: { connect: { id: currentPipeline.id } }, pipeline: { connect: { id: currentPipeline.id } },
type: 'ongoing', type: 'ongoing',
}, },
},
});
}, },
}); [createOnePipelineStep],
}; );
const handlePipelineStepDelete = async (boardColumnId: string) => { const handlePipelineStepDelete = useRecoilCallback(
if (!currentPipeline?.id) return; ({ snapshot }) =>
async (boardColumnId: string) => {
const currentPipeline = await snapshot.getPromise(currentPipelineState);
if (!currentPipeline?.id) return;
return deleteOnePipelineStep?.(boardColumnId); return deleteOnePipelineStep?.(boardColumnId);
}; },
[deleteOnePipelineStep],
);
return { handlePipelineStepAdd, handlePipelineStepDelete }; return { handlePipelineStepAdd, handlePipelineStepDelete };
}; };

View File

@ -1,9 +1,10 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
import { IconComponent } from '@/ui/display/icon/types/IconComponent'; import { iconsState } from '@/ui/input/states/iconsState';
export const useLazyLoadIcons = () => { export const useLazyLoadIcons = () => {
const [icons, setIcons] = useState<Record<string, IconComponent>>({}); const [icons, setIcons] = useRecoilState(iconsState);
const [isLoadingIcons, setIsLoadingIcons] = useState(true); const [isLoadingIcons, setIsLoadingIcons] = useState(true);
useEffect(() => { useEffect(() => {
@ -11,7 +12,7 @@ export const useLazyLoadIcons = () => {
setIcons(lazyLoadedIcons); setIcons(lazyLoadedIcons);
setIsLoadingIcons(false); setIsLoadingIcons(false);
}); });
}, []); }, [setIcons]);
return { icons, isLoadingIcons }; return { icons, isLoadingIcons };
}; };

View File

@ -0,0 +1,8 @@
import { atom } from 'recoil';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
export const iconsState = atom<Record<string, IconComponent>>({
key: 'iconsState',
default: {},
});

View File

@ -1,5 +1,4 @@
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { useApolloClient } from '@apollo/client';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350 import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
@ -9,6 +8,9 @@ import { Opportunity } from '@/pipeline/types/Opportunity';
import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { StyledBoard } from '@/ui/layout/board/components/StyledBoard'; import { StyledBoard } from '@/ui/layout/board/components/StyledBoard';
import { BoardColumnContext } from '@/ui/layout/board/contexts/BoardColumnContext'; import { BoardColumnContext } from '@/ui/layout/board/contexts/BoardColumnContext';
import { useSetCardSelected } from '@/ui/layout/board/hooks/useSetCardSelected';
import { useUpdateBoardCardIds } from '@/ui/layout/board/hooks/useUpdateBoardCardIds';
import { boardColumnsState } from '@/ui/layout/board/states/boardColumnsState';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
@ -16,10 +18,6 @@ import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { logError } from '~/utils/logError'; import { logError } from '~/utils/logError';
import { useCurrentCardSelected } from '../hooks/useCurrentCardSelected';
import { useSetCardSelected } from '../hooks/useSetCardSelected';
import { useUpdateBoardCardIds } from '../hooks/useUpdateBoardCardIds';
import { boardColumnsState } from '../states/boardColumnsState';
import { BoardColumnRecoilScopeContext } from '../states/recoil-scope-contexts/BoardColumnRecoilScopeContext'; import { BoardColumnRecoilScopeContext } from '../states/recoil-scope-contexts/BoardColumnRecoilScopeContext';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition'; import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
import { BoardOptions } from '../types/BoardOptions'; import { BoardOptions } from '../types/BoardOptions';
@ -53,16 +51,13 @@ export const EntityBoard = ({
onEditColumnTitle, onEditColumnTitle,
}: EntityBoardProps) => { }: EntityBoardProps) => {
const boardColumns = useRecoilValue(boardColumnsState); const boardColumns = useRecoilValue(boardColumnsState);
const setCardSelected = useSetCardSelected();
const { updateOneObject: updateOneOpportunity } = const { updateOneObject: updateOneOpportunity } =
useUpdateOneObjectRecord<Opportunity>({ useUpdateOneObjectRecord<Opportunity>({
objectNameSingular: 'opportunity', objectNameSingular: 'opportunity',
}); });
const apolloClient = useApolloClient(); const { unselectAllActiveCards, setCardSelected } = useSetCardSelected();
const { unselectAllActiveCards } = useCurrentCardSelected();
const updatePipelineProgressStageInDB = useCallback( const updatePipelineProgressStageInDB = useCallback(
async (pipelineProgressId: string, pipelineStepId: string) => { async (pipelineProgressId: string, pipelineStepId: string) => {
@ -72,19 +67,8 @@ export const EntityBoard = ({
pipelineStepId: pipelineStepId, pipelineStepId: pipelineStepId,
}, },
}); });
const cache = apolloClient.cache;
cache.modify({
id: cache.identify({
id: pipelineProgressId,
__typename: 'PipelineProgress',
}),
fields: {
pipelineStepId: () => pipelineStepId,
},
});
}, },
[apolloClient.cache, updateOneOpportunity], [updateOneOpportunity],
); );
useListenClickOutsideByClassName({ useListenClickOutsideByClassName({
@ -135,7 +119,7 @@ export const EntityBoard = ({
PageHotkeyScope.OpportunitiesPage, PageHotkeyScope.OpportunitiesPage,
); );
return (boardColumns?.length ?? 0) > 0 ? ( return (
<StyledWrapper> <StyledWrapper>
<StyledBoardHeader /> <StyledBoardHeader />
<ScrollWrapper> <ScrollWrapper>
@ -172,7 +156,5 @@ export const EntityBoard = ({
onDragSelectionChange={setCardSelected} onDragSelectionChange={setCardSelected}
/> />
</StyledWrapper> </StyledWrapper>
) : (
<></>
); );
}; };

View File

@ -1,16 +1,16 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { activeCardIdsState } from '@/ui/layout/board/states/activeCardIdsState';
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState'; import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
import { BoardCardIdContext } from '../contexts/BoardCardIdContext'; import { BoardCardIdContext } from '../contexts/BoardCardIdContext';
import { activeCardIdsState } from '../states/activeCardIdsState';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState'; import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
export const useCurrentCardSelected = () => { export const useCurrentCardSelected = () => {
const currentCardId = useContext(BoardCardIdContext); const currentCardId = useContext(BoardCardIdContext);
const isCardSelected = useRecoilValue( const isCurrentCardSelected = useRecoilValue(
isCardSelectedFamilyState(currentCardId ?? ''), isCardSelectedFamilyState(currentCardId ?? ''),
); );
@ -38,24 +38,8 @@ export const useCurrentCardSelected = () => {
[currentCardId, setActiveCardIds], [currentCardId, setActiveCardIds],
); );
const unselectAllActiveCards = useRecoilCallback(
({ set, snapshot }) =>
() => {
const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents;
activeCardIds.forEach((cardId: string) => {
set(isCardSelectedFamilyState(cardId), false);
});
set(activeCardIdsState, []);
set(actionBarOpenState, false);
},
[],
);
return { return {
currentCardSelected: isCardSelected, isCurrentCardSelected,
setCurrentCardSelected, setCurrentCardSelected,
unselectAllActiveCards,
}; };
}; };

View File

@ -1,4 +1,4 @@
import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState'; import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
@ -6,15 +6,13 @@ import { activeCardIdsState } from '../states/activeCardIdsState';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState'; import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
export const useSetCardSelected = () => { export const useSetCardSelected = () => {
const setActionBarOpenState = useSetRecoilState(actionBarOpenState); const setCardSelected = useRecoilCallback(
return useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
(cardId: string, selected: boolean) => { (cardId: string, selected: boolean) => {
const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents; const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents;
set(isCardSelectedFamilyState(cardId), selected); set(isCardSelectedFamilyState(cardId), selected);
setActionBarOpenState(selected || activeCardIds.length > 0); set(actionBarOpenState, selected || activeCardIds.length > 0);
if (selected) { if (selected) {
set(activeCardIdsState, [...activeCardIds, cardId]); set(activeCardIdsState, [...activeCardIds, cardId]);
@ -26,4 +24,24 @@ export const useSetCardSelected = () => {
} }
}, },
); );
const unselectAllActiveCards = useRecoilCallback(
({ set, snapshot }) =>
() => {
const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents;
activeCardIds.forEach((cardId: string) => {
set(isCardSelectedFamilyState(cardId), false);
});
set(activeCardIdsState, []);
set(actionBarOpenState, false);
},
[],
);
return {
setCardSelected,
unselectAllActiveCards,
};
}; };

View File

@ -1,9 +1,7 @@
import { useEffect } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { CompanyBoard } from '@/companies/board/components/CompanyBoard'; import { CompanyBoard } from '@/companies/board/components/CompanyBoard';
import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope-contexts/CompanyBoardRecoilScopeContext'; import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord'; import { useUpdateOneObjectRecord } from '@/object-record/hooks/useUpdateOneObjectRecord';
import { PipelineAddButton } from '@/pipeline/components/PipelineAddButton'; import { PipelineAddButton } from '@/pipeline/components/PipelineAddButton';
import { usePipelineSteps } from '@/pipeline/hooks/usePipelineSteps'; import { usePipelineSteps } from '@/pipeline/hooks/usePipelineSteps';
@ -14,7 +12,6 @@ import { PageBody } from '@/ui/layout/page/PageBody';
import { PageContainer } from '@/ui/layout/page/PageContainer'; import { PageContainer } from '@/ui/layout/page/PageContainer';
import { PageHeader } from '@/ui/layout/page/PageHeader'; import { PageHeader } from '@/ui/layout/page/PageHeader';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useView } from '@/views/hooks/useView';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions'; import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
const StyledBoardContainer = styled.div` const StyledBoardContainer = styled.div`
@ -46,18 +43,6 @@ export const Opportunities = () => {
}); });
}; };
const opportunitiesV2MetadataId = useObjectMetadataItem({
objectNameSingular: 'opportunity',
}).objectMetadataItem?.id;
const { setViewObjectMetadataId } = useView({
viewScopeId: 'company-board-view',
});
useEffect(() => {
setViewObjectMetadataId?.(opportunitiesV2MetadataId);
}, [opportunitiesV2MetadataId, setViewObjectMetadataId]);
return ( return (
<PageContainer> <PageContainer>
<RecoilScope> <RecoilScope>