From 0054b00d01cbab149ff8cd024ce8a86dc767ba86 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 19 Aug 2024 17:45:36 -0400 Subject: [PATCH] - 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": ""