diff --git a/WIP Changelog.txt b/WIP Changelog.txt index 78d172c..905956d 100644 --- a/WIP Changelog.txt +++ b/WIP Changelog.txt @@ -1,4 +1,8 @@ +New Features: +- Added a reference guide for MPI Grouping Guidelines next to the Group Descriptor on the estimate screen. +- Added ability to filter for jobs with no close date on job search. + Bug Fixes: -- Added remanufactured as a part type dislpay instead of 'PAM'. -- Resolve an issue where group would occasionally not be preserved when changed. -- Added 'Reman' as a displayed part type. \ No newline at end of file +- Added better error handling on reporting for jobs that did not have a group set. +- Added a confirmation on changes to shop settings to indicate that a restart is required for them to take effect. +- Added auto update download for users who are not seeing progress during update download. \ No newline at end of file diff --git a/electron/changelog.json b/electron/changelog.json index d0a009c..98e7f9d 100644 --- a/electron/changelog.json +++ b/electron/changelog.json @@ -13,5 +13,10 @@ "title": "Release Notes for 1.0.11", "date": "11/24/2020", "notes": "Bug Fixes: \n- Added remanufactured as a part type dislpay instead of 'PAM'.\n- Resolve an issue where group would occasionally not be preserved when changed.\n- Added 'Reman' as a displayed part type." + }, + "1.0.12": { + "title": "Release Notes for 1.0.12", + "date": "11/26/2020", + "notes": "New Features: \n- Added a reference guide for MPI Grouping Guidelines next to the Group Descriptor on the estimate screen.\n- Added ability to filter for jobs with no close date on job search.\n\nBug Fixes: \n- Added better error handling on reporting for jobs that did not have a group set.\n- Added a confirmation on changes to shop settings to indicate that a restart is required for them to take effect.\n- Added auto update download for users who are not seeing progress during update download." } } diff --git a/electron/main.js b/electron/main.js index 0568aee..d71da0a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -19,7 +19,7 @@ const Nucleus = require("nucleus-nodejs"); require("./ipc-main-handler"); require("./analytics"); -autoUpdater.autoDownload = false; +autoUpdater.autoDownload = true; autoUpdater.logger = log; autoUpdater.logger.transports.file.level = "info"; @@ -314,6 +314,10 @@ autoUpdater.on("download-progress", (ev) => { autoUpdater.on("update-downloaded", (ev, info) => { Nucleus.track("UPDATE_DOWNLOADED", ev); // if (process.env.NODE_ENV === "production") { + mainWindow.webContents.send(ipcTypes.app.toRenderer.downloadProgress, { + ...ev, + percent: 100, + }); dialog .showMessageBox({ diff --git a/package.json b/package.json index f3d90d3..3c8703c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "ImEX RPS", "author": "ImEX Systems Inc. ", "description": "ImEX RPS", - "version": "1.0.11", + "version": "1.0.12", "main": "electron/main.js", "homepage": "./", "dependencies": { diff --git a/src/App/App.jsx b/src/App/App.jsx index 471a210..0349a6b 100644 --- a/src/App/App.jsx +++ b/src/App/App.jsx @@ -1,9 +1,10 @@ import { ApolloProvider } from "@apollo/client"; -import { ConfigProvider, Spin } from "antd"; +import { ConfigProvider } from "antd"; import enLocale from "antd/es/locale/en_US"; import React, { useEffect } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import LoadingSpinnerAtom from "../components/atoms/loading-spinner/loading-spinner.atom"; import RoutesPage from "../components/pages/routes/routes.page"; import SignInPage from "../components/pages/sign-in/sign-in.page"; import client from "../graphql/GraphQLClient"; @@ -34,7 +35,7 @@ export function App({ currentUser, checkUserSession }) { }, []); if (currentUser.authorized === null) { - return ; + return ; } return ( diff --git a/src/components/atoms/loading-spinner/loading-spinner.atom.jsx b/src/components/atoms/loading-spinner/loading-spinner.atom.jsx new file mode 100644 index 0000000..890713b --- /dev/null +++ b/src/components/atoms/loading-spinner/loading-spinner.atom.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Spin } from "antd"; +import "./loading-spinner.styles.scss"; + +export default function LoadingSpinnerAtom({ + loading = true, + message, + ...props +}) { + return ( +
+ + {props.children} + +
+ ); +} diff --git a/src/components/atoms/loading-spinner/loading-spinner.styles.scss b/src/components/atoms/loading-spinner/loading-spinner.styles.scss new file mode 100644 index 0000000..ed927a8 --- /dev/null +++ b/src/components/atoms/loading-spinner/loading-spinner.styles.scss @@ -0,0 +1,8 @@ +.loading-spinner { + display: flex; + flex: 1; + height: 100%; + align-items: center; + justify-content: center; + margin: 1em; +} diff --git a/src/components/atoms/vehicle-group-alert/vehicle-group-alert.atom.jsx b/src/components/atoms/vehicle-group-alert/vehicle-group-alert.atom.jsx index 7a16923..f2e2e53 100644 --- a/src/components/atoms/vehicle-group-alert/vehicle-group-alert.atom.jsx +++ b/src/components/atoms/vehicle-group-alert/vehicle-group-alert.atom.jsx @@ -14,6 +14,7 @@ const models = [ "journey", "nv200", "rav4", + "odyssey", ]; export default function VehicleGroupAlertAtom({ job, showGroup = false }) { diff --git a/src/components/molecules/job-group/job-group.molecule.jsx b/src/components/molecules/job-group/job-group.molecule.jsx index db1d277..ef5e368 100644 --- a/src/components/molecules/job-group/job-group.molecule.jsx +++ b/src/components/molecules/job-group/job-group.molecule.jsx @@ -7,6 +7,8 @@ import { createStructuredSelector } from "reselect"; import { UPDATE_JOB } from "../../../graphql/jobs.queries"; import ipcTypes from "../../../ipc.types"; import { selectBodyshop } from "../../../redux/user/user.selectors"; +import { AlertFilled } from "@ant-design/icons"; +import JobsGroupModalMolecule from "../jobs-group-modal/jobs-group-modal.molecule"; const { ipcRenderer } = window; const mapStateToProps = createStructuredSelector({ @@ -51,12 +53,21 @@ export function JobGroupMolecule({ bodyshop, jobId, group, job }) { ); return ( - - e.preventDefault()}> - {group} - - {loading && } - - + <> + + e.preventDefault()}> + {group} + + {loading && } + + + + {!group && ( +
+ + No group set. +
+ )} + ); } diff --git a/src/components/molecules/jobs-group-modal/jobs-group-modal.molecule.jsx b/src/components/molecules/jobs-group-modal/jobs-group-modal.molecule.jsx new file mode 100644 index 0000000..136a5b1 --- /dev/null +++ b/src/components/molecules/jobs-group-modal/jobs-group-modal.molecule.jsx @@ -0,0 +1,175 @@ +import { Modal, List, Card, Input } from "antd"; +import React, { useState } from "react"; +import { InfoCircleFilled } from "@ant-design/icons"; + +const data = [ + { + group: "Group 1", + makes: [ + "GEO", + "ALFA ROMEO", + "TESLA", + "PORSCHE", + "MERCEDES BENZ-Truck", + "LAND ROVER", + "MERCEDES BENZ-Van", + "LINCOLN-Truck", + "BUICK-Truck", + "AM GENERAL", + "VOLKSWAGEN-Truck", + "JAGUAR", + "SMART", + "HUMMER-Truck", + "MERCEDES BENZ", + "RAM-Van", + "GENESIS", + "AUDI BMW-Truck", + ], + }, + { + group: "Group 2", + makes: [ + "NISSAN-Van", + "VOLVO", + "MINI", + "LEXUS", + "LAND ROVER-Truck", + "SAAB", + "SUBARU", + "BMW", + ], + }, + { + group: "Group 3", + makes: [ + "MAZDA-Truck", + "SCION", + "NISSAN-Truck", + "DODGE-Van", + "INFINITI", + "JEEP-Truck", + "MONACO-Van", + "JEEP", + "LINCOLN", + "KIA", + "VOLKSWAGEN", + "FIAT", + "TOYOTA-Truck", + "HYUNDAI", + "MAZDA", + "SUBARU-Truck", + "HUMMER", + "EAGLE", + "FORD-Truck", + "ISUZU", + ], + }, + { + group: "Group 4", + makes: [ + "ACURA", + "HONDA", + "HONDA-Truck", + "FORD", + "DODGE-Truck", + "CADILLAC", + "TOYOTA", + "BUICK", + "CHEVROLET-Truck", + "PLYMOUTH", + "GMC-Truck", + "RAM", + "AUDI-Truck", + "MITSUBISHI", + "NISSAN", + ], + }, + { + group: "Group 5", + makes: [ + "PONTIAC-Truck", + "CHRYSLER-Truck", + "GMC", + "CHRYSLER", + "SUZUKI", + "DODGE", + "ELDORADO", + "CHEVROLET", + ], + }, + { + group: "Group 6", + makes: [ + "CHEVROLET-Van", + "PONTIAC", + "HYUNDAI-Truck", + "CHRYSLER-Van", + "GMC-Van", + "SUZUKI-Truck", + "CADILLAC-Truck", + "MERCURY", + "RAM-Truck", + "OLDSMOBILE", + "KIA-Truck", + "SATURN", + "MITSUBISHI-Truck", + ], + }, +]; + +export default function JobsGroupModalMolecule() { + const [visible, setVisible] = useState(false); + const [search, setSearch] = useState(""); + return ( +
+ setVisible(false)} + onOk={() => setVisible(false)} + width="90%" + title="MPI Group Guidelines" + > +
+ setSearch(e.target.value)} + style={{ marginBottom: ".5rem" }} + /> + ( + + +
    + {item.makes.map((make, idx) => ( +
  • + {make} +
  • + ))} +
+
+
+ )} + /> +
+ This grouping information is provided for reference only and is not + guaranteed to be correct. Please confirm with grouping guidelines + provided by MPI. +
+
+
+ setVisible(true)} /> +
+ ); +} diff --git a/src/components/molecules/jobs-search-fields/jobs-search-fields.molecule.jsx b/src/components/molecules/jobs-search-fields/jobs-search-fields.molecule.jsx index 5e869da..b5b2c65 100644 --- a/src/components/molecules/jobs-search-fields/jobs-search-fields.molecule.jsx +++ b/src/components/molecules/jobs-search-fields/jobs-search-fields.molecule.jsx @@ -1,8 +1,9 @@ import { SearchOutlined } from "@ant-design/icons"; -import { Button, DatePicker, Form, Input } from "antd"; +import { Button, Checkbox, DatePicker, Form, Input } from "antd"; import React from "react"; import ipcTypes from "../../../ipc.types"; const { ipcRenderer } = window; + export default function JobsSearchFieldsMolecule({ callSearchQuery }) { const [form] = Form.useForm(); @@ -17,6 +18,9 @@ export default function JobsSearchFieldsMolecule({ callSearchQuery }) { search: values.search || "", startDate: (values.dateRange && values.dateRange[0]) || null, endDate: (values.dateRange && values.dateRange[1]) || null, + ...(values.closeDateIsNull + ? { closeDateIsNull: values.closeDateIsNull } + : {}), }, }); }; @@ -35,10 +39,29 @@ export default function JobsSearchFieldsMolecule({ callSearchQuery }) { - + + Only Jobs with No Close Date + + + {() => { + const disabled = + !!!form.getFieldValue("search") && + !!!form.getFieldValue("dateRange") && + !form.getFieldValue("closeDateIsNull"); + + return ( + + ); + }} + ); diff --git a/src/components/molecules/shop-settings-form/shop-settings-form.molecule.jsx b/src/components/molecules/shop-settings-form/shop-settings-form.molecule.jsx index bb3baad..173a4d8 100644 --- a/src/components/molecules/shop-settings-form/shop-settings-form.molecule.jsx +++ b/src/components/molecules/shop-settings-form/shop-settings-form.molecule.jsx @@ -1,5 +1,13 @@ import { DeleteFilled } from "@ant-design/icons"; -import { Button, Form, Input, InputNumber, Select, Typography } from "antd"; +import { + Button, + Form, + Input, + InputNumber, + Popconfirm, + Select, + Typography, +} from "antd"; import React, { useState } from "react"; import FormListMoveArrows from "../../atoms/form-list-move-arrows/form-list-move-arrows.atom"; import LayoutFormRow from "../../atoms/layout-form-row/layout-form-row.atom"; @@ -15,13 +23,15 @@ export default function ShopSettingsFormMolecule({ form, saveLoading }) { return (
Shop Settings - + + + ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) + setSelectedJobId: (id) => dispatch(setSelectedJobId(id)), }); -export default connect(mapStateToProps, mapDispatchToProps)(ReportingPage); -export function ReportingPage({ dates }) { +export function ReportingPage({ dates, error, setSelectedJobId }) { return (
- {dates && dates.startDate && dates.endDate && ( + {error && ( +
+ {error.message} +
    + {error.jobs.map((j, idx) => ( +
  • + setSelectedJobId(j.id)} to={"/"}> + {`${j.clm_no} - ${j.error}`} + +
  • + ))} +
+
+ )} + {!error && dates && dates.startDate && dates.endDate && (
@@ -41,3 +61,5 @@ export function ReportingPage({ dates }) {
); } + +export default connect(mapStateToProps, mapDispatchToProps)(ReportingPage); diff --git a/src/graphql/jobs.queries.js b/src/graphql/jobs.queries.js index 1077399..ddc342b 100644 --- a/src/graphql/jobs.queries.js +++ b/src/graphql/jobs.queries.js @@ -44,11 +44,13 @@ export const SEARCH_JOBS_PAGINATED = gql` $search: String $startDate: date $endDate: date + $closeDateIsNull: Boolean ) { search_jobs( offset: $offset limit: $limit args: { enddate: $endDate, search: $search, startdate: $startDate } + where: { close_date: { _is_null: $closeDateIsNull } } ) { ownr_fn ownr_ln diff --git a/src/redux/reporting/reporting.reducer.js b/src/redux/reporting/reporting.reducer.js index 64076cb..3d36428 100644 --- a/src/redux/reporting/reporting.reducer.js +++ b/src/redux/reporting/reporting.reducer.js @@ -13,11 +13,17 @@ const applicationReducer = (state = INITIAL_STATE, action) => { return { ...state, loading: true, + error: null, dates: { startDate: action.payload.startDate.toISOString(), endDate: action.payload.endDate.toISOString(), }, }; + case ReportingActionTypes.SET_REPORTING_ERROR: + return { + ...state, + error: action.payload, + }; case ReportingActionTypes.SET_REPORTING_DATA: return { ...state, data: action.payload }; case ReportingActionTypes.SET_SCORE_CARD: diff --git a/src/redux/reporting/reporting.sagas.js b/src/redux/reporting/reporting.sagas.js index f22c7f9..0f4cfa4 100644 --- a/src/redux/reporting/reporting.sagas.js +++ b/src/redux/reporting/reporting.sagas.js @@ -13,6 +13,7 @@ import { calculateScorecard, setReportingData, setScoreCard, + setReportingError, } from "./reporting.actions"; import ReportingApplicationTypes from "./reporting.types"; @@ -62,6 +63,23 @@ export function* handleCalculateScoreCard({ payload: jobs }) { const targets = yield select((state) => state.user.bodyshop.targets); const groups = yield select((state) => state.user.bodyshop.groups); + //Check to ensure every job has a group. + const jobsWithNoGroup = jobs + .filter((j) => !j.group) + .map((j) => { + return { ...j, error: "No group set." }; + }); + + if (jobsWithNoGroup.length > 0) { + yield put( + setReportingError({ + message: "There is an issue with the following jobs.", + jobs: [...jobsWithNoGroup], + }) + ); + return; + } + const scoreCard = { shopRpsTotalDollars: Dinero(), shopRpsExpectedDollars: Dinero(), @@ -152,6 +170,7 @@ export function* handleCalculateScoreCard({ payload: jobs }) { event: "CALCULATE_SCORE_CARD_ERROR", error: error, }); + yield put(setReportingError({ message: error, jobs: [] })); } }