From 2a1ec4eff3f18a3af132c1cdd1104011d5586412 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 6 Aug 2024 09:15:37 -0700 Subject: [PATCH 01/53] IO-2861 Disable Editing of Vendor and Invoice Number when In House Signed-off-by: Allan Carr --- .../bill-detail-edit/bill-detail-edit-component.jsx | 7 ++++--- client/src/components/bill-form/bill-form.component.jsx | 7 ++++--- client/src/components/bill-form/bill-form.container.jsx | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) 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 ? ( - + Date: Tue, 6 Aug 2024 11:54:22 -0700 Subject: [PATCH 02/53] IO-2864 Adjust to LAET and LAUT adjustment to target both Rome and ProManager Signed-off-by: Allan Carr --- .../jobs-available-table.container.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx index 5e7681853..d9964d090 100644 --- a/client/src/components/jobs-available-table/jobs-available-table.container.jsx +++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx @@ -3,7 +3,6 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Col, Row, notification } from "antd"; import Axios from "axios"; import _ from "lodash"; -import dayjs from "../../utils/day"; import queryString from "query-string"; import React, { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -24,6 +23,8 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto import AuditTrailMapping from "../../utils/AuditTrailMappings"; import confirmDialog from "../../utils/asyncConfirm"; import CriticalPartsScan from "../../utils/criticalPartsScan"; +import dayjs from "../../utils/day"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; import AlertComponent from "../alert/alert.component"; import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component"; import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container"; @@ -32,7 +33,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util"; import HeaderFields from "./jobs-available-supplement.headerfields"; import JobsAvailableTableComponent from "./jobs-available-table.component"; -import InstanceRenderManager from "../../utils/instanceRenderMgr"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -580,12 +580,13 @@ function ResolveCCCLineIssues(estData, bodyshop) { InstanceRenderManager({ executeFunction: true, args: [], - promanager: () => { + 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" }); }); From 883043cde379ce594743f3a6ac598d370813d15a Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 6 Aug 2024 17:39:55 -0700 Subject: [PATCH 03/53] IO-2866 Add OP20 to masterdata Signed-off-by: Allan Carr --- .../1722990947416_add_op20_to_masterdata/down.sql | 9 +++++++++ .../1722990947416_add_op20_to_masterdata/up.sql | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 hasura/migrations/1722990947416_add_op20_to_masterdata/down.sql create mode 100644 hasura/migrations/1722990947416_add_op20_to_masterdata/up.sql diff --git a/hasura/migrations/1722990947416_add_op20_to_masterdata/down.sql b/hasura/migrations/1722990947416_add_op20_to_masterdata/down.sql new file mode 100644 index 000000000..c9f184e41 --- /dev/null +++ b/hasura/migrations/1722990947416_add_op20_to_masterdata/down.sql @@ -0,0 +1,9 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- UPDATE "public"."masterdata" +-- SET value = jsonb_set( +-- value::jsonb, +-- '{OP20}', +-- '{"desc": "REMOVE AND REINSTALL", "opcode": "OP20", "partcode": "PAE"}'::jsonb, +-- true +-- ); diff --git a/hasura/migrations/1722990947416_add_op20_to_masterdata/up.sql b/hasura/migrations/1722990947416_add_op20_to_masterdata/up.sql new file mode 100644 index 000000000..583e21e05 --- /dev/null +++ b/hasura/migrations/1722990947416_add_op20_to_masterdata/up.sql @@ -0,0 +1,7 @@ +UPDATE "public"."masterdata" +SET value = jsonb_set( + value::jsonb, + '{OP20}', + '{"desc": "REMOVE AND REINSTALL", "opcode": "OP20", "partcode": "PAE"}'::jsonb, + true +); From 5d1f61753b2ecc12d39eef8294ac11c9ce9e715e Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 7 Aug 2024 10:36:00 -0400 Subject: [PATCH 04/53] - Add Tasks to production board (and required refactors) Signed-off-by: Dave Richer --- ...ard-kanban-card-color-legend.component.jsx | 0 ...production-board-kanban-card.component.jsx | 20 +++++- .../production-board-kanban.component.jsx | 15 ++--- .../production-board-kanban.statistics.jsx | 62 ++++++++----------- .../settings/InformationSettings.jsx | 3 +- .../settings/defaultKanbanSettings.js | 25 +++++++- ...uction-board-kanban.settings.component.jsx | 29 +++++---- .../trello-board/controllers/Lane.jsx | 2 +- client/src/graphql/jobs.queries.js | 5 ++ client/src/translations/en_us/common.json | 8 ++- client/src/translations/es/common.json | 8 ++- client/src/translations/fr/common.json | 8 ++- 12 files changed, 121 insertions(+), 64 deletions(-) rename client/src/components/{production-board-kanban-card => production-board-kanban}/production-board-kanban-card-color-legend.component.jsx (100%) rename client/src/components/{production-board-kanban-card => production-board-kanban}/production-board-kanban-card.component.jsx (96%) 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 96% 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..e3fe39ff8 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,7 +352,8 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe cardSettings?.production_note || cardSettings?.partsstatus || cardSettings?.estimator || - cardSettings?.subtotal + cardSettings?.subtotal || + cardSettings?.tasks ); }, [cardSettings]); @@ -393,6 +410,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe employee_csr={employee_csr} /> + 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/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 51ff6927c..569936b7c 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -2465,6 +2465,11 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql` export const QUERY_JOBS_IN_PRODUCTION = gql` query QUERY_JOBS_IN_PRODUCTION { jobs(where: { inproduction: { _eq: true } }) { + tasks_aggregate(where: { completed: { _eq: false }, deleted: { _eq: false } }) { + aggregate { + count + } + } id updated_at comment diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index f3a95722e..f35baa1d8 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2757,7 +2757,9 @@ "total_lab_on_board": "Body Hours on Board", "total_lar_on_board": "Refinish Hours 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": { @@ -2792,6 +2794,7 @@ "model_info": "Vehicle Info", "actual_in": "Actual In", "alert": "Alert", + "tasks": "Tasks", "alertoff": "Remove alert from Job", "alerton": "Add alert to Job", "ats": "Alternative Transportation", @@ -2845,6 +2848,9 @@ "total_lar_on_board": "Refinish Hours on Board", "total_amount_on_board": "Dollars 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", "currency_symbol": "$", "jobs": "Jobs" diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 30cac520b..1d89e15ea 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2757,7 +2757,9 @@ "total_lab_on_board": "", "total_lar_on_board": "", "total_amount_on_board": "", - "total_jobs_on_board": "" + "total_jobs_on_board": "", + "tasks_in_production": "", + "tasks_on_board": "" } }, "actions": { @@ -2792,6 +2794,7 @@ "model_info": "", "actual_in": "", "alert": "", + "tasks": "", "alertoff": "", "alerton": "", "ats": "", @@ -2845,6 +2848,9 @@ "total_lar_on_board": "", "total_amount_on_board": "", "total_jobs_on_board": "", + "tasks_in_production": "", + "tasks_on_board": "", + "tasks": "", "hours": "", "currency_symbol": "", "jobs": "" diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 967aa9003..4806de18c 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2757,7 +2757,9 @@ "total_lab_on_board": "", "total_lar_on_board": "", "total_amount_on_board": "", - "total_jobs_on_board": "" + "total_jobs_on_board": "", + "tasks_in_production": "", + "tasks_on_board": "" } }, "actions": { @@ -2792,6 +2794,7 @@ "model_info": "", "actual_in": "", "alert": "", + "tasks": "", "alertoff": "", "alerton": "", "ats": "", @@ -2845,6 +2848,9 @@ "total_lar_on_board": "", "total_amount_on_board": "", "total_jobs_on_board": "", + "tasks_in_production": "", + "tasks_on_board": "", + "tasks": "", "hours": "", "currency_symbol": "", "jobs": "" From efda254981ef0a558696a73b52ea54f393bc1954 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 8 Aug 2024 11:46:42 -0400 Subject: [PATCH 05/53] - Fix alert in table view - (modify some docs) Signed-off-by: Dave Richer --- _reference/productionBoardNotes.md | 8 ++++++ ...production-board-kanban-card.component.jsx | 9 +------ ...roduction-list-columns.alert.component.jsx | 27 ++++++++++--------- .../production-list-columns.data.jsx | 8 +++--- 4 files changed, 28 insertions(+), 24 deletions(-) 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/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx b/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx index 4febb86c0..632480a12 100644 --- a/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx +++ b/client/src/components/production-board-kanban-card/production-board-kanban-card.component.jsx @@ -343,14 +343,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe const headerContent = (
- + {metadata?.suspended && } {metadata?.iouparent && ( ({ ) }); -const ProductionListColumnAlert = ({ record, insertAuditTrail }) => { +const ProductionListColumnAlert = ({ id, productionVars, refetch, insertAuditTrail }) => { const [updateAlert] = useMutation(UPDATE_JOB); 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 +46,17 @@ 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 ); } diff --git a/client/src/components/production-board-kanban/production-board-kanban.utils.js b/client/src/components/production-board-kanban/production-board-kanban.utils.js index fed3d12db..ba3974c1a 100644 --- a/client/src/components/production-board-kanban/production-board-kanban.utils.js +++ b/client/src/components/production-board-kanban/production-board-kanban.utils.js @@ -29,7 +29,7 @@ const sortByParentId = (arr) => { // Function to create board data based on statuses and jobs, with optional filtering export const createBoardData = ({ statuses, data, filter, cardSettings }) => { - const { search, employeeId } = filter; + const { search, employeeId, alert } = filter; const lanes = statuses.map((status) => ({ id: status, @@ -52,6 +52,11 @@ export const createBoardData = ({ statuses, data, filter, cardSettings }) => { ); } + // Filter jobs by alert if alert filter is true + if (alert) { + filteredJobs = filteredJobs.filter((job) => job.production_vars?.alert); + } + const DataGroupedByStatus = groupBy(filteredJobs, "status"); Object.keys(DataGroupedByStatus).forEach((statusGroupKey) => { diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 4dd123fd8..c2d776fbf 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2833,7 +2833,8 @@ "sublets": "Sublets", "totalhours": "Total Hrs ", "touchtime": "T/T", - "viewname": "View Name" + "viewname": "View Name", + "alerts": "Alerts" }, "successes": { "removed": "Job removed from production." diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index bafe9ced5..c2da8510a 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -2833,7 +2833,8 @@ "sublets": "", "totalhours": "", "touchtime": "", - "viewname": "" + "viewname": "", + "alerts": "" }, "successes": { "removed": "" diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 5fd0c1280..005accb1c 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -2833,7 +2833,8 @@ "sublets": "", "totalhours": "", "touchtime": "", - "viewname": "" + "viewname": "", + "alerts": "" }, "successes": { "removed": "" From 134ce05d27b1616a0eadcf168f84641341ae3b26 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 16 Aug 2024 11:16:20 -0400 Subject: [PATCH 23/53] - Add Alert Filter to visual production board Signed-off-by: Dave Richer --- .../production-board-filters.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/production-board-filters/production-board-filters.component.jsx b/client/src/components/production-board-filters/production-board-filters.component.jsx index 90afb4fe7..eea1fc3d9 100644 --- a/client/src/components/production-board-filters/production-board-filters.component.jsx +++ b/client/src/components/production-board-filters/production-board-filters.component.jsx @@ -41,7 +41,7 @@ export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) style={{ minWidth: "20rem" }} options={bodyshop.employees.filter((e) => e.active)} value={filter.employeeId} - placeholder={t("production.labels.alerts")} + placeholder={t("production.labels.employeesearch")} onChange={(emp) => setFilter({ ...filter, employeeId: emp })} allowClear /> From f691aca2417323a2626deba6d66197132eac5f43 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 16 Aug 2024 09:11:29 -0700 Subject: [PATCH 24/53] IO-2884 Production List Board Filter Signed-off-by: Allan Carr --- .../production-list-columns.data.jsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/client/src/components/production-list-columns/production-list-columns.data.jsx b/client/src/components/production-list-columns/production-list-columns.data.jsx index 2c7b349e3..fbc9236a5 100644 --- a/client/src/components/production-list-columns/production-list-columns.data.jsx +++ b/client/src/components/production-list-columns/production-list-columns.data.jsx @@ -2,10 +2,13 @@ import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons"; import { Checkbox, Space, Tooltip } from "antd"; import i18n from "i18next"; import { Link } from "react-router-dom"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { store } from "../../redux/store"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { TimeFormatter } from "../../utils/DateFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter"; import { onlyUnique } from "../../utils/arrayHelper"; +import InstanceRenderManager from "../../utils/instanceRenderMgr"; import { alphaSort, dateSort, statusSort } from "../../utils/sorters"; import JobAltTransportChange from "../job-at-change/job-at-change.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; @@ -24,9 +27,6 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c import ProductionListColumnCategory from "./production-list-columns.status.category"; import ProductionListColumnStatus from "./production-list-columns.status.component"; import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component"; -import { store } from "../../redux/store"; -import { setModalContext } from "../../redux/modals/modals.actions"; -import InstanceRenderManager from "../../utils/instanceRenderMgr"; const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => { const { Enhanced_Payroll } = treatments; @@ -258,7 +258,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme { text: "True", value: true }, { text: "False", value: false } ], - onFilter: (value, record) => value.includes(record.special_coverage_policy), + onFilter: (value, record) => value === record.special_coverage_policy, render: (text, record) => }, @@ -349,6 +349,11 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme key: "alert", sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false), sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order, + filters: [ + { text: "True", value: true }, + { text: "False", value: false } + ], + onFilter: (value, record) => value === (record.production_vars?.alert || false), render: (text, record) => ( ) From 01cbdf14a9192d81a28b29af2353303053645d5e Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Fri, 16 Aug 2024 12:14:52 -0400 Subject: [PATCH 25/53] - Add Alert Filter to visual production board Signed-off-by: Dave Richer --- .../production-board-kanban.component.jsx | 12 +++++------- .../settings/defaultKanbanSettings.js | 4 +++- .../production-board-kanban.settings.component.jsx | 9 ++++++++- 3 files changed, 16 insertions(+), 9 deletions(-) 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 62917e9bd..0c515132b 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 @@ -21,7 +21,7 @@ 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 { mergeWithDefaults } from "./settings/defaultKanbanSettings.js"; +import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js"; import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container"; const mapStateToProps = createStructuredSelector({ @@ -41,7 +41,7 @@ const mapDispatchToProps = (dispatch) => ({ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, associationSettings, statuses }) { const [boardLanes, setBoardLanes] = useState({ lanes: [] }); - const [filter, setFilter] = useState({ search: "", employeeId: null }); + const [filter, setFilter] = useState(defaultFilters); const [loading, setLoading] = useState(true); const [isMoving, setIsMoving] = useState(false); const [orientation, setOrientation] = useState("vertical"); @@ -187,11 +187,9 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr return mergeWithDefaults(kanbanSettings); }, [associationSettings]); - const handleSettingsChange = useCallback((newSettings) => { - setLoading(true); - setOrientation(newSettings.orientation ? "vertical" : "horizontal"); - setLoading(false); - }, []); + const handleSettingsChange = () => { + setFilter(defaultFilters); + }; if (loading) { return ; diff --git a/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js b/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js index 0d10e4e39..87b364ea2 100644 --- a/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js +++ b/client/src/components/production-board-kanban/settings/defaultKanbanSettings.js @@ -48,6 +48,8 @@ const defaultKanbanSettings = { selectedEstimators: [] }; +const defaultFilters = { search: "", employeeId: null, alert: false }; + const mergeWithDefaults = (settings) => { // Create a new object that starts with the default settings const mergedSettings = { ...defaultKanbanSettings }; @@ -64,4 +66,4 @@ const mergeWithDefaults = (settings) => { return mergedSettings; }; -export { defaultKanbanSettings, statisticsItems, mergeWithDefaults }; +export { defaultKanbanSettings, statisticsItems, mergeWithDefaults, defaultFilters }; 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 23da13412..0d78416f8 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 @@ -9,8 +9,9 @@ import InformationSettings from "./InformationSettings.jsx"; import StatisticsSettings from "./StatisticsSettings.jsx"; import FilterSettings from "./FilterSettings.jsx"; import PropTypes from "prop-types"; +import { isFunction } from "lodash"; -function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) { +function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) { const [form] = Form.useForm(); const [open, setOpen] = useState(false); const [loading, setLoading] = useState(false); @@ -61,6 +62,11 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod setOpen(false); setLoading(false); parentLoading(false); + + if (onSettingsChange && isFunction(onSettingsChange)) { + onSettingsChange(values); + } + setHasChanges(false); }; @@ -156,6 +162,7 @@ ProductionBoardKanbanSettings.propTypes = { associationSettings: PropTypes.object, parentLoading: PropTypes.func.isRequired, bodyshop: PropTypes.object.isRequired, + onSettingsChange: PropTypes.func, data: PropTypes.array }; From e628b1364c3914a01f5859093774542c937e38a5 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Fri, 16 Aug 2024 12:27:26 -0700 Subject: [PATCH 26/53] IO-2879 Adjust placement of variable Signed-off-by: Allan Carr --- server/accounting/qb-receivables-lines.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/accounting/qb-receivables-lines.js b/server/accounting/qb-receivables-lines.js index 40b5bec9c..0dedb4472 100644 --- a/server/accounting/qb-receivables-lines.js +++ b/server/accounting/qb-receivables-lines.js @@ -20,7 +20,6 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes //Otherwise, calculate them and add them to the default MAPA and MASH centers. let hasMapaLine = false; let hasMashLine = false; - let isTowingLine = false; //Create the invoice lines mapping. jobs_by_pk.joblines.map((jobline) => { @@ -31,6 +30,9 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes if (jobline.db_ref === "936007") { hasMashLine = true; } + + //Check if the line is a Towing Line and flag as such. + let isTowingLine = false; if (jobline.db_ref === "936001" && jobline.line_desc.includes("Towing")) { isTowingLine = true; } From 0054b00d01cbab149ff8cd024ce8a86dc767ba86 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 19 Aug 2024 17:45:36 -0400 Subject: [PATCH 27/53] - Improve profile handling in product list view Signed-off-by: Dave Richer --- ...tion-list-save-config-button.component.jsx | 93 ----- ...oduction-list-config-manager.component.jsx | 347 ++++++++++++++++++ ...ction-list-table-view-select.component.jsx | 172 --------- .../production-list-table.component.jsx | 27 +- client/src/translations/en_us/common.json | 15 +- client/src/translations/es/common.json | 17 +- client/src/translations/fr/common.json | 15 +- 7 files changed, 397 insertions(+), 289 deletions(-) delete mode 100644 client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx create mode 100644 client/src/components/production-list-table/production-list-config-manager.component.jsx delete mode 100644 client/src/components/production-list-table/production-list-table-view-select.component.jsx diff --git a/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx b/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx deleted file mode 100644 index a5f0b0f40..000000000 --- a/client/src/components/production-list-save-config-button/production-list-save-config-button.component.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useMutation } from "@apollo/client"; -import React, { useState } from "react"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import { Button, Form, Input, notification, Popover, Space } from "antd"; -import { useTranslation } from "react-i18next"; -import { UPDATE_SHOP } from "../../graphql/bodyshop.queries"; -import { logImEXEvent } from "../../firebase/firebase.utils"; -import { isFunction } from "lodash"; - -const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser - bodyshop: selectBodyshop -}); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); - -export function ProductionListSaveConfigButton({ columns, bodyshop, tableState, onSave }) { - const [updateShop] = useMutation(UPDATE_SHOP); - const [loading, setLoading] = useState(false); - const [open, setOpen] = useState(false); - const [form] = Form.useForm(); - - const { t } = useTranslation(); - - const handleSaveConfig = async (values) => { - logImEXEvent("production_save_config"); - setLoading(true); - const result = await updateShop({ - variables: { - id: bodyshop.id, - shop: { - production_config: [ - ...bodyshop.production_config.filter((b) => b.name !== values.name), - //Assign it to the name - { - name: values.name, - columns: { - columnKeys: columns.map((i) => { - return { key: i.key, width: i.width }; - }), - tableState - } - } - ] - } - } - }); - if (!!!result.errors) { - notification["success"]({ message: t("bodyshop.successes.save") }); - if (onSave && isFunction(onSave)) { - onSave(); - } - } else { - notification["error"]({ - message: t("bodyshop.errors.saving", { - error: JSON.stringify(result.errors) - }) - }); - } - form.resetFields(); - setOpen(false); - setLoading(false); - }; - const popMenu = ( -
- - - - - - - - - - -
- ); - - return ( - - - - ); -} - -export default connect(mapStateToProps, mapDispatchToProps)(ProductionListSaveConfigButton); diff --git a/client/src/components/production-list-table/production-list-config-manager.component.jsx b/client/src/components/production-list-table/production-list-config-manager.component.jsx new file mode 100644 index 000000000..08d2773bc --- /dev/null +++ b/client/src/components/production-list-table/production-list-config-manager.component.jsx @@ -0,0 +1,347 @@ +import { DeleteOutlined, ExclamationCircleOutlined, PlusOutlined } from "@ant-design/icons"; +import { useMutation } from "@apollo/client"; +import { Button, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries"; +import { UPDATE_SHOP } from "../../graphql/bodyshop.queries"; +import ProductionListColumns from "../production-list-columns/production-list-columns.data"; +import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { isFunction } from "lodash"; + +const { confirm } = Modal; + +export function ProductionListConfigManager({ + refetch, + bodyshop, + technician, + currentUser, + state, + data, + columns, + setColumns, + setState, + onSave, + defaultView, + hasUnsavedChanges, + setHasUnsavedChanges +}) { + const { t } = useTranslation(); + const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW); + const [updateShop] = useMutation(UPDATE_SHOP); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [isAddingNewProfile, setIsAddingNewProfile] = useState(false); + const [form] = Form.useForm(); + const [activeView, setActiveView] = useState(() => { + const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); + return assoc && assoc.default_prod_list_view; + }); + + const defaultState = { + sortedInfo: { + columnKey: "ro_number", + order: null + }, + filteredInfo: {} + }; + + const ensureDefaultState = (state) => { + return { + sortedInfo: state?.sortedInfo || defaultState.sortedInfo, + filteredInfo: state?.filteredInfo || defaultState.filteredInfo, + ...state + }; + }; + + const createDefaultView = async () => { + const defaultConfig = { + name: t("production.constants.main_profile"), + columns: { + columnKeys: columns.map((i) => ({ key: i.key, width: i.width })), + tableState: ensureDefaultState(state) + } + }; + + const result = await updateShop({ + variables: { + id: bodyshop.id, + shop: { + production_config: [defaultConfig] + } + } + }); + + if (!result.errors) { + await updateActiveProdView(t("production.constants.main_profile")); + setColumns(defaultConfig.columns.columnKeys); + setState(defaultConfig.columns.tableState); + notification.success({ message: t("bodyshop.successes.defaultviewcreated") }); + } else { + notification.error({ + message: t("bodyshop.errors.creatingdefaultview", { + error: JSON.stringify(result.errors) + }) + }); + } + }; + + const { + treatments: { Enhanced_Payroll } + } = useSplitTreatments({ + attributes: {}, + names: ["Enhanced_Payroll"], + splitKey: bodyshop.imexshopid + }); + + const updateActiveProdView = async (viewName) => { + const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); + if (assoc) { + await updateDefaultProdView({ + variables: { assocId: assoc.id, view: viewName }, + update(cache) { + cache.modify({ + id: cache.identify(bodyshop), + fields: { + associations(existingAssociations) { + return existingAssociations.map((a) => { + if (a.useremail !== currentUser.email) return a; + return { ...a, default_prod_list_view: viewName }; + }); + } + } + }); + } + }); + setActiveView(viewName); + setHasUnsavedChanges(false); + } + }; + + const handleSelect = async (value) => { + if (hasUnsavedChanges) { + confirm({ + title: t("general.labels.unsavedchanges"), + icon: , + content: t("general.messages.unsavedchangespopup"), + onOk: () => proceedWithSelect(value), + onCancel() { + // Do nothing if canceled + } + }); + } else { + await proceedWithSelect(value); + } + }; + + const proceedWithSelect = async (value) => { + if (value === "add_new") { + setIsAddingNewProfile(true); + setOpen(true); + return; + } + + const selectedConfig = bodyshop.production_config.find((pc) => pc.name === value); + + if (selectedConfig) { + const newColumns = selectedConfig.columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + bodyshop, + refetch, + technician, + state: ensureDefaultState(state), + data: data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width + }; + }); + setColumns(newColumns); + const newState = ensureDefaultState(selectedConfig.columns.tableState); + setState(newState); + + await updateActiveProdView(value); + if (onSave && isFunction(onSave)) { + onSave(); + } + } + }; + + const handleTrash = async (name) => { + if (name === t("production.constants.main_profile")) return; + + const remainingConfigs = bodyshop.production_config.filter((b) => b.name !== name); + await updateShop({ + variables: { + id: bodyshop.id, + shop: { + production_config: remainingConfigs + } + }, + awaitRefetchQueries: true + }); + + if (name === activeView) { + if (remainingConfigs.length > 0) { + const nextConfig = remainingConfigs[0]; + await updateActiveProdView(nextConfig.name); + setColumns( + nextConfig.columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + technician, + state: ensureDefaultState(state), + refetch, + data: data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width + }; + }) + ); + setState(ensureDefaultState(nextConfig.columns.tableState)); + } else { + await updateActiveProdView(null); + setColumns([]); + setState(defaultState); // Reset to default state if no configs are left + } + } + }; + + const handleSaveConfig = async (values) => { + logImEXEvent("production_save_config"); + setLoading(true); + + const profileName = isAddingNewProfile ? values.name : activeView; + + const result = await updateShop({ + variables: { + id: bodyshop.id, + shop: { + production_config: [ + ...bodyshop.production_config.filter((b) => b.name !== profileName), + { + name: profileName, + columns: { + columnKeys: columns.map((i) => ({ key: i.key, width: i.width })), + tableState: ensureDefaultState(state) + } + } + ] + } + } + }); + + if (!result.errors) { + notification.success({ message: t("bodyshop.successes.save") }); + if (isAddingNewProfile) { + await updateActiveProdView(profileName); + } + if (onSave && isFunction(onSave)) { + onSave(); + } + setHasUnsavedChanges(false); + } else { + notification.error({ + message: t("bodyshop.errors.saving", { + error: JSON.stringify(result.errors) + }) + }); + } + + form.resetFields(); + setOpen(false); + setLoading(false); + setIsAddingNewProfile(false); + }; + + useEffect(() => { + if (!bodyshop.production_config || bodyshop.production_config.length === 0) { + createDefaultView().catch((e) => { + console.error("Something went wrong saving the production list view Config."); + }); + } else { + setActiveView(defaultView); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [defaultView, bodyshop.production_config]); + + const popMenu = ( +
+
+ {isAddingNewProfile && ( + + + + )} + + + + +
+
+ ); + + return ( + + + + + + + ); +} diff --git a/client/src/components/production-list-table/production-list-table-view-select.component.jsx b/client/src/components/production-list-table/production-list-table-view-select.component.jsx deleted file mode 100644 index f8297fe02..000000000 --- a/client/src/components/production-list-table/production-list-table-view-select.component.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import { DeleteOutlined } from "@ant-design/icons"; -import { useMutation } from "@apollo/client"; -import { Popconfirm, Select } from "antd"; -import React from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries"; -import { UPDATE_SHOP } from "../../graphql/bodyshop.queries"; -import { selectTechnician } from "../../redux/tech/tech.selectors"; -import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; -import ProductionListColumns from "../production-list-columns/production-list-columns.data"; -import { useSplitTreatments } from "@splitsoftware/splitio-react"; -import { isFunction } from "lodash"; - -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - technician: selectTechnician, - currentUser: selectCurrentUser -}); - -export function ProductionListTable({ - refetch, - bodyshop, - technician, - currentUser, - state, - data, - setColumns, - setState, - onProfileChange -}) { - const { t } = useTranslation(); - const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW); - const [updateShop] = useMutation(UPDATE_SHOP); - - const { - treatments: { Enhanced_Payroll } - } = useSplitTreatments({ - attributes: {}, - names: ["Enhanced_Payroll"], - splitKey: bodyshop.imexshopid - }); - - const handleSelect = async (value, option) => { - const newColumns = bodyshop.production_config - .filter((pc) => pc.name === value)[0] - .columns.columnKeys.map((k) => { - return { - ...ProductionListColumns({ - bodyshop, - refetch, - technician, - state, - data: data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments: { Enhanced_Payroll } - }).find((e) => e.key === k.key), - width: k.width - }; - }); - setColumns(newColumns); - const newState = bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState; - setState(newState); - - const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); - - if (assoc) { - await updateDefaultProdView({ - variables: { assocId: assoc.id, view: value }, - update(cache) { - cache.modify({ - id: cache.identify(bodyshop), - fields: { - associations(existingAssociations, { readField }) { - return existingAssociations.map((a) => { - if (a.useremail !== currentUser.email) return a; - return { ...a, default_prod_list_view: value }; - }); - } - } - }); - } - }); - } - - if (onProfileChange && isFunction(onProfileChange)) { - onProfileChange({ value, option, newColumns, newState, assoc }); - } - }; - - const handleTrash = async (name) => { - await updateShop({ - variables: { - id: bodyshop.id, - shop: { - production_config: bodyshop.production_config.filter((b) => b.name !== name) - } - }, - awaitRefetchQueries: true - }); - - setColumns( - bodyshop.production_config[0].columns.columnKeys.map((k) => { - return { - ...ProductionListColumns({ - technician, - state, - refetch, - data: data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments: { Enhanced_Payroll } - }).find((e) => e.key === k.key), - width: k.width - }; - }) - ); - - setState(bodyshop.production_config[0].columns.tableState); - }; - const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); - - const defaultView = assoc && assoc.default_prod_list_view; - return ( -
- -
- ); -} - -export default connect(mapStateToProps, null)(ProductionListTable); diff --git a/client/src/components/production-list-table/production-list-table.component.jsx b/client/src/components/production-list-table/production-list-table.component.jsx index 6e9e1f918..728330ece 100644 --- a/client/src/components/production-list-table/production-list-table.component.jsx +++ b/client/src/components/production-list-table/production-list-table.component.jsx @@ -10,15 +10,14 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component"; import ProductionListColumns from "../production-list-columns/production-list-columns.data"; import ProductionListDetail from "../production-list-detail/production-list-detail.component"; -import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component"; import ProductionListPrint from "./production-list-print.component"; -import ProductionListTableViewSelect from "./production-list-table-view-select.component"; import ResizeableTitle from "./production-list-table.resizeable.component"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { SyncOutlined } from "@ant-design/icons"; import Prompt from "../../utils/prompt.js"; import _ from "lodash"; import AlertComponent from "../alert/alert.component.jsx"; +import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -270,23 +269,23 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici data={data} onColumnAdd={addColumn} /> - { - setHasUnsavedChanges(false); - }} - /> - { - initialStateRef.current = state; - setHasUnsavedChanges(false); - }} + setState={setState} refetch={refetch} data={data} + bodyshop={bodyshop} + technician={technician} + currentUser={currentUser} + defaultView={defaultView} + setHasUnsavedChanges={setHasUnsavedChanges} + hasUnsavedChanges={hasUnsavedChanges} + onSave={() => { + setHasUnsavedChanges(false); + initialStateRef.current = state; + }} /> setSearchText(e.target.value)} diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index c2d776fbf..79e208d91 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -271,7 +271,8 @@ }, "errors": { "loading": "Unable to load shop details. Please call technical support.", - "saving": "Error encountered while saving. {{message}}" + "saving": "Error encountered while saving. {{message}}", + "creatingdefaultview": "Error creating default view." }, "fields": { "ReceivableCustomField": "QBO Receivable Custom Field {{number}}", @@ -699,7 +700,10 @@ "workingdays": "Working Days" }, "successes": { - "save": "Shop configuration saved successfully. " + "save": "Shop configuration saved successfully. ", + "unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?", + "areyousure": "Are you sure you want to continue?", + "defaultviewcreated": "Default view created successfully." }, "validation": { "centermustexist": "The chosen responsibility center does not exist.", @@ -1176,6 +1180,7 @@ "vehicle": "Vehicle" }, "labels": { + "unsavedchanges": "Unsaved change.", "actions": "Actions", "areyousure": "Are you sure?", "barcode": "Barcode", @@ -2731,6 +2736,9 @@ } }, "production": { + "constants":{ + "main_profile": "Main" + }, "options": { "small": "Small", "medium": "Medium", @@ -2834,7 +2842,8 @@ "totalhours": "Total Hrs ", "touchtime": "T/T", "viewname": "View Name", - "alerts": "Alerts" + "alerts": "Alerts", + "addnewprofile": "Add New Profile" }, "successes": { "removed": "Job removed from production." diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index c2da8510a..76f537e61 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -271,7 +271,8 @@ }, "errors": { "loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico.", - "saving": "" + "saving": "", + "creatingdefaultview": "" }, "fields": { "ReceivableCustomField": "", @@ -699,8 +700,11 @@ "workingdays": "" }, "successes": { - "save": "" - }, + "save": "", + "unsavedchanges": "", + "areyousure": "", + "defaultviewcreated": "" + }, "validation": { "centermustexist": "", "larsplit": "", @@ -1176,6 +1180,7 @@ "vehicle": "" }, "labels": { + "unsavedchanges": "", "actions": "Comportamiento", "areyousure": "", "barcode": "código de barras", @@ -2731,6 +2736,9 @@ } }, "production": { + "constants":{ + "main_profile": "" + }, "options": { "small": "", "medium": "", @@ -2834,7 +2842,8 @@ "totalhours": "", "touchtime": "", "viewname": "", - "alerts": "" + "alerts": "", + "addnewprofile": "" }, "successes": { "removed": "" diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 005accb1c..fccb1ce65 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -271,7 +271,8 @@ }, "errors": { "loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.", - "saving": "" + "saving": "", + "creatingdefaultview": "" }, "fields": { "ReceivableCustomField": "", @@ -699,7 +700,10 @@ "workingdays": "" }, "successes": { - "save": "" + "save": "", + "unsavedchanges": "", + "areyousure": "", + "defaultviewcreated": "" }, "validation": { "centermustexist": "", @@ -1176,6 +1180,7 @@ "vehicle": "" }, "labels": { + "unsavedchanges": "", "actions": "actes", "areyousure": "", "barcode": "code à barre", @@ -2731,6 +2736,9 @@ } }, "production": { + "constants":{ + "main_profile": "" + }, "options": { "small": "", "medium": "", @@ -2834,7 +2842,8 @@ "totalhours": "", "touchtime": "", "viewname": "", - "alerts": "" + "alerts": "", + "addnewprofile": "" }, "successes": { "removed": "" From f3e43334c4019a5a8153f3d3618038b0a3c25b34 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 Aug 2024 10:44:38 -0400 Subject: [PATCH 28/53] - Improve profile handling in product list view Signed-off-by: Dave Richer --- .../production-list-config-manager.component.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/production-list-table/production-list-config-manager.component.jsx b/client/src/components/production-list-table/production-list-config-manager.component.jsx index 08d2773bc..c16f8e40f 100644 --- a/client/src/components/production-list-table/production-list-config-manager.component.jsx +++ b/client/src/components/production-list-table/production-list-config-manager.component.jsx @@ -302,6 +302,9 @@ export function ProductionListConfigManager({ - + - + - + - + @@ -228,7 +224,7 @@ export default function CourtesyCarCreateFormComponent({
- + p.nextservicedate !== c.nextservicedate}> {() => { @@ -260,7 +256,7 @@ export default function CourtesyCarCreateFormComponent({
- + p.registrationexpires !== c.registrationexpires}> {() => { @@ -293,7 +289,7 @@ export default function CourtesyCarCreateFormComponent({ } ]} > - + p.insuranceexpires !== c.insuranceexpires}> {() => { diff --git a/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.component.jsx b/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.component.jsx index 589c97ad4..3a7ec4c6b 100644 --- a/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.component.jsx +++ b/client/src/components/courtesy-car-return-modal/courtesy-car-return-modal.component.jsx @@ -2,7 +2,7 @@ import { Form, InputNumber } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; export default function CourtesyCarReturnModalComponent() { const { t } = useTranslation(); @@ -19,7 +19,7 @@ export default function CourtesyCarReturnModalComponent() { } ]} > - + - + diff --git a/client/src/components/eula/eula.component.jsx b/client/src/components/eula/eula.component.jsx index 7399c279b..8d2227e02 100644 --- a/client/src/components/eula/eula.component.jsx +++ b/client/src/components/eula/eula.component.jsx @@ -4,7 +4,6 @@ import Markdown from "react-markdown"; import { createStructuredSelector } from "reselect"; import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors"; import { connect } from "react-redux"; -import { FormDatePicker } from "../form-date-picker/form-date-picker.component"; import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries"; import { useMutation } from "@apollo/client"; import { acceptEula } from "../../redux/user/user.actions"; @@ -12,6 +11,7 @@ import { useTranslation } from "react-i18next"; import day from "../../utils/day"; import "./eula.styles.scss"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; const Eula = ({ currentEula, currentUser, acceptEula }) => { const [formReady, setFormReady] = useState(false); @@ -216,7 +216,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => ( } ]} > - + diff --git a/client/src/components/form-date-picker/form-date-picker.component.jsx b/client/src/components/form-date-picker/form-date-picker.component.jsx deleted file mode 100644 index 500e22d45..000000000 --- a/client/src/components/form-date-picker/form-date-picker.component.jsx +++ /dev/null @@ -1,123 +0,0 @@ -import { DatePicker } from "antd"; -import dayjs from "../../utils/day"; -import React, { useRef } from "react"; - -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectBodyshop } from "../../redux/user/user.selectors"; - -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop -}); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); -export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker); - -const dateFormat = "MM/DD/YYYY"; - -export function FormDatePicker({ - bodyshop, - value, - onChange, - onBlur, - onlyFuture, - onlyToday, - isDateOnly = true, - ...restProps -}) { - const ref = useRef(); - - const handleChange = (newDate) => { - if (value !== newDate && onChange) { - onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate); - } - }; - - const handleKeyDown = (e) => { - if (e.key.toLowerCase() === "t") { - if (onChange) { - onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs()); - } - } else if (e.key.toLowerCase() === "enter") { - if (ref.current && ref.current.blur) ref.current.blur(); - } - }; - - const handleBlur = (e) => { - const v = e.target.value; - if (!v) return; - - const formats = [ - "MMDDYY", - "MMDDYYYY", - "MM/DD/YY", - "MM/DD/YYYY", - "M/DD/YY", - "M/DD/YYYY", - "MM/D/YY", - "MM/D/YYYY", - "M/D/YY", - "M/D/YYYY", - "D/MM/YY", - "D/MM/YYYY", - "DD/M/YY", - "DD/M/YYYY", - "D/M/YY", - "D/M/YYYY" - ]; - - let _a; - - // Iterate through formats to find the correct one - for (let format of formats) { - _a = dayjs(v, format); - if (v === _a.format(format)) { - break; - } - } - - if (_a.isValid() && value && value.isValid && value.isValid()) { - _a.set({ - hours: value.hours(), - minutes: value.minutes(), - seconds: value.seconds(), - milliseconds: value.milliseconds() - }); - } - - if (_a.isValid() && onChange) { - if (onlyFuture) { - if (dayjs().subtract(1, "day").isBefore(_a)) { - onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); - } else { - onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs()); - } - } else { - onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); - } - } - }; - - return ( -
- { - if (onlyToday) { - return !dayjs().isSame(d, "day"); - } else if (onlyFuture) { - return dayjs().subtract(1, "day").isAfter(d); - } - }} - {...restProps} - /> -
- ); -} diff --git a/client/src/components/form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx b/client/src/components/form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx index 5d41fc5e3..cdabfdfb5 100644 --- a/client/src/components/form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx +++ b/client/src/components/form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx @@ -12,7 +12,6 @@ const mapStateToProps = createStructuredSelector({ const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced); const dateFormat = "MM/DD/YYYY h:mm a"; @@ -46,3 +45,5 @@ export function FormDateTimePickerEnhanced({
); } + +export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced); diff --git a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx index 23af7008a..c1bf6f749 100644 --- a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx +++ b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx @@ -1,45 +1,204 @@ -import React, { forwardRef } from "react"; -//import DatePicker from "react-datepicker"; -//import "react-datepicker/src/stylesheets/datepicker.scss"; -import { Space, TimePicker } from "antd"; +import React, { forwardRef, useState } from "react"; +import { DatePicker } from "antd"; import dayjs from "../../utils/day"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; -//To be used as a form element only. -const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => { - // const handleChange = (newDate) => { - // if (value !== newDate && onChange) { - // onChange(newDate); - // } - // }; +// To be used as a form element only. + +const DateTimePicker = ( + { value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }, + ref +) => { + const [isManualInput, setIsManualInput] = useState(false); + + const handleChange = (newDate) => { + if (newDate && onChange) { + onChange(newDate); + } + setIsManualInput(false); // Reset the manual input flag when using GUI + }; + + const handleKeyDown = (e) => { + setIsManualInput(true); // User is typing, so set the manual input flag + + if (e.key.toLowerCase() === "t") { + if (onChange) { + onChange(dayjs()); + } + } else if (e.key.toLowerCase() === "enter") { + handleBlur(e); + } + }; + + const handleBlur = (e) => { + if (!isManualInput) { + // If the input is not manual, skip the format processing + return; + } + + setIsManualInput(false); // Reset the flag after processing + + const v = e.target.value; + if (!v) return; + + // Convert input to uppercase to handle 'am/pm' as well as 'AM/PM' + const upperV = v.toUpperCase(); + + let _a; + + // Handling common shorthand datetime inputs + const shorthandFormats = [ + "M/D/YY hA", + "M/D/YY h:mmA", + "M/D/YYYY hA", + "M/D/YYYY h:mmA", + "M/D/YY ha", + "M/D/YY h:mma", + "M/D/YYYY ha", + "M/D/YYYY h:mma" + ]; + + for (let format of shorthandFormats) { + _a = dayjs(upperV, format); + if (_a.isValid()) break; + } + + // If shorthand parsing didn't work, fall back to existing formats + if (!_a || !_a.isValid()) { + const formats = [ + "MMDDYY", + "MMDDYYYY", + "MM/DD/YY", + "MM/DD/YYYY", + "M/DD/YY", + "M/DD/YYYY", + "MM/D/YY", + "MM/D/YYYY", + "M/D/YY", + "M/D/YYYY", + "D/MM/YY", + "D/MM/YYYY", + "DD/M/YY", + "DD/M/YYYY", + "D/M/YY", + "D/M/YYYY", + "MMDDYY hh:mm A", + "MMDDYYYY hh:mm A", + "MM/DD/YY hh:mm A", + "MM/DD/YYYY hh:mm A", + "M/DD/YY hh:mm A", + "M/DD/YYYY hh:mm A", + "MM/D/YY hh:mm A", + "MM/D/YYYY hh:mm A", + "M/D/YY hh:mm A", + "M/D/YYYY hh:mm A", + "D/MM/YY hh:mm A", + "D/MM/YYYY hh:mm A", + "DD/M/YY hh:mm A", + "DD/M/YYYY hh:mm A", + "D/M/YY hh:mm A", + "D/M/YYYY hh:mm A", + "MMDDYY hh:mm:ss A", + "MMDDYYYY hh:mm:ss A", + "MM/DD/YY hh:mm:ss A", + "MM/DD/YYYY hh:mm:ss A", + "M/DD/YY hh:mm:ss A", + "M/DD/YYYY hh:mm:ss A", + "MM/D/YY hh:mm:ss A", + "MM/D/YYYY hh:mm:ss A", + "M/D/YY hh:mm:ss A", + "M/D/YYYY hh:mm:ss A", + "D/MM/YY hh:mm:ss A", + "D/MM/YYYY hh:mm:ss A", + "DD/M/YY hh:mm:ss A", + "DD/M/YYYY hh:mm:ss A", + "D/M/YY hh:mm:ss A", + "D/M/YYYY hh:mm:ss A", + "MMDDYY HH:mm", + "MMDDYYYY HH:mm", + "MM/DD/YY HH:mm", + "MM/DD/YYYY HH:mm", + "M/DD/YY HH:mm", + "M/DD/YYYY HH:mm", + "MM/D/YY HH:mm", + "MM/D/YYYY HH:mm", + "M/D/YY HH:mm", + "M/D/YYYY HH:mm", + "D/MM/YY HH:mm", + "D/MM/YYYY HH:mm", + "DD/M/YY HH:mm", + "DD/M/YYYY HH:mm", + "D/M/YY HH:mm", + "D/M/YYYY HH:mm", + "MMDDYY HH:mm:ss", + "MMDDYYYY HH:mm:ss", + "MM/DD/YY HH:mm:ss", + "MM/DD/YYYY HH:mm:ss", + "M/DD/YY HH:mm:ss", + "M/DD/YYYY HH:mm:ss", + "MM/D/YY HH:mm:ss", + "MM/D/YYYY HH:mm:ss", + "M/D/YY HH:mm:ss", + "M/D/YYYY HH:mm:ss", + "D/MM/YY HH:mm:ss", + "D/MM/YYYY HH:mm:ss", + "DD/M/YY HH:mm:ss", + "DD/M/YYYY HH:mm:ss", + "D/M/YY HH:mm:ss", + "D/M/YYYY HH:mm:ss" + ]; + + for (let format of formats) { + _a = dayjs(upperV, format); + if (_a.isValid()) break; + } + } + + if (_a && _a.isValid()) { + if (isDateOnly) { + _a = _a.startOf("day"); // Only apply startOf("day") when isDateOnly is true + } + + if (value && value.isValid && value.isValid()) { + _a.set({ + hours: value.hours(), + minutes: value.minutes(), + seconds: value.seconds(), + milliseconds: value.milliseconds() + }); + } + + if (onlyFuture) { + if (dayjs().subtract(1, "day").isBefore(_a)) { + onChange(_a); + } else { + onChange(dayjs().startOf("day")); + } + } else { + onChange(_a); + } + } + }; return ( - - dayjs().subtract(1, "day").isAfter(d) - })} - value={value} - onBlur={onBlur} - onChange={onChange} - onlyFuture={onlyFuture} - isDateOnly={false} - /> - - + dayjs().isAfter(d) - })} - onChange={onChange} - disableSeconds={true} - minuteStep={15} - onBlur={onBlur} - format="hh:mm a" + onChange={handleChange} + onBlur={onBlur || handleBlur} + disabledDate={(d) => { + if (onlyToday) { + return !dayjs().isSame(d, "day"); + } else if (onlyFuture) { + return dayjs().subtract(1, "day").isAfter(d); + } + return false; + }} {...restProps} /> - +
); }; diff --git a/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx b/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx index a8c673552..e17368ed9 100644 --- a/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx +++ b/client/src/components/job-scoreboard-add-button/job-scoreboard-add-button.component.jsx @@ -10,8 +10,8 @@ import { QUERY_SCOREBOARD_ENTRY, UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) { const { t } = useTranslation(); @@ -86,7 +86,7 @@ export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) } ]} > - + ({ - insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })) + insertAuditTrail: ({ jobid, operation, type }) => + dispatch( + insertAuditTrail({ + jobid, + operation, + type + }) + ) }); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange); @@ -87,7 +93,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) { - + diff --git a/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx b/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx index 54708449b..c50894005 100644 --- a/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx +++ b/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx @@ -1,18 +1,9 @@ -import { - Collapse, - Form, - Input, - InputNumber, - Select, - Space, - Switch, -} from "antd"; +import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; @@ -29,6 +20,7 @@ import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.c import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -61,10 +53,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) { - + @@ -116,7 +105,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) { - + diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx index d3026676d..e38c2f28d 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx @@ -2,9 +2,9 @@ import { Form, Input } from "antd"; import React, { useContext } from "react"; import { useTranslation } from "react-i18next"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; export default function JobsCreateVehicleInfoNewComponent({ form }) { const [state] = useContext(JobCreateContext); @@ -113,7 +113,7 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) { - + diff --git a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx index 80598717e..f32f1b94d 100644 --- a/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx +++ b/client/src/components/jobs-detail-dates/jobs-detail-dates.component.jsx @@ -5,7 +5,6 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import FormRow from "../layout-form-row/layout-form-row.component"; @@ -30,7 +29,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
- + @@ -45,7 +44,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) { - + @@ -85,7 +84,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) { rules={[ { required: jobInPostProduction - //message: t("general.validation.required"), } ]} > diff --git a/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx b/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx index df2b43cd0..1fee1a6b5 100644 --- a/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx +++ b/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx @@ -5,7 +5,6 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; @@ -13,6 +12,7 @@ import Car from "../job-damage-visual/job-damage-visual.component"; import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component"; import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component"; import FormRow from "../layout-form-row/layout-form-row.component"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; const mapStateToProps = createStructuredSelector({ jobRO: selectJobReadOnly, @@ -152,7 +152,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) { - + diff --git a/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx b/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx index dc27b3601..db0d49f70 100644 --- a/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx +++ b/client/src/components/parts-order-backorder-eta/parts-order-backorder-eta.component.jsx @@ -8,8 +8,8 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries"; import { selectBodyshop } from "../../redux/user/user.selectors"; import { DateFormatter } from "../../utils/DateFormatter"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import { CalendarFilled } from "@ant-design/icons"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop @@ -62,7 +62,7 @@ export function PartsOrderBackorderEta({
- + diff --git a/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx b/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx index ace62cf27..a696ab5c6 100644 --- a/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx +++ b/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next"; import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import { generateInternalReflections } from "./report-center-modal-utils"; -import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) { return ( @@ -196,7 +196,8 @@ function FiltersSection({ filters, form, bodyshop }) { // We have a type of date, so we will use a date picker if (type === "date") { return ( - form.setFieldValue(fieldPath, date)} /> diff --git a/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx b/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx index 4c099a13c..9d09dedcd 100644 --- a/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx +++ b/client/src/components/scoreboard-entry-edit/scoreboard-entry-edit.component.jsx @@ -4,7 +4,7 @@ import dayjs from "../../utils/day"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; export default function ScoreboardEntryEdit({ entry }) { const [open, setOpen] = useState(false); @@ -52,7 +52,7 @@ export default function ScoreboardEntryEdit({ entry }) { } ]} > - + - + - + - + - - + diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx index b05c9658e..7a86d0d56 100644 --- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx +++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx @@ -7,8 +7,8 @@ import { createStructuredSelector } from "reselect"; import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; -import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; +import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import JobSearchSelect from "../job-search-select/job-search-select.component"; import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component"; import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility"; @@ -60,8 +60,8 @@ export function TimeTicketModalComponent({ {item.cost_center === "timetickets.labels.shift" ? t(item.cost_center) : bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on" - ? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`) - : item.cost_center} + ? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`) + : item.cost_center} ))} @@ -111,7 +111,7 @@ export function TimeTicketModalComponent({ } ]} > - + - + From 1e7c285fefb281278f366e38a09659b38fedc320 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 Aug 2024 13:46:35 -0400 Subject: [PATCH 31/53] - Address changes to profile from call Signed-off-by: Dave Richer --- ...oduction-list-config-manager.component.jsx | 25 ++++++++++++++++--- client/src/translations/en_us/common.json | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/client/src/components/production-list-table/production-list-config-manager.component.jsx b/client/src/components/production-list-table/production-list-config-manager.component.jsx index c16f8e40f..ebe001c3e 100644 --- a/client/src/components/production-list-table/production-list-config-manager.component.jsx +++ b/client/src/components/production-list-table/production-list-config-manager.component.jsx @@ -59,11 +59,30 @@ export function ProductionListConfigManager({ const defaultConfig = { name: t("production.constants.main_profile"), columns: { - columnKeys: columns.map((i) => ({ key: i.key, width: i.width })), + columnKeys: [ + { key: "ro_number", width: 100 }, + { key: "ownr", width: 100 }, + { key: "vehicle", width: 100 }, + { key: "ins_co_nm", width: 100 }, + { key: "actual_in", width: 100 }, + { key: "scheduled_completion", width: 100 }, + { key: "labhrs", width: 100 }, + { key: "employee_body", width: 100 }, + { key: "larhrs", width: 100 }, + { key: "employee_refinish", width: 100 }, + { key: "tt", width: 100 }, + { key: "status", width: 100 }, + { key: "sublets", width: 100 }, + { key: "viewdetail", width: 100 } + ], tableState: ensureDefaultState(state) } }; + // Immediately update the columns and state to reflect the new default view + setColumns(defaultConfig.columns.columnKeys); + setState(defaultConfig.columns.tableState); + const result = await updateShop({ variables: { id: bodyshop.id, @@ -75,9 +94,7 @@ export function ProductionListConfigManager({ if (!result.errors) { await updateActiveProdView(t("production.constants.main_profile")); - setColumns(defaultConfig.columns.columnKeys); - setState(defaultConfig.columns.tableState); - notification.success({ message: t("bodyshop.successes.defaultviewcreated") }); + window.location.reload(); // Reload the page } else { notification.error({ message: t("bodyshop.errors.creatingdefaultview", { diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 79e208d91..ff93baff8 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -2737,7 +2737,7 @@ }, "production": { "constants":{ - "main_profile": "Main" + "main_profile": "Default" }, "options": { "small": "Small", From 8018daa2dca4ca5cb968e9a07312376d1fe31941 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 Aug 2024 14:12:19 -0400 Subject: [PATCH 32/53] - Address changes to profile from call Signed-off-by: Dave Richer --- ...oduction-list-config-manager.component.jsx | 83 +++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/client/src/components/production-list-table/production-list-config-manager.component.jsx b/client/src/components/production-list-table/production-list-config-manager.component.jsx index ebe001c3e..c33b164ff 100644 --- a/client/src/components/production-list-table/production-list-config-manager.component.jsx +++ b/client/src/components/production-list-table/production-list-config-manager.component.jsx @@ -79,10 +79,6 @@ export function ProductionListConfigManager({ } }; - // Immediately update the columns and state to reflect the new default view - setColumns(defaultConfig.columns.columnKeys); - setState(defaultConfig.columns.tableState); - const result = await updateShop({ variables: { id: bodyshop.id, @@ -161,6 +157,41 @@ export function ProductionListConfigManager({ const selectedConfig = bodyshop.production_config.find((pc) => pc.name === value); + // If the selected profile doesn't exist, revert to the main profile + if (!selectedConfig) { + const mainProfileConfig = bodyshop.production_config.find( + (pc) => pc.name === t("production.constants.main_profile") + ); + + if (mainProfileConfig) { + await updateActiveProdView(t("production.constants.main_profile")); + setColumns( + mainProfileConfig.columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + bodyshop, + refetch, + technician, + state: ensureDefaultState(state), + data: data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width + }; + }) + ); + const newState = ensureDefaultState(mainProfileConfig.columns.tableState); + setState(newState); + + if (onSave && isFunction(onSave)) { + onSave(); + } + return; + } + } + + // If the selected profile exists, proceed as normal if (selectedConfig) { const newColumns = selectedConfig.columns.columnKeys.map((k) => { return { @@ -191,6 +222,7 @@ export function ProductionListConfigManager({ if (name === t("production.constants.main_profile")) return; const remainingConfigs = bodyshop.production_config.filter((b) => b.name !== name); + await updateShop({ variables: { id: bodyshop.id, @@ -202,6 +234,7 @@ export function ProductionListConfigManager({ }); if (name === activeView) { + // Only switch profiles if the deleted profile was the active profile if (remainingConfigs.length > 0) { const nextConfig = remainingConfigs[0]; await updateActiveProdView(nextConfig.name); @@ -277,12 +310,50 @@ export function ProductionListConfigManager({ }; useEffect(() => { + const validateAndSetDefaultView = () => { + const configExists = bodyshop.production_config.some((pc) => pc.name === defaultView); + + if (!configExists) { + // If the default view doesn't exist, revert to the main profile + const mainProfileConfig = bodyshop.production_config.find( + (pc) => pc.name === t("production.constants.main_profile") + ); + + if (mainProfileConfig) { + setActiveView(t("production.constants.main_profile")); + + setColumns( + mainProfileConfig.columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + bodyshop, + refetch, + technician, + state: ensureDefaultState(state), + data: data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width + }; + }) + ); + setState(ensureDefaultState(mainProfileConfig.columns.tableState)); + + updateActiveProdView(t("production.constants.main_profile")); + } + } else { + // If the default view exists, set it as active + setActiveView(defaultView); + } + }; + if (!bodyshop.production_config || bodyshop.production_config.length === 0) { createDefaultView().catch((e) => { console.error("Something went wrong saving the production list view Config."); }); } else { - setActiveView(defaultView); + validateAndSetDefaultView(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultView, bodyshop.production_config]); @@ -326,7 +397,7 @@ export function ProductionListConfigManager({ placeholder={t("production.labels.selectview")} optionLabelProp="label" popupMatchSelectWidth={false} - value={activeView} + value={activeView} // Ensure this only changes when appropriate > {bodyshop.production_config .slice() From 46da3285f846a44aecb46201736fefc3f865f464 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 Aug 2024 14:20:58 -0400 Subject: [PATCH 33/53] - Revert ZOHO (put back in) Signed-off-by: Dave Richer --- client/index.html | 88 +++++++++-------------------------------------- 1 file changed, 17 insertions(+), 71 deletions(-) diff --git a/client/index.html b/client/index.html index 7e4aa27db..9d415aaf6 100644 --- a/client/index.html +++ b/client/index.html @@ -46,77 +46,23 @@ <% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %> Rome Online - - - - - - - - - - - + <% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %> ProManager From 1c186f7fa5eedd09b23ce6f49707ca3b8ad5b04f Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 Aug 2024 14:27:28 -0400 Subject: [PATCH 34/53] - fix missed FormDatePicker reference Signed-off-by: Dave Richer --- .../shop-employees/shop-employees-add-vacation.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/shop-employees/shop-employees-add-vacation.component.jsx b/client/src/components/shop-employees/shop-employees-add-vacation.component.jsx index dd69b3115..a60a91d21 100644 --- a/client/src/components/shop-employees/shop-employees-add-vacation.component.jsx +++ b/client/src/components/shop-employees/shop-employees-add-vacation.component.jsx @@ -90,7 +90,7 @@ export default function ShopEmployeeAddVacation({ employee }) { }) ]} > - + From 9058aca16e53e273d7f34906ceb8c5bc62ff3e0c Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Tue, 20 Aug 2024 14:44:28 -0400 Subject: [PATCH 35/53] - only load chataffix in prod, no more annoying messages / alert dismissals in dev Signed-off-by: Dave Richer --- client/src/pages/manage/manage.page.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index db7146e15..dc81b0b0c 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -571,7 +571,7 @@ export function Manage({ conflict, bodyshop }) { return ( <> - + {import.meta.env.PROD && } From cc2d474fdae00f7a76ff88d30f48317858d74401 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Tue, 20 Aug 2024 17:12:11 -0700 Subject: [PATCH 36/53] IO-2887 Null out BillData if returnfrombill is not available Signed-off-by: Allan Carr --- .../parts-order-list-table-drawer.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/parts-order-list-table/parts-order-list-table-drawer.component.jsx b/client/src/components/parts-order-list-table/parts-order-list-table-drawer.component.jsx index 5dde61a72..168f7687e 100644 --- a/client/src/components/parts-order-list-table/parts-order-list-table-drawer.component.jsx +++ b/client/src/components/parts-order-list-table/parts-order-list-table-drawer.component.jsx @@ -103,7 +103,7 @@ export function PartsOrderListTableDrawerComponent({ } catch (error) { console.error("Error fetching bill data:", error); } - } + } else setBillData(null); }; fetchData(); }, [selectedPartsOrderRecord, billQuery]); From 678ca591c16020e21a6eab1b01689448dd3fcbe2 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 Aug 2024 12:50:11 -0400 Subject: [PATCH 37/53] - enhancements / improvements / stuff Signed-off-by: Dave Richer --- ...rm-date-time-picker-enhanced.component.jsx | 49 ---- .../form-date-time-picker.component.jsx | 276 +++++++----------- .../form-date-time-picker/formats.js | 93 ++++++ .../task-upsert-modal.component.jsx | 8 +- 4 files changed, 197 insertions(+), 229 deletions(-) delete mode 100644 client/src/components/form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx create mode 100644 client/src/components/form-date-time-picker/formats.js diff --git a/client/src/components/form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx b/client/src/components/form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx deleted file mode 100644 index cdabfdfb5..000000000 --- a/client/src/components/form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import { DatePicker } from "antd"; -import dayjs from "../../utils/day.js"; -import React, { useRef } from "react"; - -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectBodyshop } from "../../redux/user/user.selectors.js"; - -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop -}); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); - -const dateFormat = "MM/DD/YYYY h:mm a"; - -export function FormDateTimePickerEnhanced({ - bodyshop, - value, - onBlur, - onlyFuture, - onlyToday, - isDateOnly = true, - ...restProps -}) { - const ref = useRef(); - return ( -
- { - if (onlyToday) { - return !dayjs().isSame(d, "day"); - } else if (onlyFuture) { - return dayjs().subtract(1, "day").isAfter(d); - } - }} - {...restProps} - /> -
- ); -} - -export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced); diff --git a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx index c1bf6f749..90f5fc16d 100644 --- a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx +++ b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx @@ -1,205 +1,135 @@ -import React, { forwardRef, useState } from "react"; +import React, { useCallback, useState } from "react"; import { DatePicker } from "antd"; import dayjs from "../../utils/day"; +import { formats, shorthandFormats } from "./formats.js"; +import PropTypes from "prop-types"; -// To be used as a form element only. - -const DateTimePicker = ( - { value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }, - ref -) => { +const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => { const [isManualInput, setIsManualInput] = useState(false); - const handleChange = (newDate) => { - if (newDate && onChange) { - onChange(newDate); - } - setIsManualInput(false); // Reset the manual input flag when using GUI - }; - - const handleKeyDown = (e) => { - setIsManualInput(true); // User is typing, so set the manual input flag - - if (e.key.toLowerCase() === "t") { - if (onChange) { - onChange(dayjs()); + const handleChange = useCallback( + (newDate) => { + if (newDate === null && onChange) { + onChange(null); + } else if (newDate && onChange) { + onChange(newDate); } - } else if (e.key.toLowerCase() === "enter") { - handleBlur(e); - } - }; + setIsManualInput(false); + }, + [onChange] + ); - const handleBlur = (e) => { - if (!isManualInput) { - // If the input is not manual, skip the format processing - return; - } + const handleBlur = useCallback( + (e) => { + if (!isManualInput) { + return; + } - setIsManualInput(false); // Reset the flag after processing + setIsManualInput(false); - const v = e.target.value; - if (!v) return; + const v = e.target.value; + if (!v) return; - // Convert input to uppercase to handle 'am/pm' as well as 'AM/PM' - const upperV = v.toUpperCase(); + const upperV = v.toUpperCase(); - let _a; + let _a; - // Handling common shorthand datetime inputs - const shorthandFormats = [ - "M/D/YY hA", - "M/D/YY h:mmA", - "M/D/YYYY hA", - "M/D/YYYY h:mmA", - "M/D/YY ha", - "M/D/YY h:mma", - "M/D/YYYY ha", - "M/D/YYYY h:mma" - ]; - - for (let format of shorthandFormats) { - _a = dayjs(upperV, format); - if (_a.isValid()) break; - } - - // If shorthand parsing didn't work, fall back to existing formats - if (!_a || !_a.isValid()) { - const formats = [ - "MMDDYY", - "MMDDYYYY", - "MM/DD/YY", - "MM/DD/YYYY", - "M/DD/YY", - "M/DD/YYYY", - "MM/D/YY", - "MM/D/YYYY", - "M/D/YY", - "M/D/YYYY", - "D/MM/YY", - "D/MM/YYYY", - "DD/M/YY", - "DD/M/YYYY", - "D/M/YY", - "D/M/YYYY", - "MMDDYY hh:mm A", - "MMDDYYYY hh:mm A", - "MM/DD/YY hh:mm A", - "MM/DD/YYYY hh:mm A", - "M/DD/YY hh:mm A", - "M/DD/YYYY hh:mm A", - "MM/D/YY hh:mm A", - "MM/D/YYYY hh:mm A", - "M/D/YY hh:mm A", - "M/D/YYYY hh:mm A", - "D/MM/YY hh:mm A", - "D/MM/YYYY hh:mm A", - "DD/M/YY hh:mm A", - "DD/M/YYYY hh:mm A", - "D/M/YY hh:mm A", - "D/M/YYYY hh:mm A", - "MMDDYY hh:mm:ss A", - "MMDDYYYY hh:mm:ss A", - "MM/DD/YY hh:mm:ss A", - "MM/DD/YYYY hh:mm:ss A", - "M/DD/YY hh:mm:ss A", - "M/DD/YYYY hh:mm:ss A", - "MM/D/YY hh:mm:ss A", - "MM/D/YYYY hh:mm:ss A", - "M/D/YY hh:mm:ss A", - "M/D/YYYY hh:mm:ss A", - "D/MM/YY hh:mm:ss A", - "D/MM/YYYY hh:mm:ss A", - "DD/M/YY hh:mm:ss A", - "DD/M/YYYY hh:mm:ss A", - "D/M/YY hh:mm:ss A", - "D/M/YYYY hh:mm:ss A", - "MMDDYY HH:mm", - "MMDDYYYY HH:mm", - "MM/DD/YY HH:mm", - "MM/DD/YYYY HH:mm", - "M/DD/YY HH:mm", - "M/DD/YYYY HH:mm", - "MM/D/YY HH:mm", - "MM/D/YYYY HH:mm", - "M/D/YY HH:mm", - "M/D/YYYY HH:mm", - "D/MM/YY HH:mm", - "D/MM/YYYY HH:mm", - "DD/M/YY HH:mm", - "DD/M/YYYY HH:mm", - "D/M/YY HH:mm", - "D/M/YYYY HH:mm", - "MMDDYY HH:mm:ss", - "MMDDYYYY HH:mm:ss", - "MM/DD/YY HH:mm:ss", - "MM/DD/YYYY HH:mm:ss", - "M/DD/YY HH:mm:ss", - "M/DD/YYYY HH:mm:ss", - "MM/D/YY HH:mm:ss", - "MM/D/YYYY HH:mm:ss", - "M/D/YY HH:mm:ss", - "M/D/YYYY HH:mm:ss", - "D/MM/YY HH:mm:ss", - "D/MM/YYYY HH:mm:ss", - "DD/M/YY HH:mm:ss", - "DD/M/YYYY HH:mm:ss", - "D/M/YY HH:mm:ss", - "D/M/YYYY HH:mm:ss" - ]; - - for (let format of formats) { + for (const format of shorthandFormats) { _a = dayjs(upperV, format); if (_a.isValid()) break; } - } - if (_a && _a.isValid()) { - if (isDateOnly) { - _a = _a.startOf("day"); // Only apply startOf("day") when isDateOnly is true - } - - if (value && value.isValid && value.isValid()) { - _a.set({ - hours: value.hours(), - minutes: value.minutes(), - seconds: value.seconds(), - milliseconds: value.milliseconds() - }); - } - - if (onlyFuture) { - if (dayjs().subtract(1, "day").isBefore(_a)) { - onChange(_a); - } else { - onChange(dayjs().startOf("day")); + if (!_a || !_a.isValid()) { + for (const format of formats) { + _a = dayjs(upperV, format); + if (_a.isValid()) break; } - } else { - onChange(_a); } - } - }; + + if (_a && _a.isValid()) { + if (isDateOnly) { + _a = _a.startOf("day"); + } + + if (value && value.isValid && value.isValid()) { + _a.set({ + hours: value.hours(), + minutes: value.minutes(), + seconds: value.seconds(), + milliseconds: value.milliseconds() + }); + } + + if (onlyFuture) { + if (dayjs().subtract(1, "day").isBefore(_a)) { + onChange(_a); + } else { + onChange(dayjs().startOf("day")); + } + } else { + onChange(_a); + } + } + }, + [isManualInput, isDateOnly, onlyFuture, onChange, value] + ); + + const handleKeyDown = useCallback( + (e) => { + setIsManualInput(true); + + if (e.key.toLowerCase() === "t" && onChange) { + onChange(dayjs()); + } else if (e.key.toLowerCase() === "enter") { + handleBlur(e); + } + }, + [onChange, handleBlur] + ); + + const handleDisabledDate = useCallback( + (current) => { + if (onlyToday) { + return !dayjs().isSame(current, "day"); + } else if (onlyFuture) { + return dayjs().subtract(1, "day").isAfter(current); + } + return false; + }, + [onlyToday, onlyFuture] + ); return (
{ - if (onlyToday) { - return !dayjs().isSame(d, "day"); - } else if (onlyFuture) { - return dayjs().subtract(1, "day").isAfter(d); - } - return false; - }} + disabledDate={handleDisabledDate} {...restProps} />
); }; -export default forwardRef(DateTimePicker); +DateTimePicker.propTypes = { + value: PropTypes.any, + onChange: PropTypes.func, + onBlur: PropTypes.func, + id: PropTypes.string, + onlyFuture: PropTypes.bool, + onlyToday: PropTypes.bool, + isDateOnly: PropTypes.bool +}; + +export default React.memo(DateTimePicker); diff --git a/client/src/components/form-date-time-picker/formats.js b/client/src/components/form-date-time-picker/formats.js new file mode 100644 index 000000000..7683f9990 --- /dev/null +++ b/client/src/components/form-date-time-picker/formats.js @@ -0,0 +1,93 @@ +export const shorthandFormats = [ + "M/D/YY hA", + "M/D/YY h:mmA", + "M/D/YYYY hA", + "M/D/YYYY h:mmA", + "M/D/YY ha", + "M/D/YY h:mma", + "M/D/YYYY ha", + "M/D/YYYY h:mma" +]; + +export const formats = [ + "MMDDYY", + "MMDDYYYY", + "MM/DD/YY", + "MM/DD/YYYY", + "M/DD/YY", + "M/DD/YYYY", + "MM/D/YY", + "MM/D/YYYY", + "M/D/YY", + "M/D/YYYY", + "D/MM/YY", + "D/MM/YYYY", + "DD/M/YY", + "DD/M/YYYY", + "D/M/YY", + "D/M/YYYY", + "MMDDYY hh:mm A", + "MMDDYYYY hh:mm A", + "MM/DD/YY hh:mm A", + "MM/DD/YYYY hh:mm A", + "M/DD/YY hh:mm A", + "M/DD/YYYY hh:mm A", + "MM/D/YY hh:mm A", + "MM/D/YYYY hh:mm A", + "M/D/YY hh:mm A", + "M/D/YYYY hh:mm A", + "D/MM/YY hh:mm A", + "D/MM/YYYY hh:mm A", + "DD/M/YY hh:mm A", + "DD/M/YYYY hh:mm A", + "D/M/YY hh:mm A", + "D/M/YYYY hh:mm A", + "MMDDYY hh:mm:ss A", + "MMDDYYYY hh:mm:ss A", + "MM/DD/YY hh:mm:ss A", + "MM/DD/YYYY hh:mm:ss A", + "M/DD/YY hh:mm:ss A", + "M/DD/YYYY hh:mm:ss A", + "MM/D/YY hh:mm:ss A", + "MM/D/YYYY hh:mm:ss A", + "M/D/YY hh:mm:ss A", + "M/D/YYYY hh:mm:ss A", + "D/MM/YY hh:mm:ss A", + "D/MM/YYYY hh:mm:ss A", + "DD/M/YY hh:mm:ss A", + "DD/M/YYYY hh:mm:ss A", + "D/M/YY hh:mm:ss A", + "D/M/YYYY hh:mm:ss A", + "MMDDYY HH:mm", + "MMDDYYYY HH:mm", + "MM/DD/YY HH:mm", + "MM/DD/YYYY HH:mm", + "M/DD/YY HH:mm", + "M/DD/YYYY HH:mm", + "MM/D/YY HH:mm", + "MM/D/YYYY HH:mm", + "M/D/YY HH:mm", + "M/D/YYYY HH:mm", + "D/MM/YY HH:mm", + "D/MM/YYYY HH:mm", + "DD/M/YY HH:mm", + "DD/M/YYYY HH:mm", + "D/M/YY HH:mm", + "D/M/YYYY HH:mm", + "MMDDYY HH:mm:ss", + "MMDDYYYY HH:mm:ss", + "MM/DD/YY HH:mm:ss", + "MM/DD/YYYY HH:mm:ss", + "M/DD/YY HH:mm:ss", + "M/DD/YYYY HH:mm:ss", + "MM/D/YY HH:mm:ss", + "MM/D/YYYY HH:mm:ss", + "M/D/YY HH:mm:ss", + "M/D/YYYY HH:mm:ss", + "D/MM/YY HH:mm:ss", + "D/MM/YYYY HH:mm:ss", + "DD/M/YY HH:mm:ss", + "DD/M/YYYY HH:mm:ss", + "D/M/YY HH:mm:ss", + "D/M/YYYY HH:mm:ss" +]; diff --git a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx index 7d498efc3..562141a40 100644 --- a/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx +++ b/client/src/components/task-upsert-modal/task-upsert-modal.component.jsx @@ -7,7 +7,6 @@ import dayjs from "../../utils/day"; import { connect } from "react-redux"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx"; -import { FormDateTimePickerEnhanced } from "../form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; const mapStateToProps = createStructuredSelector({ @@ -279,12 +278,7 @@ export function TaskUpsertModalComponent({ } ]} > - + From a567d0d6dd4e5ee62f45d74034ce6e14e3998441 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 Aug 2024 13:46:08 -0400 Subject: [PATCH 38/53] - enhancements / improvements / stuff Signed-off-by: Dave Richer --- client/src/components/partner-ping/partner-ping.component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/partner-ping/partner-ping.component.jsx b/client/src/components/partner-ping/partner-ping.component.jsx index f40566fa1..aa0366e5c 100644 --- a/client/src/components/partner-ping/partner-ping.component.jsx +++ b/client/src/components/partner-ping/partner-ping.component.jsx @@ -23,7 +23,7 @@ export function PartnerPingComponent({ bodyshop }) { // Execute the created function directly checkPartnerStatus(bodyshop); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [bodyshop]); + }, [bodyshop?.id]); return <>; } From 153cf6a84044e1145b313ca8fa86599758f40bae Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 Aug 2024 13:53:43 -0400 Subject: [PATCH 39/53] - enhancements / improvements / stuff Signed-off-by: Dave Richer --- .../form-date-time-picker/form-date-time-picker.component.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx index 90f5fc16d..20388a740 100644 --- a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx +++ b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx @@ -79,6 +79,7 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is setIsManualInput(true); if (e.key.toLowerCase() === "t" && onChange) { + setIsManualInput(false); onChange(dayjs()); } else if (e.key.toLowerCase() === "enter") { handleBlur(e); From ad1ce7b22069f5e35285def1e077386547ea7302 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 21 Aug 2024 11:00:59 -0700 Subject: [PATCH 40/53] IO-2888 Production List Employee Sort Enhacement Signed-off-by: Allan Carr --- .../production-list-columns.data.jsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/client/src/components/production-list-columns/production-list-columns.data.jsx b/client/src/components/production-list-columns/production-list-columns.data.jsx index fbc9236a5..16a450dab 100644 --- a/client/src/components/production-list-columns/production-list-columns.data.jsx +++ b/client/src/components/production-list-columns/production-list-columns.data.jsx @@ -28,6 +28,11 @@ import ProductionListColumnCategory from "./production-list-columns.status.categ import ProductionListColumnStatus from "./production-list-columns.status.component"; import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component"; +const getEmployeeName = (employeeId, employees) => { + const employee = employees.find((e) => e.id === employeeId); + return employee ? `${employee.first_name} ${employee.last_name}` : ""; +}; + const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => { const { Enhanced_Payroll } = treatments; return [ @@ -426,8 +431,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order, sorter: (a, b) => alphaSort( - bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name, - bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name + getEmployeeName(a.employee_body, bodyshop.employees), + getEmployeeName(b.employee_body, bodyshop.employees) ), render: (text, record) => ( @@ -440,8 +445,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order, sorter: (a, b) => alphaSort( - bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name, - bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name + getEmployeeName(a.employee_prep, bodyshop.employees), + getEmployeeName(b.employee_prep, bodyshop.employees) ), render: (text, record) => ( @@ -460,8 +465,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order, sorter: (a, b) => alphaSort( - bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name, - bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name + getEmployeeName(a.employee_csr, bodyshop.employees), + getEmployeeName(b.employee_csr, bodyshop.employees) ), render: (text, record) => ( @@ -474,8 +479,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order, sorter: (a, b) => alphaSort( - bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name, - bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name + getEmployeeName(a.employee_refinish, bodyshop.employees), + getEmployeeName(b.employee_refinish, bodyshop.employees) ), render: (text, record) => ( From c3e6d3dc48bb15b6c8a7a4ffbc6ca19d453d6e51 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 Aug 2024 15:09:13 -0400 Subject: [PATCH 41/53] - Checkpoint Signed-off-by: Dave Richer --- ...oduction-list-config-manager.component.jsx | 105 ++++++++++-------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/client/src/components/production-list-table/production-list-config-manager.component.jsx b/client/src/components/production-list-table/production-list-config-manager.component.jsx index c33b164ff..95f4ceccf 100644 --- a/client/src/components/production-list-table/production-list-config-manager.component.jsx +++ b/client/src/components/production-list-table/production-list-config-manager.component.jsx @@ -376,7 +376,14 @@ export function ProductionListConfigManager({ > {t("general.actions.save")} - +
@@ -384,55 +391,55 @@ export function ProductionListConfigManager({ return ( - - + + + - ); } From c89e4f1b414e2f8f41263f144aff90186c5ed619 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 Aug 2024 15:14:33 -0400 Subject: [PATCH 42/53] - Checkpoint Signed-off-by: Dave Richer --- client/src/translations/en_us/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index ff93baff8..1304eff5f 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1180,7 +1180,7 @@ "vehicle": "Vehicle" }, "labels": { - "unsavedchanges": "Unsaved change.", + "unsavedchanges": "Unsaved changes.", "actions": "Actions", "areyousure": "Are you sure?", "barcode": "Barcode", From cc9979ff4b80cd25786a680cbcc4f4f459d1af4b Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 21 Aug 2024 12:25:38 -0700 Subject: [PATCH 43/53] IO-2834 Split Date and DateTime formats, remove shorthand and checks Signed-off-by: Allan Carr --- .../form-date-time-picker.component.jsx | 18 +- .../form-date-time-picker/formats.js | 200 ++++++++++-------- 2 files changed, 122 insertions(+), 96 deletions(-) diff --git a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx index 20388a740..85349ff06 100644 --- a/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx +++ b/client/src/components/form-date-time-picker/form-date-time-picker.component.jsx @@ -1,8 +1,8 @@ -import React, { useCallback, useState } from "react"; import { DatePicker } from "antd"; -import dayjs from "../../utils/day"; -import { formats, shorthandFormats } from "./formats.js"; import PropTypes from "prop-types"; +import React, { useCallback, useState } from "react"; +import dayjs from "../../utils/day"; +import { formats } from "./formats.js"; const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => { const [isManualInput, setIsManualInput] = useState(false); @@ -34,18 +34,14 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is let _a; - for (const format of shorthandFormats) { + for (const format of formats) { + console.log("format",format); _a = dayjs(upperV, format); + console.log("🚀 ~ DateTimePicker ~ _a:", _a) + console.log("isvalid",_a.isValid()); if (_a.isValid()) break; } - if (!_a || !_a.isValid()) { - for (const format of formats) { - _a = dayjs(upperV, format); - if (_a.isValid()) break; - } - } - if (_a && _a.isValid()) { if (isDateOnly) { _a = _a.startOf("day"); diff --git a/client/src/components/form-date-time-picker/formats.js b/client/src/components/form-date-time-picker/formats.js index 7683f9990..0ca0d52c6 100644 --- a/client/src/components/form-date-time-picker/formats.js +++ b/client/src/components/form-date-time-picker/formats.js @@ -1,93 +1,123 @@ -export const shorthandFormats = [ - "M/D/YY hA", - "M/D/YY h:mmA", - "M/D/YYYY hA", - "M/D/YYYY h:mmA", - "M/D/YY ha", +export const dateTimeFormats = [ + "MMDDYY h:mma", + "MMDDYYYY h:mma", "M/D/YY h:mma", - "M/D/YYYY ha", - "M/D/YYYY h:mma" + "M/DD/YY h:mma", + "MM/D/YY h:mma", + "MM/DD/YY h:mma", + "M/D/YYYY h:mma", + "M/DD/YYYY h:mma", + "MM/D/YYYY h:mma", + "MM/DD/YYYY h:mma", + + "MMDDYY h:mmA", + "MMDDYYYY h:mmA", + "M/D/YY h:mmA", + "M/DD/YY h:mmA", + "MM/D/YY h:mmA", + "MM/DD/YY h:mmA", + "M/D/YYYY h:mmA", + "M/DD/YYYY h:mmA", + "MM/D/YYYY h:mmA", + "MM/DD/YYYY h:mmA", + + "MMDDYY h:mm a", + "MMDDYYYY h:mm a", + "M/D/YY h:mm a", + "M/DD/YY h:mm a", + "MM/D/YY h:mm a", + "MM/DD/YY h:mm a", + "M/D/YYYY h:mm a", + "M/DD/YYYY h:mm a", + "MM/D/YYYY h:mm a", + "MM/DD/YYYY h:mm a", + + "MMDDYY h:mm A", + "MMDDYYYY h:mm A", + "M/D/YY h:mm A", + "M/DD/YY h:mm A", + "MM/D/YY h:mm A", + "MM/DD/YY h:mm A", + "M/D/YYYY h:mm A", + "M/DD/YYYY h:mm A", + "MM/D/YYYY h:mm A", + "MM/DD/YYYY h:mm A", + + "MMDDYY h:mm:ssa", + "MMDDYYYY h:mm:ssa", + "M/D/YY h:mm:ssa", + "M/DD/YY h:mm:ssa", + "MM/D/YY h:mm:ssa", + "MM/DD/YY h:mm:ssa", + "M/D/YYYY h:mm:ssa", + "M/DD/YYYY h:mm:ssa", + "MM/D/YYYY h:mm:ssa", + "MM/DD/YYYY h:mm:ssa", + + "MMDDYY h:mm:ssA", + "MMDDYYYY h:mm:ssA", + "M/D/YY h:mm:ssA", + "M/DD/YY h:mm:ssA", + "MM/D/YY h:mm:ssA", + "MM/DD/YY h:mm:ssA", + "M/D/YYYY h:mm:ssA", + "M/DD/YYYY h:mm:ssA", + "MM/D/YYYY h:mm:ssA", + "MM/DD/YYYY h:mm:ssA", + + "MMDDYY h:mm:ss a", + "MMDDYYYY h:mm:ss a", + "M/D/YY h:mm:ss a", + "M/DD/YY h:mm:ss a", + "MM/D/YY h:mm:ss a", + "MM/DD/YY h:mm:ss a", + "M/D/YYYY h:mm:ss a", + "M/DD/YYYY h:mm:ss a", + "MM/D/YYYY h:mm:ss a", + "MM/DD/YYYY h:mm:ss a", + + "MMDDYY h:mm:ss A", + "MMDDYYYY h:mm:ss A", + "M/D/YY h:mm:ss A", + "M/DD/YY h:mm:ss A", + "MM/D/YY h:mm:ss A", + "MM/DD/YY h:mm:ss A", + "M/D/YYYY h:mm:ss A", + "M/DD/YYYY h:mm:ss A", + "MM/D/YYYY h:mm:ss A", + "MM/DD/YYYY h:mm:ss A", + + "MMDDYY H:mm", + "MMDDYYYY H:mm", + "M/D/YY H:mm", + "M/DD/YY H:mm", + "MM/D/YY H:mm", + "MM/DD/YY H:mm", + "M/D/YYYY H:mm", + "M/DD/YYYY H:mm", + "MM/D/YYYY H:mm", + "MM/DD/YYYY H:mm", + + "MMDDYY H:mm:ss", + "MMDDYYYY H:mm:ss", + "M/D/YY H:mm:ss", + "M/DD/YY H:mm:ss", + "MM/D/YY H:mm:ss", + "MM/DD/YY H:mm:ss", + "M/D/YYYY H:mm:ss", + "MM/D/YYYY H:mm:ss", + "MM/DD/YYYY H:mm:ss" ]; -export const formats = [ +export const dateFormats = [ "MMDDYY", "MMDDYYYY", - "MM/DD/YY", - "MM/DD/YYYY", - "M/DD/YY", - "M/DD/YYYY", - "MM/D/YY", - "MM/D/YYYY", "M/D/YY", + "M/DD/YY", + "MM/D/YY", + "MM/DD/YY", "M/D/YYYY", - "D/MM/YY", - "D/MM/YYYY", - "DD/M/YY", - "DD/M/YYYY", - "D/M/YY", - "D/M/YYYY", - "MMDDYY hh:mm A", - "MMDDYYYY hh:mm A", - "MM/DD/YY hh:mm A", - "MM/DD/YYYY hh:mm A", - "M/DD/YY hh:mm A", - "M/DD/YYYY hh:mm A", - "MM/D/YY hh:mm A", - "MM/D/YYYY hh:mm A", - "M/D/YY hh:mm A", - "M/D/YYYY hh:mm A", - "D/MM/YY hh:mm A", - "D/MM/YYYY hh:mm A", - "DD/M/YY hh:mm A", - "DD/M/YYYY hh:mm A", - "D/M/YY hh:mm A", - "D/M/YYYY hh:mm A", - "MMDDYY hh:mm:ss A", - "MMDDYYYY hh:mm:ss A", - "MM/DD/YY hh:mm:ss A", - "MM/DD/YYYY hh:mm:ss A", - "M/DD/YY hh:mm:ss A", - "M/DD/YYYY hh:mm:ss A", - "MM/D/YY hh:mm:ss A", - "MM/D/YYYY hh:mm:ss A", - "M/D/YY hh:mm:ss A", - "M/D/YYYY hh:mm:ss A", - "D/MM/YY hh:mm:ss A", - "D/MM/YYYY hh:mm:ss A", - "DD/M/YY hh:mm:ss A", - "DD/M/YYYY hh:mm:ss A", - "D/M/YY hh:mm:ss A", - "D/M/YYYY hh:mm:ss A", - "MMDDYY HH:mm", - "MMDDYYYY HH:mm", - "MM/DD/YY HH:mm", - "MM/DD/YYYY HH:mm", - "M/DD/YY HH:mm", - "M/DD/YYYY HH:mm", - "MM/D/YY HH:mm", - "MM/D/YYYY HH:mm", - "M/D/YY HH:mm", - "M/D/YYYY HH:mm", - "D/MM/YY HH:mm", - "D/MM/YYYY HH:mm", - "DD/M/YY HH:mm", - "DD/M/YYYY HH:mm", - "D/M/YY HH:mm", - "D/M/YYYY HH:mm", - "MMDDYY HH:mm:ss", - "MMDDYYYY HH:mm:ss", - "MM/DD/YY HH:mm:ss", - "MM/DD/YYYY HH:mm:ss", - "M/DD/YY HH:mm:ss", - "M/DD/YYYY HH:mm:ss", - "MM/D/YY HH:mm:ss", - "MM/D/YYYY HH:mm:ss", - "M/D/YY HH:mm:ss", - "M/D/YYYY HH:mm:ss", - "D/MM/YY HH:mm:ss", - "D/MM/YYYY HH:mm:ss", - "DD/M/YY HH:mm:ss", - "DD/M/YYYY HH:mm:ss", - "D/M/YY HH:mm:ss", - "D/M/YYYY HH:mm:ss" + "M/DD/YYYY", + "MM/D/YYYY", + "MM/DD/YYYY" ]; From 90532427b67f3833f54ad8df71190a8801533206 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 Aug 2024 15:32:22 -0400 Subject: [PATCH 44/53] - Checkpoint Signed-off-by: Dave Richer --- ...oduction-list-config-manager.component.jsx | 40 +++++++++++++++++-- client/src/translations/en_us/common.json | 7 +++- client/src/translations/es/common.json | 7 +++- client/src/translations/fr/common.json | 7 +++- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/client/src/components/production-list-table/production-list-config-manager.component.jsx b/client/src/components/production-list-table/production-list-config-manager.component.jsx index 95f4ceccf..80c32985f 100644 --- a/client/src/components/production-list-table/production-list-config-manager.component.jsx +++ b/client/src/components/production-list-table/production-list-config-manager.component.jsx @@ -362,7 +362,25 @@ export function ProductionListConfigManager({
{isAddingNewProfile && ( - + { + if (!value) { + return Promise.resolve(); + } + const nameExists = bodyshop.production_config.some((pc) => pc.name === value); + if (nameExists) { + return Promise.reject(new Error(t("production.errors.name_exists"))); + } + return Promise.resolve(); + } + } + ]} + > )} @@ -376,13 +394,22 @@ export function ProductionListConfigManager({ > {t("general.actions.save")} + @@ -417,7 +444,14 @@ export function ProductionListConfigManager({ .map((config) => (
- + {config.name} {config.name !== t("production.constants.main_profile") && ( diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 1304eff5f..98d9418ec 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1165,7 +1165,8 @@ "tryagain": "Try Again", "view": "View", "viewreleasenotes": "See What's Changed", - "remove_alert": "Are you sure you want to dismiss the alert?" + "remove_alert": "Are you sure you want to dismiss the alert?", + "saveas": "Save As" }, "errors": { "fcm": "You must allow notification permissions to have real time messaging. Click to try again.", @@ -2788,7 +2789,9 @@ "errors": { "boardupdate": "Error encountered updating Job. {{message}}", "removing": "Error removing from production board. {{error}}", - "settings": "Error saving board settings: {{error}}" + "settings": "Error saving board settings: {{error}}", + "name_exists": "A Profile with this name already exists. Please choose a different name.", + "name_required": "Profile name is required." }, "labels": { "kiosk_mode": "Kiosk Mode", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 76f537e61..18edcbeeb 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1165,7 +1165,8 @@ "tryagain": "", "view": "", "viewreleasenotes": "", - "remove_alert": "" + "remove_alert": "", + "saveas": "" }, "errors": { "fcm": "", @@ -2788,7 +2789,9 @@ "errors": { "boardupdate": "", "removing": "", - "settings": "" + "settings": "", + "name_exists": "", + "name_required": "" }, "labels": { "kiosk_mode": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index fccb1ce65..adc23a1a0 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1165,7 +1165,8 @@ "tryagain": "", "view": "", "viewreleasenotes": "", - "remove_alert": "" + "remove_alert": "", + "saveas": "" }, "errors": { "fcm": "", @@ -2788,7 +2789,9 @@ "errors": { "boardupdate": "", "removing": "", - "settings": "" + "settings": "", + "name_exists": "", + "name_required": "" }, "labels": { "kiosk_mode": "", From 11785f3b862af6a3145fc1c46f92049e6d2b0715 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Wed, 21 Aug 2024 15:39:16 -0400 Subject: [PATCH 45/53] - Checkpoint Signed-off-by: Dave Richer --- ...oduction-list-config-manager.component.jsx | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/client/src/components/production-list-table/production-list-config-manager.component.jsx b/client/src/components/production-list-table/production-list-config-manager.component.jsx index 80c32985f..26fc45511 100644 --- a/client/src/components/production-list-table/production-list-config-manager.component.jsx +++ b/client/src/components/production-list-table/production-list-config-manager.component.jsx @@ -394,15 +394,17 @@ export function ProductionListConfigManager({ > {t("general.actions.save")} - + {!isAddingNewProfile && ( + + )}