diff --git a/electron/audit/audit-ipc.js b/electron/audit/audit-ipc.js index 4b411e8..5182ff2 100644 --- a/electron/audit/audit-ipc.js +++ b/electron/audit/audit-ipc.js @@ -28,7 +28,7 @@ ipcMain.on(ipcTypes.default.audit.toMain.browseForFile, async (event, { sheetNam } else if (foundHeaderRow && !foundTotalRow && line[0] && line[0] !== "Grand Total") { //Add it to the array const row = { - clm_no: line[0], + clm_no: line[0].startsWith("00") ? line[0].slice(2) : line[0], close_date: line[1], v_model_yr: line[3], v_makedesc: line[4], diff --git a/electron/main.js b/electron/main.js index 2206815..4a95f6a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -1,14 +1,5 @@ const path = require("path"); -const { - app, - BrowserWindow, - Tray, - Menu, - ipcMain, - dialog, - shell, - globalShortcut, -} = require("electron"); +const { app, BrowserWindow, Tray, Menu, ipcMain, dialog, shell, globalShortcut } = require("electron"); const isDev = require("electron-is-dev"); const { default: ipcTypes } = require("../src/ipc.types.commonjs"); const { store } = require("./electron-store"); @@ -26,8 +17,8 @@ Sentry.init({ ignoreErrors: [ "SimpleURLLoaderWrapper", "Cannot read properties of null (reading 'webContents')", - "EBUSY: resource busy or locked", - ], + "EBUSY: resource busy or locked" + ] }); autoUpdater.autoDownload = true; @@ -52,28 +43,28 @@ var menu = Menu.buildFromTemplate([ click() { app.exit(); app.relaunch(); - }, + } }, { label: "Clear Settings", click() { store.reset("filePaths"); mainWindow.webContents.session.clearStorageData(); - }, + } }, { label: "Sign Out", click() { mainWindow.webContents.send(ipcTypes.app.toRenderer.signOut); - }, + } }, { label: "Exit", click() { app.quit(); - }, - }, - ], + } + } + ] // Other code removed for brevity }, { @@ -83,13 +74,13 @@ var menu = Menu.buildFromTemplate([ label: "Rescue", click() { shell.openExternal("http://imexrescue.com"); - }, + } }, { label: `Check for Updates (currently ${app.getVersion()})`, click() { checkForUpdates(); - }, + } }, { label: `Show Release Notes`, @@ -98,28 +89,28 @@ var menu = Menu.buildFromTemplate([ ipcTypes.app.toRenderer.setReleaseNotes, require("./changelog.json")[app.getVersion()] ); - }, + } }, { label: "Open Config File", click() { shell.openPath(store.path); - }, + } }, { label: "Open Log File", click() { shell.openPath(path.join(app.getPath("appData"), "ImeX RPS\\logs")); - }, + } }, { label: "Third Party Notices", click() { openNoticeWindow(); - }, - }, - ], - }, + } + } + ] + } ]); let mainWindow; @@ -142,8 +133,8 @@ function createWindow() { webSecurity: true, worldSafeExecuteJavaScript: true, contextIsolation: true, - preload: path.join(__dirname, "preload.js"), // use a preload script - }, + preload: path.join(__dirname, "preload.js") // use a preload script + } }); const gotTheLock = app.requestSingleInstanceLock(); @@ -162,11 +153,7 @@ function createWindow() { // and load the index.html of the app. // win.loadFile("index.html"); - mainWindow.loadURL( - isDev - ? "http://localhost:3000" - : `file://${path.join(__dirname, "/../build/index.html")}` - ); + mainWindow.loadURL(isDev ? "http://localhost:3000" : `file://${path.join(__dirname, "/../build/index.html")}`); // mainWindow.on("close", function (event) { // event.preventDefault(); @@ -256,15 +243,15 @@ function createTray() { label: "Show", click: function () { mainWindow.show(); - }, + } }, { label: "Exit", click: function () { app.isQuiting = true; app.quit(); - }, - }, + } + } ]); appIcon.on("double-click", function (event) { @@ -297,7 +284,7 @@ function openNoticeWindow() { noticeWindow = new BrowserWindow({ height: 600, width: 800, - title: "ImEX RPS - Third Party Notices", + title: "ImEX RPS - Third Party Notices" }); noticeWindow.loadURL("file://" + __dirname + "/licenses.txt"); @@ -333,7 +320,7 @@ autoUpdater.on("update-downloaded", (ev, info) => { // if (process.env.NODE_ENV === "production") { mainWindow.webContents.send(ipcTypes.app.toRenderer.downloadProgress, { ...ev, - percent: 100, + percent: 100 }); dialog @@ -341,7 +328,7 @@ autoUpdater.on("update-downloaded", (ev, info) => { type: "info", title: "ImeX RPS Update Manager", message: `ImEX RPS is ready to update to Version ${ev.version}. It is highly recommended that you update immediately. Would you like to update now? RPS will automatically restart.`, - buttons: ["Yes", "No"], + buttons: ["Yes", "No"] }) .then(({ response }) => { if (response === 0) { diff --git a/package-lock.json b/package-lock.json index f78ee85..a0c778e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "react-infinite-scroller": "^1.2.6", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", + "react-to-print": "^2.15.1", "recharts": "^2.12.5", "redux": "^5.0.1", "redux-logger": "^3.0.6", @@ -13473,6 +13474,15 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-to-print": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.15.1.tgz", + "integrity": "sha512-1foogIFbCpzAVxydkhBiDfMiFYhIMphiagDOfcG4X/EcQ+fBPqJ0rby9Wv/emzY1YLkIQy/rEgOrWQT+rBKhjw==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/package.json b/package.json index 7d36e53..fceb4f0 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "react-infinite-scroller": "^1.2.6", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", + "react-to-print": "^2.15.1", "recharts": "^2.12.5", "redux": "^5.0.1", "redux-logger": "^3.0.6", diff --git a/src/App/App.styles.scss b/src/App/App.styles.scss index 85a5aee..d1b15e6 100644 --- a/src/App/App.styles.scss +++ b/src/App/App.styles.scss @@ -93,3 +93,17 @@ body { opacity: 0; } } + +@media print { + .no-print { + display: none; + } + + body { + height: unset; + page-break-before: always; + } + @page { + // size: landscape; + } +} diff --git a/src/components/organisms/audit-results/audit-results.organism.jsx b/src/components/organisms/audit-results/audit-results.organism.jsx index 99efac1..1e7c6c1 100644 --- a/src/components/organisms/audit-results/audit-results.organism.jsx +++ b/src/components/organisms/audit-results/audit-results.organism.jsx @@ -1,43 +1,71 @@ import { Card, Col, Divider, Space, Table, Tooltip } from "antd"; +import Dinero from "dinero.js"; import React from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { setSelectedJobTargetPc } from "../../../redux/application/application.actions"; -import { selectAuditData } from "../../../redux/reporting/reporting.selectors"; +import { setSelectedJobId, setSelectedJobTargetPc } from "../../../redux/application/application.actions"; +import { selectAuditData, selectAuditLoading } from "../../../redux/reporting/reporting.selectors"; import { DateFormat } from "../../../util/constants"; import dayjs from "../../../util/day"; -import Dinero from "dinero.js"; import { alphaSort, dateSort } from "../../../util/sorters"; +import { Link } from "react-router-dom"; const mapStateToProps = createStructuredSelector({ - selectAuditData: selectAuditData + selectAuditData: selectAuditData, + auditLoading: selectAuditLoading }); const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) + setSelectedJobId: (id) => dispatch(setSelectedJobId(id)), setSelectedJobTargetPc: (job) => dispatch(setSelectedJobTargetPc(job)) }); -export function AuditResultsOrganism({ selectAuditData }) { +export function AuditResultsOrganism({ auditLoading, setSelectedJobId, selectAuditData }) { const missingColumns = [ { key: "clm_no", - width: "20%", + width: "12%", title: "Claim No.", sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - dataIndex: "clm_no" + dataIndex: "clm_no", + render: (text, record) => ( + setSelectedJobId(record.id)} to={"/"}> + {text} + + ) }, { key: "close_date", - width: "20%", + width: "12%", title: "[RPS] R4P", dataIndex: "close_date", defaultSortOrder: "ascend", sorter: (a, b) => dateSort(a.close_date, b.close_date), render: (text, record) => dayjs(record.close_date).format(DateFormat) }, - { key: "v_model_yr", width: "20%", title: "Model Year", dataIndex: "v_model_yr" }, + { key: "v_model_yr", width: "12%", title: "Model Year", dataIndex: "v_model_yr" }, { key: "v_makedesc", width: "20%", title: "Make", dataIndex: "v_makedesc" }, - { key: "v_model", width: "20%", title: "Model", dataIndex: "v_model" } + { key: "v_model", width: "20%", title: "Model", dataIndex: "v_model" }, + { + key: "expected_rps", + width: "12%", + title: "Expected RPS ", + dataIndex: ["audit", "expectedRpsDollars"], + render: (text, record) => + record.expectedRpsDollars + ? record?.expectedRpsDollars?.toFormat() + : Dinero({ amount: Math.round((record?.expected_rps_dollars || 0) * 100) }).toFormat() + }, + { + key: "actual_rps", + width: "12%", + title: " Actual RPS ", + dataIndex: ["audit", "jobRpsDollars"], + render: (text, record) => + record?.jobRpsDollars + ? record?.jobRpsDollars.toFormat() + : Dinero({ amount: Math.round((record?.actual_rps_dollars || 0) * 100) }).toFormat() + } ]; const mismatchColumns = [ @@ -71,7 +99,7 @@ export function AuditResultsOrganism({ selectAuditData }) { { key: "expected_rps", width: "12%", - title: "Expected RPS", + title: "Expected RPS (RPS/Audit)", dataIndex: ["audit", "expectedRpsDollars"], render: (text, record) => ( }> @@ -85,7 +113,7 @@ export function AuditResultsOrganism({ selectAuditData }) { { key: "actual_rps", width: "12%", - title: " Actual RPS", + title: " Actual RPS (RPS/Audit)", dataIndex: ["audit", "jobRpsDollars"], render: (text, record) => ( }> @@ -101,22 +129,46 @@ export function AuditResultsOrganism({ selectAuditData }) { <> - +
-
+
-
+
-
+
diff --git a/src/components/pages/audit/audit.page.jsx b/src/components/pages/audit/audit.page.jsx index 5b3ddb4..fbffef6 100644 --- a/src/components/pages/audit/audit.page.jsx +++ b/src/components/pages/audit/audit.page.jsx @@ -1,6 +1,8 @@ -import { Alert, Button, Card, Col, DatePicker, Form, Input, Row } from "antd"; -import React from "react"; +import { PrinterFilled } from "@ant-design/icons"; +import { Alert, Button, Card, Col, DatePicker, Form, Input, Row, Space } from "antd"; +import React, { useRef } from "react"; import { connect } from "react-redux"; +import { useReactToPrint } from "react-to-print"; import { createStructuredSelector } from "reselect"; import ipcTypes from "../../../ipc.types"; import { queryReportingData } from "../../../redux/reporting/reporting.actions"; @@ -29,17 +31,25 @@ export function AuditPage({ auditError, queryReportingData }) { }); ipcRenderer.send(ipcTypes.audit.toMain.browseForFile, { sheetName }); }; + const componentRef = useRef(); + const handlePrint = useReactToPrint({ + content: () => componentRef.current, + bodyClass: "audit-container-print" + }); + + window.ref = componentRef.current; if (auditError) console.log("Error when opening audit file.", auditError); return ( -
- +
+
- - - - + + + + + + + +
diff --git a/src/components/templates/feature-wrapper.jsx b/src/components/templates/feature-wrapper.jsx index 3b2055f..f8d90be 100644 --- a/src/components/templates/feature-wrapper.jsx +++ b/src/components/templates/feature-wrapper.jsx @@ -16,7 +16,7 @@ function FeatureWrapper({ bodyshop, featureName, noauth, children, ...restProps noauth || ( diff --git a/src/redux/reporting/reporting.reducer.js b/src/redux/reporting/reporting.reducer.js index 2db406d..7593615 100644 --- a/src/redux/reporting/reporting.reducer.js +++ b/src/redux/reporting/reporting.reducer.js @@ -5,6 +5,7 @@ const INITIAL_STATE = { scoreCard: null, error: null, loading: false, + auditLoading: false, audit: {}, auditError: null }; @@ -30,10 +31,12 @@ const applicationReducer = (state = INITIAL_STATE, action) => { return { ...state, data: action.payload }; case ReportingActionTypes.SET_SCORE_CARD: return { ...state, loading: false, scoreCard: action.payload }; + case ReportingActionTypes.CALCULATE_AUDIT: + return { ...state, auditLoading: true }; case ReportingActionTypes.SET_AUDIT_RESULTS: - return { ...state, loading: false, auditError: null, audit: action.payload }; + return { ...state, loading: false, auditLoading: false, auditError: null, audit: action.payload }; case ReportingActionTypes.SET_AUDIT_ERROR: - return { ...state, auditError: action.payload }; + return { ...state, auditLoading: false, auditError: action.payload }; case ReportingActionTypes.TOGGLE_GROUP_VERIFIED: return { ...state, diff --git a/src/redux/reporting/reporting.selectors.js b/src/redux/reporting/reporting.selectors.js index d2bb7dc..2427308 100644 --- a/src/redux/reporting/reporting.selectors.js +++ b/src/redux/reporting/reporting.selectors.js @@ -9,7 +9,7 @@ export const selectReportingError = createSelector([selectReporting], (reporting export const selectReportData = createSelector([selectReporting], (reporting) => reporting.data); export const selectAuditData = createSelector([selectReporting], (reporting) => reporting.audit); export const selectAuditError = createSelector([selectReporting], (reporting) => reporting.auditError); - +export const selectAuditLoading = createSelector([selectReporting], (reporting) => reporting.auditLoading); // export const selectWatchedPaths = createSelector( // [selectReporting], // (application) => application.watchedPaths