- Lane Checkpoint: In Horizontal mode, all lanes will be the height of the largest lane, this way if you are dragging cards from the bottom of one lane, you do not need to drag to the top of another.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -17,7 +17,7 @@ import ProductionBoardFilters from "../production-board-filters/production-board
|
||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
import { createBoardData, createFakeBoardData } from "./production-board-kanban.utils.js";
|
||||
import ProductionBoardKanbanSettings from "./production-board-kanban.settings.component.jsx";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
||||
import _ from "lodash";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useCallback, useMemo } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
@@ -19,22 +19,44 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
export function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
||||
const client = useApolloClient();
|
||||
const [joblist, setJoblist] = useState([]);
|
||||
|
||||
const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
|
||||
pollInterval: 3600000,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
const client = useApolloClient();
|
||||
const [joblist, setJoblist] = useState([]);
|
||||
|
||||
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION);
|
||||
|
||||
const { loading: associationSettingsLoading, data: associationSettings } = useQuery(QUERY_KANBAN_SETTINGS, {
|
||||
variables: { email: currentUser.email }
|
||||
});
|
||||
|
||||
const getUpdatedJobData = useCallback(
|
||||
async (jobId) => {
|
||||
await client.query({
|
||||
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
variables: { id: jobId }
|
||||
});
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
const getUpdatedJobsData = useCallback(
|
||||
async (jobIds) => {
|
||||
await client.query({
|
||||
query: QUERY_EXACT_JOBS_IN_PRODUCTION,
|
||||
variables: { ids: jobIds }
|
||||
});
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!(data && data.jobs)) return;
|
||||
setJoblist(
|
||||
data.jobs.map((j) => {
|
||||
return { id: j.id, updated_at: j.updated_at };
|
||||
})
|
||||
);
|
||||
if (!data?.jobs) return;
|
||||
setJoblist(data.jobs.map((j) => ({ id: j.id, updated_at: j.updated_at })));
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -46,46 +68,25 @@ export function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
||||
(a, b) => a.id === b.id && a.updated_at === b.updated_at
|
||||
);
|
||||
|
||||
jobDiff.forEach((job) => {
|
||||
getUpdatedJobData(job.id);
|
||||
});
|
||||
if (jobDiff.length > 1) {
|
||||
getUpdatedJobsData(jobDiff.map((j) => j.id));
|
||||
} else if (jobDiff.length === 1) {
|
||||
jobDiff.forEach((job) => {
|
||||
getUpdatedJobData(job.id);
|
||||
});
|
||||
getUpdatedJobData(jobDiff[0].id);
|
||||
}
|
||||
|
||||
setJoblist(updatedJobs.jobs);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [updatedJobs]);
|
||||
}, [updatedJobs, joblist, getUpdatedJobData, getUpdatedJobsData]);
|
||||
|
||||
const getUpdatedJobData = async (jobId) => {
|
||||
client.query({
|
||||
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
variables: { id: jobId }
|
||||
});
|
||||
};
|
||||
const getUpdatedJobsData = async (jobIds) => {
|
||||
client.query({
|
||||
query: QUERY_EXACT_JOBS_IN_PRODUCTION,
|
||||
variables: { ids: jobIds }
|
||||
});
|
||||
};
|
||||
|
||||
const { loading: associationSettingsLoading, data: associationSettings } = useQuery(QUERY_KANBAN_SETTINGS, {
|
||||
variables: { email: currentUser.email }
|
||||
});
|
||||
const filteredAssociationSettings = useMemo(() => {
|
||||
return associationSettings?.associations[0] || null;
|
||||
}, [associationSettings]);
|
||||
|
||||
return (
|
||||
<ProductionBoardKanbanComponent
|
||||
loading={loading || associationSettingsLoading}
|
||||
data={data ? data.jobs : []}
|
||||
refetch={refetch}
|
||||
associationSettings={
|
||||
associationSettings && associationSettings.associations[0] ? associationSettings.associations[0] : null
|
||||
}
|
||||
associationSettings={filteredAssociationSettings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
.react-trello-board {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.item .is-dragging {
|
||||
box-shadow: 2px 2px grey;
|
||||
rotate: 5deg;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button } from "antd";
|
||||
import React, { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
@@ -13,11 +12,17 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
|
||||
const { t } = useTranslation();
|
||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||
|
||||
const handleAlertToggle = useCallback(() => {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* Height Memory Wrapper
|
||||
* @param children
|
||||
* @param maxHeight
|
||||
* @param setMaxHeight
|
||||
* @returns {Element}
|
||||
* @constructor
|
||||
*/
|
||||
const HeightMemoryWrapper = ({ children, maxHeight, setMaxHeight }) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const currentRef = ref.current; // Step 1: Capture the current ref value
|
||||
const updateHeight = () => {
|
||||
const currentHeight = currentRef?.firstChild?.clientHeight || 0;
|
||||
setMaxHeight((prevHeight) => Math.max(prevHeight, currentHeight));
|
||||
};
|
||||
|
||||
const resizeObserver = new ResizeObserver(updateHeight);
|
||||
if (currentRef?.firstChild) {
|
||||
// Step 2: Use the captured ref for observing
|
||||
resizeObserver.observe(currentRef.firstChild);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (currentRef?.firstChild) {
|
||||
// Step 2: Use the captured ref for observing
|
||||
resizeObserver.unobserve(currentRef.firstChild);
|
||||
}
|
||||
};
|
||||
}, [setMaxHeight]);
|
||||
|
||||
return (
|
||||
<div ref={ref} style={{ minHeight: maxHeight }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
HeightMemoryWrapper.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
maxHeight: PropTypes.number.isRequired,
|
||||
setMaxHeight: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default HeightMemoryWrapper;
|
||||
@@ -15,11 +15,13 @@ import { BoardWrapper } from "../styles/Base.js";
|
||||
* @component
|
||||
* @param {Object} props - Component props
|
||||
* @param {Object} props.data - The initial data for the board
|
||||
* @param {Function} props.onDataChange - Callback function when the data changes
|
||||
* @param {Function} props.onDragEnd - Callback function when a drag ends
|
||||
* @param {Function} props.laneSortFunction - Callback function when a drag ends
|
||||
* @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
|
||||
*
|
||||
* @returns {JSX.Element} A Trello-like board
|
||||
*/
|
||||
const BoardContainer = ({
|
||||
@@ -34,6 +36,7 @@ const BoardContainer = ({
|
||||
}) => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [maxLaneHeight, setMaxLaneHeight] = useState(0);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
|
||||
@@ -146,6 +149,8 @@ const BoardContainer = ({
|
||||
isDragging={isDragging}
|
||||
isProcessing={isProcessing}
|
||||
cardSettings={cardSettings}
|
||||
maxLaneHeight={maxLaneHeight}
|
||||
setMaxLaneHeight={setMaxLaneHeight}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -157,7 +162,6 @@ const BoardContainer = ({
|
||||
|
||||
BoardContainer.propTypes = {
|
||||
id: PropTypes.string,
|
||||
actions: PropTypes.object,
|
||||
data: PropTypes.object.isRequired,
|
||||
reducerData: PropTypes.object,
|
||||
onDataChange: PropTypes.func,
|
||||
|
||||
@@ -13,7 +13,25 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../../redux/user/user.selectors.js";
|
||||
import { selectTechnician } from "../../../redux/tech/tech.selectors.js";
|
||||
import ProductionBoardCard from "../../production-board-kanban-card/production-board-kanban-card.component.jsx";
|
||||
import HeightMemoryWrapper from "../components/Lane/HeightMemoryWrapper.jsx";
|
||||
|
||||
/**
|
||||
* Lane is a React component that represents a lane in a Trello-like board.
|
||||
* @param id
|
||||
* @param title
|
||||
* @param index
|
||||
* @param isProcessing
|
||||
* @param laneSortFunction
|
||||
* @param cards
|
||||
* @param cardSettings
|
||||
* @param orientation
|
||||
* @param maxLaneHeight
|
||||
* @param setMaxLaneHeight
|
||||
* @param technician -- connected to redux
|
||||
* @param bodyshop -- connected to redux
|
||||
* @returns {Element}
|
||||
* @constructor
|
||||
*/
|
||||
const Lane = ({
|
||||
id,
|
||||
title,
|
||||
@@ -23,6 +41,8 @@ const Lane = ({
|
||||
cards,
|
||||
cardSettings = {},
|
||||
orientation = "vertical",
|
||||
maxLaneHeight,
|
||||
setMaxLaneHeight,
|
||||
technician,
|
||||
bodyshop
|
||||
}) => {
|
||||
@@ -92,7 +112,6 @@ const Lane = ({
|
||||
(provided, renderedCards) => {
|
||||
const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso;
|
||||
const FinalComponent = collapsed ? "div" : Component;
|
||||
|
||||
const commonProps = {
|
||||
useWindowScroll: true,
|
||||
data: renderedCards
|
||||
@@ -119,14 +138,25 @@ const Lane = ({
|
||||
components: { Item: HeightPreservingItem },
|
||||
overscan: { main: 3, reverse: 3 },
|
||||
itemContent: (index, item) => renderDraggable(index, item),
|
||||
scrollerRef: provided.innerRef
|
||||
scrollerRef: provided.innerRef,
|
||||
style: {
|
||||
minHeight: maxLaneHeight
|
||||
}
|
||||
};
|
||||
|
||||
const componentProps = orientation === "vertical" ? verticalProps : horizontalProps;
|
||||
const finalComponentProps = collapsed ? {} : componentProps;
|
||||
const finalComponentProps = collapsed
|
||||
? orientation === "horizontal"
|
||||
? {
|
||||
style: {
|
||||
height: orientation === "vertical" ? 10 : maxLaneHeight
|
||||
}
|
||||
}
|
||||
: {}
|
||||
: componentProps;
|
||||
|
||||
return (
|
||||
<div style={{ height: "100%" }}>
|
||||
return orientation === "horizontal" ? (
|
||||
<HeightMemoryWrapper maxHeight={maxLaneHeight} setMaxHeight={setMaxLaneHeight}>
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
@@ -134,12 +164,21 @@ const Lane = ({
|
||||
style={{ ...provided.droppableProps.style }}
|
||||
>
|
||||
{isVisible && <FinalComponent {...finalComponentProps} />}
|
||||
{(orientation === "horizontal" || renderedCards.length === 0 || collapsed) && provided.placeholder}
|
||||
</div>
|
||||
</HeightMemoryWrapper>
|
||||
) : (
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
className={`react-trello-lane ${collapsed ? "lane-collapsed" : ""}`}
|
||||
style={{ ...provided.droppableProps.style }}
|
||||
>
|
||||
{isVisible && <FinalComponent {...finalComponentProps} />}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[orientation, collapsed, isVisible, renderDraggable]
|
||||
[orientation, collapsed, isVisible, renderDraggable, maxLaneHeight, setMaxLaneHeight]
|
||||
);
|
||||
|
||||
const renderDragContainer = useCallback(
|
||||
@@ -194,15 +233,15 @@ const Lane = ({
|
||||
|
||||
Lane.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.node,
|
||||
index: PropTypes.number,
|
||||
title: PropTypes.node.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
laneSortFunction: PropTypes.func,
|
||||
cards: PropTypes.array,
|
||||
orientation: PropTypes.string,
|
||||
isProcessing: PropTypes.bool,
|
||||
cardSettings: PropTypes.object,
|
||||
technician: PropTypes.object,
|
||||
bodyshop: PropTypes.object
|
||||
cards: PropTypes.array.isRequired,
|
||||
orientation: PropTypes.string.isRequired,
|
||||
isProcessing: PropTypes.bool.isRequired,
|
||||
cardSettings: PropTypes.object.isRequired,
|
||||
maxLaneHeight: PropTypes.number.isRequired,
|
||||
setMaxLaneHeight: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { PopoverContainer, PopoverContent } from "react-popopo";
|
||||
import styled, { css } from "styled-components";
|
||||
import styled from "styled-components";
|
||||
|
||||
const getBoardWrapperStyles = (props) => {
|
||||
if (props.orientation === "vertical") {
|
||||
@@ -64,6 +63,7 @@ export const StyleHorizontal = styled.div`
|
||||
white-space: nowrap;
|
||||
min-width: 4%;
|
||||
}
|
||||
|
||||
.react-trello-column-header {
|
||||
border-radius: 5px;
|
||||
min-height: 15px;
|
||||
@@ -72,6 +72,7 @@ export const StyleHorizontal = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.react-trello-card {
|
||||
height: auto;
|
||||
margin: 2px;
|
||||
@@ -88,6 +89,7 @@ export const StyleVertical = styled.div`
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.react-trello-lane {
|
||||
min-height: 5px;
|
||||
height: 100%;
|
||||
|
||||
Reference in New Issue
Block a user