- Optimize Production Board Card Component,

- Fix issue with production note
- Refactor shared Global styles into their own global style.

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-05-23 15:12:49 -04:00
parent 2f8f058c5c
commit 296afdbeee
4 changed files with 110 additions and 119 deletions

View File

@@ -6,7 +6,7 @@ import {
PauseCircleOutlined PauseCircleOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Card, Col, Row, Space, Tooltip } from "antd"; import { Card, Col, Row, Space, Tooltip } from "antd";
import React from "react"; import React, { useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
@@ -18,60 +18,78 @@ import dayjs from "../../utils/day";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
/**
* Get the color of the card based on the total hours
* @param ssbuckets
* @param totalHrs
* @returns {{r: number, b: number, g: number}}
*/
const cardColor = (ssbuckets, totalHrs) => { const cardColor = (ssbuckets, totalHrs) => {
const bucket = ssbuckets.filter((bucket) => bucket.gte <= totalHrs && (!!bucket.lt ? bucket.lt > totalHrs : true))[0]; const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
let color = { r: 255, g: 255, b: 255 }; let color = { r: 255, g: 255, b: 255 };
if (bucket && bucket.color) { if (bucket && bucket.color) {
color = bucket.color; color = bucket.color.rgb || bucket.color;
if (bucket.color.rgb) {
color = bucket.color.rgb;
}
} }
return color; return color;
}; };
function getContrastYIQ(bgColor) { /**
const yiq = (bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000; * Get the contrast color based on the background color
* @param bgColor
return yiq >= 128 ? "black" : "white"; * @returns {string}
} */
const getContrastYIQ = (bgColor) =>
(bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white";
/**
* Production Board Card component
* @param technician
* @param card
* @param bodyshop
* @param cardSettings
* @returns {Element}
* @constructor
*/
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings }) { export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings }) {
const { t } = useTranslation(); const { t } = useTranslation();
let employee_body, employee_prep, employee_refinish, employee_csr; let employee_body, employee_prep, employee_refinish, employee_csr;
if (card && card.metadata && card.metadata.employee_body) { // Destructure metadata
employee_body = bodyshop.employees.find((e) => e.id === card.metadata.employee_body); const { metadata } = card;
if (metadata?.employee_body) {
employee_body = bodyshop.employees.find((e) => e.id === metadata.employee_body);
} }
if (card && card.metadata && card.metadata.employee_prep) { if (metadata?.employee_prep) {
employee_prep = bodyshop.employees.find((e) => e.id === card.metadata.employee_prep); employee_prep = bodyshop.employees.find((e) => e.id === metadata.employee_prep);
} }
if (card && card.metadata && card.metadata.employee_refinish) { if (metadata?.employee_refinish) {
employee_refinish = bodyshop.employees.find((e) => e.id === card.metadata.employee_refinish); employee_refinish = bodyshop.employees.find((e) => e.id === metadata.employee_refinish);
} }
if (card && card.metadata && card.metadata.employee_csr) { if (metadata?.employee_csr) {
employee_csr = bodyshop.employees.find((e) => e.id === card.metadata.employee_csr); employee_csr = bodyshop.employees.find((e) => e.id === metadata.employee_csr);
} }
// if (card && card.metadata && card.metadata.employee_csr) { // if (metadata.?employee_csr) {
// employee_csr = bodyshop.employees.find((e) => e.id === card.metadata.employee_csr); // employee_csr = bodyshop.employees.find((e) => e.id === metadata.employee_csr);
// } // }
const pastDueAlert = const pastDueAlert =
!!card?.metadata?.scheduled_completion && !!metadata?.scheduled_completion &&
((dayjs().isSameOrAfter(dayjs(card.metadata.scheduled_completion), "day") && "production-completion-past") || ((dayjs().isSameOrAfter(dayjs(metadata.scheduled_completion), "day") && "production-completion-past") ||
(dayjs().add(1, "day").isSame(dayjs(card.metadata.scheduled_completion), "day") && "production-completion-soon")); (dayjs().add(1, "day").isSame(dayjs(metadata.scheduled_completion), "day") && "production-completion-soon"));
const totalHrs = const totalHrs = useMemo(() => {
card && card?.metadata?.labhrs && card?.metadata?.larhrs return metadata?.labhrs && metadata?.larhrs
? card.metadata.labhrs.aggregate.sum.mod_lb_hrs + card.metadata.larhrs.aggregate.sum.mod_lb_hrs ? metadata.labhrs.aggregate.sum.mod_lb_hrs + metadata.larhrs.aggregate.sum.mod_lb_hrs
: 0; : 0;
}, [metadata]);
const bgColor = cardColor(bodyshop.ssbuckets, totalHrs); const bgColor = useMemo(() => cardColor(bodyshop.ssbuckets, totalHrs), [bodyshop.ssbuckets, totalHrs]);
const contrastYIQ = useMemo(() => getContrastYIQ(bgColor), [bgColor]);
return ( return (
<Card <Card
@@ -80,22 +98,22 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
style={{ style={{
backgroundColor: backgroundColor:
cardSettings && cardSettings.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`, cardSettings && cardSettings.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
color: cardSettings && cardSettings.cardcolor && getContrastYIQ(bgColor), color: cardSettings && cardSettings.cardcolor && contrastYIQ,
maxWidth: "250px", maxWidth: "250px",
margin: "5px" margin: "5px"
}} }}
title={ title={
<Space> <Space>
<ProductionAlert record={card} key="alert" /> <ProductionAlert record={card} key="alert" />
{card.metadata.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />} {metadata?.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
{card.metadata.iouparent && ( {metadata?.iouparent && (
<Tooltip title={t("jobs.labels.iou")}> <Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} /> <BranchesOutlined style={{ color: "orangered" }} />
</Tooltip> </Tooltip>
)} )}
<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}`}>
{card.metadata.ro_number || t("general.labels.na")} {metadata?.ro_number || t("general.labels.na")}
</Link> </Link>
</span> </span>
</Space> </Space>
@@ -110,7 +128,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
{cardSettings && cardSettings.ownr_nm && ( {cardSettings && cardSettings.ownr_nm && (
<Col span={24}> <Col span={24}>
{cardSettings && cardSettings.compact ? ( {cardSettings && cardSettings.compact ? (
<div className="ellipses">{`${card.metadata.ownr_ln || ""} ${card.metadata.ownr_co_nm || ""}`}</div> <div className="ellipses">{`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`}</div>
) : ( ) : (
<div className="ellipses"> <div className="ellipses">
<OwnerNameDisplay ownerObject={card} /> <OwnerNameDisplay ownerObject={card} />
@@ -119,18 +137,18 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
</Col> </Col>
)} )}
<Col span={24}> <Col span={24}>
<div className="ellipses">{`${card.metadata.v_model_yr || ""} ${ <div className="ellipses">{`${metadata.v_model_yr || ""} ${
card.metadata.v_make_desc || "" metadata.v_make_desc || ""
} ${card.metadata.v_model_desc || ""}`}</div> } ${metadata.v_model_desc || ""}`}</div>
</Col> </Col>
{cardSettings && cardSettings.ins_co_nm && card.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">{card.metadata.ins_co_nm || ""}</div> <div className="ellipses">{metadata.ins_co_nm || ""}</div>
</Col> </Col>
)} )}
{cardSettings && cardSettings.clm_no && card.metadata.clm_no && ( {cardSettings && cardSettings.clm_no && metadata.clm_no && (
<Col span={cardSettings && cardSettings.compact ? 24 : 12}> <Col span={cardSettings && cardSettings.compact ? 24 : 12}>
<div className="ellipses">{card.metadata.clm_no || ""}</div> <div className="ellipses">{metadata.clm_no || ""}</div>
</Col> </Col>
)} )}
@@ -139,7 +157,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
<Row> <Row>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${ <Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${
employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : "" employee_body ? `${employee_body.first_name.substr(0, 3)} ${employee_body.last_name.charAt(0)}` : ""
} ${card.metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col> } ${metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`P: ${ <Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`P: ${
employee_prep ? `${employee_prep.first_name.substr(0, 3)} ${employee_prep.last_name.charAt(0)}` : "" employee_prep ? `${employee_prep.first_name.substr(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""
}`}</Col> }`}</Col>
@@ -147,7 +165,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
employee_refinish employee_refinish
? `${employee_refinish.first_name.substr(0, 3)} ${employee_refinish.last_name.charAt(0)}` ? `${employee_refinish.first_name.substr(0, 3)} ${employee_refinish.last_name.charAt(0)}`
: "" : ""
} ${card.metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col> } ${metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${ <Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${
employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : "" employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""
}`}</Col> }`}</Col>
@@ -158,48 +176,56 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
<Col span={24}> <Col span={24}>
<Row> <Row>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${ <Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${
card.metadata.labhrs.aggregate.sum.mod_lb_hrs || "?" metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"
} hrs`}</Col> } hrs`}</Col>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`R: ${ <Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`R: ${
card.metadata.larhrs.aggregate.sum.mod_lb_hrs || "?" metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"
} hrs`}</Col> } hrs`}</Col>
</Row> </Row>
</Col> </Col>
)} */} )} */}
{cardSettings && cardSettings.actual_in && card.metadata.actual_in && ( {cardSettings && cardSettings.actual_in && metadata.actual_in && (
<Col span={cardSettings && cardSettings.compact ? 24 : 12}> <Col span={cardSettings && cardSettings.compact ? 24 : 12}>
<Space> <Space>
<DownloadOutlined /> <DownloadOutlined />
<DateTimeFormatter format="MM/DD">{card.metadata.actual_in}</DateTimeFormatter> <DateTimeFormatter format="MM/DD">{metadata.actual_in}</DateTimeFormatter>
</Space> </Space>
</Col> </Col>
)} )}
{cardSettings && cardSettings.scheduled_completion && card.metadata.scheduled_completion && ( {cardSettings && cardSettings.scheduled_completion && metadata.scheduled_completion && (
<Col span={cardSettings && cardSettings.compact ? 24 : 12}> <Col span={cardSettings && cardSettings.compact ? 24 : 12}>
<Space className={pastDueAlert}> <Space className={pastDueAlert}>
<CalendarOutlined /> <CalendarOutlined />
<DateTimeFormatter format="MM/DD">{card.metadata.scheduled_completion}</DateTimeFormatter> <DateTimeFormatter format="MM/DD">{metadata.scheduled_completion}</DateTimeFormatter>
</Space> </Space>
</Col> </Col>
)} )}
{cardSettings && cardSettings.ats && card.metadata.alt_transport && ( {cardSettings && cardSettings.ats && metadata.alt_transport && (
<Col span={12}> <Col span={12}>
<div>{card.metadata.alt_transport || ""}</div> <div>{metadata.alt_transport || ""}</div>
</Col> </Col>
)} )}
{cardSettings && cardSettings.sublets && ( {cardSettings && cardSettings.sublets && (
<Col span={12}> <Col span={12}>
<ProductionSubletsManageComponent subletJobLines={card.metadata.subletLines} /> <ProductionSubletsManageComponent subletJobLines={metadata.subletLines} />
</Col> </Col>
)} )}
{cardSettings && cardSettings.production_note && ( {cardSettings && cardSettings.production_note && (
<Col span={24}> <Col span={24}>
{cardSettings && cardSettings.production_note && <ProductionListColumnProductionNote record={card} />} {cardSettings && cardSettings.production_note && (
<ProductionListColumnProductionNote
record={{
production_vars: card?.metadata.production_vars,
id: card?.id,
refetch: card?.refetch
}}
/>
)}
</Col> </Col>
)} )}
{cardSettings && cardSettings.partsstatus && ( {cardSettings && cardSettings.partsstatus && (
<Col span={24}> <Col span={24}>
<JobPartsQueueCount parts={card.metadata.joblines_status} /> <JobPartsQueueCount parts={metadata.joblines_status} />
</Col> </Col>
)} )}
</Row> </Row>

View File

@@ -6,11 +6,12 @@ import NewLaneForm from "./NewLaneForm.jsx";
import NewCardForm from "./NewCardForm.jsx"; import NewCardForm from "./NewCardForm.jsx";
import AddCardLink from "./AddCardLink"; import AddCardLink from "./AddCardLink";
import NewLaneSection from "./NewLaneSection.jsx"; import NewLaneSection from "./NewLaneSection.jsx";
import { BoardWrapper, GlobalStyleHorizontal, GlobalStyleVertical, ScrollableLane, Section } from "../styles/Base"; import { BoardWrapper, StyleHorizontal, GlobalStyle, StyleVertical, ScrollableLane, Section } from "../styles/Base";
const exports = { const exports = {
GlobalStyleHorizontal, StyleHorizontal,
GlobalStyleVertical, StyleVertical,
GlobalStyle,
BoardWrapper, BoardWrapper,
Loader, Loader,
ScrollableLane, ScrollableLane,

View File

@@ -7,19 +7,21 @@ const Board = ({ id, className, components, orientation, ...additionalProps }) =
const [storeId] = useState(id || v1()); const [storeId] = useState(id || v1());
const allClassNames = classNames("react-trello-board", className || ""); const allClassNames = classNames("react-trello-board", className || "");
const OrientationStyle = orientation === "horizontal" ? components.StyleHorizontal : components.StyleVertical;
const GlobalStyle = orientation === "horizontal" ? components.GlobalStyleHorizontal : components.GlobalStyleVertical;
return ( return (
<GlobalStyle> <>
<BoardContainer <components.GlobalStyle />
components={components} <OrientationStyle>
orientation={orientation} <BoardContainer
{...additionalProps} components={components}
id={storeId} orientation={orientation}
className={allClassNames} {...additionalProps}
/> id={storeId}
</GlobalStyle> className={allClassNames}
/>
</OrientationStyle>
</>
); );
}; };

View File

@@ -24,48 +24,12 @@ const getSectionStyles = (props) => {
return ""; return "";
}; };
export const GlobalStyleHorizontal = styled.div` export const GlobalStyle = createGlobalStyle`
.comPlainTextContentEditable { .comPlainTextContentEditable {
-webkit-user-modify: read-write-plaintext-only; -webkit-user-modify: read-write-plaintext-only;
cursor: text; 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;
}
`;
export const GlobalStyleVertical = styled.div`
.comPlainTextContentEditable {
-webkit-user-modify: read-write-plaintext-only;
cursor: text;
}
.smooth-dnd-container.horizontal { .smooth-dnd-container.horizontal {
} }
@@ -100,23 +64,21 @@ export const GlobalStyleVertical = styled.div`
width: 32px; width: 32px;
} }
.react-trello-column-header {
border-radius: 5px;
}
`;
export const StyleHorizontal = styled.div``;
export const StyleVertical = styled.div`
.react-trello-column-header {
text-align: left;
}
.smooth-dnd-container { .smooth-dnd-container {
// TODO ? This is the question. We need the same drag-zone we get in horizontal mode // TODO ? This is the question. We need the same drag-zone we get in horizontal mode
min-height: 50px; // Not needed, just for extra landing space min-height: 50px; // Not needed, just for extra landing space
} }
.react-trello-lane {
border: 1px solid #ccc;
border-radius: 5px;
}
.react-trello-column-header {
border: 1px solid #ccc;
border-radius: 5px;
padding: 5px;
text-align: left;
}
.react-trello-board { .react-trello-board {
overflow-y: hidden !important; overflow-y: hidden !important;
} }
@@ -225,7 +187,7 @@ export const ScrollableLane = styled.div`
align-self: center; align-self: center;
// TODO: This was commented out to match existing board style // TODO: This was commented out to match existing board style
//max-height: 90vh; //max-height: 90vh;
margin-top: 10px; //margin-top: 10px;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
`; `;