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; pipelineProgress: Pick; }; export type CompanyProgressDict = { [key: string]: CompanyProgress; }; type BoardProps = { pipelineId: string; columns: Omit[]; initialBoard: Column[]; initialItems: CompanyProgressDict; onCardMove?: (itemKey: string, columnId: Column['id']) => Promise; onCardUpdate: ( pipelineProgress: Pick, ) => Promise; }; const StyledPlaceholder = styled.div` min-height: 1px; `; const BoardColumnCardsContainer = ({ children, droppableProvided, }: { children: React.ReactNode; droppableProvided: DroppableProvided; }) => { return (
{children} {droppableProvided?.placeholder}
); }; 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 ? ( {columns.map((column, columnIndex) => ( {(droppableProvided) => ( {board[columnIndex].itemKeys.map( (itemKey, index) => boardItems[itemKey] && ( {(draggableProvided) => (
handleSelect(itemKey)} />
)}
), )}
)}
))}
) : ( <> ); }