From 0054b00d01cbab149ff8cd024ce8a86dc767ba86 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 19 Aug 2024 17:45:36 -0400 Subject: [PATCH 01/27] - 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 02/27] - 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 05/27] - 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 06/27] - 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 07/27] - 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 08/27] - 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 09/27] - 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 10/27] 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 11/27] - 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 12/27] - 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 13/27] - 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 14/27] 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 15/27] - 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 16/27] - 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 17/27] 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 18/27] - 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 19/27] - 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 && ( + + )}