Files
bodyshop/client/src/components/trello-board/controllers/BoardContainer.jsx
Dave Richer 6f2c8dba5a - Optimization and Edgecases
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-07-03 01:10:11 -04:00

277 lines
8.4 KiB
JavaScript

import React, { useCallback, useEffect, useState, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { DragDropContext } from "../dnd/lib";
import PropTypes from "prop-types";
import pick from "lodash/pick";
import isEqual from "lodash/isEqual";
import Lane from "./Lane";
import { PopoverWrapper } from "react-popopo";
import * as actions from "../../../redux/trello/trello.actions.js";
/**
* BoardContainer is a React component that represents a Trello-like board.
* It uses Redux for state management and provides a variety of props to customize its behavior.
*
* @component
* @param {Object} props - Component props
* @param {string} props.id - The unique identifier for the board
* @param {Object} props.components - Custom components to use in the board
* @param {Object} props.data - The initial data for the board
* @param {boolean} props.draggable - Whether the board is draggable
* @param {Object} props.style - The CSS styles to apply to the board
* @param {Function} props.onDataChange - Callback function when the board data changes
* @param {Function} props.onDragEnd - Callback function when a drag ends
* @param {boolean} props.editable - Whether the board is editable
* @param {boolean} props.canAddLanes - Whether lanes can be added to the board
* @param {Object} props.laneStyle - The CSS styles to apply to the lanes
* @param {string} props.orientation - The orientation of the board ("horizontal" or "vertical")
* @param {Function} props.eventBusHandle - Function to handle events from the event bus
* @param {Object} props.reducerData - The initial data for the Redux reducer
* @param {Object} props.cardStyle - The CSS styles to apply to the cards
* @param {Object} props.otherProps - Any other props to pass to the board
* @returns {JSX.Element} A Trello-like board
*/
const BoardContainer = ({
id,
components,
data,
draggable = false,
style,
onDataChange = () => {},
onDragEnd = () => {},
editable = false,
canAddLanes = false,
laneStyle,
orientation = "horizontal",
eventBusHandle,
reducerData,
cardStyle,
...otherProps
}) => {
const [isDragging, setIsDragging] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const dispatch = useDispatch();
const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
const groupName = `TrelloBoard${id}`;
const wireEventBus = useCallback(() => {
const eventBus = {
publish: (event) => {
switch (event.type) {
case "ADD_CARD":
return dispatch(actions.addCard({ laneId: event.laneId, card: event.card }));
case "REMOVE_CARD":
return dispatch(actions.removeCard({ laneId: event.laneId, cardId: event.cardId }));
case "REFRESH_BOARD":
return dispatch(actions.loadBoard(event.data));
case "MOVE_CARD":
return dispatch(
actions.moveCardAcrossLanes({
fromLaneId: event.fromLaneId,
toLaneId: event.toLaneId,
cardId: event.cardId,
index: event.index,
event
})
);
case "UPDATE_CARDS":
return dispatch(actions.updateCards({ laneId: event.laneId, cards: event.cards }));
case "UPDATE_CARD":
return dispatch(actions.updateCard({ laneId: event.laneId, updatedCard: event.card }));
case "UPDATE_LANES":
return dispatch(actions.updateLanes(event.lanes));
case "UPDATE_LANE":
return dispatch(actions.updateLane(event.lane));
default:
return;
}
}
};
eventBusHandle(eventBus);
}, [dispatch, eventBusHandle]);
useEffect(() => {
dispatch(actions.loadBoard(data));
if (eventBusHandle) {
wireEventBus();
}
}, [data, eventBusHandle, dispatch, wireEventBus]);
useEffect(() => {
if (!isEqual(currentReducerData, reducerData)) {
onDataChange(currentReducerData);
}
}, [currentReducerData, reducerData, onDataChange]);
const onDragStart = useCallback(() => {
setIsDragging(true);
}, [setIsDragging]);
const getCardDetails = useCallback(
(laneId, cardIndex) => {
return currentReducerData.lanes.find((lane) => lane.id === laneId).cards[cardIndex];
},
[currentReducerData]
);
const passThroughProps = useMemo(
() =>
pick(
{
id,
components,
data,
draggable,
style,
onDataChange,
editable,
canAddLanes,
laneStyle,
orientation,
eventBusHandle,
reducerData,
cardStyle,
...otherProps
},
[
"onLaneScroll",
"onLaneDelete",
"onLaneUpdate",
"onCardClick",
"onBeforeCardDelete",
"onCardDelete",
"onCardAdd",
"onCardUpdate",
"onLaneClick",
"laneSortFunction",
"draggable",
"cardDraggable",
"collapsibleLanes",
"canAddLanes",
"hideCardDeleteIcon",
"tagStyle",
"handleDragStart",
"handleDragEnd",
"cardDragClass",
"editLaneTitle",
"orientation"
]
),
[
id,
components,
data,
draggable,
style,
onDataChange,
editable,
canAddLanes,
laneStyle,
orientation,
eventBusHandle,
reducerData,
cardStyle,
otherProps
]
);
const onLaneDrag = useCallback(
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
setIsDragging(false);
if (!type || type !== "lane" || !source || !destination) return;
setIsProcessing(true);
dispatch(
actions.moveCardAcrossLanes({
fromLaneId: source.droppableId,
toLaneId: destination.droppableId,
cardId: draggableId,
index: destination.index
})
);
try {
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
} catch (err) {
console.error("Error in onLaneDrag", err);
} finally {
setIsProcessing(false);
}
},
[dispatch, onDragEnd]
);
return (
<components.BoardWrapper style={style} orientation={orientation}>
<PopoverWrapper>
<DragDropContext onDragEnd={onLaneDrag} onDragStart={onDragStart} contextId={groupName}>
{currentReducerData.lanes.map((lane, index) => {
const { id, droppable, ...laneOtherProps } = lane;
return (
<Lane
key={id}
boardId={groupName}
components={components}
id={id}
getCardDetails={getCardDetails}
index={index}
droppable={droppable === undefined ? true : droppable}
style={laneStyle || lane.style || {}}
labelStyle={lane.labelStyle || {}}
cardStyle={cardStyle || lane.cardStyle}
editable={editable && !lane.disallowAddingCard}
{...laneOtherProps}
{...passThroughProps}
cards={lane.cards}
isDragging={isDragging}
isProcessing={isProcessing}
/>
);
})}
</DragDropContext>
</PopoverWrapper>
</components.BoardWrapper>
);
};
BoardContainer.propTypes = {
id: PropTypes.string,
components: PropTypes.object,
actions: PropTypes.object,
data: PropTypes.object.isRequired,
reducerData: PropTypes.object,
onDataChange: PropTypes.func,
eventBusHandle: PropTypes.func,
onLaneScroll: PropTypes.func,
onCardClick: PropTypes.func,
onBeforeCardDelete: PropTypes.func,
onCardDelete: PropTypes.func,
onCardAdd: PropTypes.func,
onCardUpdate: PropTypes.func,
onLaneAdd: PropTypes.func,
onLaneDelete: PropTypes.func,
onLaneClick: PropTypes.func,
onLaneUpdate: PropTypes.func,
laneSortFunction: PropTypes.func,
draggable: PropTypes.bool,
collapsibleLanes: PropTypes.bool,
editable: PropTypes.bool,
canAddLanes: PropTypes.bool,
hideCardDeleteIcon: PropTypes.bool,
handleDragStart: PropTypes.func,
handleDragEnd: PropTypes.func,
style: PropTypes.object,
tagStyle: PropTypes.object,
cardDraggable: PropTypes.bool,
cardDragClass: PropTypes.string,
orientation: PropTypes.string,
cardStyle: PropTypes.object
};
export default BoardContainer;