Merged in feature/IO-2868-Add-Tasks-To-Production-Board (pull request #1581)

- Add Tasks to production board (and required refactors)
This commit is contained in:
Dave Richer
2024-08-08 14:15:41 +00:00
12 changed files with 121 additions and 64 deletions

View File

@@ -290,6 +290,22 @@ const PartsStatusComponent = ({ metadata, cardSettings }) =>
</Col> </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 }) { export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { metadata } = card; const { metadata } = card;
@@ -336,7 +352,8 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
cardSettings?.production_note || cardSettings?.production_note ||
cardSettings?.partsstatus || cardSettings?.partsstatus ||
cardSettings?.estimator || cardSettings?.estimator ||
cardSettings?.subtotal cardSettings?.subtotal ||
cardSettings?.tasks
); );
}, [cardSettings]); }, [cardSettings]);
@@ -393,6 +410,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
employee_csr={employee_csr} employee_csr={employee_csr}
/> />
<EstimatorToolTip metadata={metadata} cardSettings={cardSettings} /> <EstimatorToolTip metadata={metadata} cardSettings={cardSettings} />
<TasksToolTip metadata={metadata} cardSettings={cardSettings} t={t} />
<SubtotalTooltip metadata={metadata} cardSettings={cardSettings} t={t} /> <SubtotalTooltip metadata={metadata} cardSettings={cardSettings} t={t} />
<ActualInToolTip metadata={metadata} cardSettings={cardSettings} /> <ActualInToolTip metadata={metadata} cardSettings={cardSettings} />
<ScheduledCompletionToolTip metadata={metadata} cardSettings={cardSettings} pastDueAlert={pastDueAlert} /> <ScheduledCompletionToolTip metadata={metadata} cardSettings={cardSettings} pastDueAlert={pastDueAlert} />

View File

@@ -15,13 +15,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component"; import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component"; import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
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-color-legend.component.jsx";
import "./production-board-kanban.styles.scss"; import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js"; import { createBoardData } from "./production-board-kanban.utils.js";
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx"; import ProductionBoardKanbanSettings from "./settings/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";
import { defaultKanbanSettings } from "./settings/defaultKanbanSettings.js"; import { mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container"; import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -182,13 +182,10 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
[boardLanes, client, getCardByID, isMoving, t, insertAuditTrail] [boardLanes, client, getCardByID, isMoving, t, insertAuditTrail]
); );
const cardSettings = useMemo( const cardSettings = useMemo(() => {
() => const kanbanSettings = associationSettings?.kanban_settings;
associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0 return mergeWithDefaults(kanbanSettings);
? associationSettings.kanban_settings }, [associationSettings]);
: defaultKanbanSettings,
[associationSettings]
);
const handleSettingsChange = useCallback((newSettings) => { const handleSettingsChange = useCallback((newSettings) => {
setLoading(true); setLoading(true);

View File

@@ -2,11 +2,13 @@ import React, { useMemo } from "react";
import { Card, Statistic } from "antd"; import { Card, Statistic } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { statisticsItems, defaultKanbanSettings } from "./settings/defaultKanbanSettings.js"; import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
export const StatisticType = { export const StatisticType = {
HOURS: "hours", HOURS: "hours",
AMOUNT: "amount", AMOUNT: "amount",
JOBS: "jobs" JOBS: "jobs",
TASKS: "tasks"
}; };
const mergeStatistics = (items, values) => { const mergeStatistics = (items, values) => {
@@ -122,6 +124,20 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
return parseFloat(total.toFixed(2)); return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalAmountOnBoard]); }, [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( const statistics = useMemo(
() => () =>
mergeStatistics(statisticsItems, [ mergeStatistics(statisticsItems, [
@@ -134,7 +150,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
{ id: 6, value: totalAmountOnBoard, type: StatisticType.AMOUNT }, { id: 6, value: totalAmountOnBoard, type: StatisticType.AMOUNT },
{ id: 7, value: totalLABOnBoard, type: StatisticType.HOURS }, { id: 7, value: totalLABOnBoard, type: StatisticType.HOURS },
{ id: 8, value: totalLAROnBoard, 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, totalHrs,
@@ -146,7 +164,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
totalAmountOnBoard, totalAmountOnBoard,
totalLABOnBoard, totalLABOnBoard,
totalLAROnBoard, totalLAROnBoard,
jobsOnBoard jobsOnBoard,
tasksOnBoard,
tasksInProduction
] ]
); );
@@ -187,37 +207,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
}; };
ProductionStatistics.propTypes = { ProductionStatistics.propTypes = {
data: PropTypes.arrayOf( data: PropTypes.array.isRequired,
PropTypes.shape({ cardSettings: PropTypes.object.isRequired,
labhrs: PropTypes.object, reducerData: 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
})
}; };
export default ProductionStatistics; export default ProductionStatistics;

View File

@@ -18,7 +18,8 @@ const InformationSettings = ({ t }) => (
"sublets", "sublets",
"partsstatus", "partsstatus",
"estimator", "estimator",
"subtotal" "subtotal",
"tasks"
].map((item) => ( ].map((item) => (
<Col span={4} key={item}> <Col span={4} key={item}>
<Form.Item name={item} valuePropName="checked"> <Form.Item name={item} valuePropName="checked">

View File

@@ -8,7 +8,9 @@ const statisticsItems = [
{ id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" }, { id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" },
{ id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" }, { id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" },
{ id: 8, name: "totalLAROnBoard", label: "total_lar_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 = { const defaultKanbanSettings = {
@@ -23,6 +25,7 @@ const defaultKanbanSettings = {
scheduled_completion: true, scheduled_completion: true,
cardcolor: false, cardcolor: false,
orientation: false, orientation: false,
tasks: false,
cardSize: "small", cardSize: "small",
model_info: true, model_info: true,
kiosk: false, kiosk: false,
@@ -35,6 +38,8 @@ const defaultKanbanSettings = {
totalLABOnBoard: false, totalLABOnBoard: false,
totalLAROnBoard: false, totalLAROnBoard: false,
jobsOnBoard: false, jobsOnBoard: false,
tasksOnBoard: false,
tasksInProduction: false,
totalAmountOnBoard: true, totalAmountOnBoard: true,
estimator: false, estimator: false,
subtotal: false, subtotal: false,
@@ -43,4 +48,20 @@ const defaultKanbanSettings = {
selectedEstimators: [] 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 };

View File

@@ -3,13 +3,14 @@ import { Button, Card, Col, Form, notification, Popover, Row, Tabs } from "antd"
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js"; 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 LayoutSettings from "./LayoutSettings.jsx";
import InformationSettings from "./InformationSettings.jsx"; import InformationSettings from "./InformationSettings.jsx";
import StatisticsSettings from "./StatisticsSettings.jsx"; import StatisticsSettings from "./StatisticsSettings.jsx";
import FilterSettings from "./FilterSettings.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 [form] = Form.useForm();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -23,16 +24,11 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
useEffect(() => { useEffect(() => {
if (associationSettings?.kanban_settings) { if (associationSettings?.kanban_settings) {
form.setFieldsValue(associationSettings.kanban_settings); const finalSettings = mergeWithDefaults(associationSettings.kanban_settings);
if (associationSettings.kanban_settings.statisticsOrder) { form.setFieldsValue(finalSettings);
setStatisticsOrder(associationSettings.kanban_settings.statisticsOrder); setStatisticsOrder(finalSettings.statisticsOrder);
} setSelectedMdInsCos(finalSettings.selectedMdInsCos);
if (associationSettings.kanban_settings.selectedMdInsCos) { setSelectedEstimators(finalSettings.selectedEstimators);
setSelectedMdInsCos(associationSettings.kanban_settings.selectedMdInsCos);
}
if (associationSettings.kanban_settings.selectedEstimators) {
setSelectedEstimators(associationSettings.kanban_settings.selectedEstimators);
}
} }
}, [form, associationSettings]); }, [form, associationSettings]);
@@ -155,3 +151,12 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
</Popover> </Popover>
); );
} }
ProductionBoardKanbanSettings.propTypes = {
associationSettings: PropTypes.object,
parentLoading: PropTypes.func.isRequired,
bodyshop: PropTypes.object.isRequired,
data: PropTypes.array
};
export default ProductionBoardKanbanSettings;

View File

@@ -12,7 +12,7 @@ import { EyeInvisibleOutlined, EyeOutlined } from "@ant-design/icons";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../../../redux/user/user.selectors.js"; import { selectBodyshop } from "../../../../redux/user/user.selectors.js";
import { selectTechnician } from "../../../../redux/tech/tech.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 HeightMemoryWrapper from "../components/HeightMemoryWrapper.jsx";
import SizeMemoryWrapper from "../components/SizeMemoryWrapper.jsx"; import SizeMemoryWrapper from "../components/SizeMemoryWrapper.jsx";
import ListComponent from "../components/ListComponent.jsx"; import ListComponent from "../components/ListComponent.jsx";

View File

@@ -2465,6 +2465,11 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
export const QUERY_JOBS_IN_PRODUCTION = gql` export const QUERY_JOBS_IN_PRODUCTION = gql`
query QUERY_JOBS_IN_PRODUCTION { query QUERY_JOBS_IN_PRODUCTION {
jobs(where: { inproduction: { _eq: true } }) { jobs(where: { inproduction: { _eq: true } }) {
tasks_aggregate(where: { completed: { _eq: false }, deleted: { _eq: false } }) {
aggregate {
count
}
}
id id
updated_at updated_at
comment comment

View File

@@ -2757,7 +2757,9 @@
"total_lab_on_board": "Body Hours on Board", "total_lab_on_board": "Body Hours on Board",
"total_lar_on_board": "Refinish Hours on Board", "total_lar_on_board": "Refinish Hours on Board",
"total_amount_on_board": "Dollars on Board", "total_amount_on_board": "Dollars on Board",
"total_jobs_on_board": "Jobs on Board" "total_jobs_on_board": "Jobs on Board",
"tasks_in_production": "Tasks in Production",
"tasks_on_board": "Tasks on Board"
} }
}, },
"actions": { "actions": {
@@ -2792,6 +2794,7 @@
"model_info": "Vehicle Info", "model_info": "Vehicle Info",
"actual_in": "Actual In", "actual_in": "Actual In",
"alert": "Alert", "alert": "Alert",
"tasks": "Tasks",
"alertoff": "Remove alert from Job", "alertoff": "Remove alert from Job",
"alerton": "Add alert to Job", "alerton": "Add alert to Job",
"ats": "Alternative Transportation", "ats": "Alternative Transportation",
@@ -2845,6 +2848,9 @@
"total_lar_on_board": "Refinish Hours on Board", "total_lar_on_board": "Refinish Hours on Board",
"total_amount_on_board": "Dollars on Board", "total_amount_on_board": "Dollars on Board",
"total_jobs_on_board": "Jobs on Board", "total_jobs_on_board": "Jobs on Board",
"tasks_in_production": "Tasks in Production",
"tasks_on_board": "Tasks on Board",
"tasks": "Tasks",
"hours": "Hours", "hours": "Hours",
"currency_symbol": "$", "currency_symbol": "$",
"jobs": "Jobs" "jobs": "Jobs"

View File

@@ -2757,7 +2757,9 @@
"total_lab_on_board": "", "total_lab_on_board": "",
"total_lar_on_board": "", "total_lar_on_board": "",
"total_amount_on_board": "", "total_amount_on_board": "",
"total_jobs_on_board": "" "total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": ""
} }
}, },
"actions": { "actions": {
@@ -2792,6 +2794,7 @@
"model_info": "", "model_info": "",
"actual_in": "", "actual_in": "",
"alert": "", "alert": "",
"tasks": "",
"alertoff": "", "alertoff": "",
"alerton": "", "alerton": "",
"ats": "", "ats": "",
@@ -2845,6 +2848,9 @@
"total_lar_on_board": "", "total_lar_on_board": "",
"total_amount_on_board": "", "total_amount_on_board": "",
"total_jobs_on_board": "", "total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": "",
"tasks": "",
"hours": "", "hours": "",
"currency_symbol": "", "currency_symbol": "",
"jobs": "" "jobs": ""

View File

@@ -2757,7 +2757,9 @@
"total_lab_on_board": "", "total_lab_on_board": "",
"total_lar_on_board": "", "total_lar_on_board": "",
"total_amount_on_board": "", "total_amount_on_board": "",
"total_jobs_on_board": "" "total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": ""
} }
}, },
"actions": { "actions": {
@@ -2792,6 +2794,7 @@
"model_info": "", "model_info": "",
"actual_in": "", "actual_in": "",
"alert": "", "alert": "",
"tasks": "",
"alertoff": "", "alertoff": "",
"alerton": "", "alerton": "",
"ats": "", "ats": "",
@@ -2845,6 +2848,9 @@
"total_lar_on_board": "", "total_lar_on_board": "",
"total_amount_on_board": "", "total_amount_on_board": "",
"total_jobs_on_board": "", "total_jobs_on_board": "",
"tasks_in_production": "",
"tasks_on_board": "",
"tasks": "",
"hours": "", "hours": "",
"currency_symbol": "", "currency_symbol": "",
"jobs": "" "jobs": ""