From 162d8bfffe77ea752595d5a121c38a73b95bf780 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 2 Jul 2024 20:50:34 -0400 Subject: [PATCH] - Optimization and Edgecases Signed-off-by: Dave Richer --- ...production-board-kanban-card.component.jsx | 31 +-- .../production-board-kanban.component.jsx | 208 +++++++++--------- ...roduction-list-columns.alert.component.jsx | 25 +-- ...-list-columns.productionnote.component.jsx | 124 ++++++----- .../production-sublets-manage.component.jsx | 147 +++++++------ .../components/Lane/HeightPreservingItem.jsx | 18 +- .../trello-board/controllers/Board.jsx | 9 +- .../controllers/BoardContainer.jsx | 191 ++++++++-------- .../trello-board/controllers/Lane.jsx | 95 ++------ .../components/trello-board/styles/Base.js | 11 +- 10 files changed, 394 insertions(+), 465 deletions(-) diff --git a/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx b/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx index 88bd2ec7e..99fbc4f99 100644 --- a/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx +++ b/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx @@ -6,49 +6,31 @@ import { PauseCircleOutlined } from "@ant-design/icons"; import { Card, Col, Row, Space, Tooltip } from "antd"; -import React, { useMemo, useCallback } from "react"; +import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { DateTimeFormatter } from "../../utils/DateFormatter"; + import ProductionAlert from "../production-list-columns/production-list-columns.alert.component"; import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component"; import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component"; + import "./production-board-card.styles.scss"; import dayjs from "../../utils/day"; + import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; -/** - * Get the color of the card based on the total hours - * @param ssbuckets - * @param totalHrs - * @returns {{r: number, b: number, g: number}} - */ const cardColor = (ssbuckets, totalHrs) => { const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs)); return bucket && bucket.color ? bucket.color.rgb || bucket.color : { r: 255, g: 255, b: 255 }; }; -/** - * Get the contrast color based on the background color - * @param bgColor - * @returns {string} - */ const getContrastYIQ = (bgColor) => (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white"; -/** - * Production Board Card component - * @param technician - * @param card - * @param bodyshop - * @param cardSettings - * @returns {Element} - * @constructor - */ export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings }) { const { t } = useTranslation(); - const { metadata } = card; const employee_body = useMemo( @@ -90,9 +72,8 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe className="react-trello-card" size="small" style={{ - backgroundColor: - cardSettings && cardSettings.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`, - color: cardSettings && cardSettings.cardcolor && contrastYIQ, + backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`, + color: cardSettings?.cardcolor && contrastYIQ, maxWidth: "250px", margin: "5px" }} diff --git a/client/src/components/production-board-kanban/production-board-kanban.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.component.jsx index bffcf77f2..0d8172585 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.component.jsx @@ -3,7 +3,7 @@ import { useApolloClient } from "@apollo/client"; import Board from "../../components/trello-board/index"; import { Button, notification, Skeleton, Space, Statistic } from "antd"; import { PageHeader } from "@ant-design/pro-layout"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useState, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -49,13 +49,11 @@ export function ProductionBoardKanbanComponent({ associationSettings }) { const [boardLanes, setBoardLanes] = useState({ lanes: [] }); - const [filter, setFilter] = useState({ search: "", employeeId: null }); const [loading, setLoading] = useState(true); const [isMoving, setIsMoving] = useState(false); const orientation = associationSettings?.kanban_settings?.orientation ? "vertical" : "horizontal"; - const { t } = useTranslation(); useEffect(() => { @@ -65,13 +63,12 @@ export function ProductionBoardKanbanComponent({ }, [associationSettings]); useEffect(() => { - const newBoardData = createFakeBoardData( + const newBoardData = createBoardData( [...bodyshop.md_ro_statuses.production_statuses, ...(bodyshop.md_ro_statuses.additional_board_statuses || [])], data, filter ); - // Build Board Lanes Data newBoardData.lanes = newBoardData.lanes.map((lane) => ({ ...lane, title: `${lane.title} (${lane.cards.length})` @@ -89,13 +86,7 @@ export function ProductionBoardKanbanComponent({ const client = useApolloClient(); - /** - * Get Card By ID - * @param data - * @param cardId - * @returns {*|any|null} - */ - const getCardByID = (data, cardId) => { + const getCardByID = useCallback((data, cardId) => { for (const lane of data.lanes) { for (const card of lane.cards) { if (card.id === cardId) { @@ -104,88 +95,90 @@ export function ProductionBoardKanbanComponent({ } } return null; - }; + }, []); - const onDragEnd = async ({ type, source, destination, draggableId }) => { - logImEXEvent("kanban_drag_end"); + const onDragEnd = useCallback( + async ({ type, source, destination, draggableId }) => { + logImEXEvent("kanban_drag_end"); - // Early gate, also if the card is already moving bail - if (!type || type !== "lane" || !source || !destination || isMoving) return; + if (!type || type !== "lane" || !source || !destination || isMoving) return; - setIsMoving(true); + setIsMoving(true); - const targetLane = boardLanes.lanes.find((lane) => lane.id === destination.droppableId); - const sourceLane = boardLanes.lanes.find((lane) => lane.id === source.droppableId); + const targetLane = boardLanes.lanes.find((lane) => lane.id === destination.droppableId); + const sourceLane = boardLanes.lanes.find((lane) => lane.id === source.droppableId); - const sameColumnTransfer = source.droppableId === destination.droppableId; - const sourceCard = getCardByID(boardLanes, draggableId); + const sameColumnTransfer = source.droppableId === destination.droppableId; + const sourceCard = getCardByID(boardLanes, draggableId); - const movedCardWillBeFirst = destination.index === 0; - const movedCardWillBeLast = destination.index > targetLane.cards.length - 1; + const movedCardWillBeFirst = destination.index === 0; + const movedCardWillBeLast = destination.index > targetLane.cards.length - 1; - const lastCardInTargetLane = targetLane.cards[targetLane.cards.length - 1]; + const lastCardInTargetLane = targetLane.cards[targetLane.cards.length - 1]; - const oldChildCard = sourceLane.cards[destination.index + 1]; + const oldChildCard = sourceLane.cards[destination.index + 1]; - const newChildCard = movedCardWillBeLast - ? null - : targetLane.cards[ - sameColumnTransfer - ? destination.index - destination.index > 0 - ? destination.index - : destination.index + 1 - : destination.index - ]; + const newChildCard = movedCardWillBeLast + ? null + : targetLane.cards[ + sameColumnTransfer + ? destination.index - destination.index > 0 + ? destination.index + : destination.index + 1 + : destination.index + ]; - const oldChildCardNewParent = oldChildCard ? sourceCard.metadata.kanbanparent : null; + const oldChildCardNewParent = oldChildCard ? sourceCard.metadata.kanbanparent : null; - let movedCardNewKanbanParent; - if (movedCardWillBeFirst) { - movedCardNewKanbanParent = "-1"; - } else if (movedCardWillBeLast) { - movedCardNewKanbanParent = lastCardInTargetLane.id; - } else if (!!newChildCard) { - movedCardNewKanbanParent = newChildCard.metadata.kanbanparent; - } else { - console.log("==> !!!!!!Couldn't find a parent.!!!! <=="); - } - const newChildCardNewParent = newChildCard ? draggableId : null; - try { - const update = await client.mutate({ - mutation: generate_UPDATE_JOB_KANBAN( - oldChildCard ? oldChildCard.id : null, - oldChildCardNewParent, - draggableId, - movedCardNewKanbanParent, - targetLane.id, - newChildCard ? newChildCard.id : null, - newChildCardNewParent - ) - }); + let movedCardNewKanbanParent; + if (movedCardWillBeFirst) { + movedCardNewKanbanParent = "-1"; + } else if (movedCardWillBeLast) { + movedCardNewKanbanParent = lastCardInTargetLane.id; + } else if (!!newChildCard) { + movedCardNewKanbanParent = newChildCard.metadata.kanbanparent; + } else { + console.log("==> !!!!!!Couldn't find a parent.!!!! <=="); + } + const newChildCardNewParent = newChildCard ? draggableId : null; + try { + const update = await client.mutate({ + mutation: generate_UPDATE_JOB_KANBAN( + oldChildCard ? oldChildCard.id : null, + oldChildCardNewParent, + draggableId, + movedCardNewKanbanParent, + targetLane.id, + newChildCard ? newChildCard.id : null, + newChildCardNewParent + ) + }); - insertAuditTrail({ - jobid: draggableId, - operation: AuditTrailMapping.jobstatuschange(targetLane.id), - type: "jobstatuschange" - }); + insertAuditTrail({ + jobid: draggableId, + operation: AuditTrailMapping.jobstatuschange(targetLane.id), + type: "jobstatuschange" + }); - if (update.errors) { + if (update.errors) { + notification["error"]({ + message: t("production.errors.boardupdate", { + message: JSON.stringify(update.errors) + }) + }); + } + } catch (error) { notification["error"]({ message: t("production.errors.boardupdate", { - message: JSON.stringify(update.errors) + message: error.message }) }); + } finally { + setIsMoving(false); } - } catch (error) { - notification["error"]({ - message: t("production.errors.boardupdate", { - message: error.message - }) - }); - } finally { - setIsMoving(false); - } - }; + }, + [boardLanes, client, getCardByID, insertAuditTrail, isMoving, t] + ); const totalHrs = useMemo( () => @@ -209,36 +202,45 @@ export function ProductionBoardKanbanComponent({ [data] ); - const Header = ({ title }) => ( -
- {title} -
+ const Header = useCallback( + ({ title }) => ( +
+ {title} +
+ ), + [] ); - const cardSettings = - associationSettings && - associationSettings.kanban_settings && - Object.keys(associationSettings.kanban_settings).length > 0 - ? associationSettings.kanban_settings - : { - ats: true, - clm_no: true, - compact: false, - ownr_nm: true, - sublets: true, - ins_co_nm: true, - production_note: true, - employeeassignments: true, - scheduled_completion: true, - stickyheader: false, - cardcolor: false, - orientation: false - }; + const cardSettings = useMemo( + () => + associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0 + ? associationSettings.kanban_settings + : { + ats: true, + clm_no: true, + compact: false, + ownr_nm: true, + sublets: true, + ins_co_nm: true, + production_note: true, + employeeassignments: true, + scheduled_completion: true, + stickyheader: false, + cardcolor: false, + orientation: false + }, + [associationSettings] + ); - const components = { - Card: (cardProps) => ProductionBoardCard({ card: cardProps, technician, bodyshop, cardSettings }), - LaneHeader: Header - }; + const components = useMemo( + () => ({ + Card: (cardProps) => ( + + ), + LaneHeader: Header + }), + [Header, bodyshop, cardSettings, technician] + ); if (loading) { return ; 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 14c4ec836..55a3207ac 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 from "react"; +import React, { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -21,9 +21,8 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) { const [updateAlert] = useMutation(UPDATE_JOB); - const handleAlertToggle = (e) => { + const handleAlertToggle = useCallback(() => { logImEXEvent("production_toggle_alert"); - //e.stopPropagation(); updateAlert({ variables: { jobId: record.id, @@ -44,10 +43,10 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) { }).then(() => { if (record.refetch) record.refetch(); }); - }; + }, [updateAlert, insertAuditTrail, record]); - const menu = { - items: [ + const menuItems = useMemo( + () => [ { key: "toggleAlert", label: @@ -56,17 +55,13 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) { : t("production.labels.alerton"), onClick: handleAlertToggle } - ] - }; + ], + [record.production_vars, t, handleAlertToggle] + ); return ( - -
+ +
{record.production_vars && record.production_vars.alert ? ( ) : null} diff --git a/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx b/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx index 506b53636..b69712e80 100644 --- a/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.productionnote.component.jsx @@ -1,7 +1,7 @@ import Icon from "@ant-design/icons"; import { useMutation } from "@apollo/client"; import { Button, Input, Popover, Space } from "antd"; -import React, { useState } from "react"; +import React, { useCallback, useState, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { FaRegStickyNote } from "react-icons/fa"; import { logImEXEvent } from "../../firebase/firebase.utils"; @@ -19,77 +19,81 @@ const mapDispatchToProps = (dispatch) => ({ function ProductionListColumnProductionNote({ record, setNoteUpsertContext }) { const { t } = useTranslation(); const [note, setNote] = useState((record.production_vars && record.production_vars.note) || ""); - const [open, setOpen] = useState(false); const [updateAlert] = useMutation(UPDATE_JOB); - const handleSaveNote = (e) => { - logImEXEvent("production_add_note"); - e.stopPropagation(); - setOpen(false); - updateAlert({ - variables: { - jobId: record.id, - job: { - production_vars: { - ...record.production_vars, - note: note + const handleSaveNote = useCallback( + (e) => { + logImEXEvent("production_add_note"); + e.stopPropagation(); + setOpen(false); + updateAlert({ + variables: { + jobId: record.id, + job: { + production_vars: { + ...record.production_vars, + note: note + } } } - } - }).then(() => { - if (record.refetch) record.refetch(); - }); - }; + }).then(() => { + if (record.refetch) record.refetch(); + }); + }, + [updateAlert, record, note] + ); - const handleChange = (e) => { + const handleChange = useCallback((e) => { e.stopPropagation(); setNote(e.target.value); - }; + }, []); - const handleOpenChange = (flag) => { - setOpen(flag); - if (flag) setNote((record.production_vars && record.production_vars.note) || ""); - }; + const handleOpenChange = useCallback( + (flag) => { + setOpen(flag); + if (flag) setNote((record.production_vars && record.production_vars.note) || ""); + }, + [record] + ); + + const popoverContent = useMemo( + () => ( +
+ + + + + +
+ ), + [note, handleSaveNote, handleChange, record, setNoteUpsertContext, t] + ); return ( - - - - - - -
- } - trigger={["click"]} - > +
{ - return { + const subletCount = useMemo( + () => ({ total: subletJobLines.filter((s) => !s.sublet_ignored).length, outstanding: subletJobLines.filter((s) => !s.sublet_ignored && !s.sublet_completed).length - }; - }, [subletJobLines]); + }), + [subletJobLines] + ); - const handleSubletMark = async (sublet, action) => { - setLoading(true); + const handleSubletMark = useCallback( + async (sublet, action) => { + setLoading(true); - const result = await updateJobLine({ - variables: { - jobId: sublet.jobid, - now: new Date(), - lineId: sublet.id, - line: { - sublet_completed: action === "complete" ? !sublet.sublet_completed : false, - sublet_ignored: action === "ignore" ? !sublet.sublet_ignored : false + const result = await updateJobLine({ + variables: { + jobId: sublet.jobid, + now: new Date(), + lineId: sublet.id, + line: { + sublet_completed: action === "complete" ? !sublet.sublet_completed : false, + sublet_ignored: action === "ignore" ? !sublet.sublet_ignored : false + } } + }); + + if (result.errors) { + notification["error"]({ + message: t("joblines.errors.updating", { + message: JSON.stringify(result.errors) + }) + }); + } else { + notification["success"]({ + message: t("joblines.successes.updated") + }); } - }); + setLoading(false); + }, + [updateJobLine, t] + ); - if (!!result.errors) { - notification["error"]({ - message: t("joblines.errors.updating", { - message: JSON.stringify(result.errors) - }) - }); - } else { - notification["success"]({ - message: t("joblines.successes.updated") - }); - } - setLoading(false); - }; - - const popContent = ( -
- e.stopPropagation()} - dataSource={subletJobLines} - renderItem={(s) => ( - { - e.stopPropagation(); - handleSubletMark(s, "complete"); - }} - type={s.sublet_completed ? "primary" : "ghost"} - > - - , - - ]} - > - - - )} - /> -
+ const popContent = useMemo( + () => ( +
+ e.stopPropagation()} + dataSource={subletJobLines} + renderItem={(s) => ( + { + e.stopPropagation(); + handleSubletMark(s, "complete"); + }} + type={s.sublet_completed ? "primary" : "ghost"} + > + + , + + ]} + > + + + )} + /> +
+ ), + [subletJobLines, loading, handleSubletMark] ); return ( @@ -93,9 +100,9 @@ export default function ProductionSubletsManageComponent({ subletJobLines }) { placement="bottom" title={t("production.labels.sublets")} > - 0 ? "tomato" : "" }}>{`${ - subletCount.total - subletCount.outstanding - } / ${subletCount.total}`} + 0 ? "tomato" : undefined }} + >{`${subletCount.total - subletCount.outstanding} / ${subletCount.total}`} ); } diff --git a/client/src/components/trello-board/components/Lane/HeightPreservingItem.jsx b/client/src/components/trello-board/components/Lane/HeightPreservingItem.jsx index 503f1e4ac..58210c076 100644 --- a/client/src/components/trello-board/components/Lane/HeightPreservingItem.jsx +++ b/client/src/components/trello-board/components/Lane/HeightPreservingItem.jsx @@ -2,20 +2,12 @@ import React, { useEffect, useState } from "react"; const HeightPreservingItem = ({ children, ...props }) => { const [size, setSize] = useState(0); - const { "data-known-size": knownSize = 0 } = props; - + const knownSize = props["data-known-size"]; useEffect(() => { - if (knownSize !== 0) { - setSize(knownSize); - } - }, [knownSize]); - // - // useEffect(() => { - // if (knownSize !== 0 && knownSize !== size) { - // setSize(knownSize); - // } - // }, [knownSize, size]); - + setSize((prevSize) => { + return knownSize === 0 ? prevSize : knownSize; + }); + }, [setSize, knownSize]); return (
{ const [storeId] = useState(id || v1()); - const allClassNames = classNames("react-trello-board", className || ""); - const OrientationStyle = orientation === "horizontal" ? components.StyleHorizontal : components.StyleVertical; + const allClassNames = useMemo(() => classNames("react-trello-board", className || ""), [className]); + const OrientationStyle = useMemo( + () => (orientation === "horizontal" ? components.StyleHorizontal : components.StyleVertical), + [orientation, components.StyleHorizontal, components.StyleVertical] + ); return ( <> diff --git a/client/src/components/trello-board/controllers/BoardContainer.jsx b/client/src/components/trello-board/controllers/BoardContainer.jsx index 283234361..849932b82 100644 --- a/client/src/components/trello-board/controllers/BoardContainer.jsx +++ b/client/src/components/trello-board/controllers/BoardContainer.jsx @@ -1,7 +1,6 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState, useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; import { DragDropContext, Droppable } from "../dnd/lib"; - import PropTypes from "prop-types"; import pick from "lodash/pick"; import isEqual from "lodash/isEqual"; @@ -35,7 +34,6 @@ import * as actions from "../../../redux/trello/trello.actions.js"; * @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 {Function} props.onCardMoveAcrossLanes - Callback function when a card is moved across 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 @@ -64,7 +62,6 @@ const BoardContainer = ({ editable = false, canAddLanes = false, laneStyle, - onCardMoveAcrossLanes = () => {}, orientation = "horizontal", eventBusHandle, reducerData, @@ -129,43 +126,15 @@ const BoardContainer = ({ } }, [currentReducerData, reducerData, onDataChange]); - /** - * onDragUpdate - * @param draggableId - * @param type - * @param source - * @param mode - * @param combine - * @param destination - */ - const onDragUpdate = ({ draggableId, type, source, mode, combine, destination }) => {}; + const onDragUpdate = () => {}; - /** - * onDragStart - * @type {(function({draggableId: *, type: *, source: *, mode: *}): void)|*} - */ - const onDragStart = useCallback( - ({ draggableId, type, source, mode }) => { - setIsDragging(true); - }, - [setIsDragging] - ); + const onDragStart = useCallback(() => { + setIsDragging(true); + }, []); - /** - * onBeforeDragStart - * @param draggableId - * @param type - * @param source - * @param mode - */ - const onBeforeDragStart = ({ draggableId, type, source, mode }) => {}; + const onBeforeDragStart = () => {}; - /** - * onBeforeCapture - * @param draggableId - * @param mode - */ - const onBeforeCapture = ({ draggableId, mode }) => {}; + const onBeforeCapture = () => {}; const getCardDetails = useCallback( (laneId, cardIndex) => { @@ -174,22 +143,77 @@ const BoardContainer = ({ [currentReducerData] ); - const hideEditableLane = () => { + const hideEditableLane = useCallback(() => { setAddLaneMode(false); - }; + }, []); - const showEditableLane = () => { + const showEditableLane = useCallback(() => { setAddLaneMode(true); - }; + }, []); - const addNewLane = (params) => { - hideEditableLane(); - dispatch(actions.addLane(params)); - onLaneAdd(params); - }; + const addNewLane = useCallback( + (params) => { + hideEditableLane(); + dispatch(actions.addLane(params)); + onLaneAdd(params); + }, + [dispatch, hideEditableLane, onLaneAdd] + ); - const passThroughProps = pick( - { + const passThroughProps = useMemo( + () => + pick( + { + id, + components, + data, + draggable, + style, + onDataChange, + onCardAdd, + onCardUpdate, + onCardClick, + onBeforeCardDelete, + onCardDelete, + onLaneScroll, + onLaneClick, + onLaneAdd, + onLaneDelete, + onLaneUpdate, + 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, @@ -209,66 +233,44 @@ const BoardContainer = ({ editable, canAddLanes, laneStyle, - onCardMoveAcrossLanes, orientation, eventBusHandle, reducerData, cardStyle, - ...otherProps - }, - [ - // "onCardMoveAcrossLanes", - "onLaneScroll", - "onLaneDelete", - "onLaneUpdate", - "onCardClick", - "onBeforeCardDelete", - "onCardDelete", - "onCardAdd", - "onCardUpdate", - "onLaneClick", - "laneSortFunction", - "draggable", - "cardDraggable", - "collapsibleLanes", - "canAddLanes", - "hideCardDeleteIcon", - "tagStyle", - "handleDragStart", - "handleDragEnd", - "cardDragClass", - "editLaneTitle", - "orientation" + otherProps ] ); - const onLaneDrag = async ({ draggableId, type, source, reason, mode, destination, combine }) => { - setIsDragging(false); + const onLaneDrag = useCallback( + async ({ draggableId, type, source, reason, mode, destination, combine }) => { + setIsDragging(false); - if (!type || type !== "lane" || !source || !destination) return; + if (!type || type !== "lane" || !source || !destination) return; - setIsProcessing(true); + setIsProcessing(true); - dispatch( - actions.moveCardAcrossLanes({ - fromLaneId: source.droppableId, - toLaneId: destination.droppableId, - cardId: draggableId, - index: destination.index - }) - ); + dispatch( + actions.moveCardAcrossLanes({ + fromLaneId: source.droppableId, + toLaneId: destination.droppableId, + cardId: draggableId, + index: destination.index + }) + ); - onDragEnd({ draggableId, type, source, reason, mode, destination, combine }) - .catch((err) => { + try { + await onDragEnd({ draggableId, type, source, reason, mode, destination, combine }); + } catch (err) { console.error("Error in onLaneDrag", err); - }) - .finally(() => { + } finally { setIsProcessing(false); - }); - }; + } + }, + [dispatch, onDragEnd] + ); return ( - + { - const [loading, setLoading] = useState(false); - const [currentPageFinal, setCurrentPageFinal] = useState(currentPage); const [addCardMode, setAddCardMode] = useState(false); const [collapsed, setCollapsed] = useState(false); - - const laneRef = useRef(null); + const [isVisible, setIsVisible] = useState(true); useEffect(() => { - if (!isEqual(cards, currentPageFinal)) { - setCurrentPageFinal(currentPage); - } - }, [cards, currentPage, currentPageFinal]); - - const handleScroll = useCallback( - (evt) => { - const node = evt.target; - const elemScrollPosition = node.scrollHeight - node.scrollTop - node.clientHeight; - if (elemScrollPosition < 1 && onLaneScroll && !loading) { - const nextPage = currentPageFinal + 1; - setLoading(true); - onLaneScroll(nextPage, id).then((moreCards) => { - if ((moreCards || []).length > 0) { - actions.paginateLane({ - laneId: id, - newCards: moreCards, - nextPage: nextPage - }); - } - setLoading(false); - }); - } - }, - [currentPageFinal, loading, onLaneScroll, id, actions] - ); - - useEffect(() => { - const node = laneRef.current; - if (node) { - node.addEventListener("scroll", handleScroll); - } - return () => { - if (node) { - node.removeEventListener("scroll", handleScroll); - } - }; - }, [handleScroll]); + setIsVisible(false); + const timer = setTimeout(() => setIsVisible(true), 0); + return () => clearTimeout(timer); + }, [cards.length]); const sortCards = useCallback((cards, sortFunction) => { if (!cards) return []; @@ -177,7 +138,6 @@ const Lane = ({ const Card = React.memo(({ provided, item: card, isDragging }) => { const onDeleteCard = () => removeCard(card.id); - return (
{ if (!item) { console.log("null Item"); @@ -217,18 +171,18 @@ const Lane = ({ } return ( - + {(provided, snapshot) => } ); }; - const renderAddCardLink = useCallback( + const renderAddCardLink = useMemo( () => editable && !addCardMode && , [editable, addCardMode, showEditableCard, id] ); - const renderNewCardForm = useCallback( + const renderNewCardForm = useMemo( () => addCardMode && , [addCardMode, hideEditableCard, addNewCard, id] ); @@ -246,12 +200,6 @@ const Lane = ({
); - /** - * Render the droppable component with the provided cards and the provided props from react-beautiful-dnd - * @param provided - * @param renderedCards - * @returns {Element} - */ const renderDroppable = (provided, renderedCards) => { const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso; const FinalComponent = collapsed ? "div" : Component; @@ -286,11 +234,8 @@ const Lane = ({
{children} @@ -306,23 +251,22 @@ const Lane = ({ reverse: 22 }, components: { Item: HeightPreservingItem }, - itemContent: (index, item) => renderDraggable(index, item), - scrollerRef: provided.innerRef + itemContent: (index, item) => renderDraggable(index, item) }; const finalComponentProps = collapsed ? {} : componentProps; return ( -
+
- - {/*{provided.placeholder}*/} + {isVisible && } + {(orientation === "horizontal" || renderedCards.length === 0 || collapsed) && provided.placeholder}
- {renderAddCardLink()} - {renderNewCardForm()} + {renderAddCardLink} + {renderNewCardForm}
); }; @@ -358,7 +302,6 @@ const Lane = ({ }; const allClassNames = classNames("react-trello-lane", collapsed ? "lane-collapsed" : "", className || ""); - const showFooter = collapsibleLanes && cards.length > 0; const passedProps = { actions, @@ -392,7 +335,6 @@ const Lane = ({ currentPage, ...otherProps }; - return ( {renderHeader({ id, cards, ...passedProps })} {renderDragContainer()} - {loading && } - {showFooter && } + {collapsibleLanes && } ); }; diff --git a/client/src/components/trello-board/styles/Base.js b/client/src/components/trello-board/styles/Base.js index fa988cdbb..4cb18b3e3 100644 --- a/client/src/components/trello-board/styles/Base.js +++ b/client/src/components/trello-board/styles/Base.js @@ -21,7 +21,7 @@ const getSectionStyles = (props) => { display: inline-flex; flex-direction: column; white-space: nowrap; - overflow-y: none; + // overflow-y: none; `; } return ` @@ -75,7 +75,6 @@ export const StyleHorizontal = styled.div` .react-trello-lane { min-width: 250px; min-height: 25px; - margin-bottom: 10px; } .react-trello-lane.lane-collapsed { @@ -84,6 +83,9 @@ export const StyleHorizontal = styled.div` .ant-card-body { padding: 4px; } + .react-trello-card { + height: auto; + } `; export const StyleVertical = styled.div` @@ -167,6 +169,7 @@ export const CustomPopoverContent = styled(PopoverContent)` export const BoardWrapper = styled.div` color: #393939; + height: 100%; overflow-x: auto; overflow-y: hidden; ${getBoardWrapperStyles}; @@ -183,7 +186,7 @@ export const Section = styled.section` background-color: #e3e3e3; border-radius: 3px; margin: 2px 2px; - padding: 2px; + height: 100%; ${getSectionStyles}; `; @@ -319,7 +322,7 @@ export const LaneSection = styled.section` position: relative; padding: 5px; display: inline-flex; - height: auto; + height: 100%; flex-direction: column; `;