From 36d92d406034cbf0b926d5d1b589c9e5fc659ec0 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 15 Aug 2024 14:59:47 -0400 Subject: [PATCH 1/3] - Production Board List View Unsaved Changes Prompt Signed-off-by: Dave Richer --- ...tion-list-save-config-button.component.jsx | 6 +- .../production-list-table.component.jsx | 102 +++++++++++++----- 2 files changed, 79 insertions(+), 29 deletions(-) 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 index bdbb2b5a6..a5f0b0f40 100644 --- 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 @@ -7,6 +7,7 @@ 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 @@ -16,7 +17,7 @@ const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); -export function ProductionListSaveConfigButton({ columns, bodyshop, tableState }) { +export function ProductionListSaveConfigButton({ columns, bodyshop, tableState, onSave }) { const [updateShop] = useMutation(UPDATE_SHOP); const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); @@ -49,6 +50,9 @@ export function ProductionListSaveConfigButton({ columns, bodyshop, tableState } }); if (!!!result.errors) { notification["success"]({ message: t("bodyshop.successes.save") }); + if (onSave && isFunction(onSave)) { + onSave(); + } } else { notification["error"]({ message: t("bodyshop.errors.saving", { 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 12938829d..5a80a8290 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 @@ -1,8 +1,6 @@ -import { SyncOutlined } from "@ant-design/icons"; -import { useSplitTreatments } from "@splitsoftware/splitio-react"; +import { useEffect, useMemo, useState } from "react"; import { Button, Dropdown, Input, Space, Statistic, Table } from "antd"; import { PageHeader } from "@ant-design/pro-layout"; -import React, { useEffect, useMemo, useState } from "react"; import ReactDragListView from "react-drag-listview"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -16,6 +14,10 @@ import ProductionListSaveConfigButton from "../production-list-save-config-butto 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"; // lodash will be used for deep comparison const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -25,6 +27,7 @@ const mapStateToProps = createStructuredSelector({ export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) { const [searchText, setSearchText] = useState(""); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const { treatments: { Production_List_Status_Colors, Enhanced_Payroll } @@ -35,7 +38,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici }); const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); - const defaultView = assoc && assoc.default_prod_list_view; const [state, setState] = useState( @@ -93,34 +95,48 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici })) || []; setColumns(newColumns); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ - //state, matchingColumnConfig, bodyshop, technician, - data - ]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed. + data, + Enhanced_Payroll, + Production_List_Status_Colors, + refetch, + state + ]); const handleTableChange = (pagination, filters, sorter) => { - setState({ + const newState = { ...state, filteredInfo: filters, sortedInfo: { columnKey: sorter.columnKey, order: sorter.order } - }); + }; + if (!_.isEqual(newState, state)) { + setState(newState); + setHasUnsavedChanges(true); + } }; const onDragEnd = (fromIndex, toIndex) => { const columnsCopy = columns.slice(); const item = columnsCopy.splice(fromIndex, 1)[0]; columnsCopy.splice(toIndex, 0, item); - setColumns(columnsCopy); + + if (!_.isEqual(columnsCopy, columns)) { + setColumns(columnsCopy); + setHasUnsavedChanges(true); + } }; const removeColumn = (e) => { const { key } = e; const newColumns = columns.filter((i) => i.key !== key); - setColumns(newColumns); + + if (!_.isEqual(newColumns, columns)) { + setColumns(newColumns); + setHasUnsavedChanges(true); + } }; const handleResize = @@ -131,9 +147,22 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici ...nextColumns[index], width: size.width }; - setColumns(nextColumns); + + if (!_.isEqual(nextColumns, columns)) { + setColumns(nextColumns); + setHasUnsavedChanges(true); + } }; + const addColumn = (newColumn) => { + const updatedColumns = [...columns, newColumn]; + + if (!_.isEqual(updatedColumns, columns)) { + setColumns(updatedColumns); + setHasUnsavedChanges(true); + } + }; + const headerItem = (col) => { const menu = { onClick: removeColumn, @@ -168,14 +197,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) ); - // const handleSelectRecord = (record) => { - // if (selected !== record.id) { - // setSelected(record.id); - // } else { - // setSelected(null); - // } - // }; - if (!!!columns) return
No columns found.
; const totalHrs = data @@ -186,8 +207,10 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici .toFixed(1); const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1); const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1); + return (
+ @@ -199,20 +222,43 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici } extra={ - - - - + + { + setHasUnsavedChanges(false); + }} + /> { + if (!_.isEqual(newState, state)) { + setState(newState); + setHasUnsavedChanges(true); + } + }} + setColumns={(newColumns) => { + if (!_.isEqual(newColumns, columns)) { + setColumns(newColumns); + setHasUnsavedChanges(true); + } + }} refetch={refetch} data={data} /> - setSearchText(e.target.value)} placeholder={t("general.labels.search")} From 879eba02472859e125359bb847ab062f7c34abe5 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 15 Aug 2024 20:16:03 -0400 Subject: [PATCH 2/3] - Add Alert - Fix 2 bugs Signed-off-by: Dave Richer --- .../production-list-columns.add.component.jsx | 60 +++++----- .../production-list-table.component.jsx | 110 +++++++++++------- 2 files changed, 101 insertions(+), 69 deletions(-) diff --git a/client/src/components/production-list-columns/production-list-columns.add.component.jsx b/client/src/components/production-list-columns/production-list-columns.add.component.jsx index 1d1ea8867..24cca5537 100644 --- a/client/src/components/production-list-columns/production-list-columns.add.component.jsx +++ b/client/src/components/production-list-columns/production-list-columns.add.component.jsx @@ -2,7 +2,6 @@ import React from "react"; import { Button, Dropdown } from "antd"; import dataSource from "./production-list-columns.data"; import { useTranslation } from "react-i18next"; - import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectTechnician } from "../../redux/tech/tech.selectors"; @@ -10,16 +9,23 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser technician: selectTechnician, bodyshop: selectBodyshop }); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); -export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent); -export function ProductionColumnsComponent({ columnState, technician, bodyshop, data, tableState, refetch }) { +const mapDispatchToProps = (dispatch) => ({ + // Add any necessary dispatch actions here +}); + +export function ProductionColumnsComponent({ + columnState, + technician, + bodyshop, + data, + tableState, + refetch, + onColumnAdd +}) { const [columns, setColumns] = columnState; const { t } = useTranslation(); const { @@ -29,18 +35,26 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop, names: ["Enhanced_Payroll"], splitKey: bodyshop.imexshopid }); + const handleAdd = (e) => { - setColumns([ - ...columns, - ...dataSource({ - bodyshop, - technician, - state: tableState, - data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments: { Enhanced_Payroll } - }).filter((i) => i.key === e.key) - ]); + const newColumn = dataSource({ + bodyshop, + technician, + state: tableState, + data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Enhanced_Payroll } + }).find((i) => i.key === e.key); + + if (newColumn) { + const updatedColumns = [...columns, newColumn]; + setColumns(updatedColumns); + + // Call the onColumnAdd function passed as a prop + if (onColumnAdd) { + onColumnAdd(newColumn); + } + } }; const columnKeys = columns.map((i) => i.key); @@ -76,12 +90,4 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop, ); } -// c.key)} -// render={(item) => item.title} -// onChange={(nextTargetKeys, direction, moveKeys) => { -// setColumns(dataSource.filter((i) => nextTargetKeys.includes(i.key))); -// }} -// /> +export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent); 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 5a80a8290..953f2e34b 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 @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { Button, Dropdown, Input, Space, Statistic, Table } from "antd"; import { PageHeader } from "@ant-design/pro-layout"; import ReactDragListView from "react-drag-listview"; @@ -17,7 +17,8 @@ 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"; // lodash will be used for deep comparison +import _ from "lodash"; +import AlertComponent from "../alert/alert.component.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -40,7 +41,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email); const defaultView = assoc && assoc.default_prod_list_view; - const [state, setState] = useState( + const initialStateRef = useRef( (bodyshop.production_config && bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) || bodyshop.production_config[0]?.columns.tableState || { @@ -49,51 +50,52 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici } ); - const { t } = useTranslation(); - - const matchingColumnConfig = useMemo(() => { - return bodyshop.production_config.find((p) => p.name === defaultView); - }, [bodyshop.production_config, defaultView]); - - const [columns, setColumns] = useState( - (state && - matchingColumnConfig && - matchingColumnConfig.columns.columnKeys.map((k) => { - return { - ...ProductionListColumns({ - bodyshop, - refetch, - technician, - state, - data, - activeStatuses: bodyshop.md_ro_statuses.active_statuses, - treatments: { Production_List_Status_Colors, Enhanced_Payroll } - }).find((e) => e.key === k.key), - width: k.width ?? 100 - }; - })) || - [] - ); - - useEffect(() => { - const newColumns = - (state && - matchingColumnConfig && - matchingColumnConfig.columns.columnKeys.map((k) => { + const initialColumnsRef = useRef( + (initialStateRef.current && + bodyshop.production_config + .find((p) => p.name === defaultView) + ?.columns.columnKeys.map((k) => { return { ...ProductionListColumns({ bodyshop, - technician, refetch, - state, - data: data, + technician, + state: initialStateRef.current, + data, activeStatuses: bodyshop.md_ro_statuses.active_statuses, treatments: { Production_List_Status_Colors, Enhanced_Payroll } }).find((e) => e.key === k.key), width: k.width ?? 100 }; })) || - []; + [] + ); + + const [state, setState] = useState(initialStateRef.current); + const [columns, setColumns] = useState(initialColumnsRef.current); + + const { t } = useTranslation(); + + const matchingColumnConfig = useMemo(() => { + return bodyshop.production_config.find((p) => p.name === defaultView); + }, [bodyshop.production_config, defaultView]); + + useEffect(() => { + const newColumns = + matchingColumnConfig?.columns.columnKeys.map((k) => { + return { + ...ProductionListColumns({ + bodyshop, + technician, + refetch, + state, + data: data, + activeStatuses: bodyshop.md_ro_statuses.active_statuses, + treatments: { Production_List_Status_Colors, Enhanced_Payroll } + }).find((e) => e.key === k.key), + width: k.width ?? 100 + }; + }) || []; setColumns(newColumns); }, [ matchingColumnConfig, @@ -156,7 +158,6 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici const addColumn = (newColumn) => { const updatedColumns = [...columns, newColumn]; - if (!_.isEqual(updatedColumns, columns)) { setColumns(updatedColumns); setHasUnsavedChanges(true); @@ -181,6 +182,12 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici ); }; + const resetChanges = () => { + setState(initialStateRef.current); + setColumns(initialColumnsRef.current); + setHasUnsavedChanges(false); + }; + const dataSource = searchText === "" ? data @@ -210,7 +217,26 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici return (
- + + {hasUnsavedChanges && ( + + {t("general.messages.unsavedchanges")} + + {t("general.actions.reset")} + +
+ } + /> + )} @@ -247,13 +273,13 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici setState={(newState) => { if (!_.isEqual(newState, state)) { setState(newState); - setHasUnsavedChanges(true); + setHasUnsavedChanges(false); } }} setColumns={(newColumns) => { if (!_.isEqual(newColumns, columns)) { setColumns(newColumns); - setHasUnsavedChanges(true); + setHasUnsavedChanges(false); } }} refetch={refetch} From 0fd945b8592510bed81e60f456ed4ebc847d52e4 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Thu, 15 Aug 2024 20:42:18 -0400 Subject: [PATCH 3/3] - fix Signed-off-by: Dave Richer --- ...ction-list-table-view-select.component.jsx | 55 ++++++++++------ .../production-list-table.component.jsx | 65 ++++++++++--------- 2 files changed, 69 insertions(+), 51 deletions(-) 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 index 9a98ede10..f8297fe02 100644 --- 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 @@ -11,6 +11,7 @@ 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, @@ -18,7 +19,17 @@ const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser }); -export function ProductionListTable({ refetch, bodyshop, technician, currentUser, state, data, setColumns, setState }) { +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); @@ -32,25 +43,25 @@ export function ProductionListTable({ refetch, bodyshop, technician, currentUser }); const handleSelect = async (value, option) => { - setColumns( - 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 - }; - }) - ); - setState(bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState); + 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); @@ -72,6 +83,10 @@ export function ProductionListTable({ refetch, bodyshop, technician, currentUser } }); } + + if (onProfileChange && isFunction(onProfileChange)) { + onProfileChange({ value, option, newColumns, newState, assoc }); + } }; const handleTrash = async (name) => { 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 953f2e34b..6e9e1f918 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 @@ -96,7 +96,11 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici width: k.width ?? 100 }; }) || []; - setColumns(newColumns); + + // Only update columns if they haven't been manually changed by the user + if (_.isEqual(initialColumnsRef.current, columns)) { + setColumns(newColumns); + } }, [ matchingColumnConfig, bodyshop, @@ -105,7 +109,8 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici Enhanced_Payroll, Production_List_Status_Colors, refetch, - state + state, + columns ]); const handleTableChange = (pagination, filters, sorter) => { @@ -121,9 +126,11 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici }; const onDragEnd = (fromIndex, toIndex) => { - const columnsCopy = columns.slice(); - const item = columnsCopy.splice(fromIndex, 1)[0]; - columnsCopy.splice(toIndex, 0, item); + if (fromIndex === toIndex) return; + + const columnsCopy = [...columns]; + const [movedItem] = columnsCopy.splice(fromIndex, 1); + columnsCopy.splice(toIndex, 0, movedItem); if (!_.isEqual(columnsCopy, columns)) { setColumns(columnsCopy); @@ -188,21 +195,23 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici setHasUnsavedChanges(false); }; - const dataSource = - searchText === "" - ? data - : data.filter( - (j) => - (j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) || - (j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.status || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.ins_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) || - (j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase()) - ); + const filterData = (item, searchText) => { + const fieldsToSearch = [ + item.ro_number, + item.ownr_co_nm, + item.ownr_fn, + item.ownr_ln, + item.status, + item.ins_co_nm, + item.clm_no, + item.v_model_desc, + item.v_make_desc + ]; + + return fieldsToSearch.some((field) => (field || "").toString().toLowerCase().includes(searchText.toLowerCase())); + }; + + const dataSource = searchText === "" ? data : data.filter((j) => filterData(j, searchText)); if (!!!columns) return
No columns found.
; @@ -270,17 +279,11 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici /> { - if (!_.isEqual(newState, state)) { - setState(newState); - setHasUnsavedChanges(false); - } - }} - setColumns={(newColumns) => { - if (!_.isEqual(newColumns, columns)) { - setColumns(newColumns); - setHasUnsavedChanges(false); - } + setState={setState} + setColumns={setColumns} + onProfileChange={() => { + initialStateRef.current = state; + setHasUnsavedChanges(false); }} refetch={refetch} data={data}