- Daily checkpoint, speed (grid), and presentation

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-07-08 17:57:26 -04:00
parent fa578efee4
commit 8a4fee7aea
5 changed files with 206 additions and 198 deletions

View File

@@ -28,26 +28,28 @@ const cardColor = (ssbuckets, totalHrs) => {
const getContrastYIQ = (bgColor) => const getContrastYIQ = (bgColor) =>
(bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white"; (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white";
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone, style }) { const findEmployeeById = (employees, id) => employees.find((e) => e.id === id);
const EllipsesToolTip = React.memo(({ title, children }) => (
<Tooltip title={title}>
<div className="ellipses">{children}</div>
</Tooltip>
));
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { metadata } = card; const { metadata } = card;
const employee_body = useMemo( const employees = useMemo(() => bodyshop.employees, [bodyshop.employees]);
() => metadata?.employee_body && bodyshop.employees.find((e) => e.id === metadata.employee_body),
[metadata?.employee_body, bodyshop.employees] const { employee_body, employee_prep, employee_refinish, employee_csr } = useMemo(() => {
); return {
const employee_prep = useMemo( employee_body: metadata?.employee_body && findEmployeeById(employees, metadata.employee_body),
() => metadata?.employee_prep && bodyshop.employees.find((e) => e.id === metadata.employee_prep), employee_prep: metadata?.employee_prep && findEmployeeById(employees, metadata.employee_prep),
[metadata?.employee_prep, bodyshop.employees] employee_refinish: metadata?.employee_refinish && findEmployeeById(employees, metadata.employee_refinish),
); employee_csr: metadata?.employee_csr && findEmployeeById(employees, metadata.employee_csr)
const employee_refinish = useMemo( };
() => metadata?.employee_refinish && bodyshop.employees.find((e) => e.id === metadata.employee_refinish), }, [metadata, employees]);
[metadata?.employee_refinish, bodyshop.employees]
);
const employee_csr = useMemo(
() => metadata?.employee_csr && bodyshop.employees.find((e) => e.id === metadata.employee_csr),
[metadata?.employee_csr, bodyshop.employees]
);
const pastDueAlert = useMemo(() => { const pastDueAlert = useMemo(() => {
if (!metadata?.scheduled_completion) return null; if (!metadata?.scheduled_completion) return null;
@@ -66,16 +68,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
const bgColor = useMemo(() => cardColor(bodyshop.ssbuckets, totalHrs), [bodyshop.ssbuckets, totalHrs]); const bgColor = useMemo(() => cardColor(bodyshop.ssbuckets, totalHrs), [bodyshop.ssbuckets, totalHrs]);
const contrastYIQ = useMemo(() => getContrastYIQ(bgColor), [bgColor]); const contrastYIQ = useMemo(() => getContrastYIQ(bgColor), [bgColor]);
return ( const headerContent = (
<Card
className="react-trello-card"
size="small"
style={{
backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
color: cardSettings?.cardcolor && contrastYIQ,
...style
}}
title={
<Space> <Space>
<ProductionAlert <ProductionAlert
record={{ record={{
@@ -87,9 +80,9 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
/> />
{metadata?.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />} {metadata?.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
{metadata?.iouparent && ( {metadata?.iouparent && (
<Tooltip title={t("jobs.labels.iou")}> <EllipsesToolTip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} /> <BranchesOutlined style={{ color: "orangered" }} />
</Tooltip> </EllipsesToolTip>
)} )}
<span style={{ fontWeight: "bolder" }}> <span style={{ fontWeight: "bolder" }}>
<Link to={technician ? `/tech/joblookup?selected=${card.id}` : `/manage/jobs/${card.id}`}> <Link to={technician ? `/tech/joblookup?selected=${card.id}` : `/manage/jobs/${card.id}`}>
@@ -97,107 +90,97 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
</Link> </Link>
</span> </span>
</Space> </Space>
} );
extra={
<Link to={{ search: `?selected=${card.id}` }}> const bodyContent = (
<EyeFilled />
</Link>
}
>
<Row> <Row>
{cardSettings?.ownr_nm && ( {cardSettings?.ownr_nm && (
<Col span={24}> <Col span={24}>
<Tooltip title={`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`}> <EllipsesToolTip title={`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`}>
{cardSettings.compact ? ( {cardSettings.compact ? (
<div className="ellipses">{`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`}</div> `${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`
) : ( ) : (
<div className="ellipses">
<OwnerNameDisplay ownerObject={card} /> <OwnerNameDisplay ownerObject={card} />
</div>
)} )}
</Tooltip> </EllipsesToolTip>
</Col> </Col>
)} )}
{cardSettings?.model_info && ( {cardSettings?.model_info && (
<Col span={24}> <Col span={24}>
<Tooltip <EllipsesToolTip
title={`${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`} title={`${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`}
> >
<div className="ellipses">{`${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`}</div> {`${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`}
</Tooltip> </EllipsesToolTip>
</Col> </Col>
)} )}
{cardSettings?.ins_co_nm && metadata.ins_co_nm && ( {cardSettings?.ins_co_nm && metadata.ins_co_nm && (
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={metadata.ins_co_nm || ""}> <EllipsesToolTip title={metadata.ins_co_nm || ""}>{metadata.ins_co_nm || ""}</EllipsesToolTip>
<div className="ellipses">{metadata.ins_co_nm || ""}</div>
</Tooltip>
</Col> </Col>
)} )}
{cardSettings?.clm_no && metadata.clm_no && ( {cardSettings?.clm_no && metadata.clm_no && (
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={metadata.clm_no || ""}> <EllipsesToolTip title={metadata.clm_no || ""}>{metadata.clm_no || ""}</EllipsesToolTip>
<div className="ellipses">{metadata.clm_no || ""}</div>
</Tooltip>
</Col> </Col>
)} )}
{cardSettings?.employeeassignments && ( {cardSettings?.employeeassignments && (
<Col span={24}> <Col span={24}>
<Row> <Row>
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
<Tooltip <EllipsesToolTip
title={`B: ${employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : ""} ${metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`} title={`B: ${employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : ""} ${metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}
> >
<div className="ellipses">{`B: ${employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : ""} ${metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</div> {`B: ${employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : ""} ${metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}
</Tooltip> </EllipsesToolTip>
</Col> </Col>
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
<Tooltip <EllipsesToolTip
title={`P: ${employee_prep ? `${employee_prep.first_name.substr(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""}`} title={`P: ${employee_prep ? `${employee_prep.first_name.substr(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""}`}
> >
<div className="ellipses">{`P: ${employee_prep ? `${employee_prep.first_name.substring(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""}`}</div> {`P: ${employee_prep ? `${employee_prep.first_name.substring(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""}`}
</Tooltip> </EllipsesToolTip>
</Col> </Col>
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
<Tooltip <EllipsesToolTip
title={`R: ${employee_refinish ? `${employee_refinish.first_name.substring(0, 3)} ${employee_refinish.last_name.charAt(0)}` : ""} ${metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`} title={`R: ${employee_refinish ? `${employee_refinish.first_name.substring(0, 3)} ${employee_refinish.last_name.charAt(0)}` : ""} ${metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}
> >
<div className="ellipses">{`R: ${employee_refinish ? `${employee_refinish.first_name.substring(0, 3)} ${employee_refinish.last_name.charAt(0)}` : ""} ${metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</div> {`R: ${employee_refinish ? `${employee_refinish.first_name.substring(0, 3)} ${employee_refinish.last_name.charAt(0)}` : ""} ${metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}
</Tooltip> </EllipsesToolTip>
</Col> </Col>
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`}> <EllipsesToolTip
<div className="ellipses">{`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`}</div> title={`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`}
</Tooltip> >
{`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`}
</EllipsesToolTip>
</Col> </Col>
</Row> </Row>
</Col> </Col>
)} )}
{cardSettings?.actual_in && metadata.actual_in && ( {cardSettings?.actual_in && metadata.actual_in && (
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={metadata.actual_in}> <EllipsesToolTip title={metadata.actual_in}>
<Space> <Space>
<DownloadOutlined /> <DownloadOutlined />
<DateTimeFormatter format="MM/DD">{metadata.actual_in}</DateTimeFormatter> <DateTimeFormatter format="MM/DD">{metadata.actual_in}</DateTimeFormatter>
</Space> </Space>
</Tooltip> </EllipsesToolTip>
</Col> </Col>
)} )}
{cardSettings?.scheduled_completion && metadata.scheduled_completion && ( {cardSettings?.scheduled_completion && metadata.scheduled_completion && (
<Col span={cardSettings.compact ? 24 : 12}> <Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={metadata.scheduled_completion}> <EllipsesToolTip title={metadata.scheduled_completion}>
<Space className={pastDueAlert}> <Space className={pastDueAlert}>
<CalendarOutlined /> <CalendarOutlined />
<DateTimeFormatter format="MM/DD">{metadata.scheduled_completion}</DateTimeFormatter> <DateTimeFormatter format="MM/DD">{metadata.scheduled_completion}</DateTimeFormatter>
</Space> </Space>
</Tooltip> </EllipsesToolTip>
</Col> </Col>
)} )}
{cardSettings?.ats && metadata.alt_transport && ( {cardSettings?.ats && metadata.alt_transport && (
<Col span={12}> <Col span={12}>
<Tooltip title={metadata.alt_transport}> <EllipsesToolTip title={metadata.alt_transport}>{metadata.alt_transport || ""}</EllipsesToolTip>
<div className="ellipses">{metadata.alt_transport || ""}</div>
</Tooltip>
</Col> </Col>
)} )}
{cardSettings?.sublets && ( {cardSettings?.sublets && (
@@ -222,6 +205,39 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
</Col> </Col>
)} )}
</Row> </Row>
);
const isBodyEmpty = !(
cardSettings?.ownr_nm ||
cardSettings?.model_info ||
(cardSettings?.ins_co_nm && metadata.ins_co_nm) ||
(cardSettings?.clm_no && metadata.clm_no) ||
cardSettings?.employeeassignments ||
(cardSettings?.actual_in && metadata.actual_in) ||
(cardSettings?.scheduled_completion && metadata.scheduled_completion) ||
(cardSettings?.ats && metadata.alt_transport) ||
cardSettings?.sublets ||
cardSettings?.production_note ||
cardSettings?.partsstatus
);
return (
<Card
className="react-trello-card"
size="small"
style={{
backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
color: cardSettings?.cardcolor && contrastYIQ
}}
title={isBodyEmpty ? null : headerContent}
extra={
!isBodyEmpty && (
<Link to={{ search: `?selected=${card.id}` }}>
<EyeFilled />
</Link>
)
}
>
{isBodyEmpty ? headerContent : bodyContent}
</Card> </Card>
); );
} }

View File

@@ -17,7 +17,7 @@ import ProductionBoardFilters from "../production-board-filters/production-board
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 cloneDeep from "lodash/cloneDeep";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";

View File

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

View File

@@ -1,4 +1,4 @@
import React, { forwardRef, useCallback, useEffect, useMemo, useState } from "react"; import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { bindActionCreators } from "redux"; import { bindActionCreators } from "redux";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -16,6 +16,24 @@ import ProductionBoardCard from "../../../production-board-kanban-card/productio
import HeightMemoryWrapper from "../components/Lane/HeightMemoryWrapper.jsx"; import HeightMemoryWrapper from "../components/Lane/HeightMemoryWrapper.jsx";
import SizeMemoryWrapper from "../components/Lane/SizeMemoryWrapper.jsx"; import SizeMemoryWrapper from "../components/Lane/SizeMemoryWrapper.jsx";
const ListComponent = forwardRef(({ style, children, ...props }, ref) => (
<div ref={ref} {...props} style={{ ...style }}>
{children}
</div>
));
const ItemComponent = ({ children, maxCardHeight, maxCardWidth, ...props }) => (
<div style={{ minWidth: maxCardWidth, minHeight: maxCardHeight }} {...props}>
{children}
</div>
);
const ItemWrapper = React.memo(({ children, ...props }) => (
<div {...props} className="item-wrapper">
{children}
</div>
));
/** /**
* Lane is a React component that represents a lane in a Trello-like board. * Lane is a React component that represents a lane in a Trello-like board.
* @param id * @param id
@@ -57,6 +75,7 @@ const Lane = ({
}) => { }) => {
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const [isVisible, setIsVisible] = useState(true); const [isVisible, setIsVisible] = useState(true);
const laneRef = useRef(null);
useEffect(() => { useEffect(() => {
setIsVisible(false); setIsVisible(false);
@@ -115,15 +134,6 @@ const Lane = ({
[isProcessing, technician, bodyshop, cardSettings, maxCardHeight, setMaxCardHeight, maxCardWidth, setMaxCardWidth] [isProcessing, technician, bodyshop, cardSettings, maxCardHeight, setMaxCardHeight, maxCardWidth, setMaxCardWidth]
); );
const ItemWrapper = useCallback(
({ children, ...props }) => (
<div {...props} className="item-wrapper">
{children}
</div>
),
[]
);
const renderDroppable = useCallback( const renderDroppable = useCallback(
(provided, renderedCards) => { (provided, renderedCards) => {
const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso; const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso;
@@ -137,17 +147,10 @@ const Lane = ({
...commonProps, ...commonProps,
listClassName: "grid-container", listClassName: "grid-container",
itemClassName: "grid-item", itemClassName: "grid-item",
customScrollParent: laneRef.current,
components: { components: {
List: forwardRef(({ style, children, ...props }, ref) => ( List: ListComponent,
<div ref={ref} {...props} style={{ ...style }}> Item: ItemComponent
{children}
</div>
)),
Item: ({ children, ...props }) => (
<div style={{ minWidth: maxCardWidth, minHeight: maxCardHeight }} {...props}>
{children}
</div>
)
}, },
itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>, itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>,
overscan: { main: 10, reverse: 10 } overscan: { main: 10, reverse: 10 }
@@ -200,7 +203,7 @@ const Lane = ({
</div> </div>
); );
}, },
[orientation, collapsed, isVisible, renderDraggable, maxLaneHeight, setMaxLaneHeight, maxCardHeight, maxCardWidth] [orientation, collapsed, isVisible, renderDraggable, maxLaneHeight, setMaxLaneHeight, maxCardWidth]
); );
const renderDragContainer = useCallback( const renderDragContainer = useCallback(
@@ -226,27 +229,15 @@ const Lane = ({
className={`clone ${snapshot.isDragging ? "is-dragging" : ""}`} className={`clone ${snapshot.isDragging ? "is-dragging" : ""}`}
key={card.id} key={card.id}
> >
{/*<SizeMemoryWrapper*/}
{/* maxHeight={maxCardHeight}*/}
{/* setMaxHeight={setMaxCardHeight}*/}
{/* maxWidth={maxCardWidth}*/}
{/* setMaxWidth={setMaxCardWidth}*/}
{/*>*/}
<ProductionBoardCard <ProductionBoardCard
technician={technician} technician={technician}
bodyshop={bodyshop} bodyshop={bodyshop}
cardSettings={cardSettings} cardSettings={cardSettings}
key={card.id} key={card.id}
className="react-trello-card" className="react-trello-card"
style={{
minHeight: maxCardHeight,
minWidth: maxCardWidth,
background_color: "red !important"
}}
card={card} card={card}
clone={false} clone={false}
/> />
{/*</SizeMemoryWrapper>*/}
</div> </div>
); );
}} }}

View File

@@ -37,6 +37,7 @@ if (import.meta.env.PROD) {
ignoreErrors: [ ignoreErrors: [
"ResizeObserver loop", "ResizeObserver loop",
"ResizeObserver loop limit exceeded",
"Module specifier, 'fs' does not start", "Module specifier, 'fs' does not start",
"Module specifier, 'zlib' does not start with" "Module specifier, 'zlib' does not start with"
], ],