@@ -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.
|
||||
- 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.
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"productName": "ImEX RPS",
|
||||
"author": "ImEX Systems Inc. <support@thinkimex.com>",
|
||||
"description": "ImEX RPS",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.12",
|
||||
"main": "electron/main.js",
|
||||
"homepage": "./",
|
||||
"dependencies": {
|
||||
|
||||
@@ -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 <Spin />;
|
||||
return <LoadingSpinnerAtom />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -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 (
|
||||
<div className="loading-spinner">
|
||||
<Spin
|
||||
spinning={loading}
|
||||
size="large"
|
||||
style={{
|
||||
position: "relative",
|
||||
alignContent: "center",
|
||||
}}
|
||||
tip={message ? message : null}
|
||||
>
|
||||
{props.children}
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.loading-spinner {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 1em;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ const models = [
|
||||
"journey",
|
||||
"nv200",
|
||||
"rav4",
|
||||
"odyssey",
|
||||
];
|
||||
|
||||
export default function VehicleGroupAlertAtom({ job, showGroup = false }) {
|
||||
|
||||
@@ -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 (
|
||||
<Dropdown overlay={menu} trigger={["click"]}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
{group}
|
||||
<DownOutlined style={{ marginLeft: ".2rem" }} />
|
||||
{loading && <LoadingOutlined />}
|
||||
</a>
|
||||
</Dropdown>
|
||||
<>
|
||||
<Dropdown overlay={menu} trigger={["click"]}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
{group}
|
||||
<DownOutlined style={{ marginLeft: ".2rem" }} />
|
||||
{loading && <LoadingOutlined />}
|
||||
</a>
|
||||
</Dropdown>
|
||||
<JobsGroupModalMolecule />
|
||||
{!group && (
|
||||
<div style={{ marginLeft: ".2rem" }}>
|
||||
<AlertFilled style={{ color: "tomato" }} className="blink_me" />
|
||||
<span style={{ color: "tomato" }}>No group set.</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div style={{ margin: ".2rem" }}>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
onOk={() => setVisible(false)}
|
||||
width="90%"
|
||||
title="MPI Group Guidelines"
|
||||
>
|
||||
<div>
|
||||
<Input.Search
|
||||
placeholder="Search for Make"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
style={{ marginBottom: ".5rem" }}
|
||||
/>
|
||||
<List
|
||||
grid={{ gutter: 8, column: 3 }}
|
||||
dataSource={data}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<Card title={item.group}>
|
||||
<ul>
|
||||
{item.makes.map((make, idx) => (
|
||||
<li
|
||||
style={{
|
||||
backgroundColor:
|
||||
search &&
|
||||
make.toLowerCase().includes(search.toLowerCase())
|
||||
? "yellow"
|
||||
: "",
|
||||
}}
|
||||
key={idx}
|
||||
>
|
||||
{make}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
This grouping information is provided for reference only and is not
|
||||
guaranteed to be correct. Please confirm with grouping guidelines
|
||||
provided by MPI.
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<InfoCircleFilled onClick={() => setVisible(true)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 }) {
|
||||
<Form.Item name="dateRange" rules={[{ type: "array" }]}>
|
||||
<DatePicker.RangePicker />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit" onClick={() => form.submit()}>
|
||||
<SearchOutlined />
|
||||
Search
|
||||
</Button>
|
||||
<Form.Item name="closeDateIsNull" valuePropName="checked">
|
||||
<Checkbox>Only Jobs with No Close Date</Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
const disabled =
|
||||
!!!form.getFieldValue("search") &&
|
||||
!!!form.getFieldValue("dateRange") &&
|
||||
!form.getFieldValue("closeDateIsNull");
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={disabled}
|
||||
htmlType="submit"
|
||||
onClick={() => form.submit()}
|
||||
>
|
||||
<SearchOutlined />
|
||||
Search
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<Typography.Title>Shop Settings</Typography.Title>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={saveLoading}
|
||||
onClick={() => form.submit()}
|
||||
<Popconfirm
|
||||
title="Changing these settings will require manually restarting RPS on all machines."
|
||||
onConfirm={() => form.submit()}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button type="primary" loading={saveLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
label="Shop Name"
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { Card } from "antd";
|
||||
import { Card, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectDates } from "../../../redux/reporting/reporting.selectors";
|
||||
import { setSelectedJobId } from "../../../redux/application/application.actions";
|
||||
import {
|
||||
selectDates,
|
||||
selectReportingError,
|
||||
} from "../../../redux/reporting/reporting.selectors";
|
||||
import ReportingTitleAtom from "../../atoms/reporting-title/reporting-title.atom";
|
||||
import ReportingDatesMolecule from "../../molecules/reporting-dates/reporting-dates.molecule";
|
||||
import ReportingJobsListMolecule from "../../molecules/reporting-jobs-list/reporting-jobs-list.molecule";
|
||||
@@ -12,19 +17,34 @@ import "./reporting.page.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
dates: selectDates,
|
||||
error: selectReportingError,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//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 (
|
||||
<div className="reporting-container">
|
||||
<Card>
|
||||
<ReportingDatesMolecule />
|
||||
</Card>
|
||||
{dates && dates.startDate && dates.endDate && (
|
||||
{error && (
|
||||
<div>
|
||||
<Typography.Title level={4}>{error.message}</Typography.Title>
|
||||
<ul>
|
||||
{error.jobs.map((j, idx) => (
|
||||
<li key={idx}>
|
||||
<Link onClick={() => setSelectedJobId(j.id)} to={"/"}>
|
||||
{`${j.clm_no} - ${j.error}`}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{!error && dates && dates.startDate && dates.endDate && (
|
||||
<div className="reporting-cards">
|
||||
<Card>
|
||||
<ReportingTitleAtom />
|
||||
@@ -41,3 +61,5 @@ export function ReportingPage({ dates }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ReportingPage);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: [] }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user