- Add Tasks to production board (and required refactors)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { Col, List, Space, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const CardColorLegend = ({ bodyshop }) => {
|
||||
const { t } = useTranslation();
|
||||
const data = bodyshop.ssbuckets.map((bucket) => {
|
||||
let color = { r: 255, g: 255, b: 255 };
|
||||
|
||||
if (bucket.color) {
|
||||
color = bucket.color;
|
||||
|
||||
if (bucket.color.rgb) {
|
||||
color = bucket.color.rgb;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: bucket.label,
|
||||
color
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<Typography>{t("production.labels.legend")}</Typography>
|
||||
<List
|
||||
grid={{
|
||||
gutter: 16
|
||||
}}
|
||||
dataSource={data}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<Space>
|
||||
<div
|
||||
style={{
|
||||
width: "1.5rem",
|
||||
aspectRatio: "1/1",
|
||||
backgroundColor: `rgba(${item.color.r},${item.color.g},${item.color.b},${item.color.a})`
|
||||
}}
|
||||
></div>
|
||||
<div>{item.label}</div>
|
||||
</Space>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardColorLegend;
|
||||
@@ -0,0 +1,444 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
CalendarOutlined,
|
||||
DownloadOutlined,
|
||||
EyeFilled,
|
||||
PauseCircleOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Col, Row, Space, Tooltip } from "antd";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
||||
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||
|
||||
const cardColor = (ssbuckets, totalHrs) => {
|
||||
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
||||
return bucket && bucket.color ? bucket.color.rgb || bucket.color : { r: 255, g: 255, b: 255 };
|
||||
};
|
||||
|
||||
const getContrastYIQ = (bgColor) =>
|
||||
(bgColor.r * 299 + bgColor.g * 587 + bgColor.b * 114) / 1000 >= 128 ? "black" : "white";
|
||||
|
||||
const findEmployeeById = (employees, id) => employees.find((e) => e.id === id);
|
||||
|
||||
const EllipsesToolTip = React.memo(({ title, children, kiosk }) => {
|
||||
if (kiosk || !title) {
|
||||
return <div className="ellipses no-select">{children}</div>;
|
||||
}
|
||||
return (
|
||||
<Tooltip title={title}>
|
||||
<div className="ellipses">{children}</div>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
|
||||
const OwnerNameToolTip = ({ metadata, cardSettings }) =>
|
||||
cardSettings?.ownr_nm && (
|
||||
<Col span={24}>
|
||||
<EllipsesToolTip
|
||||
title={metadata.ownr_ln || metadata.ownr_co_nm ? <OwnerNameDisplay ownerObject={metadata} /> : null}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{metadata.ownr_ln || metadata.ownr_co_nm ? (
|
||||
cardSettings.compact ? (
|
||||
`${metadata.ownr_ln || ""} ${metadata.ownr_co_nm || ""}`
|
||||
) : (
|
||||
<OwnerNameDisplay ownerObject={metadata} />
|
||||
)
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const ModelInfoToolTip = ({ metadata, cardSettings }) =>
|
||||
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 || ""}`
|
||||
: null
|
||||
}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{metadata.v_model_yr || metadata.v_make_desc || metadata.v_model_desc ? (
|
||||
`${metadata.v_model_yr || ""} ${metadata.v_make_desc || ""} ${metadata.v_model_desc || ""}`
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const InsuranceCompanyToolTip = ({ metadata, cardSettings }) =>
|
||||
cardSettings?.ins_co_nm && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip title={metadata.ins_co_nm || null} kiosk={cardSettings.kiosk}>
|
||||
{metadata.ins_co_nm ? metadata.ins_co_nm : <span> </span>}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const ClaimNumberToolTip = ({ metadata, cardSettings }) =>
|
||||
cardSettings?.clm_no && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip title={metadata.clm_no || null} kiosk={cardSettings.kiosk}>
|
||||
{metadata.clm_no ? metadata.clm_no : <span> </span>}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const EmployeeAssignmentsToolTip = ({
|
||||
metadata,
|
||||
cardSettings,
|
||||
employee_body,
|
||||
employee_prep,
|
||||
employee_refinish,
|
||||
employee_csr
|
||||
}) =>
|
||||
cardSettings?.employeeassignments && (
|
||||
<Col span={24}>
|
||||
<Row>
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip
|
||||
title={
|
||||
employee_body || metadata.labhrs.aggregate.sum.mod_lb_hrs
|
||||
? `B: ${employee_body ? `${employee_body.first_name.substring(0, 3)} ${employee_body.last_name.charAt(0)}` : ""} ${metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`
|
||||
: null
|
||||
}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{employee_body || metadata.labhrs.aggregate.sum.mod_lb_hrs ? (
|
||||
`B: ${employee_body ? `${employee_body.first_name.substring(0, 3)} ${employee_body.last_name.charAt(0)}` : ""} ${metadata.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip
|
||||
title={
|
||||
employee_prep
|
||||
? `P: ${employee_prep ? `${employee_prep.first_name.substring(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""}`
|
||||
: null
|
||||
}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{employee_prep ? (
|
||||
`P: ${employee_prep ? `${employee_prep.first_name.substring(0, 3)} ${employee_prep.last_name.charAt(0)}` : ""}`
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip
|
||||
title={
|
||||
employee_refinish || metadata.larhrs.aggregate.sum.mod_lb_hrs
|
||||
? `R: ${employee_refinish ? `${employee_refinish.first_name.substring(0, 3)} ${employee_refinish.last_name.charAt(0)}` : ""} ${metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`
|
||||
: null
|
||||
}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{employee_refinish || metadata.larhrs.aggregate.sum.mod_lb_hrs ? (
|
||||
`R: ${employee_refinish ? `${employee_refinish.first_name.substring(0, 3)} ${employee_refinish.last_name.charAt(0)}` : ""} ${metadata.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip
|
||||
title={
|
||||
employee_csr ? `C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}` : null
|
||||
}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{employee_csr ? (
|
||||
`C: ${employee_csr ? `${employee_csr.first_name} ${employee_csr.last_name}` : ""}`
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const ActualInToolTip = ({ metadata, cardSettings }) =>
|
||||
cardSettings?.actual_in && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip title={metadata.actual_in || null} kiosk={cardSettings.kiosk}>
|
||||
{metadata.actual_in ? (
|
||||
<Space>
|
||||
<DownloadOutlined />
|
||||
<DateTimeFormatter format="MM/DD">{metadata.actual_in}</DateTimeFormatter>
|
||||
</Space>
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const EstimatorToolTip = ({ metadata, cardSettings }) => {
|
||||
return (
|
||||
cardSettings?.estimator && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip
|
||||
title={metadata.est_ct_fn && metadata.est_ct_ln ? `${metadata.est_ct_fn} ${metadata.est_ct_ln}` : null}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{metadata.est_ct_fn && metadata.est_ct_ln ? (
|
||||
<span>E: {`${metadata.est_ct_fn} ${metadata.est_ct_ln}`}</span>
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const SubtotalTooltip = ({ metadata, cardSettings, t }) => {
|
||||
const amount = metadata?.job_totals?.totals?.subtotal?.amount;
|
||||
const dineroAmount = amount ? Dinero({ amount: parseInt(amount * 100) }).toFormat("0,0.00") : null;
|
||||
|
||||
return (
|
||||
cardSettings?.subtotal && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip
|
||||
title={!!amount ? `${t("production.statistics.currency_symbol")}${dineroAmount}` : null}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{!!amount ? (
|
||||
<span>{`${t("production.statistics.currency_symbol")}${dineroAmount}`}</span>
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const ScheduledCompletionToolTip = ({ metadata, cardSettings, pastDueAlert }) =>
|
||||
cardSettings?.scheduled_completion && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip title={metadata.scheduled_completion || null} kiosk={cardSettings.kiosk}>
|
||||
{metadata.scheduled_completion ? (
|
||||
<Space className={pastDueAlert}>
|
||||
<CalendarOutlined />
|
||||
<DateTimeFormatter format="MM/DD">{metadata.scheduled_completion}</DateTimeFormatter>
|
||||
</Space>
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const AltTransportToolTip = ({ metadata, cardSettings }) =>
|
||||
cardSettings?.ats && (
|
||||
<Col span={12}>
|
||||
<EllipsesToolTip title={metadata.alt_transport || null} kiosk={cardSettings.kiosk}>
|
||||
{metadata.alt_transport ? metadata.alt_transport : <span> </span>}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const SubletsComponent = ({ metadata, cardSettings }) =>
|
||||
cardSettings?.sublets && (
|
||||
<Col span={12}>
|
||||
{metadata.subletLines ? (
|
||||
<ProductionSubletsManageComponent subletJobLines={metadata.subletLines} />
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
</Col>
|
||||
);
|
||||
|
||||
const ProductionNoteComponent = ({ metadata, cardSettings, card }) =>
|
||||
cardSettings?.production_note && (
|
||||
<Col span={24} style={{ margin: "2px 0" }}>
|
||||
<ProductionListColumnProductionNote
|
||||
record={{
|
||||
production_vars: metadata?.production_vars,
|
||||
id: card?.id,
|
||||
refetch: card?.refetch
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
|
||||
const PartsStatusComponent = ({ metadata, cardSettings }) =>
|
||||
cardSettings?.partsstatus && (
|
||||
<Col span={24} style={{ textAlign: "center" }}>
|
||||
{metadata.joblines_status ? <JobPartsQueueCount parts={metadata.joblines_status} /> : <span> </span>}
|
||||
</Col>
|
||||
);
|
||||
|
||||
const TasksToolTip = ({ metadata, cardSettings, t }) =>
|
||||
cardSettings?.tasks && (
|
||||
<Col span={12}>
|
||||
<EllipsesToolTip
|
||||
title={`${t("production.labels.tasks")}: ${metadata.tasks_aggregate?.aggregate?.count || 0}`}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{metadata.tasks_aggregate?.aggregate?.count ? (
|
||||
`T: ${metadata.tasks_aggregate.aggregate.count}`
|
||||
) : (
|
||||
<span>T: 0</span>
|
||||
)}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
);
|
||||
|
||||
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) {
|
||||
const { t } = useTranslation();
|
||||
const { metadata } = card;
|
||||
|
||||
const employees = useMemo(() => bodyshop.employees, [bodyshop.employees]);
|
||||
|
||||
const { employee_body, employee_prep, employee_refinish, employee_csr } = useMemo(() => {
|
||||
return {
|
||||
employee_body: metadata?.employee_body && findEmployeeById(employees, metadata.employee_body),
|
||||
employee_prep: metadata?.employee_prep && findEmployeeById(employees, metadata.employee_prep),
|
||||
employee_refinish: metadata?.employee_refinish && findEmployeeById(employees, metadata.employee_refinish),
|
||||
employee_csr: metadata?.employee_csr && findEmployeeById(employees, metadata.employee_csr)
|
||||
};
|
||||
}, [metadata, employees]);
|
||||
|
||||
const pastDueAlert = useMemo(() => {
|
||||
if (!metadata?.scheduled_completion) return null;
|
||||
const completionDate = dayjs(metadata.scheduled_completion);
|
||||
if (dayjs().isSameOrAfter(completionDate, "day")) return "production-completion-past";
|
||||
if (dayjs().add(1, "day").isSame(completionDate, "day")) return "production-completion-soon";
|
||||
return null;
|
||||
}, [metadata?.scheduled_completion]);
|
||||
|
||||
const totalHrs = useMemo(() => {
|
||||
return metadata?.labhrs && metadata?.larhrs
|
||||
? metadata.labhrs.aggregate.sum.mod_lb_hrs + metadata.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0;
|
||||
}, [metadata?.labhrs, metadata?.larhrs]);
|
||||
|
||||
const bgColor = useMemo(() => cardColor(bodyshop.ssbuckets, totalHrs), [bodyshop.ssbuckets, totalHrs]);
|
||||
const contrastYIQ = useMemo(() => getContrastYIQ(bgColor), [bgColor]);
|
||||
|
||||
const isBodyEmpty = useMemo(() => {
|
||||
return !(
|
||||
cardSettings?.ownr_nm ||
|
||||
cardSettings?.model_info ||
|
||||
cardSettings?.ins_co_nm ||
|
||||
cardSettings?.clm_no ||
|
||||
cardSettings?.employeeassignments ||
|
||||
cardSettings?.actual_in ||
|
||||
cardSettings?.scheduled_completion ||
|
||||
cardSettings?.ats ||
|
||||
cardSettings?.sublets ||
|
||||
cardSettings?.production_note ||
|
||||
cardSettings?.partsstatus ||
|
||||
cardSettings?.estimator ||
|
||||
cardSettings?.subtotal ||
|
||||
cardSettings?.tasks
|
||||
);
|
||||
}, [cardSettings]);
|
||||
|
||||
const headerContent = (
|
||||
<div className="header-content-container">
|
||||
<div className="inner-container">
|
||||
<ProductionAlert
|
||||
record={{
|
||||
id: card.id,
|
||||
production_vars: card?.metadata.production_vars,
|
||||
refetch: card?.refetch
|
||||
}}
|
||||
key="alert"
|
||||
/>
|
||||
{metadata?.suspended && <PauseCircleOutlined className="circle-outline" key="suspended" />}
|
||||
{metadata?.iouparent && (
|
||||
<EllipsesToolTip
|
||||
title={t("jobs.labels.iou")}
|
||||
key="iouparent"
|
||||
className="iouparent"
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
<BranchesOutlined className="branches-outlined" />
|
||||
</EllipsesToolTip>
|
||||
)}
|
||||
</div>
|
||||
<span className="tech-container">
|
||||
<Link to={technician ? `/tech/joblookup?selected=${card.id}` : `/manage/jobs/${card.id}`}>
|
||||
{metadata?.ro_number || t("general.labels.na")}
|
||||
</Link>
|
||||
</span>
|
||||
{isBodyEmpty && (
|
||||
<div className="body-empty-container">
|
||||
<Link to={{ search: `?selected=${card.id}` }}>
|
||||
<EyeFilled />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const bodyContent = (
|
||||
<Row>
|
||||
<OwnerNameToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<ModelInfoToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<InsuranceCompanyToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<ClaimNumberToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<EmployeeAssignmentsToolTip
|
||||
metadata={metadata}
|
||||
cardSettings={cardSettings}
|
||||
employee_body={employee_body}
|
||||
employee_prep={employee_prep}
|
||||
employee_refinish={employee_refinish}
|
||||
employee_csr={employee_csr}
|
||||
/>
|
||||
<EstimatorToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<TasksToolTip metadata={metadata} cardSettings={cardSettings} t={t} />
|
||||
<SubtotalTooltip metadata={metadata} cardSettings={cardSettings} t={t} />
|
||||
<ActualInToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<ScheduledCompletionToolTip metadata={metadata} cardSettings={cardSettings} pastDueAlert={pastDueAlert} />
|
||||
<AltTransportToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||
<SubletsComponent metadata={metadata} cardSettings={cardSettings} />
|
||||
<ProductionNoteComponent metadata={metadata} cardSettings={cardSettings} card={card} />
|
||||
<PartsStatusComponent metadata={metadata} cardSettings={cardSettings} />
|
||||
</Row>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={`react-trello-card ${cardSettings.kiosk ? "kiosk-mode" : ""}`}
|
||||
size="small"
|
||||
style={{
|
||||
backgroundColor: cardSettings?.cardcolor && `rgba(${bgColor.r},${bgColor.g},${bgColor.b},${bgColor.a})`,
|
||||
color: cardSettings?.cardcolor && contrastYIQ
|
||||
}}
|
||||
title={!isBodyEmpty ? headerContent : null}
|
||||
extra={
|
||||
!isBodyEmpty && (
|
||||
<Link to={{ search: `?selected=${card.id}` }}>
|
||||
<EyeFilled />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
>
|
||||
{isBodyEmpty ? headerContent : bodyContent}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -15,13 +15,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.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-color-legend.component.jsx";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
|
||||
import { mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -182,13 +182,10 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
[boardLanes, client, getCardByID, isMoving, t, insertAuditTrail]
|
||||
);
|
||||
|
||||
const cardSettings = useMemo(
|
||||
() =>
|
||||
associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0
|
||||
? associationSettings.kanban_settings
|
||||
: defaultKanbanSettings,
|
||||
[associationSettings]
|
||||
);
|
||||
const cardSettings = useMemo(() => {
|
||||
const kanbanSettings = associationSettings?.kanban_settings;
|
||||
return mergeWithDefaults(kanbanSettings);
|
||||
}, [associationSettings]);
|
||||
|
||||
const handleSettingsChange = useCallback((newSettings) => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -2,11 +2,13 @@ import React, { useMemo } from "react";
|
||||
import { Card, Statistic } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import { statisticsItems, defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
|
||||
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
||||
|
||||
export const StatisticType = {
|
||||
HOURS: "hours",
|
||||
AMOUNT: "amount",
|
||||
JOBS: "jobs"
|
||||
JOBS: "jobs",
|
||||
TASKS: "tasks"
|
||||
};
|
||||
|
||||
const mergeStatistics = (items, values) => {
|
||||
@@ -122,6 +124,20 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
return parseFloat(total.toFixed(2));
|
||||
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
||||
|
||||
const tasksInProduction = useMemo(() => {
|
||||
if (!data || !cardSettings.tasksInProduction) return null;
|
||||
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
||||
}, [data, cardSettings.tasksInProduction]);
|
||||
|
||||
const tasksOnBoard = useMemo(() => {
|
||||
if (!reducerData || !cardSettings.tasksOnBoard) return null;
|
||||
return reducerData.lanes.reduce((acc, lane) => {
|
||||
return (
|
||||
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata.tasks_aggregate?.aggregate?.count || 0), 0)
|
||||
);
|
||||
}, 0);
|
||||
}, [reducerData, cardSettings.tasksOnBoard]);
|
||||
|
||||
const statistics = useMemo(
|
||||
() =>
|
||||
mergeStatistics(statisticsItems, [
|
||||
@@ -134,7 +150,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
{ id: 6, value: totalAmountOnBoard, type: StatisticType.AMOUNT },
|
||||
{ id: 7, value: totalLABOnBoard, type: StatisticType.HOURS },
|
||||
{ id: 8, value: totalLAROnBoard, type: StatisticType.HOURS },
|
||||
{ id: 9, value: jobsOnBoard, type: StatisticType.JOBS }
|
||||
{ id: 9, value: jobsOnBoard, type: StatisticType.JOBS },
|
||||
{ id: 10, value: tasksOnBoard, type: StatisticType.TASKS },
|
||||
{ id: 11, value: tasksInProduction, type: StatisticType.TASKS }
|
||||
]),
|
||||
[
|
||||
totalHrs,
|
||||
@@ -146,7 +164,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
totalAmountOnBoard,
|
||||
totalLABOnBoard,
|
||||
totalLAROnBoard,
|
||||
jobsOnBoard
|
||||
jobsOnBoard,
|
||||
tasksOnBoard,
|
||||
tasksInProduction
|
||||
]
|
||||
);
|
||||
|
||||
@@ -187,37 +207,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
};
|
||||
|
||||
ProductionStatistics.propTypes = {
|
||||
data: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
labhrs: PropTypes.object,
|
||||
larhrs: PropTypes.object,
|
||||
job_totals: PropTypes.object
|
||||
})
|
||||
).isRequired,
|
||||
cardSettings: PropTypes.shape({
|
||||
totalHrs: PropTypes.bool,
|
||||
totalLAB: PropTypes.bool,
|
||||
totalLAR: PropTypes.bool,
|
||||
jobsInProduction: PropTypes.bool,
|
||||
totalAmountInProduction: PropTypes.bool,
|
||||
totalHrsOnBoard: PropTypes.bool,
|
||||
totalLABOnBoard: PropTypes.bool,
|
||||
totalLAROnBoard: PropTypes.bool,
|
||||
jobsOnBoard: PropTypes.bool,
|
||||
totalAmountOnBoard: PropTypes.bool,
|
||||
statisticsOrder: PropTypes.arrayOf(PropTypes.number)
|
||||
}).isRequired,
|
||||
reducerData: PropTypes.shape({
|
||||
lanes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
cards: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
metadata: PropTypes.object
|
||||
})
|
||||
).isRequired
|
||||
})
|
||||
).isRequired
|
||||
})
|
||||
data: PropTypes.array.isRequired,
|
||||
cardSettings: PropTypes.object.isRequired,
|
||||
reducerData: PropTypes.object
|
||||
};
|
||||
|
||||
export default ProductionStatistics;
|
||||
|
||||
@@ -18,7 +18,8 @@ const InformationSettings = ({ t }) => (
|
||||
"sublets",
|
||||
"partsstatus",
|
||||
"estimator",
|
||||
"subtotal"
|
||||
"subtotal",
|
||||
"tasks"
|
||||
].map((item) => (
|
||||
<Col span={4} key={item}>
|
||||
<Form.Item name={item} valuePropName="checked">
|
||||
|
||||
@@ -8,7 +8,9 @@ const statisticsItems = [
|
||||
{ id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" },
|
||||
{ id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" },
|
||||
{ id: 8, name: "totalLAROnBoard", label: "total_lar_on_board" },
|
||||
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" }
|
||||
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" },
|
||||
{ id: 10, name: "tasksOnBoard", label: "tasks_on_board" },
|
||||
{ id: 11, name: "tasksInProduction", label: "tasks_in_production" }
|
||||
];
|
||||
|
||||
const defaultKanbanSettings = {
|
||||
@@ -23,6 +25,7 @@ const defaultKanbanSettings = {
|
||||
scheduled_completion: true,
|
||||
cardcolor: false,
|
||||
orientation: false,
|
||||
tasks: false,
|
||||
cardSize: "small",
|
||||
model_info: true,
|
||||
kiosk: false,
|
||||
@@ -35,6 +38,8 @@ const defaultKanbanSettings = {
|
||||
totalLABOnBoard: false,
|
||||
totalLAROnBoard: false,
|
||||
jobsOnBoard: false,
|
||||
tasksOnBoard: false,
|
||||
tasksInProduction: false,
|
||||
totalAmountOnBoard: true,
|
||||
estimator: false,
|
||||
subtotal: false,
|
||||
@@ -43,4 +48,20 @@ const defaultKanbanSettings = {
|
||||
selectedEstimators: []
|
||||
};
|
||||
|
||||
export { defaultKanbanSettings, statisticsItems };
|
||||
const mergeWithDefaults = (settings) => {
|
||||
// Create a new object that starts with the default settings
|
||||
const mergedSettings = { ...defaultKanbanSettings };
|
||||
|
||||
// Override with the provided settings, if any
|
||||
if (settings) {
|
||||
for (const key in settings) {
|
||||
if (settings.hasOwnProperty(key)) {
|
||||
mergedSettings[key] = settings[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mergedSettings;
|
||||
};
|
||||
|
||||
export { defaultKanbanSettings, statisticsItems, mergeWithDefaults };
|
||||
|
||||
@@ -3,13 +3,14 @@ import { Button, Card, Col, Form, notification, Popover, Row, Tabs } from "antd"
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
|
||||
import { defaultKanbanSettings } from "./defaultKanbanSettings.js";
|
||||
import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
|
||||
import LayoutSettings from "./LayoutSettings.jsx";
|
||||
import InformationSettings from "./InformationSettings.jsx";
|
||||
import StatisticsSettings from "./StatisticsSettings.jsx";
|
||||
import FilterSettings from "./FilterSettings.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) {
|
||||
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) {
|
||||
const [form] = Form.useForm();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -23,16 +24,11 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
|
||||
|
||||
useEffect(() => {
|
||||
if (associationSettings?.kanban_settings) {
|
||||
form.setFieldsValue(associationSettings.kanban_settings);
|
||||
if (associationSettings.kanban_settings.statisticsOrder) {
|
||||
setStatisticsOrder(associationSettings.kanban_settings.statisticsOrder);
|
||||
}
|
||||
if (associationSettings.kanban_settings.selectedMdInsCos) {
|
||||
setSelectedMdInsCos(associationSettings.kanban_settings.selectedMdInsCos);
|
||||
}
|
||||
if (associationSettings.kanban_settings.selectedEstimators) {
|
||||
setSelectedEstimators(associationSettings.kanban_settings.selectedEstimators);
|
||||
}
|
||||
const finalSettings = mergeWithDefaults(associationSettings.kanban_settings);
|
||||
form.setFieldsValue(finalSettings);
|
||||
setStatisticsOrder(finalSettings.statisticsOrder);
|
||||
setSelectedMdInsCos(finalSettings.selectedMdInsCos);
|
||||
setSelectedEstimators(finalSettings.selectedEstimators);
|
||||
}
|
||||
}, [form, associationSettings]);
|
||||
|
||||
@@ -155,3 +151,12 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
ProductionBoardKanbanSettings.propTypes = {
|
||||
associationSettings: PropTypes.object,
|
||||
parentLoading: PropTypes.func.isRequired,
|
||||
bodyshop: PropTypes.object.isRequired,
|
||||
data: PropTypes.array
|
||||
};
|
||||
|
||||
export default ProductionBoardKanbanSettings;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { EyeInvisibleOutlined, EyeOutlined } 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";
|
||||
import ProductionBoardCard from "../../production-board-kanban-card.component.jsx";
|
||||
import HeightMemoryWrapper from "../components/HeightMemoryWrapper.jsx";
|
||||
import SizeMemoryWrapper from "../components/SizeMemoryWrapper.jsx";
|
||||
import ListComponent from "../components/ListComponent.jsx";
|
||||
|
||||
Reference in New Issue
Block a user