diff --git a/client/src/components/production-list-columns/production-list-columns.alert.component.jsx b/client/src/components/production-list-columns/production-list-columns.alert.component.jsx index 55a3207ac..218d6295a 100644 --- a/client/src/components/production-list-columns/production-list-columns.alert.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.alert.component.jsx @@ -1,7 +1,7 @@ import { ExclamationCircleFilled } from "@ant-design/icons"; import { useMutation } from "@apollo/client"; -import { Dropdown } from "antd"; -import React, { useCallback, useMemo } from "react"; +import { Button } from "antd"; +import React, { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -16,58 +16,45 @@ const mapDispatchToProps = (dispatch) => ({ insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) }); -export function ProductionListColumnAlert({ record, insertAuditTrail }) { +const ProductionListColumnAlert = ({ record, insertAuditTrail }) => { const { t } = useTranslation(); - const [updateAlert] = useMutation(UPDATE_JOB); const handleAlertToggle = useCallback(() => { logImEXEvent("production_toggle_alert"); + + const newAlertState = !!record.production_vars?.alert ? !record.production_vars.alert : true; + updateAlert({ variables: { jobId: record.id, job: { production_vars: { ...record.production_vars, - alert: !!record.production_vars && !!record.production_vars.alert ? !record.production_vars.alert : true + alert: newAlertState } } } + }).catch((err) => { + console.error(`Something went wrong updating production note: ${err.message || ""}`); }); + insertAuditTrail({ jobid: record.id, - operation: AuditTrailMapping.alertToggle( - !!record.production_vars && !!record.production_vars.alert ? !record.production_vars.alert : true - ), + operation: AuditTrailMapping.alertToggle(newAlertState), type: "alertToggle" }).then(() => { if (record.refetch) record.refetch(); }); }, [updateAlert, insertAuditTrail, record]); - const menuItems = useMemo( - () => [ - { - key: "toggleAlert", - label: - record.production_vars && record.production_vars.alert - ? t("production.labels.alertoff") - : t("production.labels.alerton"), - onClick: handleAlertToggle - } - ], - [record.production_vars, t, handleAlertToggle] - ); - return ( - -
- {record.production_vars && record.production_vars.alert ? ( - - ) : null} -
-
+
+ {record.production_vars?.alert && ( +
); -} +}; export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert); diff --git a/client/src/components/trello-board/components/AddCardLink.jsx b/client/src/components/trello-board/components/AddCardLink.jsx deleted file mode 100644 index df259a9d9..000000000 --- a/client/src/components/trello-board/components/AddCardLink.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; -import { AddCardLink } from "../styles/Base"; -import { useTranslation } from "react-i18next"; - -const AddCardLinkComponent = ({ onClick, laneId }) => { - const { t } = useTranslation(); - - return {t("trello.labels.add_card")}; -}; - -export default AddCardLinkComponent; diff --git a/client/src/components/trello-board/components/Card/Tag.jsx b/client/src/components/trello-board/components/Card/Tag.jsx deleted file mode 100644 index 78bd78063..000000000 --- a/client/src/components/trello-board/components/Card/Tag.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { TagSpan } from "../../styles/Base"; - -const Tag = ({ title, color, bgcolor, tagStyle, ...otherProps }) => { - const style = { color: color || "white", backgroundColor: bgcolor || "orange", ...tagStyle }; - return ( - - {title} - - ); -}; - -Tag.propTypes = { - title: PropTypes.string.isRequired, - color: PropTypes.string, - bgcolor: PropTypes.string, - tagStyle: PropTypes.object -}; - -export default Tag; diff --git a/client/src/components/trello-board/components/Lane/LaneHeader.jsx b/client/src/components/trello-board/components/Lane/LaneHeader.jsx deleted file mode 100644 index d849bb0a6..000000000 --- a/client/src/components/trello-board/components/Lane/LaneHeader.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import InlineInput from "../../widgets/InlineInput.jsx"; -import { LaneHeader, RightContent, Title } from "../../styles/Base"; -import LaneMenu from "./LaneHeader/LaneMenu.jsx"; -import { useTranslation } from "react-i18next"; - -const LaneHeaderComponent = ({ - updateTitle, - canAddLanes, - onDelete, - onDoubleClick, - editLaneTitle, - label, - title, - titleStyle, - labelStyle -}) => { - const { t } = useTranslation(); - - return ( - - - {editLaneTitle ? ( - <InlineInput - value={title} - border - placeholder={t("trello.labels.title")} - resize="vertical" - onSave={updateTitle} - /> - ) : ( - title - )} - - {label && ( - - {label} - - )} - {canAddLanes && } - - ); -}; - -LaneHeaderComponent.propTypes = { - updateTitle: PropTypes.func, - editLaneTitle: PropTypes.bool, - canAddLanes: PropTypes.bool, - label: PropTypes.string, - title: PropTypes.string, - onDelete: PropTypes.func, - onDoubleClick: PropTypes.func -}; - -LaneHeaderComponent.defaultProps = { - updateTitle: () => {}, - editLaneTitle: false, - canAddLanes: false -}; - -export default LaneHeaderComponent; diff --git a/client/src/components/trello-board/components/Lane/LaneHeader/LaneMenu.jsx b/client/src/components/trello-board/components/Lane/LaneHeader/LaneMenu.jsx deleted file mode 100644 index 0dc6610bd..000000000 --- a/client/src/components/trello-board/components/Lane/LaneHeader/LaneMenu.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from "react"; - -import { Popover } from "react-popopo"; - -import { CustomPopoverContainer, CustomPopoverContent } from "../../../styles/Base"; - -import { - DeleteWrapper, - GenDelButton, - LaneMenuContent, - LaneMenuHeader, - LaneMenuItem, - LaneMenuTitle, - MenuButton -} from "../../../styles/Elements"; -import { useTranslation } from "react-i18next"; - -const LaneMenu = ({ onDelete }) => { - const { t } = useTranslation(); - - return ( - ⋮} - > - - {t("trello.labels.lane_actions")} - - - - - - {t("trello.labels.delete_lane")} - - - ); -}; - -export default LaneMenu; diff --git a/client/src/components/trello-board/components/NewCardForm.jsx b/client/src/components/trello-board/components/NewCardForm.jsx deleted file mode 100644 index 585cc202b..000000000 --- a/client/src/components/trello-board/components/NewCardForm.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState } from "react"; -import PropTypes from "prop-types"; -import { CardForm, CardHeader, CardRightContent, CardTitle, CardWrapper, Detail } from "../styles/Base"; -import { AddButton, CancelButton } from "../styles/Elements"; -import EditableLabel from "../widgets/EditableLabel.jsx"; -import { useTranslation } from "react-i18next"; - -const NewCardForm = ({ onCancel, onAdd }) => { - const [state, setState] = useState({}); - const { t } = useTranslation(); - - const updateField = (field, value) => { - setState((prevState) => ({ ...prevState, [field]: value })); - }; - - const handleAdd = () => { - onAdd(state); - }; - - return ( - - - - - updateField("title", val)} - autoFocus - /> - - - updateField("label", val)} /> - - - - updateField("description", val)} - /> - - - {t("trello.labels.add_card")} - {t("trello.labels.cancel")} - - ); -}; - -NewCardForm.propTypes = { - onCancel: PropTypes.func.isRequired, - onAdd: PropTypes.func.isRequired -}; - -export default NewCardForm; diff --git a/client/src/components/trello-board/components/NewLaneForm.jsx b/client/src/components/trello-board/components/NewLaneForm.jsx deleted file mode 100644 index 1aa38e584..000000000 --- a/client/src/components/trello-board/components/NewLaneForm.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useRef } from "react"; -import PropTypes from "prop-types"; -import { LaneTitle, NewLaneButtons, Section } from "../styles/Base"; -import { AddButton, CancelButton } from "../styles/Elements"; -import NewLaneTitleEditor from "../widgets/NewLaneTitleEditor.jsx"; -import { v1 } from "uuid"; -import { useTranslation } from "react-i18next"; - -const NewLane = ({ onCancel, onAdd }) => { - const refInput = useRef(null); - const { t } = useTranslation(); - - const handleSubmit = () => { - onAdd({ - id: v1(), - title: getValue() - }); - }; - - const getValue = () => refInput.current.getValue(); - - // TODO: Commented out because it was never called and it was causing a error - // const onClickOutside = (a, b, c) => { - // if (getValue().length > 0) { - // handleSubmit(); - // } else { - // onCancel(); - // } - // }; - - return ( -
- - - - - {t("trello.labels.add_lane")} - {t("trello.labels.cancel")} - -
- ); -}; - -NewLane.propTypes = { - onCancel: PropTypes.func.isRequired, - onAdd: PropTypes.func.isRequired -}; - -export default NewLane; diff --git a/client/src/components/trello-board/components/NewLaneSection.jsx b/client/src/components/trello-board/components/NewLaneSection.jsx deleted file mode 100644 index edfbe5434..000000000 --- a/client/src/components/trello-board/components/NewLaneSection.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; -import { NewLaneSection } from "../styles/Base"; -import { AddLaneLink } from "../styles/Elements"; -import { useTranslation } from "react-i18next"; - -const NewLaneSectionComponent = ({ onClick }) => { - const { t } = useTranslation(); - - return ( - - {t("trello.labels.add_lane")} - - ); -}; - -export default NewLaneSectionComponent; diff --git a/client/src/components/trello-board/components/index.js b/client/src/components/trello-board/components/index.js index 59a41d085..78448c5b1 100644 --- a/client/src/components/trello-board/components/index.js +++ b/client/src/components/trello-board/components/index.js @@ -1,11 +1,7 @@ -import LaneHeader from "./Lane/LaneHeader"; import LaneFooter from "./Lane/LaneFooter"; import Card from "./Card"; import Loader from "./Loader.jsx"; -import NewLaneForm from "./NewLaneForm.jsx"; -import NewCardForm from "./NewCardForm.jsx"; -import AddCardLink from "./AddCardLink"; -import NewLaneSection from "./NewLaneSection.jsx"; + import { BoardWrapper, StyleHorizontal, GlobalStyle, StyleVertical, ScrollableLane, Section } from "../styles/Base"; const exports = { @@ -15,14 +11,9 @@ const exports = { BoardWrapper, Loader, ScrollableLane, - LaneHeader, LaneFooter, Section, - NewLaneForm, - NewLaneSection, - NewCardForm, - Card, - AddCardLink + Card }; export default exports; diff --git a/client/src/components/trello-board/controllers/Board.jsx b/client/src/components/trello-board/controllers/Board.jsx index 028e41518..be5c317dc 100644 --- a/client/src/components/trello-board/controllers/Board.jsx +++ b/client/src/components/trello-board/controllers/Board.jsx @@ -1,12 +1,11 @@ import { BoardContainer } from "../index"; -import classNames from "classnames"; import { useMemo, useState } from "react"; import { v1 } from "uuid"; const Board = ({ id, className, components, orientation, ...additionalProps }) => { const [storeId] = useState(id || v1()); - const allClassNames = useMemo(() => classNames("react-trello-board", className || ""), [className]); + const allClassNames = useMemo(() => `react-trello-board ${className || ""}`.trim(), [className]); const OrientationStyle = useMemo( () => (orientation === "horizontal" ? components.StyleHorizontal : components.StyleVertical), [orientation, components.StyleHorizontal, components.StyleVertical] diff --git a/client/src/components/trello-board/controllers/BoardContainer.jsx b/client/src/components/trello-board/controllers/BoardContainer.jsx index 4cb642a05..1b761e486 100644 --- a/client/src/components/trello-board/controllers/BoardContainer.jsx +++ b/client/src/components/trello-board/controllers/BoardContainer.jsx @@ -7,6 +7,7 @@ import isEqual from "lodash/isEqual"; import Lane from "./Lane"; import { PopoverWrapper } from "react-popopo"; import * as actions from "../../../redux/trello/trello.actions.js"; +import { BoardWrapper } from "../styles/Base.js"; /** * BoardContainer is a React component that represents a Trello-like board. @@ -17,17 +18,10 @@ import * as actions from "../../../redux/trello/trello.actions.js"; * @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 */ @@ -35,17 +29,11 @@ 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); @@ -60,12 +48,20 @@ const BoardContainer = ({ 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 "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 "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)); case "MOVE_CARD": return dispatch( actions.moveCardAcrossLanes({ @@ -76,14 +72,7 @@ const BoardContainer = ({ 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; } @@ -109,13 +98,6 @@ const BoardContainer = ({ setIsDragging(true); }, [setIsDragging]); - const getCardDetails = useCallback( - (laneId, cardIndex) => { - return currentReducerData.lanes.find((lane) => lane.id === laneId).cards[cardIndex]; - }, - [currentReducerData] - ); - const passThroughProps = useMemo( () => pick( @@ -123,59 +105,15 @@ const BoardContainer = ({ 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" - ] + ["laneSortFunction", "collapsibleLanes", "orientation"] ), - [ - id, - components, - data, - draggable, - style, - onDataChange, - - editable, - canAddLanes, - laneStyle, - orientation, - eventBusHandle, - reducerData, - cardStyle, - otherProps - ] + [id, components, data, onDataChange, orientation, eventBusHandle, reducerData, otherProps] ); const onLaneDrag = useCallback( @@ -207,7 +145,7 @@ const BoardContainer = ({ ); return ( - + {currentReducerData.lanes.map((lane, index) => { @@ -218,13 +156,7 @@ const BoardContainer = ({ 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} @@ -235,7 +167,7 @@ const BoardContainer = ({ })} - + ); }; @@ -247,30 +179,10 @@ BoardContainer.propTypes = { 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 + orientation: PropTypes.string }; export default BoardContainer; diff --git a/client/src/components/trello-board/controllers/Lane.jsx b/client/src/components/trello-board/controllers/Lane.jsx index 5e26d60ea..2e67e5857 100644 --- a/client/src/components/trello-board/controllers/Lane.jsx +++ b/client/src/components/trello-board/controllers/Lane.jsx @@ -1,14 +1,14 @@ -import React, { forwardRef, useCallback, useMemo, useState, useEffect } from "react"; -import classNames from "classnames"; +import React, { forwardRef, useCallback, useEffect, useMemo, useState } from "react"; import PropTypes from "prop-types"; import { bindActionCreators } from "redux"; import { connect } from "react-redux"; -import { v1 } from "uuid"; import * as actions from "../../../redux/trello/trello.actions.js"; import { Draggable, Droppable } from "../dnd/lib"; import { Virtuoso, VirtuosoGrid } from "react-virtuoso"; import HeightPreservingItem from "../components/Lane/HeightPreservingItem.jsx"; +import { Section } from "../styles/Base.js"; +import LaneFooter from "../components/Lane/LaneFooter.jsx"; const Lane = ({ actions, @@ -20,36 +20,14 @@ const Lane = ({ laneSortFunction, style = {}, cardStyle = {}, - tagStyle = {}, - titleStyle = {}, - labelStyle = {}, cards, - label, - draggable = false, collapsibleLanes = false, - droppable = true, - onCardClick = () => {}, - onBeforeCardDelete = () => {}, - onCardDelete = () => {}, - onCardAdd = () => {}, - onCardUpdate = () => {}, - onLaneDelete = () => {}, - onLaneUpdate = () => {}, - onLaneClick = () => {}, - onLaneScroll = () => {}, - editable = false, - cardDraggable = true, - cardDragClass, - cardDropClass, - canAddLanes = false, - hideCardDeleteIcon = false, + components = {}, orientation = "vertical", className, - currentPage, ...otherProps }) => { - const [addCardMode, setAddCardMode] = useState(false); const [collapsed, setCollapsed] = useState(false); const [isVisible, setIsVisible] = useState(true); @@ -65,76 +43,11 @@ const Lane = ({ return cards.concat().sort((card1, card2) => sortFunction(card1, card2)); }, []); - const removeCard = useCallback( - (cardId) => { - if (onBeforeCardDelete && typeof onBeforeCardDelete === "function") { - onBeforeCardDelete(() => { - onCardDelete && onCardDelete(cardId, id); - actions.removeCard({ laneId: id, cardId: cardId }); - }); - } else { - onCardDelete && onCardDelete(cardId, id); - actions.removeCard({ laneId: id, cardId: cardId }); - } - }, - [onBeforeCardDelete, onCardDelete, actions, id] - ); - - const handleCardClick = useCallback( - (e, card) => { - onCardClick && onCardClick(card.id, card.metadata, card.laneId); - e.stopPropagation(); - }, - [onCardClick] - ); - - const showEditableCard = useCallback(() => { - setAddCardMode(true); - }, [setAddCardMode]); - - const hideEditableCard = useCallback(() => { - setAddCardMode(false); - }, [setAddCardMode]); - - const addNewCard = useCallback( - (params) => { - const laneId = id; - const newCardId = v1(); - hideEditableCard(); - let card = { id: newCardId, ...params }; - actions.addCard({ laneId, card }); - onCardAdd(card, laneId); - }, - [actions, id, onCardAdd, hideEditableCard] - ); - - const updateCard = useCallback( - (updatedCard) => { - actions.updateCard({ laneId: id, card: updatedCard }); - onCardUpdate(id, updatedCard); - }, - [actions, id, onCardUpdate] - ); - - const removeLane = useCallback(() => { - actions.removeLane({ laneId: id }); - onLaneDelete(id); - }, [actions, id, onLaneDelete]); - - const updateTitle = useCallback( - (value) => { - actions.updateLane({ id, title: value }); - onLaneUpdate(id, { title: value }); - }, - [actions, id, onLaneUpdate] - ); - const toggleLaneCollapsed = useCallback(() => { collapsibleLanes && setCollapsed(!collapsed); }, [collapsibleLanes, collapsed]); const Card = React.memo(({ provided, item: card, isDragging }) => { - const onDeleteCard = () => removeCard(card.id); return (
- handleCardClick(e, card)} - onChange={(updatedCard) => updateCard(updatedCard)} - showDeleteButton={!hideCardDeleteIcon} - tagStyle={tagStyle} - cardDraggable={cardDraggable} - editable={editable} - {...card} - /> +
); }); @@ -174,16 +75,6 @@ const Lane = ({ ); }; - const renderAddCardLink = useMemo( - () => editable && !addCardMode && , - [editable, addCardMode, showEditableCard, id] - ); - - const renderNewCardForm = useMemo( - () => addCardMode && , - [addCardMode, hideEditableCard, addNewCard, id] - ); - const ItemWrapper = ({ children, ...props }) => (
{children} @@ -196,43 +87,46 @@ const Lane = ({ const commonProps = { useWindowScroll: true, - data: renderedCards, - overscan: { - main: 22, - reverse: 22 - } + data: renderedCards }; - const componentProps = - orientation === "vertical" - ? { - ...commonProps, - scrollerRef: provided.innerRef, - listClassName: "grid-container", - itemClassName: "grid-item", - components: { - List: forwardRef(({ style, children, ...props }, ref) => ( -
- {children} -
- )), - Item: ({ children, ...props }) =>
{children}
- }, - itemContent: (index, item) => {renderDraggable(index, item)} - } - : { - ...commonProps, - components: { Item: HeightPreservingItem }, - itemContent: (index, item) => renderDraggable(index, item), - scrollerRef: provided.innerRef - }; + const verticalProps = { + ...commonProps, + // we are using the useWindowScroll, so we don't need to pass the scrollerRef + // scrollerRef: provided.innerRef, + listClassName: "grid-container", + itemClassName: "grid-item", + components: { + List: forwardRef(({ style, children, ...props }, ref) => ( +
+ {children} +
+ )), + Item: ({ children, ...props }) =>
{children}
+ }, + itemContent: (index, item) => {renderDraggable(index, item)}, + overscan: { main: 10, reverse: 10 } + }; + + const horizontalProps = { + ...commonProps, + components: { Item: HeightPreservingItem }, + overscan: { main: 1, reverse: 1 }, + itemContent: (index, item) => renderDraggable(index, item), + scrollerRef: provided.innerRef + }; + + const componentProps = orientation === "vertical" ? verticalProps : horizontalProps; + + // A collapsed lane does not need to render the Virtuoso component const finalComponentProps = collapsed ? {} : componentProps; + return (
} {(orientation === "horizontal" || renderedCards.length === 0 || collapsed) && provided.placeholder}
- {renderAddCardLink} - {renderNewCardForm}
); }; @@ -275,18 +167,13 @@ const Lane = ({ }; const renderHeader = (pickedProps) => { - return ( - - ); + return ; }; - const allClassNames = classNames("react-trello-lane", collapsed ? "lane-collapsed" : "", className || ""); - + const allClassNames = useMemo( + () => `react-trello-lane ${collapsed ? "lane-collapsed" : ""} ${className || ""}`.trim(), + [collapsed, className] + ); const passedProps = { actions, id, @@ -296,37 +183,20 @@ const Lane = ({ laneSortFunction, style, cardStyle, - tagStyle, - titleStyle, - labelStyle, cards, - label, - draggable, collapsibleLanes, - droppable, - editable, - cardDraggable, - cardDragClass, - cardDropClass, - canAddLanes, - hideCardDeleteIcon, + components, orientation, className, - currentPage, ...otherProps }; return ( - onLaneClick && onLaneClick(id)} - orientation={orientation} - {...passedProps} - > +
{renderHeader({ id, cards, ...passedProps })} {renderDragContainer()} - {collapsibleLanes && } - + {collapsibleLanes && } +
); }; @@ -339,20 +209,9 @@ Lane.propTypes = { laneSortFunction: PropTypes.func, style: PropTypes.object, cardStyle: PropTypes.object, - tagStyle: PropTypes.object, - titleStyle: PropTypes.object, - labelStyle: PropTypes.object, cards: PropTypes.array, label: PropTypes.string, - draggable: PropTypes.bool, collapsibleLanes: PropTypes.bool, - droppable: PropTypes.bool, - editable: PropTypes.bool, - cardDraggable: PropTypes.bool, - cardDragClass: PropTypes.string, - cardDropClass: PropTypes.string, - canAddLanes: PropTypes.bool, - hideCardDeleteIcon: PropTypes.bool, components: PropTypes.object, orientation: PropTypes.string }; diff --git a/client/src/components/trello-board/helpers/LaneHelper.js b/client/src/components/trello-board/helpers/LaneHelper.js index 69b9edb52..d4f31d704 100644 --- a/client/src/components/trello-board/helpers/LaneHelper.js +++ b/client/src/components/trello-board/helpers/LaneHelper.js @@ -1,10 +1,25 @@ import update from "immutability-helper"; import cloneDeep from "lodash/cloneDeep"; +/** + * Update the lanes in the state + * @param state + * @param lanes + * @returns {unknown} + */ const updateLanes = (state, lanes) => update(state, { lanes: { $set: lanes } }); -const updateLaneCards = (lane, cards) => update(lane, { cards: { $set: cards } }); +/** + * Helper functions for managing lanes + * @type {{moveCardAcrossLanes: (function(*, {fromLaneId: *, toLaneId: *, cardId: *, index: *}): *), initialiseLanes: (function(*, {lanes: *}): {lanes: *})}} + */ const LaneHelper = { + /** + * Initialise the lanes + * @param state + * @param lanes + * @returns {unknown} + */ initialiseLanes: (state, { lanes }) => { const newLanes = lanes.map((lane) => { lane.currentPage = 1; @@ -14,79 +29,6 @@ const LaneHelper = { return updateLanes(state, newLanes); }, - paginateLane: (state, { laneId, newCards, nextPage }) => { - const updatedLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: newCards }); - updatedLanes.find((lane) => lane.id === laneId).currentPage = nextPage; - return updateLanes(state, updatedLanes); - }, - - appendCardsToLane: (state, { laneId, newCards, index }) => { - const lane = state.lanes.find((lane) => lane.id === laneId); - newCards = newCards - .map((c) => update(c, { laneId: { $set: laneId } })) - .filter((c) => lane.cards.find((card) => card.id === c.id) == null); - return state.lanes.map((lane) => { - if (lane.id === laneId) { - const cardsToUpdate = - index !== undefined - ? [...lane.cards.slice(0, index), ...newCards, ...lane.cards.slice(index)] - : [...lane.cards, ...newCards]; - return updateLaneCards(lane, cardsToUpdate); - } else { - return lane; - } - }); - }, - - appendCardToLane: (state, { laneId, card, index }) => { - const newLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: [card], index }); - return updateLanes(state, newLanes); - }, - - addLane: (state, lane) => { - const newLane = { cards: [], ...lane }; - return updateLanes(state, [...state.lanes, newLane]); - }, - - updateLane: (state, updatedLane) => { - const newLanes = state.lanes.map((lane) => (updatedLane.id === lane.id ? { ...lane, ...updatedLane } : lane)); - return updateLanes(state, newLanes); - }, - - removeCardFromLane: (state, { laneId, cardId }) => { - // Clone the state to avoid mutation - const newLanes = cloneDeep(state.lanes); - - // Find the lane from which the card will be removed - const lane = newLanes.find((lane) => lane.id === laneId); - - // Find the card in the lane - const cardIndex = lane.cards.findIndex((card) => card.id === cardId); - if (cardIndex === -1) { - throw new Error("Card not found in the lane"); - } - - // Remove the card from the lane - lane.cards.splice(cardIndex, 1); - - let idx = 0; - - // Update the lane and card indexes for all lanes - newLanes.forEach((lane, laneIndex) => { - lane.cards.forEach((card, cardIndex) => { - card.idx = idx; - card.laneIndex = laneIndex; - card.cardIndex = cardIndex; - card.laneId = lane.id; - idx++; - }); - }); - - return update(state, { - lanes: { $set: newLanes } - }); - }, - /** * Move a card from one lane to another * @param state @@ -136,28 +78,104 @@ const LaneHelper = { return update(state, { lanes: { $set: newLanes } }); - }, - updateCardsForLane: (state, { laneId, cards }) => { - const lanes = state.lanes.map((lane) => (lane.id === laneId ? updateLaneCards(lane, cards) : lane)); - return updateLanes(state, lanes); - }, - - updateCardForLane: (state, { laneId, card: updatedCard }) => { - const lanes = state.lanes.map((lane) => { - if (lane.id === laneId) { - const cards = lane.cards.map((card) => (card.id === updatedCard.id ? { ...card, ...updatedCard } : card)); - return updateLaneCards(lane, cards); - } else { - return lane; - } - }); - return updateLanes(state, lanes); - }, - - removeLane: (state, { laneId }) => { - const updatedLanes = state.lanes.filter((lane) => lane.id !== laneId); - return updateLanes(state, updatedLanes); } + + // TODO: (Note), the rest of this commented out logic is for the way the original Trello board was implemented + // It would not be hard to adapt should we need to use it in the future + + // const updateLaneCards = (lane, cards) => update(lane, { cards: { $set: cards } }); + // paginateLane: (state, { laneId, newCards, nextPage }) => { + // const updatedLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: newCards }); + // updatedLanes.find((lane) => lane.id === laneId).currentPage = nextPage; + // return updateLanes(state, updatedLanes); + // }, + // appendCardsToLane: (state, { laneId, newCards, index }) => { + // const lane = state.lanes.find((lane) => lane.id === laneId); + // newCards = newCards + // .map((c) => update(c, { laneId: { $set: laneId } })) + // .filter((c) => lane.cards.find((card) => card.id === c.id) == null); + // return state.lanes.map((lane) => { + // if (lane.id === laneId) { + // const cardsToUpdate = + // index !== undefined + // ? [...lane.cards.slice(0, index), ...newCards, ...lane.cards.slice(index)] + // : [...lane.cards, ...newCards]; + // return updateLaneCards(lane, cardsToUpdate); + // } else { + // return lane; + // } + // }); + // }, + + // appendCardToLane: (state, { laneId, card, index }) => { + // const newLanes = LaneHelper.appendCardsToLane(state, { laneId: laneId, newCards: [card], index }); + // return updateLanes(state, newLanes); + // }, + // + // addLane: (state, lane) => { + // const newLane = { cards: [], ...lane }; + // return updateLanes(state, [...state.lanes, newLane]); + // }, + // + // updateLane: (state, updatedLane) => { + // const newLanes = state.lanes.map((lane) => (updatedLane.id === lane.id ? { ...lane, ...updatedLane } : lane)); + // return updateLanes(state, newLanes); + // }, + // + // removeCardFromLane: (state, { laneId, cardId }) => { + // // Clone the state to avoid mutation + // const newLanes = cloneDeep(state.lanes); + // + // // Find the lane from which the card will be removed + // const lane = newLanes.find((lane) => lane.id === laneId); + // + // // Find the card in the lane + // const cardIndex = lane.cards.findIndex((card) => card.id === cardId); + // if (cardIndex === -1) { + // throw new Error("Card not found in the lane"); + // } + // + // // Remove the card from the lane + // lane.cards.splice(cardIndex, 1); + // + // let idx = 0; + // + // // Update the lane and card indexes for all lanes + // newLanes.forEach((lane, laneIndex) => { + // lane.cards.forEach((card, cardIndex) => { + // card.idx = idx; + // card.laneIndex = laneIndex; + // card.cardIndex = cardIndex; + // card.laneId = lane.id; + // idx++; + // }); + // }); + // + // return update(state, { + // lanes: { $set: newLanes } + // }); + // }, + // updateCardsForLane: (state, { laneId, cards }) => { + // const lanes = state.lanes.map((lane) => (lane.id === laneId ? updateLaneCards(lane, cards) : lane)); + // return updateLanes(state, lanes); + // }, + // + // updateCardForLane: (state, { laneId, card: updatedCard }) => { + // const lanes = state.lanes.map((lane) => { + // if (lane.id === laneId) { + // const cards = lane.cards.map((card) => (card.id === updatedCard.id ? { ...card, ...updatedCard } : card)); + // return updateLaneCards(lane, cards); + // } else { + // return lane; + // } + // }); + // return updateLanes(state, lanes); + // }, + // + // removeLane: (state, { laneId }) => { + // const updatedLanes = state.lanes.filter((lane) => lane.id !== laneId); + // return updateLanes(state, updatedLanes); + // } }; export default LaneHelper; diff --git a/client/src/components/trello-board/styles/Base.js b/client/src/components/trello-board/styles/Base.js index 4d2166a97..f45aa4b5d 100644 --- a/client/src/components/trello-board/styles/Base.js +++ b/client/src/components/trello-board/styles/Base.js @@ -24,6 +24,10 @@ const getSectionStyles = (props) => { white-space: nowrap; // overflow-y: none; min-width: 8.5%; + flex-wrap: wrap; + align-content: center; + justify-content: center; + align-items: stretch; `; } return ` @@ -31,6 +35,18 @@ const getSectionStyles = (props) => { `; }; +const getLaneFooterStyles = (props) => { + if (props.operation === "horizontal") { + return ` + height: 50px; + margin-top: 20px; + `; + } + return ` + height: 25px; + `; +}; + export const GlobalStyle = createGlobalStyle` .comPlainTextContentEditable { -webkit-user-modify: read-write-plaintext-only; @@ -84,15 +100,18 @@ export const StyleHorizontal = styled.div` .react-trello-lane.lane-collapsed { min-height: 15px; } + .ant-card-body { padding: 4px; } + .item-wrapper { display: flex; flex: 1 1 auto; white-space: nowrap; min-width: 4%; } + .react-trello-card { height: auto; margin: 2px; @@ -118,10 +137,12 @@ export const StyleVertical = styled.div` width: 100%; height: 100%; } + .grid-container { display: flex; flex-wrap: wrap; } + .grid-item { width: 8%; // TODO: THIS IS WHERE WE GET VERTICAL CARD CUSTOMIZATION display: flex; @@ -133,6 +154,7 @@ export const StyleVertical = styled.div` height: 100%; width: 100%; } + .item-wrapper { width: 100%; height: 100%; @@ -219,27 +241,12 @@ export const Section = styled.section` ${getSectionStyles}; `; -export const LaneHeader = styled(Header)` - margin-bottom: 0; - ${(props) => - props.editLaneTitle && - css` - padding: 0; - line-height: 30px; - `} ${(props) => - !props.editLaneTitle && - css` - padding: 0 5px; - `}; -`; - export const LaneFooter = styled.div` display: flex; justify-content: center; align-items: center; width: 100%; - position: relative; - height: 10px; + ${getLaneFooterStyles}; `; export const ScrollableLane = styled.div` diff --git a/client/src/components/trello-board/widgets/DeleteButton.jsx b/client/src/components/trello-board/widgets/DeleteButton.jsx deleted file mode 100644 index 0076921c8..000000000 --- a/client/src/components/trello-board/widgets/DeleteButton.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; -import { DeleteWrapper } from "../styles/Elements"; -import { Button } from "antd"; - -const DeleteButton = (props) => { - return ( - - - - ); -}; - -export default DeleteButton; diff --git a/client/src/components/trello-board/widgets/EditableLabel.jsx b/client/src/components/trello-board/widgets/EditableLabel.jsx deleted file mode 100644 index 13564234e..000000000 --- a/client/src/components/trello-board/widgets/EditableLabel.jsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -class EditableLabel extends React.Component { - constructor({value}) { - super() - this.state = {value: value} - } - - getText = el => { - return el.innerText - } - - onTextChange = ev => { - const value = this.getText(ev.target) - this.setState({value: value}) - } - - componentDidMount() { - if (this.props.autoFocus) { - this.refDiv.focus() - } - } - - onBlur = () => { - this.props.onChange(this.state.value) - } - - onPaste = ev => { - ev.preventDefault() - const value = ev.clipboardData.getData('text') - document.execCommand('insertText', false, value) - } - - getClassName = () => { - const placeholder = this.state.value === '' ? 'comPlainTextContentEditable--has-placeholder' : '' - return `comPlainTextContentEditable ${placeholder}` - } - - onKeyDown = e => { - if (e.keyCode === 13) { - this.props.onChange(this.state.value) - this.refDiv.blur() - e.preventDefault() - } - if (e.keyCode === 27) { - this.refDiv.value = this.props.value - this.setState({value: this.props.value}) - // this.refDiv.blur() - e.preventDefault() - e.stopPropagation() - } - } - - render() { - const placeholder = this.props.value.length > 0 ? false : this.props.placeholder - return ( -
(this.refDiv = ref)} - contentEditable="true" - className={this.getClassName()} - onPaste={this.onPaste} - onBlur={this.onBlur} - onInput={this.onTextChange} - onKeyDown={this.onKeyDown} - placeholder={placeholder} - /> - ) - } -} - -EditableLabel.propTypes = { - onChange: PropTypes.func, - placeholder: PropTypes.string, - autoFocus: PropTypes.bool, - inline: PropTypes.bool, - value: PropTypes.string -} - -EditableLabel.defaultProps = { - onChange: () => {}, - placeholder: '', - autoFocus: false, - inline: false, - value: '' -} -export default EditableLabel diff --git a/client/src/components/trello-board/widgets/InlineInput.jsx b/client/src/components/trello-board/widgets/InlineInput.jsx deleted file mode 100644 index 56c311931..000000000 --- a/client/src/components/trello-board/widgets/InlineInput.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import PropTypes from "prop-types"; -import { InlineInput } from "../styles/Base"; -import autosize from "autosize"; - -const InlineInputController = ({ onSave, border, placeholder, value, autoFocus, resize, onCancel }) => { - const inputRef = useRef(null); - const [inputValue, setInputValue] = useState(value); - - // Effect for autosizing and initial autoFocus - useEffect(() => { - if (inputRef.current && resize !== "none") { - autosize(inputRef.current); - } - if (inputRef.current && autoFocus) { - inputRef.current.focus(); - } - }, [resize, autoFocus]); - - // Effect to update value when props change - useEffect(() => { - setInputValue(value); - }, [value]); - - const handleFocus = (e) => e.target.select(); - - const handleMouseDown = (e) => { - if (document.activeElement !== e.target) { - e.preventDefault(); - inputRef.current.focus(); - } - }; - - const handleBlur = () => { - updateValue(); - }; - - const handleKeyDown = (e) => { - if (e.keyCode === 13) { - // Enter - inputRef.current.blur(); - e.preventDefault(); - } else if (e.keyCode === 27) { - // Escape - setInputValue(value); // Reset to initial value - inputRef.current.blur(); - e.preventDefault(); - } else if (e.keyCode === 9) { - // Tab - if (inputValue.length === 0) { - onCancel(); - } - inputRef.current.blur(); - e.preventDefault(); - } - }; - - const updateValue = () => { - if (inputValue !== value) { - onSave(inputValue); - } - }; - - return ( - setInputValue(e.target.value)} - autoComplete="off" - autoCorrect="off" - autoCapitalize="off" - spellCheck="false" - dataGramm="false" - rows={1} - autoFocus={autoFocus} - /> - ); -}; - -InlineInputController.propTypes = { - onSave: PropTypes.func, - onCancel: PropTypes.func, - border: PropTypes.bool, - placeholder: PropTypes.string, - value: PropTypes.string, - autoFocus: PropTypes.bool, - resize: PropTypes.oneOf(["none", "vertical", "horizontal"]) -}; - -InlineInputController.defaultProps = { - onSave: () => {}, - onCancel: () => {}, - placeholder: "", - value: "", - border: false, - autoFocus: false, - resize: "none" -}; - -export default InlineInputController; diff --git a/client/src/components/trello-board/widgets/NewLaneTitleEditor.jsx b/client/src/components/trello-board/widgets/NewLaneTitleEditor.jsx deleted file mode 100644 index 572046d70..000000000 --- a/client/src/components/trello-board/widgets/NewLaneTitleEditor.jsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { InlineInput } from "../styles/Base"; -import autosize from "autosize"; - -class NewLaneTitleEditor extends React.Component { - onKeyDown = (e) => { - if (e.keyCode === 13) { - this.refInput.blur(); - this.props.onSave(); - e.preventDefault(); - } - if (e.keyCode === 27) { - this.cancel(); - e.preventDefault(); - } - - if (e.keyCode === 9) { - if (this.getValue().length === 0) { - this.cancel(); - } else { - this.props.onSave(); - } - e.preventDefault(); - } - }; - - cancel = () => { - this.setValue(""); - this.props.onCancel(); - this.refInput.blur(); - }; - - getValue = () => this.refInput.value; - setValue = (value) => (this.refInput.value = value); - - saveValue = () => { - if (this.getValue() !== this.props.value) { - this.props.onSave(this.getValue()); - } - }; - - focus = () => this.refInput.focus(); - - setRef = (ref) => { - this.refInput = ref; - if (this.props.resize !== "none") { - autosize(this.refInput); - } - }; - - render() { - const { autoFocus, resize, border, autoResize, value, placeholder } = this.props; - - return ( - - ); - } -} - -NewLaneTitleEditor.propTypes = { - onSave: PropTypes.func, - onCancel: PropTypes.func, - border: PropTypes.bool, - placeholder: PropTypes.string, - value: PropTypes.string, - autoFocus: PropTypes.bool, - autoResize: PropTypes.bool, - resize: PropTypes.oneOf(["none", "vertical", "horizontal"]) -}; - -NewLaneTitleEditor.defaultProps = { - inputRef: () => {}, - onSave: () => {}, - onCancel: () => {}, - placeholder: "", - value: "", - border: false, - autoFocus: false, - autoResize: false, - resize: "none" -}; - -export default NewLaneTitleEditor; diff --git a/client/src/components/trello-board/widgets/index.jsx b/client/src/components/trello-board/widgets/index.jsx deleted file mode 100644 index b58eedfd6..000000000 --- a/client/src/components/trello-board/widgets/index.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import DeleteButton from "./DeleteButton"; -import EditableLabel from "./EditableLabel"; -import InlineInput from "./InlineInput"; - -const exports = { - DeleteButton, - EditableLabel, - InlineInput -}; - -export default exports;