diff --git a/_reference/productionBoardNotes.md b/_reference/productionBoardNotes.md index 31bf2b4dc..96ea0d46f 100644 --- a/_reference/productionBoardNotes.md +++ b/_reference/productionBoardNotes.md @@ -31,3 +31,11 @@ These allow users to turn fields on or off, turning them all off will show the card in the most minimal form + +### Statistics + +- The statistics section allows users to see accumulations of both jobs on the board, and jobs in production. +- you can click a statistic to turn it on and off, and drag and drop the statistics to rearrange them + +### Filters +- Allows you to set, and persist filters for estimators and insurance companies diff --git a/client/index.html b/client/index.html index d5561433e..e75f91325 100644 --- a/client/index.html +++ b/client/index.html @@ -1,174 +1,174 @@ - - - <% if (env.VITE_APP_INSTANCE === 'IMEX') { %> - - <% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %> - - <% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %> - - <% } %> + + + <% if (env.VITE_APP_INSTANCE === 'IMEX') { %> + + <% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %> + + <% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %> + + <% } %> - - - - - - - - + + + + + - <% if (env.VITE_APP_INSTANCE === 'IMEX') { %> - - ImEX Online - - <% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %> - - Rome Online + Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will + work correctly both with client-side routing and a non-root public URL. + Learn how to configure a non-root public URL by running `npm run build`. + --> + <% if (env.VITE_APP_INSTANCE === 'IMEX') { %> + + ImEX Online + + <% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %> + + Rome Online - + - + - - - - - + + + + + - <% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %> - ProManager - + <% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %> + ProManager + - <% } %> - - - - -
+ !(function () { + for (var o = 0; o < e.length; o++) { + var r = e[o]; + n[r] = t(r); + } + })(), + (function () { + var e = document.createElement('script'); + (e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js'); + var n = document.head; + n.insertBefore(e, n.firstChild); + })(); + } + })(); + + + + +
- - + + diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index dd79912c1..827d0dfc7 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -161,3 +161,15 @@ .rowWithColor > td { background-color: var(--bgColor) !important; } + +.muted-button { + color: lightgray; + border: none; + background: none; + cursor: pointer; + font-size: 16px; /* Adjust as needed */ +} + +.muted-button:hover { + color: darkgrey; +} diff --git a/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx b/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx index f5ce3d3d4..937ff9ce1 100644 --- a/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx +++ b/client/src/components/bill-detail-edit/bill-detail-edit-component.jsx @@ -1,6 +1,6 @@ +import { PageHeader } from "@ant-design/pro-layout"; import { useMutation, useQuery } from "@apollo/client"; import { Button, Divider, Form, Popconfirm, Space } from "antd"; -import dayjs from "../../utils/day"; import queryString from "query-string"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -13,6 +13,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; +import dayjs from "../../utils/day"; import AlertComponent from "../alert/alert.component"; import BillFormContainer from "../bill-form/bill-form.container"; import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component"; @@ -22,7 +23,6 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import BillDetailEditReturn from "./bill-detail-edit-return.component"; -import { PageHeader } from "@ant-design/pro-layout"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -153,6 +153,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail if (!search.billid) return <>; //
{t("bills.labels.noneselected")}
; const exported = data && data.bills_by_pk && data.bills_by_pk.exported; + const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse; return ( <> @@ -188,7 +189,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail } />
- + {t("general.labels.media")} {bodyshop.uselocalmediaserver ? ( - + { + rome: () => { if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") { // line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`; line.mod_lbr_ty = "LAR"; } - } + }, + promanager: "USE_ROME" }); }); diff --git a/client/src/components/production-board-kanban-card/production-board-kanban-card-color-legend.component.jsx b/client/src/components/production-board-kanban/production-board-kanban-card-color-legend.component.jsx similarity index 100% rename from client/src/components/production-board-kanban-card/production-board-kanban-card-color-legend.component.jsx rename to client/src/components/production-board-kanban/production-board-kanban-card-color-legend.component.jsx diff --git a/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx b/client/src/components/production-board-kanban/production-board-kanban-card.component.jsx similarity index 95% rename from client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx rename to client/src/components/production-board-kanban/production-board-kanban-card.component.jsx index 4febb86c0..798f7f7ec 100644 --- a/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban-card.component.jsx @@ -290,6 +290,22 @@ const PartsStatusComponent = ({ metadata, cardSettings }) => ); +const TasksToolTip = ({ metadata, cardSettings, t }) => + cardSettings?.tasks && ( + + + {metadata.tasks_aggregate?.aggregate?.count ? ( + `T: ${metadata.tasks_aggregate.aggregate.count}` + ) : ( + T: 0 + )} + + + ); + export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) { const { t } = useTranslation(); const { metadata } = card; @@ -336,21 +352,15 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe cardSettings?.production_note || cardSettings?.partsstatus || cardSettings?.estimator || - cardSettings?.subtotal + cardSettings?.subtotal || + cardSettings?.tasks ); }, [cardSettings]); const headerContent = (
- + {metadata?.suspended && } {metadata?.iouparent && ( + diff --git a/client/src/components/production-board-kanban/production-board-kanban.component.jsx b/client/src/components/production-board-kanban/production-board-kanban.component.jsx index afd0e94e1..62917e9bd 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.component.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.component.jsx @@ -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); diff --git a/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx b/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx index 482ddb726..1af5ec59c 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx +++ b/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx @@ -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; diff --git a/client/src/components/production-board-kanban/settings/InformationSettings.jsx b/client/src/components/production-board-kanban/settings/InformationSettings.jsx index c50f61697..3725d1ab8 100644 --- a/client/src/components/production-board-kanban/settings/InformationSettings.jsx +++ b/client/src/components/production-board-kanban/settings/InformationSettings.jsx @@ -18,7 +18,8 @@ const InformationSettings = ({ t }) => ( "sublets", "partsstatus", "estimator", - "subtotal" + "subtotal", + "tasks" ].map((item) => ( diff --git a/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js b/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js index ce0760637..0d10e4e39 100644 --- a/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js +++ b/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js @@ -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 }; diff --git a/client/src/components/production-board-kanban/settings/production-board-kanban.settings.component.jsx b/client/src/components/production-board-kanban/settings/production-board-kanban.settings.component.jsx index 38b0c9350..23da13412 100644 --- a/client/src/components/production-board-kanban/settings/production-board-kanban.settings.component.jsx +++ b/client/src/components/production-board-kanban/settings/production-board-kanban.settings.component.jsx @@ -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 ); } + +ProductionBoardKanbanSettings.propTypes = { + associationSettings: PropTypes.object, + parentLoading: PropTypes.func.isRequired, + bodyshop: PropTypes.object.isRequired, + data: PropTypes.array +}; + +export default ProductionBoardKanbanSettings; diff --git a/client/src/components/production-board-kanban/trello-board/controllers/Lane.jsx b/client/src/components/production-board-kanban/trello-board/controllers/Lane.jsx index 493812840..cd30952fb 100644 --- a/client/src/components/production-board-kanban/trello-board/controllers/Lane.jsx +++ b/client/src/components/production-board-kanban/trello-board/controllers/Lane.jsx @@ -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"; diff --git a/client/src/components/production-list-columns/production-list-columns.alert.component.jsx b/client/src/components/production-list-columns/production-list-columns.alert.component.jsx index e9a4e2d62..db219745b 100644 --- a/client/src/components/production-list-columns/production-list-columns.alert.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.alert.component.jsx @@ -1,6 +1,6 @@ -import { ExclamationCircleFilled } from "@ant-design/icons"; +import { ExclamationCircleFilled, PlusCircleFilled } from "@ant-design/icons"; import { useMutation } from "@apollo/client"; -import { Button } from "antd"; +import { Button, Popconfirm } from "antd"; import React, { useCallback } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -8,6 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; +import { useTranslation } from "react-i18next"; const mapStateToProps = createStructuredSelector({}); @@ -22,22 +23,24 @@ const mapDispatchToProps = (dispatch) => ({ ) }); -const ProductionListColumnAlert = ({ record, insertAuditTrail }) => { +const ProductionListColumnAlert = ({ id, productionVars, refetch, insertAuditTrail }) => { const [updateAlert] = useMutation(UPDATE_JOB); + const { t } = useTranslation(); const handleAlertToggle = useCallback(() => { logImEXEvent("production_toggle_alert"); - const newAlertState = !!record.production_vars?.alert ? !record.production_vars.alert : true; + const newAlertState = !!productionVars?.alert ? !productionVars?.alert : true; + const finalProductionVars = { + ...productionVars, + alert: newAlertState + }; updateAlert({ variables: { - jobId: record.id, + jobId: id, job: { - production_vars: { - ...record.production_vars, - alert: newAlertState - } + production_vars: finalProductionVars } } }).catch((err) => { @@ -45,17 +48,26 @@ const ProductionListColumnAlert = ({ record, insertAuditTrail }) => { }); insertAuditTrail({ - jobid: record.id, + jobid: id, operation: AuditTrailMapping.alertToggle(newAlertState), type: "alertToggle" }); - if (record.refetch) record.refetch(); - }, [updateAlert, insertAuditTrail, record]); + if (refetch) refetch(); + }, [updateAlert, insertAuditTrail, id, productionVars, refetch]); - if (!record.production_vars?.alert) return null; - - return