First round of refactor EntityBoards (#1067)

This commit is contained in:
Lucas Bordeau
2023-08-04 16:16:34 +02:00
committed by GitHub
parent 11e7266f8a
commit c790cc5d0c
35 changed files with 513 additions and 414 deletions

View File

@ -1,49 +0,0 @@
import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState } from 'recoil';
import { IconTrash } from '@/ui/icon/index';
import { EntityTableActionBarButton } from '@/ui/table/action-bar/components/EntityTableActionBarButton';
import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql';
import { GET_PIPELINES } from '../queries';
import { boardState } from '../states/boardState';
import { selectedBoardCardsState } from '../states/selectedBoardCardsState';
export function BoardActionBarButtonDeletePipelineProgress() {
const [selectedBoardItems, setSelectedBoardItems] = useRecoilState(
selectedBoardCardsState,
);
const [board, setBoard] = useRecoilState(boardState);
const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
});
async function handleDeleteClick() {
setBoard(
board?.map((pipelineStage) => ({
...pipelineStage,
pipelineProgressIds: pipelineStage.pipelineProgressIds.filter(
(pipelineProgressId) =>
!selectedBoardItems.includes(pipelineProgressId),
),
})),
);
setSelectedBoardItems([]);
await deletePipelineProgress({
variables: {
ids: selectedBoardItems,
},
});
}
return (
<EntityTableActionBarButton
label="Delete"
icon={<IconTrash size={16} />}
type="warning"
onClick={handleDeleteClick}
/>
);
}

View File

@ -1,122 +0,0 @@
import { useCallback } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import { useTheme } from '@emotion/react';
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 { IconList } from '@tabler/icons-react';
import { useRecoilState } from 'recoil';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { BoardHeader } from '@/ui/board/components/BoardHeader';
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import {
PipelineProgress,
PipelineProgressOrderByWithRelationInput,
PipelineStage,
useUpdateOnePipelineProgressStageMutation,
} from '~/generated/graphql';
import {
getOptimisticlyUpdatedBoard,
StyledBoard,
} from '../../ui/board/components/Board';
import { GET_PIPELINE_PROGRESS } from '../queries';
import { BoardColumnContext } from '../states/BoardColumnContext';
import { boardState } from '../states/boardState';
import { BoardOptions } from '../types/BoardOptions';
import { EntityBoardColumn } from './EntityBoardColumn';
const StyledBoardWithHeader = styled.div`
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
`;
export function EntityBoard({
boardOptions,
updateSorts,
}: {
boardOptions: BoardOptions;
updateSorts: (
sorts: Array<SelectedSortType<PipelineProgressOrderByWithRelationInput>>,
) => void;
}) {
const [board, setBoard] = useRecoilState(boardState);
const theme = useTheme();
const [updatePipelineProgressStage] =
useUpdateOnePipelineProgressStageMutation();
const updatePipelineProgressStageInDB = useCallback(
async (
pipelineProgressId: NonNullable<PipelineProgress['id']>,
pipelineStageId: NonNullable<PipelineStage['id']>,
) => {
updatePipelineProgressStage({
variables: {
id: pipelineProgressId,
pipelineStageId,
},
refetchQueries: [getOperationName(GET_PIPELINE_PROGRESS) ?? ''],
});
},
[updatePipelineProgressStage],
);
const onDragEnd: OnDragEndResponder = useCallback(
async (result) => {
if (!board) return;
const newBoard = getOptimisticlyUpdatedBoard(board, result);
if (!newBoard) return;
setBoard(newBoard);
try {
const draggedEntityId = result.draggableId;
const destinationColumnId = result.destination?.droppableId;
draggedEntityId &&
destinationColumnId &&
updatePipelineProgressStageInDB &&
(await updatePipelineProgressStageInDB(
draggedEntityId,
destinationColumnId,
));
} catch (e) {
console.error(e);
}
},
[board, updatePipelineProgressStageInDB, setBoard],
);
const sortedBoard = board
? [...board].sort((a, b) => {
return a.index - b.index;
})
: [];
return (board?.length ?? 0) > 0 ? (
<StyledBoardWithHeader>
<BoardHeader
viewName="All opportunities"
viewIcon={<IconList size={theme.icon.size.md} />}
availableSorts={boardOptions.sorts}
onSortsUpdate={updateSorts}
context={CompanyBoardContext}
/>
<StyledBoard>
<DragDropContext onDragEnd={onDragEnd}>
{sortedBoard.map((column) => (
<RecoilScope
SpecificContext={BoardColumnContext}
key={column.pipelineStageId}
>
<EntityBoardColumn boardOptions={boardOptions} column={column} />
</RecoilScope>
))}
</DragDropContext>
</StyledBoard>
</StyledBoardWithHeader>
) : (
<></>
);
}

View File

@ -1,15 +0,0 @@
import React from 'react';
import { useRecoilValue } from 'recoil';
import { ActionBar } from '@/ui/action-bar/components/ActionBar';
import { selectedBoardCardsState } from '../states/selectedBoardCardsState';
type OwnProps = {
children: React.ReactNode | React.ReactNode[];
};
export function EntityBoardActionBar({ children }: OwnProps) {
const selectedBoardCards = useRecoilValue(selectedBoardCardsState);
return <ActionBar selectedIds={selectedBoardCards}>{children}</ActionBar>;
}

View File

@ -1,45 +0,0 @@
import { useEffect } from 'react';
import { Draggable } from '@hello-pangea/dnd';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { BoardCardContext } from '../states/BoardCardContext';
import { pipelineProgressIdScopedState } from '../states/pipelineProgressIdScopedState';
import { BoardOptions } from '../types/BoardOptions';
export function EntityBoardCard({
boardOptions,
pipelineProgressId,
index,
}: {
boardOptions: BoardOptions;
pipelineProgressId: string;
index: number;
}) {
const [pipelineProgressIdFromRecoil, setPipelineProgressId] =
useRecoilScopedState(pipelineProgressIdScopedState, BoardCardContext);
useEffect(() => {
if (pipelineProgressIdFromRecoil !== pipelineProgressId) {
setPipelineProgressId(pipelineProgressId);
}
}, [pipelineProgressId, setPipelineProgressId, pipelineProgressIdFromRecoil]);
return (
<Draggable
key={pipelineProgressId}
draggableId={pipelineProgressId}
index={index}
>
{(draggableProvided) => (
<div
ref={draggableProvided?.innerRef}
{...draggableProvided?.dragHandleProps}
{...draggableProvided?.draggableProps}
>
{boardOptions.cardComponent}
</div>
)}
</Draggable>
);
}

View File

@ -1,146 +0,0 @@
import { useEffect } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { Draggable, Droppable, DroppableProvided } from '@hello-pangea/dnd';
import { useRecoilValue } from 'recoil';
import { BoardPipelineStageColumn } from '@/ui/board/components/Board';
import { BoardColumn } from '@/ui/board/components/BoardColumn';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useUpdatePipelineStageMutation } from '~/generated/graphql';
import { GET_PIPELINES } from '../queries';
import { BoardCardContext } from '../states/BoardCardContext';
import { BoardColumnContext } from '../states/BoardColumnContext';
import { boardColumnTotalsFamilySelector } from '../states/boardColumnTotalsFamilySelector';
import { pipelineStageIdScopedState } from '../states/pipelineStageIdScopedState';
import { BoardOptions } from '../types/BoardOptions';
import { EntityBoardCard } from './EntityBoardCard';
const StyledPlaceholder = styled.div`
min-height: 1px;
`;
const StyledNewCardButtonContainer = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(4)};
`;
const StyledColumnCardsContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
`;
const BoardColumnCardsContainer = ({
children,
droppableProvided,
}: {
children: React.ReactNode;
droppableProvided: DroppableProvided;
}) => {
return (
<StyledColumnCardsContainer
ref={droppableProvided?.innerRef}
{...droppableProvided?.droppableProps}
>
{children}
<StyledPlaceholder>{droppableProvided?.placeholder}</StyledPlaceholder>
</StyledColumnCardsContainer>
);
};
export function EntityBoardColumn({
column,
boardOptions,
}: {
column: BoardPipelineStageColumn;
boardOptions: BoardOptions;
}) {
const [pipelineStageId, setPipelineStageId] = useRecoilScopedState(
pipelineStageIdScopedState,
BoardColumnContext,
);
const boardColumnTotal = useRecoilValue(
boardColumnTotalsFamilySelector(column.pipelineStageId),
);
useEffect(() => {
if (pipelineStageId !== column.pipelineStageId) {
setPipelineStageId(column.pipelineStageId);
}
}, [column, setPipelineStageId, pipelineStageId]);
const [updatePipelineStage] = useUpdatePipelineStageMutation();
function handleEditColumnTitle(value: string) {
updatePipelineStage({
variables: {
id: pipelineStageId,
data: { name: value },
},
refetchQueries: [getOperationName(GET_PIPELINES) || ''],
});
}
function handleEditColumnColor(value: string) {
updatePipelineStage({
variables: {
id: pipelineStageId,
data: { color: value },
},
refetchQueries: [getOperationName(GET_PIPELINES) || ''],
});
}
return (
<Droppable droppableId={column.pipelineStageId}>
{(droppableProvided) => (
<BoardColumn
onColumnColorEdit={handleEditColumnColor}
onTitleEdit={handleEditColumnTitle}
title={column.title}
color={column.colorCode}
pipelineStageId={column.pipelineStageId}
totalAmount={boardColumnTotal}
isFirstColumn={column.index === 0}
numChildren={column.pipelineProgressIds.length}
>
<BoardColumnCardsContainer droppableProvided={droppableProvided}>
{column.pipelineProgressIds.map((pipelineProgressId, index) => (
<RecoilScope
SpecificContext={BoardCardContext}
key={pipelineProgressId}
>
<EntityBoardCard
index={index}
pipelineProgressId={pipelineProgressId}
boardOptions={boardOptions}
/>
</RecoilScope>
))}
<Draggable
draggableId={`new-${column.pipelineStageId}`}
index={column.pipelineProgressIds.length}
>
{(draggableProvided) => (
<div
ref={draggableProvided?.innerRef}
{...{
...draggableProvided.dragHandleProps,
draggable: false,
}}
{...draggableProvided?.draggableProps}
>
<StyledNewCardButtonContainer>
<RecoilScope>{boardOptions.newCardComponent}</RecoilScope>
</StyledNewCardButtonContainer>
</div>
)}
</Draggable>
</BoardColumnCardsContainer>
</BoardColumn>
)}
</Droppable>
);
}