Refacto board (#661)

* Refacto pipeline progress board to be entity agnostic

* Abstract hooks as well

* Move files

* Pass specific components as props

* Move board hook to the generic component

* Make dnd and update logic part of the board

* Remove useless call and getch pipelineProgress from hook

* Minot

* improve typing

* Revert "improve typing"

This reverts commit 49bf7929b6231747cc460cbb98f68c3c10424659.

* wip

* Get board from initial component

* Move files again

* Lint

* Fix story

* Lint

* Mock pipeline progress

* Fix storybook

* WIP refactor recoil

* Checkpoint: compilation

* Fix dnd

* Fix unselect card

* Checkpoint: compilation

* Checkpoint: New card OK

* Checkpoint: feature complete

* Fix latency for delete

* Linter

* Fix rebase

* Move files

* lint

* Update Stories tests

* lint

* Fix test

* Refactor hook for company progress indexing

* Remove useless type

* Move boardState

* remove gardcoded Id

* Nit

* Fix

* Rename state
This commit is contained in:
Emilien Chauvet
2023-07-14 17:51:16 -07:00
committed by GitHub
parent e93a96b3b1
commit 0a319bcf86
47 changed files with 975 additions and 730 deletions

View File

@ -0,0 +1,138 @@
import { useEffect } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import {
CompanyForBoard,
CompanyProgress,
PipelineProgressForBoard,
} from '@/companies/types/CompanyProgress';
import { BoardPipelineStageColumn } from '@/ui/board/components/Board';
import {
Pipeline,
PipelineStage,
useGetCompaniesQuery,
useGetPipelineProgressQuery,
useGetPipelinesQuery,
} from '~/generated/graphql';
import { boardState } from '../../modules/pipeline-progress/states/boardState';
import { companyProgressesFamilyState } from './companyProgressesFamilyState';
import { currentPipelineState } from './currentPipelineState';
import { isBoardLoadedState } from './isBoardLoadedState';
export function HookCompanyBoard() {
const [currentPipeline, setCurrentPipeline] =
useRecoilState(currentPipelineState);
const [, setBoard] = useRecoilState(boardState);
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);
useGetPipelinesQuery({
onCompleted: async (data) => {
const pipeline = data?.findManyPipeline[0] as Pipeline;
setCurrentPipeline(pipeline);
const pipelineStages = pipeline?.pipelineStages;
const orderedPipelineStages = pipelineStages
? [...pipelineStages].sort((a, b) => {
if (!a.index || !b.index) return 0;
return a.index - b.index;
})
: [];
const initialBoard: BoardPipelineStageColumn[] =
orderedPipelineStages?.map((pipelineStage) => ({
pipelineStageId: pipelineStage.id,
title: pipelineStage.name,
colorCode: pipelineStage.color,
pipelineProgressIds:
pipelineStage.pipelineProgresses?.map(
(item) => item.id as string,
) || [],
})) || [];
setBoard(initialBoard);
setIsBoardLoaded(true);
},
});
const pipelineProgressIds = currentPipeline?.pipelineStages
?.map((pipelineStage: PipelineStage) =>
(
pipelineStage.pipelineProgresses?.map((item) => item.id as string) || []
).flat(),
)
.flat();
const pipelineProgressesQuery = useGetPipelineProgressQuery({
variables: {
where: {
id: { in: pipelineProgressIds },
},
},
});
const pipelineProgresses =
pipelineProgressesQuery.data?.findManyPipelineProgress || [];
const entitiesQueryResult = useGetCompaniesQuery({
variables: {
where: {
id: {
in: pipelineProgresses.map((item) => item.progressableId),
},
},
},
});
const indexCompanyByIdReducer = (
acc: { [key: string]: CompanyForBoard },
company: CompanyForBoard,
) => ({
...acc,
[company.id]: company,
});
const companiesDict =
entitiesQueryResult.data?.companies.reduce(
indexCompanyByIdReducer,
{} as { [key: string]: CompanyForBoard },
) || {};
const indexPipelineProgressByIdReducer = (
acc: {
[key: string]: CompanyProgress;
},
pipelineProgress: PipelineProgressForBoard,
) => {
const company = companiesDict[pipelineProgress.progressableId];
return {
...acc,
[pipelineProgress.id]: {
pipelineProgress,
company,
},
};
};
const companyBoardIndex = pipelineProgresses.reduce(
indexPipelineProgressByIdReducer,
{} as { [key: string]: CompanyProgress },
);
const synchronizeCompanyProgresses = useRecoilCallback(
({ set }) =>
(companyBoardIndex: { [key: string]: CompanyProgress }) => {
Object.entries(companyBoardIndex).forEach(([id, companyProgress]) => {
set(companyProgressesFamilyState(id), companyProgress);
});
},
[],
);
const loading =
entitiesQueryResult.loading || pipelineProgressesQuery.loading;
useEffect(() => {
!loading && synchronizeCompanyProgresses(companyBoardIndex);
}, [loading, companyBoardIndex, synchronizeCompanyProgresses]);
return <></>;
}

View File

@ -1,97 +1,31 @@
import { useCallback, useMemo } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
import { companyBoardOptions } from '@/companies/components/companyBoardOptions';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { BoardActionBarButtonDeletePipelineProgress } from '@/pipeline-progress/components/BoardActionBarButtonDeletePipelineProgress';
import { EntityBoard } from '@/pipeline-progress/components/EntityBoard';
import { EntityBoardActionBar } from '@/pipeline-progress/components/EntityBoardActionBar';
import { GET_PIPELINES } from '@/pipeline-progress/queries';
import { RecoilScope } from '@/recoil-scope/components/RecoilScope';
import { IconTargetArrow } from '@/ui/icons/index';
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
import {
PipelineProgress,
PipelineStage,
useGetPipelinesQuery,
useUpdateOnePipelineProgressMutation,
useUpdateOnePipelineProgressStageMutation,
} from '../../generated/graphql';
import { Board } from '../../modules/pipeline-progress/components/Board';
import { useBoard } from '../../modules/pipeline-progress/hooks/useBoard';
import { HookCompanyBoard } from './HookCompanyBoard';
export function Opportunities() {
const theme = useTheme();
const pipelines = useGetPipelinesQuery();
const pipelineId = pipelines.data?.findManyPipeline[0]?.id;
const { initialBoard, items } = useBoard(pipelineId || '');
const columns = useMemo(
() =>
initialBoard?.map(({ id, colorCode, title }) => ({
id,
colorCode,
title,
})),
[initialBoard],
);
const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation();
const [updatePipelineProgressStage] =
useUpdateOnePipelineProgressStageMutation();
const handleCardUpdate = useCallback(
async (
pipelineProgress: Pick<PipelineProgress, 'id' | 'amount' | 'closeDate'>,
) => {
await updatePipelineProgress({
variables: {
id: pipelineProgress.id,
amount: pipelineProgress.amount,
closeDate: pipelineProgress.closeDate || null,
},
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
});
},
[updatePipelineProgress],
);
const handleCardMove = useCallback(
async (
pipelineProgressId: NonNullable<PipelineProgress['id']>,
pipelineStageId: NonNullable<PipelineStage['id']>,
) => {
updatePipelineProgressStage({
variables: {
id: pipelineProgressId,
pipelineStageId,
},
});
},
[updatePipelineProgressStage],
);
return (
<WithTopBarContainer
title="Opportunities"
icon={<IconTargetArrow size={theme.icon.size.md} />}
>
{items && pipelineId ? (
<>
<Board
pipelineId={pipelineId}
columns={columns || []}
initialBoard={initialBoard}
initialItems={items}
onCardMove={handleCardMove}
onCardUpdate={handleCardUpdate}
/>
<EntityBoardActionBar>
<BoardActionBarButtonDeletePipelineProgress />
</EntityBoardActionBar>
</>
) : (
<></>
)}
<HookCompanyBoard />
<RecoilScope SpecificContext={CompanyBoardContext}>
<EntityBoard boardOptions={companyBoardOptions} />
<EntityBoardActionBar>
<BoardActionBarButtonDeletePipelineProgress />
</EntityBoardActionBar>
</RecoilScope>
</WithTopBarContainer>
);
}

View File

@ -0,0 +1,11 @@
import { atomFamily } from 'recoil';
import { CompanyProgress } from '@/companies/types/CompanyProgress';
export const companyProgressesFamilyState = atomFamily<
CompanyProgress | undefined,
string
>({
key: 'companyProgressesFamilyState',
default: undefined,
});

View File

@ -0,0 +1,8 @@
import { atom } from 'recoil';
import { Pipeline } from '~/generated/graphql';
export const currentPipelineState = atom<Pipeline | undefined>({
key: 'currentPipelineState',
default: undefined,
});

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const isBoardLoadedState = atom<boolean>({
key: 'isBoardLoadedState',
default: false,
});