- 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,162 +68,176 @@ 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]);
const headerContent = (
<Space>
<ProductionAlert
record={{
id: card.id,
production_vars: card?.metadata.production_vars,
refetch: card?.refetch
}}
key="alert"
/>
{metadata?.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
{metadata?.iouparent && (
<EllipsesToolTip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</EllipsesToolTip>
)}
<span style={{ fontWeight: "bolder" }}>
<Link to={technician ? `/tech/joblookup?selected=${card.id}` : `/manage/jobs/${card.id}`}>
{metadata?.ro_number || t("general.labels.na")}
</Link>
</span>
</Space>
);
const bodyContent = (
<Row>
{cardSettings?.ownr_nm && (
<Col span={24}>
<EllipsesToolTip title={`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`}>
{cardSettings.compact ? (
`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`
) : (
<OwnerNameDisplay ownerObject={card} />
)}
</EllipsesToolTip>
</Col>
)}
{cardSettings?.model_info && (
<Col span={24}>
<EllipsesToolTip
title={`${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`}
>
{`${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`}
</EllipsesToolTip>
</Col>
)}
{cardSettings?.ins_co_nm && metadata.ins_co_nm && (
<Col span={cardSettings.compact ? 24 : 12}>
<EllipsesToolTip title={metadata.ins_co_nm || ""}>{metadata.ins_co_nm || ""}</EllipsesToolTip>
</Col>
)}
{cardSettings?.clm_no && metadata.clm_no && (
<Col span={cardSettings.compact ? 24 : 12}>
<EllipsesToolTip title={metadata.clm_no || ""}>{metadata.clm_no || ""}</EllipsesToolTip>
</Col>
)}
{cardSettings?.employeeassignments && (
<Col span={24}>
<Row>
<Col span={cardSettings.compact ? 24 : 12}>
<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`}
>
{`B: ${employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : ""} ${metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}
</EllipsesToolTip>
</Col>
<Col span={cardSettings.compact ? 24 : 12}>
<EllipsesToolTip
title={`P: ${employee_prep ? `${employee_prep.first_name.substr(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""}`}
>
{`P: ${employee_prep ? `${employee_prep.first_name.substring(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""}`}
</EllipsesToolTip>
</Col>
<Col span={cardSettings.compact ? 24 : 12}>
<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`}
>
{`R: ${employee_refinish ? `${employee_refinish.first_name.substring(0, 3)} ${employee_refinish.last_name.charAt(0)}` : ""} ${metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}
</EllipsesToolTip>
</Col>
<Col span={cardSettings.compact ? 24 : 12}>
<EllipsesToolTip
title={`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`}
>
{`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`}
</EllipsesToolTip>
</Col>
</Row>
</Col>
)}
{cardSettings?.actual_in && metadata.actual_in && (
<Col span={cardSettings.compact ? 24 : 12}>
<EllipsesToolTip title={metadata.actual_in}>
<Space>
<DownloadOutlined />
<DateTimeFormatter format="MM/DD">{metadata.actual_in}</DateTimeFormatter>
</Space>
</EllipsesToolTip>
</Col>
)}
{cardSettings?.scheduled_completion && metadata.scheduled_completion && (
<Col span={cardSettings.compact ? 24 : 12}>
<EllipsesToolTip title={metadata.scheduled_completion}>
<Space className={pastDueAlert}>
<CalendarOutlined />
<DateTimeFormatter format="MM/DD">{metadata.scheduled_completion}</DateTimeFormatter>
</Space>
</EllipsesToolTip>
</Col>
)}
{cardSettings?.ats && metadata.alt_transport && (
<Col span={12}>
<EllipsesToolTip title={metadata.alt_transport}>{metadata.alt_transport || ""}</EllipsesToolTip>
</Col>
)}
{cardSettings?.sublets && (
<Col span={12}>
<ProductionSubletsManageComponent subletJobLines={metadata.subletLines} />
</Col>
)}
{cardSettings?.production_note && (
<Col span={24}>
<ProductionListColumnProductionNote
record={{
production_vars: card?.metadata.production_vars,
id: card?.id,
refetch: card?.refetch
}}
/>
</Col>
)}
{cardSettings?.partsstatus && (
<Col span={24}>
<JobPartsQueueCount parts={metadata.joblines_status} />
</Col>
)}
</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 ( return (
<Card <Card
className="react-trello-card" className="react-trello-card"
size="small" size="small"
style={{ style={{
backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`, backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
color: cardSettings?.cardcolor && contrastYIQ, color: cardSettings?.cardcolor && contrastYIQ
...style
}} }}
title={ title={isBodyEmpty ? null : headerContent}
<Space>
<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")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
<span style={{ fontWeight: "bolder" }}>
<Link to={technician ? `/tech/joblookup?selected=${card.id}` : `/manage/jobs/${card.id}`}>
{metadata?.ro_number || t("general.labels.na")}
</Link>
</span>
</Space>
}
extra={ extra={
<Link to={{ search: `?selected=${card.id}` }}> !isBodyEmpty && (
<EyeFilled /> <Link to={{ search: `?selected=${card.id}` }}>
</Link> <EyeFilled />
</Link>
)
} }
> >
<Row> {isBodyEmpty ? headerContent : bodyContent}
{cardSettings?.ownr_nm && (
<Col span={24}>
<Tooltip title={`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`}>
{cardSettings.compact ? (
<div className="ellipses">{`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`}</div>
) : (
<div className="ellipses">
<OwnerNameDisplay ownerObject={card} />
</div>
)}
</Tooltip>
</Col>
)}
{cardSettings?.model_info && (
<Col span={24}>
<Tooltip
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>
</Tooltip>
</Col>
)}
{cardSettings?.ins_co_nm && metadata.ins_co_nm && (
<Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={metadata.ins_co_nm || ""}>
<div className="ellipses">{metadata.ins_co_nm || ""}</div>
</Tooltip>
</Col>
)}
{cardSettings?.clm_no && metadata.clm_no && (
<Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={metadata.clm_no || ""}>
<div className="ellipses">{metadata.clm_no || ""}</div>
</Tooltip>
</Col>
)}
{cardSettings?.employeeassignments && (
<Col span={24}>
<Row>
<Col span={cardSettings.compact ? 24 : 12}>
<Tooltip
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>
</Tooltip>
</Col>
<Col span={cardSettings.compact ? 24 : 12}>
<Tooltip
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>
</Tooltip>
</Col>
<Col span={cardSettings.compact ? 24 : 12}>
<Tooltip
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>
</Tooltip>
</Col>
<Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`}>
<div className="ellipses">{`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`}</div>
</Tooltip>
</Col>
</Row>
</Col>
)}
{cardSettings?.actual_in && metadata.actual_in && (
<Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={metadata.actual_in}>
<Space>
<DownloadOutlined />
<DateTimeFormatter format="MM/DD">{metadata.actual_in}</DateTimeFormatter>
</Space>
</Tooltip>
</Col>
)}
{cardSettings?.scheduled_completion && metadata.scheduled_completion && (
<Col span={cardSettings.compact ? 24 : 12}>
<Tooltip title={metadata.scheduled_completion}>
<Space className={pastDueAlert}>
<CalendarOutlined />
<DateTimeFormatter format="MM/DD">{metadata.scheduled_completion}</DateTimeFormatter>
</Space>
</Tooltip>
</Col>
)}
{cardSettings?.ats && metadata.alt_transport && (
<Col span={12}>
<Tooltip title={metadata.alt_transport}>
<div className="ellipses">{metadata.alt_transport || ""}</div>
</Tooltip>
</Col>
)}
{cardSettings?.sublets && (
<Col span={12}>
<ProductionSubletsManageComponent subletJobLines={metadata.subletLines} />
</Col>
)}
{cardSettings?.production_note && (
<Col span={24}>
<ProductionListColumnProductionNote
record={{
production_vars: card?.metadata.production_vars,
id: card?.id,
refetch: card?.refetch
}}
/>
</Col>
)}
{cardSettings?.partsstatus && (
<Col span={24}>
<JobPartsQueueCount parts={metadata.joblines_status} />
</Col>
)}
</Row>
</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"
], ],