- Checkpoint

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-07-02 14:16:25 -04:00
parent 7f75eeadb9
commit 7524d06126
10 changed files with 151 additions and 144 deletions

View File

@@ -1,11 +0,0 @@
.imex-kanban-card {
padding: 0px !important;
.ant-card-body {
padding: 0.8rem;
}
.ant-card-head {
padding: 0rem 0.8rem;
}
}

View File

@@ -136,11 +136,14 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
)} )}
</Col> </Col>
)} )}
<Col span={24}> {cardSettings && cardSettings.model_info && (
<div className="ellipses">{`${metadata.v_model_yr || ""} ${ <Col span={24}>
metadata.v_make_desc || "" <div className="ellipses">{`${metadata.v_model_yr || ""} ${
} ${metadata.v_model_desc || ""}`}</div> metadata.v_make_desc || ""
</Col> } ${metadata.v_model_desc || ""}`}</div>
</Col>
)}
{cardSettings && cardSettings.ins_co_nm && metadata.ins_co_nm && ( {cardSettings && cardSettings.ins_co_nm && metadata.ins_co_nm && (
<Col span={cardSettings && cardSettings.compact ? 24 : 12}> <Col span={cardSettings && cardSettings.compact ? 24 : 12}>
<div className="ellipses">{metadata.ins_co_nm || ""}</div> <div className="ellipses">{metadata.ins_co_nm || ""}</div>

View File

@@ -19,8 +19,10 @@ import ProductionBoardCard from "../production-board-kanban-card/production-boar
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component"; 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 CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
import "./production-board-kanban.styles.scss"; 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 ProductionBoardKanbanSettings from "./production-board-kanban.settings.component.jsx";
import cloneDeep from "lodash/cloneDeep";
import isEqual from "lodash/isEqual";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -63,25 +65,27 @@ export function ProductionBoardKanbanComponent({
}, [associationSettings]); }, [associationSettings]);
useEffect(() => { useEffect(() => {
const boardData = createBoardData( const newBoardData = createFakeBoardData(
[...bodyshop.md_ro_statuses.production_statuses, ...(bodyshop.md_ro_statuses.additional_board_statuses || [])], [...bodyshop.md_ro_statuses.production_statuses, ...(bodyshop.md_ro_statuses.additional_board_statuses || [])],
data, data,
filter filter
); );
// Build Board Lanes Data // Build Board Lanes Data
boardData.lanes = boardData.lanes.map((lane) => { newBoardData.lanes = newBoardData.lanes.map((lane) => ({
return { ...lane,
...lane, title: `${lane.title} (${lane.cards.length})`
title: `${lane.title} (${lane.cards.length})` }));
};
setBoardLanes((prevBoardLanes) => {
const deepClonedData = cloneDeep(newBoardData);
if (!isEqual(prevBoardLanes, deepClonedData)) {
return deepClonedData;
}
return prevBoardLanes;
}); });
setBoardLanes(boardData);
setIsMoving(false); setIsMoving(false);
}, [data, setBoardLanes, setIsMoving, bodyshop.md_ro_statuses, filter]); }, [data, bodyshop.md_ro_statuses, filter]);
//
const client = useApolloClient(); const client = useApolloClient();
@@ -102,13 +106,11 @@ export function ProductionBoardKanbanComponent({
return null; return null;
}; };
// TODO, refine and use action const onDragEnd = async ({ type, source, destination, draggableId }) => {
const onDragEnd = async ({ type, source, destination, draggableId, ...args }) => {
// //cardId, sourceLaneId, targetLaneId, position, cardDetails
logImEXEvent("kanban_drag_end"); logImEXEvent("kanban_drag_end");
// Early Gate // Early gate, also if the card is already moving bail
if (!type || type !== "lane" || !source || !destination) return; if (!type || type !== "lane" || !source || !destination || isMoving) return;
setIsMoving(true); setIsMoving(true);
@@ -120,13 +122,11 @@ export function ProductionBoardKanbanComponent({
const movedCardWillBeFirst = destination.index === 0; const movedCardWillBeFirst = destination.index === 0;
const movedCardWillBeLast = destination.index > targetLane.cards.length - 1; const movedCardWillBeLast = destination.index > targetLane.cards.length - 1;
const movedCardIsFirstNewCard = movedCardWillBeFirst && movedCardWillBeLast;
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 const newChildCard = movedCardWillBeLast
? null ? null
: targetLane.cards[ : targetLane.cards[
@@ -138,7 +138,7 @@ export function ProductionBoardKanbanComponent({
]; ];
const oldChildCardNewParent = oldChildCard ? sourceCard.metadata.kanbanparent : null; const oldChildCardNewParent = oldChildCard ? sourceCard.metadata.kanbanparent : null;
//
let movedCardNewKanbanParent; let movedCardNewKanbanParent;
if (movedCardWillBeFirst) { if (movedCardWillBeFirst) {
movedCardNewKanbanParent = "-1"; movedCardNewKanbanParent = "-1";
@@ -186,6 +186,7 @@ export function ProductionBoardKanbanComponent({
setIsMoving(false); setIsMoving(false);
} }
}; };
const totalHrs = useMemo( const totalHrs = useMemo(
() => () =>
data data

View File

@@ -51,7 +51,7 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
const renderCardSettings = () => ( const renderCardSettings = () => (
<> <>
<Card title={t("settings.sections.layout")} style={cardStyle}> <Card title={t("production.settings.layout")} style={cardStyle}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={4}> <Col span={4}>
<Form.Item name="compact" valuePropName="checked"> <Form.Item name="compact" valuePropName="checked">
@@ -65,8 +65,13 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
</Col> </Col>
</Row> </Row>
</Card> </Card>
<Card title={t("settings.sections.information")} style={cardStyle}> <Card title={t("production.settings.information")} style={cardStyle}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={4}>
<Form.Item name="model_info" valuePropName="checked">
<Checkbox>{t("production.labels.model_info")}</Checkbox>
</Form.Item>
</Col>
<Col span={4}> <Col span={4}>
<Form.Item name="ownr_nm" valuePropName="checked"> <Form.Item name="ownr_nm" valuePropName="checked">
<Checkbox>{t("production.labels.ownr_nm")}</Checkbox> <Checkbox>{t("production.labels.ownr_nm")}</Checkbox>
@@ -119,21 +124,12 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
</Col> </Col>
</Row> </Row>
</Card> </Card>
<Card title={t("settings.sections.beta")} style={cardStyle}>
<Row gutter={[16, 16]}>
<Col span={4}>
<Form.Item name="stickyheader" valuePropName="checked">
<Checkbox>{t("production.labels.stickyheader")}</Checkbox>
</Form.Item>
</Col>
</Row>
</Card>
</> </>
); );
const renderBoardSettings = () => ( const renderBoardSettings = () => (
<> <>
<Card title={t("settings.sections.layout")} style={cardStyle}> <Card title={t("production.settings.layout")} style={cardStyle}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={4} style={{ display: "flex", alignItems: "center" }}> <Col span={4} style={{ display: "flex", alignItems: "center" }}>
<span style={{ marginRight: "8px" }}>Orientation</span> <span style={{ marginRight: "8px" }}>Orientation</span>
@@ -167,7 +163,7 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
const renderLaneSettings = () => ( const renderLaneSettings = () => (
<> <>
<Card title={t("settings.sections.layout")} style={cardStyle}> <Card title={t("production.settings.layout")} style={cardStyle}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{/*<Col span={4}>*/} {/*<Col span={4}>*/}
{/* <Form.Item name="lane_setting_1" valuePropName="checked">*/} {/* <Form.Item name="lane_setting_1" valuePropName="checked">*/}
@@ -207,13 +203,13 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
<Card> <Card>
<Form form={form} onFinish={handleFinish} layout="vertical" onValuesChange={handleValuesChange}> <Form form={form} onFinish={handleFinish} layout="vertical" onValuesChange={handleValuesChange}>
<Tabs defaultActiveKey="1"> <Tabs defaultActiveKey="1">
<TabPane tab={t("settings.tabs.card")} key="1"> <TabPane tab={t("production.settings.tabs.card")} key="1">
{renderCardSettings()} {renderCardSettings()}
</TabPane> </TabPane>
<TabPane tab={t("settings.tabs.board")} key="2"> <TabPane tab={t("production.settings.tabs.board")} key="2">
{renderBoardSettings()} {renderBoardSettings()}
</TabPane> </TabPane>
<TabPane tab={t("settings.tabs.lane")} key="3"> <TabPane tab={t("production.settings.tabs.lane")} key="3">
{renderLaneSettings()} {renderLaneSettings()}
</TabPane> </TabPane>
</Tabs> </Tabs>
@@ -236,7 +232,7 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
return ( return (
<Popover content={overlay} open={open} placement="topRight"> <Popover content={overlay} open={open} placement="topRight">
<Button loading={loading} onClick={() => setOpen(!open)}> <Button loading={loading} onClick={() => setOpen(!open)}>
{t("settings.buttons.boardSettings")} {t("production.settings.board_settings")}
</Button> </Button>
</Popover> </Popover>
); );

View File

@@ -1,5 +1,5 @@
import { groupBy } from "lodash"; import { groupBy } from "lodash";
import fakeData from "./testData/board1200.json"; import fakeData from "./testData/board600.json";
const sortByParentId = (arr) => { const sortByParentId = (arr) => {
// return arr.reduce((accumulator, currentValue) => { // return arr.reduce((accumulator, currentValue) => {

View File

@@ -8,7 +8,6 @@ import isEqual from "lodash/isEqual";
import Lane from "./Lane"; import Lane from "./Lane";
import { PopoverWrapper } from "react-popopo"; import { PopoverWrapper } from "react-popopo";
import * as actions from "../../../redux/trello/trello.actions.js"; import * as actions from "../../../redux/trello/trello.actions.js";
import { isFunction } from "lodash";
/** /**
* BoardContainer is a React component that represents a Trello-like board. * BoardContainer is a React component that represents a Trello-like board.
@@ -73,6 +72,8 @@ const BoardContainer = ({
...otherProps ...otherProps
}) => { }) => {
const [addLaneMode, setAddLaneMode] = useState(false); const [addLaneMode, setAddLaneMode] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const dispatch = useDispatch(); const dispatch = useDispatch();
const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {})); const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
@@ -143,7 +144,12 @@ const BoardContainer = ({
* onDragStart * onDragStart
* @type {(function({draggableId: *, type: *, source: *, mode: *}): void)|*} * @type {(function({draggableId: *, type: *, source: *, mode: *}): void)|*}
*/ */
const onDragStart = useCallback(({ draggableId, type, source, mode }) => {}, []); const onDragStart = useCallback(
({ draggableId, type, source, mode }) => {
setIsDragging(true);
},
[setIsDragging]
);
/** /**
* onBeforeDragStart * onBeforeDragStart
@@ -236,8 +242,13 @@ const BoardContainer = ({
] ]
); );
const onLaneDrag = ({ draggableId, type, source, reason, mode, destination, combine }) => { const onLaneDrag = 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);
dispatch( dispatch(
actions.moveCardAcrossLanes({ actions.moveCardAcrossLanes({
fromLaneId: source.droppableId, fromLaneId: source.droppableId,
@@ -246,29 +257,26 @@ const BoardContainer = ({
index: destination.index index: destination.index
}) })
); );
// onCardMoveAcrossLanes(payload.laneId, laneId, payload.id, addedIndex);
};
const combinedDragEnd = (...params) => { onDragEnd({ draggableId, type, source, reason, mode, destination, combine })
// Early Gate .catch((err) => {
if (!params || !params[0]) return; console.error("Error in onLaneDrag", err);
onLaneDrag(params[0]); })
if (isFunction(onDragEnd)) { .finally(() => {
onDragEnd(params[0]); setIsProcessing(false);
} });
}; };
return ( return (
<components.BoardWrapper style={style} orientation={orientation} draggable={false}> <components.BoardWrapper style={style} orientation={orientation} draggable={false}>
<PopoverWrapper> <PopoverWrapper>
<DragDropContext <DragDropContext
onDragEnd={combinedDragEnd} onDragEnd={onLaneDrag}
onDragUpdate={onDragUpdate} onDragUpdate={onDragUpdate}
onDragStart={onDragStart} onDragStart={onDragStart}
onBeforeDragStart={onBeforeDragStart} onBeforeDragStart={onBeforeDragStart}
onBeforeCapture={onBeforeCapture} onBeforeCapture={onBeforeCapture}
contextId={groupName} contextId={groupName}
// getChildPayload={(index) => getLaneDetails(index)}
> >
{currentReducerData.lanes.map((lane, index) => { {currentReducerData.lanes.map((lane, index) => {
const { id, droppable, ...laneOtherProps } = lane; const { id, droppable, ...laneOtherProps } = lane;
@@ -288,6 +296,8 @@ const BoardContainer = ({
{...laneOtherProps} {...laneOtherProps}
{...passThroughProps} {...passThroughProps}
cards={lane.cards} cards={lane.cards}
isDragging={isDragging}
isProcessing={isProcessing}
/> />
); );
})} })}

View File

@@ -11,12 +11,14 @@ import { Draggable, Droppable } from "../dnd/lib";
import { Virtuoso, VirtuosoGrid } from "react-virtuoso"; import { Virtuoso, VirtuosoGrid } from "react-virtuoso";
import HeightPreservingItem from "../components/Lane/HeightPreservingItem.jsx"; import HeightPreservingItem from "../components/Lane/HeightPreservingItem.jsx";
function Lane({ const Lane = ({
actions, actions,
id, id,
boardId, boardId,
title, title,
index, index,
isDragging,
isProcessing,
laneSortFunction, laneSortFunction,
style = {}, style = {},
cardStyle = {}, cardStyle = {},
@@ -51,7 +53,7 @@ function Lane({
className, className,
currentPage, currentPage,
...otherProps ...otherProps
}) { }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [currentPageFinal, setCurrentPageFinal] = useState(currentPage); const [currentPageFinal, setCurrentPageFinal] = useState(currentPage);
const [addCardMode, setAddCardMode] = useState(false); const [addCardMode, setAddCardMode] = useState(false);
@@ -99,77 +101,80 @@ function Lane({
}; };
}, [handleScroll]); }, [handleScroll]);
const sortCards = (cards, sortFunction) => { const sortCards = useCallback((cards, sortFunction) => {
if (!cards) return []; if (!cards) return [];
if (!sortFunction) return cards; if (!sortFunction) return cards;
return cards.concat().sort((card1, card2) => sortFunction(card1, card2)); return cards.concat().sort((card1, card2) => sortFunction(card1, card2));
}; }, []);
const removeCard = (cardId) => { const removeCard = useCallback(
if (onBeforeCardDelete && typeof onBeforeCardDelete === "function") { (cardId) => {
onBeforeCardDelete(() => { if (onBeforeCardDelete && typeof onBeforeCardDelete === "function") {
onBeforeCardDelete(() => {
onCardDelete && onCardDelete(cardId, id);
actions.removeCard({ laneId: id, cardId: cardId });
});
} else {
onCardDelete && onCardDelete(cardId, id); onCardDelete && onCardDelete(cardId, id);
actions.removeCard({ laneId: id, cardId: cardId }); actions.removeCard({ laneId: id, cardId: cardId });
}); }
} else { },
onCardDelete && onCardDelete(cardId, id); [onBeforeCardDelete, onCardDelete, actions, id]
actions.removeCard({ laneId: id, cardId: cardId }); );
}
};
const handleCardClick = (e, card) => { const handleCardClick = useCallback(
onCardClick && onCardClick(card.id, card.metadata, card.laneId); (e, card) => {
e.stopPropagation(); onCardClick && onCardClick(card.id, card.metadata, card.laneId);
}; e.stopPropagation();
},
[onCardClick]
);
const showEditableCard = () => { const showEditableCard = useCallback(() => {
setAddCardMode(true); setAddCardMode(true);
}; }, []);
const hideEditableCard = () => { const hideEditableCard = useCallback(() => {
setAddCardMode(false); setAddCardMode(false);
}; }, []);
const addNewCard = (params) => { const addNewCard = useCallback(
const laneId = id; (params) => {
const newCardId = v1(); const laneId = id;
hideEditableCard(); const newCardId = v1();
let card = { id: newCardId, ...params }; hideEditableCard();
actions.addCard({ laneId, card }); let card = { id: newCardId, ...params };
onCardAdd(card, laneId); actions.addCard({ laneId, card });
}; onCardAdd(card, laneId);
},
[actions, id, onCardAdd, hideEditableCard]
);
// const onDragStart = ({ payload }) => { const updateCard = useCallback(
// handleDragStart && handleDragStart(payload.id, payload.laneId); (updatedCard) => {
// }; actions.updateCard({ laneId: id, card: updatedCard });
onCardUpdate(id, updatedCard);
},
[actions, id, onCardUpdate]
);
// const shouldAcceptDrop = (sourceContainerOptions) => { const removeLane = useCallback(() => {
// return droppable && sourceContainerOptions.groupName === groupName;
// };
const updateCard = (updatedCard) => {
actions.updateCard({ laneId: id, card: updatedCard });
onCardUpdate(id, updatedCard);
};
const removeLane = () => {
actions.removeLane({ laneId: id }); actions.removeLane({ laneId: id });
onLaneDelete(id); onLaneDelete(id);
}; }, [actions, id, onLaneDelete]);
const updateTitle = (value) => { const updateTitle = useCallback(
actions.updateLane({ id, title: value }); (value) => {
onLaneUpdate(id, { title: value }); actions.updateLane({ id, title: value });
}; onLaneUpdate(id, { title: value });
},
[actions, id, onLaneUpdate]
);
const toggleLaneCollapsed = () => { const toggleLaneCollapsed = useCallback(() => {
collapsibleLanes && setCollapsed(!collapsed); collapsibleLanes && setCollapsed(!collapsed);
}; }, [collapsibleLanes, collapsed]);
/**
* Card component
* @type {React.NamedExoticComponent<{readonly item?: *, readonly provided?: *, readonly isDragging?: *}>}
*/
const Card = React.memo(({ provided, item: card, isDragging }) => { const Card = React.memo(({ provided, item: card, isDragging }) => {
const onDeleteCard = () => removeCard(card.id); const onDeleteCard = () => removeCard(card.id);
return ( return (
@@ -210,27 +215,16 @@ function Lane({
</Draggable> </Draggable>
); );
/** const renderAddCardLink = useCallback(
* Render the add card link () => editable && !addCardMode && <components.AddCardLink onClick={showEditableCard} laneId={id} />,
* @returns {false|React.JSX.Element} [editable, addCardMode, showEditableCard, id]
*/ );
const renderAddCardLink = () =>
editable && !addCardMode && <components.AddCardLink onClick={showEditableCard} laneId={id} />;
/** const renderNewCardForm = useCallback(
* Render the new card form () => addCardMode && <components.NewCardForm onCancel={hideEditableCard} laneId={id} onAdd={addNewCard} />,
* @returns {false|React.JSX.Element} [addCardMode, hideEditableCard, addNewCard, id]
*/ );
const renderNewCardForm = () =>
addCardMode && <components.NewCardForm onCancel={hideEditableCard} laneId={id} onAdd={addNewCard} />;
/**
* Wrapper for the item in the grid layout
* @param children
* @param props
* @returns {React.JSX.Element}
* @constructor
*/
const ItemWrapper = ({ children, ...props }) => ( const ItemWrapper = ({ children, ...props }) => (
<div <div
{...props} {...props}
@@ -401,12 +395,11 @@ function Lane({
> >
{renderHeader({ id, cards, ...passedProps })} {renderHeader({ id, cards, ...passedProps })}
{renderDragContainer()} {renderDragContainer()}
{/*{renderDragContainer(isDraggingOver)}*/}
{loading && <components.Loader />} {loading && <components.Loader />}
{showFooter && <components.LaneFooter onClick={toggleLaneCollapsed} collapsed={collapsed} />} {showFooter && <components.LaneFooter onClick={toggleLaneCollapsed} collapsed={collapsed} />}
</components.Section> </components.Section>
); );
} };
Lane.propTypes = { Lane.propTypes = {
actions: PropTypes.object, actions: PropTypes.object,

View File

@@ -100,8 +100,6 @@ const LaneHelper = {
// Clone the state to avoid mutation // Clone the state to avoid mutation
const newLanes = cloneDeep(state.lanes); const newLanes = cloneDeep(state.lanes);
console.dir({ fromLaneId, toLaneId, cardId, index });
// Find the source and destination lanes using the lane IDs // Find the source and destination lanes using the lane IDs
const fromLane = newLanes.find((lane) => lane.id === fromLaneId); const fromLane = newLanes.find((lane) => lane.id === fromLaneId);
const toLane = newLanes.find((lane) => lane.id === toLaneId); const toLane = newLanes.find((lane) => lane.id === toLaneId);

View File

@@ -81,6 +81,9 @@ export const StyleHorizontal = styled.div`
.react-trello-lane.lane-collapsed { .react-trello-lane.lane-collapsed {
min-height: 15px; min-height: 15px;
} }
.ant-card-body {
padding: 4px;
}
`; `;
export const StyleVertical = styled.div` export const StyleVertical = styled.div`
@@ -98,6 +101,7 @@ export const StyleVertical = styled.div`
.react-trello-card { .react-trello-card {
flex: 0 1 auto; flex: 0 1 auto;
min-width: 120px;
} }
.react-trello-board { .react-trello-board {
@@ -109,6 +113,9 @@ export const StyleVertical = styled.div`
.grid-item { .grid-item {
} }
.ant-card-body {
}
`; `;
export const CustomPopoverContainer = styled(PopoverContainer)` export const CustomPopoverContainer = styled(PopoverContainer)`

View File

@@ -2719,6 +2719,16 @@
} }
}, },
"production": { "production": {
"settings": {
"layout": "Layout",
"information": "Information",
"board_settings": "Board Settings",
"tabs": {
"card": "Card",
"board": "Board",
"lane": "Lane"
}
},
"actions": { "actions": {
"addcolumns": "Add Columns", "addcolumns": "Add Columns",
"bodypriority-clear": "Clear Body Priority", "bodypriority-clear": "Clear Body Priority",