Enable opportunity card deletion (#490)

* Add checkbox

* Add state management for selected opportunities

* Use recoil for selected items state, show action bar

* Deduplicate code

* Add delete action

* Enable delete

* Add color for selected cards

* update board state on delete

* Add stories

* Enable empty board

* Fix story

* Handle dark mdoe

* Nits

* Rename module

* Better naming

* Fix naming confusion process<>progress
This commit is contained in:
Emilien Chauvet
2023-07-03 14:11:39 -07:00
committed by GitHub
parent c871d1cc10
commit db5dfb3bdf
22 changed files with 275 additions and 81 deletions

View File

@ -0,0 +1,167 @@
import { useCallback, useEffect, useState } from 'react';
import styled from '@emotion/styled';
import {
DragDropContext,
Draggable,
Droppable,
DroppableProvided,
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 { useRecoilState } from 'recoil';
import { BoardColumn } from '@/ui/components/board/BoardColumn';
import { Company } from '~/generated/graphql';
import {
Column,
getOptimisticlyUpdatedBoard,
StyledBoard,
} from '../../ui/components/board/Board';
import { boardColumnsState } from '../states/boardColumnsState';
import { boardItemsState } from '../states/boardItemsState';
import { selectedBoardItemsState } from '../states/selectedBoardItemsState';
import { CompanyBoardCard } from './CompanyBoardCard';
import { NewButton } from './NewButton';
export type CompanyProgress = Pick<
Company,
'id' | 'name' | 'domainName' | 'createdAt'
>;
export type CompanyProgressDict = {
[key: string]: CompanyProgress;
};
type BoardProps = {
pipelineId: string;
columns: Omit<Column, 'itemKeys'>[];
initialBoard: Column[];
initialItems: CompanyProgressDict;
onUpdate?: (itemKey: string, columnId: Column['id']) => Promise<void>;
};
const StyledPlaceholder = styled.div`
min-height: 1px;
`;
const BoardColumnCardsContainer = ({
children,
droppableProvided,
}: {
children: React.ReactNode;
droppableProvided: DroppableProvided;
}) => {
return (
<div
ref={droppableProvided?.innerRef}
{...droppableProvided?.droppableProps}
>
{children}
<StyledPlaceholder>{droppableProvided?.placeholder}</StyledPlaceholder>
</div>
);
};
export function Board({
columns,
initialBoard,
initialItems,
onUpdate,
pipelineId,
}: BoardProps) {
const [board, setBoard] = useRecoilState(boardColumnsState);
const [boardItems, setBoardItems] = useRecoilState(boardItemsState);
const [selectedBoardItems, setSelectedBoardItems] = useRecoilState(
selectedBoardItemsState,
);
const [isInitialBoardLoaded, setIsInitialBoardLoaded] = useState(false);
useEffect(() => {
setBoard(initialBoard);
if (Object.keys(initialItems).length === 0 || isInitialBoardLoaded) return;
setBoardItems(initialItems);
setIsInitialBoardLoaded(true);
}, [
initialBoard,
setBoard,
initialItems,
setBoardItems,
setIsInitialBoardLoaded,
isInitialBoardLoaded,
]);
const onDragEnd: OnDragEndResponder = useCallback(
async (result) => {
const newBoard = getOptimisticlyUpdatedBoard(board, result);
if (!newBoard) return;
setBoard(newBoard);
try {
const draggedEntityId = result.draggableId;
const destinationColumnId = result.destination?.droppableId;
draggedEntityId &&
destinationColumnId &&
onUpdate &&
(await onUpdate(draggedEntityId, destinationColumnId));
} catch (e) {
console.error(e);
}
},
[board, onUpdate, setBoard],
);
function handleSelect(itemKey: string) {
if (selectedBoardItems.includes(itemKey)) {
setSelectedBoardItems(
selectedBoardItems.filter((key) => key !== itemKey),
);
} else {
setSelectedBoardItems([...selectedBoardItems, itemKey]);
}
}
return board.length > 0 ? (
<StyledBoard>
<DragDropContext onDragEnd={onDragEnd}>
{columns.map((column, columnIndex) => (
<Droppable key={column.id} droppableId={column.id}>
{(droppableProvided) => (
<BoardColumn title={column.title} colorCode={column.colorCode}>
<BoardColumnCardsContainer
droppableProvided={droppableProvided}
>
{board[columnIndex].itemKeys.map(
(itemKey, index) =>
boardItems[itemKey] && (
<Draggable
key={itemKey}
draggableId={itemKey}
index={index}
>
{(draggableProvided) => (
<div
ref={draggableProvided?.innerRef}
{...draggableProvided?.dragHandleProps}
{...draggableProvided?.draggableProps}
>
<CompanyBoardCard
company={boardItems[itemKey]}
selected={selectedBoardItems.includes(itemKey)}
onSelect={() => handleSelect(itemKey)}
/>
</div>
)}
</Draggable>
),
)}
</BoardColumnCardsContainer>
<NewButton pipelineId={pipelineId} columnId={column.id} />
</BoardColumn>
)}
</Droppable>
))}
</DragDropContext>
</StyledBoard>
) : (
<></>
);
}