Feature/filter and sort board (#725)

* Get pipeline progress from stage IDs

* Rename hooks file

* Addd first amount filter

* Add remaining filters

* Design fixes

* Add filtering on creation date or amount

* Fix card updates and creations with the new state management

* Keep ordering when dropping a card

* Add remainint sorts

* Make board header more generic

* Move available filters and sorts to board options

* Fix decorators for test

* Add pipeline stage ids to mock data

* Adapt mock data

* Linter
This commit is contained in:
Emilien Chauvet
2023-07-17 19:32:47 -07:00
committed by GitHub
parent 9895c1d5d6
commit 6301bc2fbf
19 changed files with 784 additions and 413 deletions

View File

@ -1,7 +1,7 @@
import { Meta, StoryObj } from '@storybook/react';
import { companyBoardOptions } from '@/companies/components/companyBoardOptions';
import { EntityBoard } from '@/pipeline/components/EntityBoard';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
import { BoardDecorator } from '~/testing/decorators';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
@ -17,7 +17,12 @@ type Story = StoryObj<typeof EntityBoard>;
export const OneColumnBoard: Story = {
render: getRenderWrapperForComponent(
<EntityBoard boardOptions={companyBoardOptions} />,
<EntityBoard
boardOptions={opportunitiesBoardOptions}
updateSorts={() => {
return;
}}
/>,
),
parameters: {
msw: graphqlMocks,

View File

@ -9,10 +9,6 @@ export const pipeline = {
name: 'New',
index: 0,
color: '#B76796',
pipelineProgresses: [
{ id: 'fe256b39-3ec3-4fe7-8998-b76aa0bfb600' },
{ id: '4a886c90-f4f2-4984-8222-882ebbb905d6' },
],
},
{
id: 'pipeline-stage-2',

View File

@ -5,7 +5,7 @@ import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
import { GET_PIPELINES } from '@/pipeline/queries';
import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
import { BoardCardContext } from '@/pipeline/states/BoardCardContext';
import { pipelineProgressIdScopedState } from '@/pipeline/states/pipelineProgressIdScopedState';
import { selectedBoardCardsState } from '@/pipeline/states/selectedBoardCardsState';
@ -108,7 +108,10 @@ export function CompanyBoardCard() {
amount: pipelineProgress.amount,
closeDate: pipelineProgress.closeDate || null,
},
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
refetchQueries: [
getOperationName(GET_PIPELINE_PROGRESS) ?? '',
getOperationName(GET_PIPELINES) ?? '',
],
});
},
[updatePipelineProgress],

View File

@ -1,21 +1,26 @@
import { Context } from 'react';
import { FilterDropdownEntitySearchSelect } from '@/ui/filter-n-sort/components/FilterDropdownEntitySearchSelect';
import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/filterDropdownSearchInputScopedState';
import { filterDropdownSelectedEntityIdScopedState } from '@/ui/filter-n-sort/states/filterDropdownSelectedEntityIdScopedState';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
import { TableContext } from '@/ui/table/states/TableContext';
import { useFilteredSearchCompanyQuery } from '../queries';
export function FilterDropdownCompanySearchSelect() {
export function FilterDropdownCompanySearchSelect({
context,
}: {
context: Context<string | null>;
}) {
const filterDropdownSearchInput = useRecoilScopedValue(
filterDropdownSearchInputScopedState,
TableContext,
context,
);
const [filterDropdownSelectedEntityId] = useRecoilScopedState(
filterDropdownSelectedEntityIdScopedState,
TableContext,
context,
);
const usersForSelect = useFilteredSearchCompanyQuery({
@ -28,7 +33,7 @@ export function FilterDropdownCompanySearchSelect() {
return (
<FilterDropdownEntitySearchSelect
entitiesForSelect={usersForSelect}
context={TableContext}
context={context}
/>
);
}

View File

@ -1,6 +1,7 @@
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { useInitializeCompanyBoardFilters } from '@/companies/hooks/useInitializeCompanyBoardFilters';
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
import {
CompanyForBoard,
@ -11,15 +12,30 @@ import { boardState } from '@/pipeline/states/boardState';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { isBoardLoadedState } from '@/pipeline/states/isBoardLoadedState';
import { BoardPipelineStageColumn } from '@/ui/board/components/Board';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { useRecoilScopedValue } from '@/ui/recoil-scope/hooks/useRecoilScopedValue';
import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By } from '~/generated/graphql';
import {
Pipeline,
PipelineStage,
useGetCompaniesQuery,
useGetPipelineProgressQuery,
useGetPipelinesQuery,
} from '~/generated/graphql';
export function HookCompanyBoard() {
import { CompanyBoardContext } from '../states/CompanyBoardContext';
export function HooksCompanyBoard({
availableFilters,
orderBy,
}: {
availableFilters: FilterDefinition[];
orderBy: PipelineProgresses_Order_By[];
}) {
useInitializeCompanyBoardFilters({
availableFilters,
});
const [currentPipeline, setCurrentPipeline] =
useRecoilState(currentPipelineState);
@ -44,29 +60,47 @@ export function HookCompanyBoard() {
title: pipelineStage.name,
colorCode: pipelineStage.color,
index: pipelineStage.index || 0,
pipelineProgressIds:
pipelineStage.pipelineProgresses?.map(
(item) => item.id as string,
) || [],
pipelineProgressIds: [],
})) || [];
setBoard(initialBoard);
setIsBoardLoaded(true);
},
});
const pipelineProgressIds = currentPipeline?.pipelineStages
?.map((pipelineStage: PipelineStage) =>
(
pipelineStage.pipelineProgresses?.map((item) => item.id as string) || []
).flat(),
)
const pipelineStageIds = currentPipeline?.pipelineStages
?.map((pipelineStage) => pipelineStage.id)
.flat();
const filters = useRecoilScopedValue(filtersScopedState, CompanyBoardContext);
const whereFilters = useMemo(() => {
return {
AND: [
{ pipelineStageId: { in: pipelineStageIds } },
...filters.map(turnFilterIntoWhereClause),
],
};
}, [filters, pipelineStageIds]) as any;
const pipelineProgressesQuery = useGetPipelineProgressQuery({
variables: {
where: {
id: { in: pipelineProgressIds },
},
where: whereFilters,
orderBy,
},
onCompleted: (data) => {
const pipelineProgresses = data?.findManyPipelineProgress || [];
setBoard((board) =>
board?.map((boardPipelineStage) => ({
...boardPipelineStage,
pipelineProgressIds: pipelineProgresses
.filter(
(pipelineProgress) =>
pipelineProgress.pipelineStageId ===
boardPipelineStage.pipelineStageId,
)
.map((pipelineProgress) => pipelineProgress.id),
})),
);
setIsBoardLoaded(true);
},
});

View File

@ -3,7 +3,7 @@ import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
import { GET_PIPELINES } from '@/pipeline/queries';
import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
import { BoardColumnContext } from '@/pipeline/states/BoardColumnContext';
import { boardState } from '@/pipeline/states/boardState';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
@ -37,7 +37,10 @@ export function NewCompanyProgressButton() {
} = usePreviousHotkeyScope();
const [createOnePipelineProgress] = useCreateOnePipelineProgressMutation({
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
refetchQueries: [
getOperationName(GET_PIPELINE_PROGRESS) ?? '',
getOperationName(GET_PIPELINES) ?? '',
],
});
const handleEntitySelect = useCallback(

View File

@ -1,13 +0,0 @@
import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard';
import { NewCompanyProgressButton } from '@/companies/components/NewCompanyProgressButton';
import { BoardOptions } from '@/pipeline/types/BoardOptions';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
export const companyBoardOptions: BoardOptions = {
newCardComponent: (
<RecoilScope>
<NewCompanyProgressButton />
</RecoilScope>
),
cardComponent: <CompanyBoardCard />,
};

View File

@ -0,0 +1,21 @@
import { useEffect } from 'react';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
export function useInitializeCompanyBoardFilters({
availableFilters,
}: {
availableFilters: FilterDefinition[];
}) {
const [, setAvailableFilters] = useRecoilScopedState(
availableFiltersScopedState,
CompanyBoardContext,
);
useEffect(() => {
setAvailableFilters(availableFilters);
}, [setAvailableFilters, availableFilters]);
}