Files
twenty/front/src/modules/pipeline-progress/components/Board.tsx
Lucas Bordeau e03d5ed8a7 Refactor/inplace input (#541)
* wip

* Changed all other components

* Removed console log

* Console.log

* lint

* Removed internal state

* Fix

* Lint
2023-07-08 16:45:52 -07:00

182 lines
5.6 KiB
TypeScript

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/board/components/BoardColumn';
import { Company, PipelineProgress } from '~/generated/graphql';
import {
Column,
getOptimisticlyUpdatedBoard,
StyledBoard,
} from '../../ui/board/components/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 = {
company: Pick<Company, 'id' | 'name' | 'domainName'>;
pipelineProgress: Pick<PipelineProgress, 'id' | 'amount' | 'closeDate'>;
};
export type CompanyProgressDict = {
[key: string]: CompanyProgress;
};
type BoardProps = {
pipelineId: string;
columns: Omit<Column, 'itemKeys'>[];
initialBoard: Column[];
initialItems: CompanyProgressDict;
onCardMove?: (itemKey: string, columnId: Column['id']) => Promise<void>;
onCardUpdate: (
pipelineProgress: Pick<PipelineProgress, 'id' | 'amount' | 'closeDate'>,
) => 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,
onCardMove,
onCardUpdate,
pipelineId,
}: BoardProps) {
const [board, setBoard] = useRecoilState(boardColumnsState);
const [boardItems, setBoardItems] = useRecoilState(boardItemsState);
const [selectedBoardItems, setSelectedBoardItems] = useRecoilState(
selectedBoardItemsState,
);
const [isInitialBoardLoaded, setIsInitialBoardLoaded] = useState(false);
useEffect(() => {
setBoardItems(initialItems);
}, [initialItems, setBoardItems]);
useEffect(() => {
if (isInitialBoardLoaded) return;
setBoard(initialBoard);
if (Object.keys(initialItems).length === 0) 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 &&
onCardMove &&
(await onCardMove(draggedEntityId, destinationColumnId));
} catch (e) {
console.error(e);
}
},
[board, onCardMove, 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].company}
pipelineProgress={
boardItems[itemKey].pipelineProgress
}
selected={selectedBoardItems.includes(itemKey)}
onCardUpdate={onCardUpdate}
onSelect={() => handleSelect(itemKey)}
/>
</div>
)}
</Draggable>
),
)}
</BoardColumnCardsContainer>
<NewButton pipelineId={pipelineId} columnId={column.id} />
</BoardColumn>
)}
</Droppable>
))}
</DragDropContext>
</StyledBoard>
) : (
<></>
);
}