- Checkpoint, so so much

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-07-04 18:00:02 -04:00
parent 524f623fd4
commit 2a51c8a2bf
14 changed files with 250 additions and 903 deletions

View File

@@ -15,7 +15,6 @@ import ProductionAlert from "../production-list-columns/production-list-columns.
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";
@@ -62,7 +61,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
return metadata?.labhrs && metadata?.larhrs
? metadata.labhrs.aggregate.sum.mod_lb_hrs + metadata.larhrs.aggregate.sum.mod_lb_hrs
: 0;
}, [metadata]);
}, [metadata?.labhrs, metadata?.larhrs]);
const bgColor = useMemo(() => cardColor(bodyshop.ssbuckets, totalHrs), [bodyshop.ssbuckets, totalHrs]);
const contrastYIQ = useMemo(() => getContrastYIQ(bgColor), [bgColor]);
@@ -77,7 +76,14 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
}}
title={
<Space>
<ProductionAlert record={card} key="alert" />
<ProductionAlert
record={{
id: card.id,
production_vars: card?.metadata.production_vars,
refetch: card?.refetch
}}
key="alert"
/>
{metadata?.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
{metadata?.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>

View File

@@ -1,32 +1,29 @@
import { SyncOutlined, UnorderedListOutlined } from "@ant-design/icons";
import { SyncOutlined } from "@ant-design/icons";
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, useCallback } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.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 "./production-board-kanban.styles.scss";
import { createBoardData, createFakeBoardData } from "./production-board-kanban.utils.js";
import { createBoardData } 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";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
@@ -40,20 +37,16 @@ const mapDispatchToProps = (dispatch) => ({
)
});
export function ProductionBoardKanbanComponent({
data,
bodyshop,
refetch,
technician,
insertAuditTrail,
associationSettings
}) {
export function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, 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 orientation = useMemo(
() => (associationSettings?.kanban_settings?.orientation ? "vertical" : "horizontal"),
[associationSettings]
);
const { t } = useTranslation();
useEffect(() => {
@@ -63,7 +56,7 @@ 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
@@ -202,15 +195,6 @@ export function ProductionBoardKanbanComponent({
[data]
);
const Header = useCallback(
({ title }) => (
<div className="react-trello-column-header" style={{ backgroundColor: "#e3e3e3" }}>
<UnorderedListOutlined style={{ marginRight: "5px" }} /> {title.substring(0, 10)}
</div>
),
[]
);
const cardSettings = useMemo(
() =>
associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0
@@ -225,23 +209,12 @@ export function ProductionBoardKanbanComponent({
production_note: true,
employeeassignments: true,
scheduled_completion: true,
stickyheader: false,
cardcolor: false,
orientation: false
},
[associationSettings]
);
const components = useMemo(
() => ({
Card: (cardProps) => (
<ProductionBoardCard card={cardProps} technician={technician} bodyshop={bodyshop} cardSettings={cardSettings} />
),
LaneHeader: Header
}),
[Header, bodyshop, cardSettings, technician]
);
if (loading) {
return <Skeleton active />;
}
@@ -270,13 +243,7 @@ export function ProductionBoardKanbanComponent({
/>
{cardSettings.cardcolor && <CardColorLegend cardSettings={cardSettings} bodyshop={bodyshop} />}
<ProductionListDetailComponent jobs={data} />
<Board
data={boardLanes}
onDragEnd={onDragEnd}
components={components}
collapsibleLanes
orientation={orientation}
/>
<Board data={boardLanes} onDragEnd={onDragEnd} orientation={orientation} cardSettings={cardSettings} />
</div>
);
}

View File

@@ -2,147 +2,27 @@
padding: 5px;
}
//.react-trello-card {
// border-radius: 3px;
// background-color: #fff;
// padding: 4px;
// margin-bottom: 7px;
//}
// .react-trello-card-skeleton,
// .react-trello-card,
// .react-trello-card-adder-form {
// box-sizing: border-box;
// max-width: 145px;
// min-width: 145px;
// }
.react-trello-card--dragging {
.item .is-dragging {
box-shadow: 2px 2px grey;
rotate: 5deg;
}
.react-trello-card__description {
padding-top: 10px;
}
.react-trello-card__title {
border-bottom: 1px solid #eee;
padding-bottom: 5px;
font-weight: bold;
display: flex;
justify-content: space-between;
}
//.react-trello-column {
// padding: 10px;
// border-radius: 2px;
// background-color: #eee;
// margin: 5px;
//}
.react-trello-column input:focus {
outline: none;
}
.react-trello-card-adder-form {
border-radius: 3px;
background-color: #fff;
padding: 10px;
margin-bottom: 7px;
}
.react-trello-card-adder-form input {
border: 0px;
font-family: inherit;
font-size: inherit;
}
.react-trello-card-adder-button {
width: 100%;
margin-top: 5px;
background-color: transparent;
cursor: pointer;
border: 1px solid #ccc;
transition: 0.3s;
border-radius: 3px;
font-size: 20px;
margin-bottom: 10px;
font-weight: bold;
}
.react-trello-card-adder-button:hover {
background-color: #ccc;
}
.react-trello-card-adder-form__title {
font-weight: bold;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
font-weight: bold;
display: flex;
justify-content: space-between;
width: 100%;
padding: 0px;
}
.height-preserving-container:empty {
min-height: calc(var(--child-height));
box-sizing: border-box;
}
.react-trello-card-adder-form__title:focus {
outline: none;
}
.react-trello-card-adder-form__description {
width: 100%;
margin-top: 10px;
}
.react-trello-card-adder-form__description:focus {
outline: none;
}
.react-trello-card-adder-form__button {
background-color: #eee;
border: none;
padding: 5px;
width: 45%;
margin-top: 5px;
border-radius: 3px;
}
.react-trello-card-adder-form__button:hover {
transition: 0.3s;
cursor: pointer;
background-color: #ccc;
}
.react-trello-column-header {
font-weight: bold;
background-color: #e3e3e3;
.icon {
margin-right: 5px;
}
}
.react-trello-column-header input:focus {
outline: none;
.production-alert {
background: transparent;
border: none;
}
.react-trello-column-header__button {
color: #333333;
background-color: #ffffff;
border-color: #cccccc;
}
.react-trello-column-header__button:hover,
.react-trello-column-header__button:focus,
.react-trello-column-header__button:active {
background-color: #e6e6e6;
}
.react-trello-column-adder-button {
border: 2px dashed #eee;
height: 132px;
margin: 5px;
}
.react-trello-column-adder-button:hover {
cursor: pointer;
}

View File

@@ -43,18 +43,14 @@ const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
jobid: record.id,
operation: AuditTrailMapping.alertToggle(newAlertState),
type: "alertToggle"
}).then(() => {
if (record.refetch) record.refetch();
});
if (record.refetch) record.refetch();
}, [updateAlert, insertAuditTrail, record]);
return (
<div style={{ height: "19px" }}>
{record.production_vars?.alert && (
<Button className="production-alert" icon={<ExclamationCircleFilled />} onClick={handleAlertToggle} />
)}
</div>
);
if (!record.production_vars?.alert) return null;
return <Button className="production-alert" icon={<ExclamationCircleFilled />} onClick={handleAlertToggle} />;
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert);

View File

@@ -349,7 +349,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
key: "alert",
sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false),
sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
render: (text, record) => <ProductionListColumnAlert record={record} />
render: (text, record) => <ProductionListColumnAlert record={{ record }} />
},
{
title: i18n.t("production.labels.note"),

View File

@@ -1,11 +1,10 @@
import LaneFooter from "./Lane/LaneFooter";
import { BoardWrapper, StyleHorizontal, GlobalStyle, StyleVertical, ScrollableLane, Section } from "../styles/Base";
import { BoardWrapper, StyleHorizontal, StyleVertical, ScrollableLane, Section } from "../styles/Base";
const exports = {
StyleHorizontal,
StyleVertical,
GlobalStyle,
BoardWrapper,
ScrollableLane,
LaneFooter,

View File

@@ -1,27 +1,21 @@
import { BoardContainer } from "../index";
import { useMemo, useState } from "react";
import { v1 } from "uuid";
import { GlobalStyle } from "../styles/Base.js";
import { useMemo } from "react";
import { StyleHorizontal, StyleVertical } from "../styles/Base.js";
const Board = ({ id, className, components, orientation, ...additionalProps }) => {
const [storeId] = useState(id || v1());
const allClassNames = useMemo(() => `react-trello-board ${className || ""}`.trim(), [className]);
const Board = ({ id, className, orientation, cardSettings, ...additionalProps }) => {
const OrientationStyle = useMemo(
() => (orientation === "horizontal" ? components.StyleHorizontal : components.StyleVertical),
[orientation, components.StyleHorizontal, components.StyleVertical]
() => (orientation === "horizontal" ? StyleHorizontal : StyleVertical),
[orientation]
);
return (
<>
<GlobalStyle />
<OrientationStyle>
<BoardContainer
components={components}
orientation={orientation}
cardSettings={cardSettings}
{...additionalProps}
id={storeId}
className={allClassNames}
className="react-trello-board"
/>
</OrientationStyle>
</>

View File

@@ -1,8 +1,7 @@
import React, { useCallback, useEffect, useState, useMemo } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { DragDropContext } from "../dnd/lib";
import PropTypes from "prop-types";
import pick from "lodash/pick";
import isEqual from "lodash/isEqual";
import Lane from "./Lane";
import { PopoverWrapper } from "react-popopo";
@@ -15,26 +14,23 @@ import { BoardWrapper } from "../styles/Base.js";
*
* @component
* @param {Object} props - Component props
* @param {string} props.id - The unique identifier for the board
* @param {Object} props.components - Custom components to use in the board
* @param {Object} props.data - The initial data for the board
* @param {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
* @param {Object} props.otherProps - Any other props to pass to the board
* @returns {JSX.Element} A Trello-like board
*/
const BoardContainer = ({
id,
components,
data,
onDataChange = () => {},
onDragEnd = () => {},
laneSortFunction = () => {},
orientation = "horizontal",
cardSettings = {},
eventBusHandle,
reducerData,
...otherProps
reducerData
}) => {
const [isDragging, setIsDragging] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
@@ -42,8 +38,6 @@ const BoardContainer = ({
const dispatch = useDispatch();
const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
const groupName = `TrelloBoard${id}`;
const wireEventBus = useCallback(() => {
const eventBus = {
publish: (event) => {
@@ -98,24 +92,6 @@ const BoardContainer = ({
setIsDragging(true);
}, [setIsDragging]);
const passThroughProps = useMemo(
() =>
pick(
{
id,
components,
data,
onDataChange,
orientation,
eventBusHandle,
reducerData,
...otherProps
},
["laneSortFunction", "collapsibleLanes", "orientation"]
),
[id, components, data, onDataChange, orientation, eventBusHandle, reducerData, otherProps]
);
const onLaneDrag = useCallback(
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
setIsDragging(false);
@@ -143,44 +119,50 @@ const BoardContainer = ({
},
[dispatch, onDragEnd]
);
// id: PropTypes.string.isRequired,
// title: PropTypes.node,
// index: PropTypes.number,
// laneSortFunction: PropTypes.func,
// cards: PropTypes.array,
// orientation: PropTypes.string,
// isProcessing: PropTypes.bool,
// cardSettings: PropTypes.object,
// technician: PropTypes.object,
// bodyshop: PropTypes.object
return (
<BoardWrapper orientation={orientation}>
<PopoverWrapper>
<DragDropContext onDragEnd={onLaneDrag} onDragStart={onDragStart} contextId={groupName}>
<PopoverWrapper>
<BoardWrapper orientation={orientation}>
<DragDropContext onDragEnd={onLaneDrag} onDragStart={onDragStart} contextId="production-board">
{currentReducerData.lanes.map((lane, index) => {
const { id, droppable, ...laneOtherProps } = lane;
return (
<Lane
key={id}
boardId={groupName}
components={components}
id={id}
key={lane.id}
id={lane.id}
title={lane.title}
index={index}
{...laneOtherProps}
{...passThroughProps}
laneSortFunction={laneSortFunction}
orientation={orientation}
cards={lane.cards}
isDragging={isDragging}
isProcessing={isProcessing}
cardSettings={cardSettings}
/>
);
})}
</DragDropContext>
</PopoverWrapper>
</BoardWrapper>
</BoardWrapper>
</PopoverWrapper>
);
};
BoardContainer.propTypes = {
id: PropTypes.string,
components: PropTypes.object,
actions: PropTypes.object,
data: PropTypes.object.isRequired,
reducerData: PropTypes.object,
onDataChange: PropTypes.func,
eventBusHandle: PropTypes.func,
laneSortFunction: PropTypes.func,
collapsibleLanes: PropTypes.bool,
handleDragEnd: PropTypes.func,
orientation: PropTypes.string
};

View File

@@ -2,31 +2,29 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useState } from "re
import PropTypes from "prop-types";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
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";
import { UnorderedListOutlined } from "@ant-design/icons";
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";
const Lane = ({
actions,
id,
boardId,
title,
index,
isProcessing,
laneSortFunction,
style = {},
cardStyle = {},
cards,
collapsibleLanes = false,
components = {},
cardSettings = {},
orientation = "vertical",
className,
...otherProps
technician,
bodyshop
}) => {
const [collapsed, setCollapsed] = useState(false);
const [isVisible, setIsVisible] = useState(true);
@@ -37,187 +35,183 @@ const Lane = ({
return () => clearTimeout(timer);
}, [cards.length]);
const sortCards = useCallback((cards, sortFunction) => {
const sortedCards = useMemo(() => {
if (!cards) return [];
if (!sortFunction) return cards;
return cards.concat().sort((card1, card2) => sortFunction(card1, card2));
}, []);
if (!laneSortFunction) return cards;
return [...cards].sort(laneSortFunction);
}, [cards, laneSortFunction]);
const toggleLaneCollapsed = useCallback(() => {
collapsibleLanes && setCollapsed(!collapsed);
}, [collapsibleLanes, collapsed]);
setCollapsed((prevCollapsed) => !prevCollapsed);
}, []);
const Card = React.memo(({ provided, item: card, isDragging }) => {
return (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
style={provided.draggableProps.style}
className={`item ${isDragging ? "is-dragging" : ""}`}
key={card.id}
>
<components.Card key={card.id} style={card.style || cardStyle} className="react-trello-card" {...card} />
</div>
);
});
const renderDraggable = (index, item) => {
if (!item) {
console.log("null Item");
return null;
}
return (
<Draggable draggableId={item.id} index={index} key={item.id} isDragDisabled={isProcessing}>
{(provided, snapshot) => <Card provided={provided} item={item} isDragging={snapshot.isDragging} />}
</Draggable>
);
};
const ItemWrapper = ({ children, ...props }) => (
<div {...props} className="item-wrapper">
{children}
</div>
const renderDraggable = useCallback(
(index, card) => {
if (!card) {
console.log("null card");
return null;
}
return (
<Draggable draggableId={card.id} index={index} key={card.id} isDragDisabled={isProcessing}>
{(provided, snapshot) => (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
style={provided.draggableProps.style}
className={`item ${snapshot.isDragging ? "is-dragging" : ""}`}
key={card.id}
>
<ProductionBoardCard
technician={technician}
bodyshop={bodyshop}
cardSettings={cardSettings}
key={card.id}
style={card.style}
card={card}
className="react-trello-card"
/>
</div>
)}
</Draggable>
);
},
[isProcessing, technician, bodyshop, cardSettings]
);
const renderDroppable = (provided, renderedCards) => {
const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso;
const FinalComponent = collapsed ? "div" : Component;
const commonProps = {
useWindowScroll: true,
data: renderedCards
};
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) => (
<div
ref={ref}
{...props}
style={{
...style
}}
>
{children}
</div>
)),
Item: ({ children, ...props }) => <div {...props}>{children}</div>
},
itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>,
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 (
<div style={{ height: "100%" }}>
<div
{...provided.droppableProps}
ref={provided.innerRef}
className={allClassNames}
style={{ ...provided.droppableProps.style }}
>
{isVisible && <FinalComponent {...finalComponentProps} />}
{(orientation === "horizontal" || renderedCards.length === 0 || collapsed) && provided.placeholder}
</div>
const ItemWrapper = useCallback(
({ children, ...props }) => (
<div {...props} className="item-wrapper">
{children}
</div>
);
};
),
[]
);
const renderDragContainer = () => {
const renderedCards = sortCards(cards, laneSortFunction);
const renderDroppable = useCallback(
(provided, renderedCards) => {
const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso;
const FinalComponent = collapsed ? "div" : Component;
return (
const commonProps = {
useWindowScroll: true,
data: renderedCards
};
const verticalProps = {
...commonProps,
listClassName: "grid-container",
itemClassName: "grid-item",
components: {
List: forwardRef(({ style, children, ...props }, ref) => (
<div ref={ref} {...props} style={{ ...style }}>
{children}
</div>
)),
Item: ({ children, ...props }) => <div {...props}>{children}</div>
},
itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>,
overscan: { main: 10, reverse: 10 }
};
const horizontalProps = {
...commonProps,
components: { Item: HeightPreservingItem },
overscan: { main: 3, reverse: 3 },
itemContent: (index, item) => renderDraggable(index, item),
scrollerRef: provided.innerRef
};
const componentProps = orientation === "vertical" ? verticalProps : horizontalProps;
const finalComponentProps = collapsed ? {} : componentProps;
return (
<div style={{ height: "100%" }}>
<div
{...provided.droppableProps}
ref={provided.innerRef}
className={`react-trello-lane ${collapsed ? "lane-collapsed" : ""}`}
style={{ ...provided.droppableProps.style }}
>
{isVisible && <FinalComponent {...finalComponentProps} />}
{(orientation === "horizontal" || renderedCards.length === 0 || collapsed) && provided.placeholder}
</div>
</div>
);
},
[orientation, collapsed, isVisible, renderDraggable]
);
const renderDragContainer = useCallback(
() => (
<Droppable
droppableId={id}
index={index}
type="lane"
direction={orientation === "horizontal" ? "vertical" : "grid"}
mode="virtual"
renderClone={(provided, snapshot, rubric) => (
<Card
clone={true}
provided={provided}
isDragging={snapshot.isDragging}
item={renderedCards[rubric.source.index]}
/>
)}
renderClone={(provided, snapshot, rubric) => {
const card = sortedCards[rubric.source.index];
return (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
style={provided.draggableProps.style}
className={`item ${snapshot.isDragging ? "is-dragging" : ""}`}
key={card.id}
>
<ProductionBoardCard
technician={technician}
bodyshop={bodyshop}
cardSettings={cardSettings}
key={card.id}
style={card.style}
className="react-trello-card"
card={card}
clone={false}
/>
</div>
);
}}
>
{(provided) => renderDroppable(provided, renderedCards)}
{(provided) => renderDroppable(provided, sortedCards)}
</Droppable>
);
};
const renderHeader = (pickedProps) => {
return <components.LaneHeader {...pickedProps} onDoubleClick={toggleLaneCollapsed} />;
};
const allClassNames = useMemo(
() => `react-trello-lane ${collapsed ? "lane-collapsed" : ""} ${className || ""}`.trim(),
[collapsed, className]
),
[id, index, orientation, renderDroppable, sortedCards, technician, bodyshop, cardSettings]
);
const passedProps = {
actions,
id,
boardId,
title,
index,
laneSortFunction,
style,
cardStyle,
cards,
collapsibleLanes,
components,
orientation,
className,
...otherProps
};
return (
<Section key={id} orientation={orientation} {...passedProps}>
{renderHeader({ id, cards, ...passedProps })}
<Section key={id} orientation={orientation}>
<div onDoubleClick={toggleLaneCollapsed} className="react-trello-column-header">
<UnorderedListOutlined className="icon" /> {title}
</div>
{renderDragContainer()}
{collapsibleLanes && <LaneFooter onClick={toggleLaneCollapsed} collapsed={collapsed} />}
<LaneFooter onClick={toggleLaneCollapsed} collapsed={collapsed} />
</Section>
);
};
Lane.propTypes = {
actions: PropTypes.object,
id: PropTypes.string.isRequired,
boardId: PropTypes.string,
title: PropTypes.node,
index: PropTypes.number,
laneSortFunction: PropTypes.func,
style: PropTypes.object,
cardStyle: PropTypes.object,
cards: PropTypes.array,
label: PropTypes.string,
collapsibleLanes: PropTypes.bool,
components: PropTypes.object,
orientation: PropTypes.string
orientation: PropTypes.string,
isProcessing: PropTypes.bool,
cardSettings: PropTypes.object,
technician: PropTypes.object,
bodyshop: PropTypes.object
};
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(actions, dispatch)
});
export default connect(null, mapDispatchToProps)(Lane);
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician
});
export default connect(mapStateToProps, mapDispatchToProps)(Lane);

View File

@@ -2,21 +2,17 @@ import React from "react";
import BoardContainer from "./controllers/BoardContainer.jsx";
import Board from "./controllers/Board.jsx";
import Lane from "./controllers/Lane.jsx";
import DefaultComponents from "./components";
import { StyleSheetManager } from "styled-components";
import isPropValid from "@emotion/is-prop-valid";
export { BoardContainer, Lane };
export { DefaultComponents as components };
export { BoardContainer };
// Enhanced default export using arrow function for simplicity
const TrelloBoard = ({ components, ...otherProps }) => {
const TrelloBoard = ({ ...otherProps }) => {
return (
<StyleSheetManager shouldForwardProp={shouldForwardProp}>
<Board components={{ ...DefaultComponents, ...components }} {...otherProps} />
<Board {...otherProps} />
</StyleSheetManager>
);
};

View File

@@ -1,5 +1,5 @@
import { PopoverContainer, PopoverContent } from "react-popopo";
import styled, { createGlobalStyle, css } from "styled-components";
import styled, { css } from "styled-components";
const getBoardWrapperStyles = (props) => {
if (props.orientation === "vertical") {
@@ -7,7 +7,6 @@ const getBoardWrapperStyles = (props) => {
}
if (props.orientation === "horizontal") {
// TODO: The white-space: nowrap; would be a good place to offer further customization
// This will be put in the lane settings and marked as 'Horizontal Wrapping'
return `
white-space: nowrap;
`;
@@ -21,13 +20,8 @@ const getSectionStyles = (props) => {
return `
display: inline-flex;
flex-direction: column;
white-space: nowrap;
// overflow-y: none;
// overflow-y: none;
min-width: 8.5%;
flex-wrap: wrap;
align-content: center;
justify-content: center;
align-items: stretch;
`;
}
return `
@@ -47,52 +41,11 @@ const getLaneFooterStyles = (props) => {
`;
};
export const GlobalStyle = createGlobalStyle`
.comPlainTextContentEditable {
-webkit-user-modify: read-write-plaintext-only;
cursor: text;
}
.comPlainTextContentEditable--has-placeholder::before {
content: attr(placeholder);
opacity: 0.5;
color: inherit;
cursor: text;
}
.react_trello_dragClass {
transform: rotate(3deg);
}
.react_trello_dragLaneClass {
transform: rotate(3deg);
}
.icon-overflow-menu-horizontal:before {
content: "\\E91F";
}
.icon-lg,
.icon-sm {
color: #798d99;
}
.icon-lg {
height: 32px;
font-size: 16px;
line-height: 32px;
width: 32px;
}
.react-trello-column-header {
border-radius: 5px;
}
`;
export const StyleHorizontal = styled.div`
.react-trello-lane {
width: 100%;
height: 100%;
min-height: 1px;
overflow-y: visible;
overflow-x: visible; // change this line
}
@@ -111,7 +64,14 @@ export const StyleHorizontal = styled.div`
white-space: nowrap;
min-width: 4%;
}
.react-trello-column-header {
border-radius: 5px;
min-height: 15px;
padding: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.react-trello-card {
height: auto;
margin: 2px;
@@ -120,9 +80,14 @@ export const StyleHorizontal = styled.div`
export const StyleVertical = styled.div`
.react-trello-column-header {
border-radius: 5px;
min-height: 15px;
padding: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.react-trello-lane {
min-height: 5px;
height: 100%;
@@ -144,15 +109,15 @@ export const StyleVertical = styled.div`
}
.grid-item {
width: 8%; // TODO: THIS IS WHERE WE GET VERTICAL CARD CUSTOMIZATION
display: flex;
width: 250px; // TODO: (Note) This is where we will set the width of the cards in the vertical orientation
align-content: stretch;
box-sizing: border-box;
}
.item {
height: 100%;
width: 100%;
width: 250px; // TODO: (Note) This is where we will set the width of the cards in the vertical orientation
}
.item-wrapper {
@@ -172,52 +137,6 @@ export const StyleVertical = styled.div`
}
`;
export const CustomPopoverContainer = styled(PopoverContainer)`
position: absolute;
right: 10px;
flex-flow: column nowrap;
`;
export const CustomPopoverContent = styled(PopoverContent)`
visibility: hidden;
margin-top: -5px;
opacity: 0;
position: absolute;
z-index: 10;
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease 0ms;
border-radius: 3px;
min-width: 7em;
flex-flow: column nowrap;
background-color: #fff;
color: #000;
padding: 5px;
left: 50%;
transform: translateX(-50%);
${(props) =>
props.active &&
`
visibility: visible;
opacity: 1;
transition-delay: 100ms;
`} &::before {
visibility: hidden;
}
a {
color: rgba(255, 255, 255, 0.56);
padding: 0.5em 1em;
margin: 0;
text-decoration: none;
&:hover {
background-color: #00bcd4 !important;
color: #37474f;
}
}
`;
export const BoardWrapper = styled.div`
color: #393939;
height: 100%;
@@ -226,13 +145,6 @@ export const BoardWrapper = styled.div`
${getBoardWrapperStyles};
`;
export const Header = styled.header`
margin-bottom: 10px;
display: flex;
flex-direction: row;
align-items: flex-start;
`;
export const Section = styled.section`
background-color: #e3e3e3;
border-radius: 3px;
@@ -258,148 +170,8 @@ export const ScrollableLane = styled.div`
justify-content: space-between;
`;
export const Title = styled.span`
font-weight: bold;
font-size: 15px;
line-height: 18px;
cursor: ${(props) => (props.draggable ? "grab" : `auto`)};
width: 70%;
`;
export const RightContent = styled.span`
width: 38%;
text-align: right;
padding-right: 10px;
font-size: 13px;
`;
export const CardWrapper = styled.article`
border-radius: 3px;
border-bottom: 1px solid #ccc;
background-color: #fff;
cursor: pointer;
margin-bottom: 7px;
max-width: 250px;
min-width: 230px;
`;
export const MovableCardWrapper = styled(CardWrapper)`
&:hover {
background-color: #f0f0f0;
color: #000;
}
`;
export const CardHeader = styled(Header)`
border-bottom: 1px solid #eee;
padding-bottom: 6px;
color: #000;
`;
export const CardTitle = styled(Title)`
font-size: 14px;
`;
export const CardRightContent = styled(RightContent)`
font-size: 10px;
`;
export const Detail = styled.div`
font-size: 12px;
color: #4d4d4d;
white-space: pre-wrap;
`;
export const Footer = styled.div`
border-top: 1px solid #eee;
padding-top: 6px;
text-align: right;
display: flex;
justify-content: flex-end;
flex-direction: row;
flex-wrap: wrap;
`;
export const TagSpan = styled.span`
padding: 2px 3px;
border-radius: 3px;
margin: 2px 5px;
font-size: 70%;
`;
export const AddCardLink = styled.a`
border-radius: 0 0 3px 3px;
color: #838c91;
display: block;
padding: 5px 2px;
margin-top: 10px;
position: relative;
text-decoration: none;
cursor: pointer;
&:hover {
//background-color: #cdd2d4;
color: #4d4d4d;
text-decoration: underline;
}
`;
export const LaneTitle = styled.div`
font-size: 15px;
width: 268px;
height: auto;
`;
export const LaneSection = styled.section`
background-color: #2b6aa3;
border-radius: 3px;
margin: 5px;
position: relative;
padding: 5px;
display: inline-flex;
height: 100%;
flex-direction: column;
`;
export const NewLaneSection = styled(LaneSection)`
width: 200px;
`;
export const NewLaneButtons = styled.div`
margin-top: 10px;
`;
export const CardForm = styled.div`
background-color: #e3e3e3;
`;
export const InlineInput = styled.textarea`
overflow-x: hidden; /* for Firefox (issue #5) */
word-wrap: break-word;
min-height: 18px;
max-height: 112px; /* optional, but recommended */
resize: none;
width: 100%;
height: 18px;
font-size: inherit;
font-weight: inherit;
line-height: inherit;
text-align: inherit;
background-color: transparent;
box-shadow: none;
box-sizing: border-box;
border-radius: 3px;
border: 0;
padding: 0 8px;
outline: 0;
${(props) =>
props.border &&
css`
&:focus {
box-shadow: inset 0 0 0 2px #0079bf;
}
`} &:focus {
background-color: white;
}
`;

View File

@@ -1,154 +1,4 @@
import styled from 'styled-components'
import {CardWrapper, MovableCardWrapper} from './Base'
export const DeleteWrapper = styled.div`
text-align: center;
position: absolute;
top: -1px;
right: 2px;
cursor: pointer;
`
export const GenDelButton = styled.button`
transition: all 0.5s ease;
display: inline-block;
border: none;
font-size: 15px;
height: 15px;
padding: 0;
margin-top: 5px;
text-align: center;
width: 15px;
background: inherit;
cursor: pointer;
`
export const DelButton = styled.button`
transition: all 0.5s ease;
display: inline-block;
border: none;
font-size: 8px;
height: 15px;
line-height: 1px;
margin: 0 0 8px;
padding: 0;
text-align: center;
width: 15px;
background: inherit;
cursor: pointer;
opacity: 0;
${MovableCardWrapper}:hover & {
opacity: 1;
}
`
export const MenuButton = styled.button`
transition: all 0.5s ease;
display: inline-block;
border: none;
outline: none;
font-size: 16px;
font-weight: bold;
height: 15px;
line-height: 1px;
margin: 0 0 8px;
padding: 0;
text-align: center;
width: 15px;
background: inherit;
cursor: pointer;
`
export const LaneMenuHeader = styled.div`
position: relative;
margin-bottom: 4px;
text-align: center;
`
export const LaneMenuContent = styled.div`
overflow-x: hidden;
overflow-y: auto;
padding: 0 12px 12px;
`
export const LaneMenuItem = styled.div`
cursor: pointer;
display: block;
font-weight: 700;
padding: 6px 12px;
position: relative;
margin: 0 -12px;
text-decoration: none;
&:hover {
background-color: #3179ba;
color: #fff;
}
`
export const LaneMenuTitle = styled.span`
box-sizing: border-box;
color: #6b808c;
display: block;
line-height: 30px;
border-bottom: 1px solid rgba(9, 45, 66, 0.13);
margin: 0 6px;
overflow: hidden;
padding: 0 32px;
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
z-index: 1;
`
export const DeleteIcon = styled.span`
position: relative;
display: inline-block;
width: 4px;
height: 4px;
opacity: 1;
overflow: hidden;
border: 1px solid #83bd42;
border-radius: 50%;
padding: 4px;
background-color: #83bd42;
${CardWrapper}:hover & {
opacity: 1;
}
&:hover::before,
&:hover::after {
background: red;
}
&:before,
&:after {
content: '';
position: absolute;
height: 2px;
width: 60%;
top: 45%;
left: 20%;
background: #fff;
border-radius: 5px;
}
&:before {
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
&:after {
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
`
import styled from "styled-components";
export const ExpandCollapseBase = styled.span`
width: 36px;
@@ -156,11 +6,11 @@ export const ExpandCollapseBase = styled.span`
font-size: 14px;
position: relative;
cursor: pointer;
`
`;
export const CollapseBtn = styled(ExpandCollapseBase)`
&:before {
content: '';
content: "";
position: absolute;
top: 0;
left: 0;
@@ -171,7 +21,7 @@ export const CollapseBtn = styled(ExpandCollapseBase)`
}
&:after {
content: '';
content: "";
position: absolute;
left: 4px;
top: 4px;
@@ -179,11 +29,11 @@ export const CollapseBtn = styled(ExpandCollapseBase)`
border-left: 3px solid transparent;
border-right: 3px solid transparent;
}
`
`;
export const ExpandBtn = styled(ExpandCollapseBase)`
&:before {
content: '';
content: "";
position: absolute;
top: 0;
left: 0;
@@ -194,7 +44,7 @@ export const ExpandBtn = styled(ExpandCollapseBase)`
}
&:after {
content: '';
content: "";
position: absolute;
left: 4px;
top: 0px;
@@ -202,50 +52,4 @@ export const ExpandBtn = styled(ExpandCollapseBase)`
border-left: 3px solid transparent;
border-right: 3px solid transparent;
}
`
export const AddButton = styled.button`
background: #5aac44;
color: #fff;
transition: background 0.3s ease;
min-height: 32px;
padding: 4px 16px;
vertical-align: top;
margin-top: 0;
margin-right: 8px;
font-weight: bold;
border-radius: 3px;
font-size: 14px;
cursor: pointer;
margin-bottom: 0;
`
export const CancelButton = styled.button`
background: #999999;
color: #fff;
transition: background 0.3s ease;
min-height: 32px;
padding: 4px 16px;
vertical-align: top;
margin-top: 0;
font-weight: bold;
border-radius: 3px;
font-size: 14px;
cursor: pointer;
margin-bottom: 0;
`
export const AddLaneLink = styled.button`
background: #2b6aa3;
border: none;
color: #fff;
transition: background 0.3s ease;
min-height: 32px;
padding: 4px 16px;
vertical-align: top;
margin-top: 0;
margin-right: 0px;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
margin-bottom: 0;
`
`;

View File

@@ -1,43 +0,0 @@
import styled, {keyframes} from 'styled-components'
const keyframeAnimation = keyframes`
0% {
transform: scale(1);
}
20% {
transform: scale(1, 2.2);
}
40% {
transform: scale(1);
}
`
export const LoaderDiv = styled.div`
text-align: center;
margin: 15px 0;
`
export const LoadingBar = styled.div`
display: inline-block;
margin: 0 2px;
width: 4px;
height: 18px;
border-radius: 4px;
animation: ${keyframeAnimation} 1s ease-in-out infinite;
background-color: #777;
&:nth-child(1) {
animation-delay: 0.0001s;
}
&:nth-child(2) {
animation-delay: 0.09s;
}
&:nth-child(3) {
animation-delay: 0.18s;
}
&:nth-child(4) {
animation-delay: 0.27s;
}
`