From e467f743620ae46c020f353ed13c33ca7d8fa678 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 15 Oct 2020 15:18:38 -0700 Subject: [PATCH] Replaced electron-settings with store. Implemented job upsert logic. --- electron/decoder/decoder.js | 3 - electron/electron-store.js | 5 + electron/file-watcher/file-watcher-ipc.js | 49 +++++-- electron/file-watcher/file-watcher.js | 49 +++++-- electron/ipc-main-handler.js | 5 +- electron/main.js | 12 +- .../notification-wrapper.js | 10 ++ package-lock.json | 135 ++++++++++-------- package.json | 2 +- src/App/App.jsx | 9 ++ .../filepath-item/filepath-item.molecule.jsx | 13 +- .../jobs-list/jobs-list.organism.jsx | 9 +- src/components/test.jsx | 1 - src/graphql/jobs.queries.js | 51 +++++++ src/ipc.types.js | 1 + src/ipc/ipc-estimate-utils.js | 131 +++++++++++++++-- src/ipc/ipc-renderer-handler.js | 30 ++-- src/redux/user/user.sagas.js | 1 + 18 files changed, 385 insertions(+), 131 deletions(-) create mode 100644 electron/electron-store.js create mode 100644 electron/notification-wrapper/notification-wrapper.js diff --git a/electron/decoder/decoder.js b/electron/decoder/decoder.js index 4fe217a..24e0cc3 100644 --- a/electron/decoder/decoder.js +++ b/electron/decoder/decoder.js @@ -5,8 +5,6 @@ const _ = require("lodash"); async function DecodeEstimate(filePath) { const parsedFilePath = path.parse(filePath); let extensionlessFilePath = `${parsedFilePath.dir}\\${parsedFilePath.name}`; - console.log("DecodeEstimate -> extensionlessFilePath", extensionlessFilePath); - const ret = { ...(await DecodeAd1File(extensionlessFilePath)), ...(await DecodeVehFile(extensionlessFilePath)), @@ -228,7 +226,6 @@ async function DecodeLinFile(extensionlessFilePath) { ]), function (result, val, key) { if (key === "UNQ_SEQ") { - console.log("unq"); return (result[key.toLowerCase()] = val.toString()); } return (result[key.toLowerCase()] = val); diff --git a/electron/electron-store.js b/electron/electron-store.js new file mode 100644 index 0000000..4e62a8a --- /dev/null +++ b/electron/electron-store.js @@ -0,0 +1,5 @@ +const Store = require("electron-store"); + +const store = new Store({ defaults: { filePaths: [] } }); + +exports.store = store; diff --git a/electron/file-watcher/file-watcher-ipc.js b/electron/file-watcher/file-watcher-ipc.js index 6a24b05..c536d48 100644 --- a/electron/file-watcher/file-watcher-ipc.js +++ b/electron/file-watcher/file-watcher-ipc.js @@ -1,34 +1,33 @@ const { ipcMain, dialog } = require("electron"); const { StartWatcher, StopWatcher } = require("./file-watcher"); const ipcTypes = require("../../src/ipc.types"); -const settings = require("electron-settings"); const { mainWindow } = require("../main"); const _ = require("lodash"); +const { store } = require("../electron-store"); +const path = require("path"); ipcMain.on( ipcTypes.default.fileWatcher.toMain.filepathsGet, async (event, object) => { - const filePaths = await settings.get("filePaths"); event.reply( ipcTypes.default.fileWatcher.toRenderer.filepathsList, - filePaths + store.get("filePaths") ); } ); ipcMain.on(ipcTypes.default.fileWatcher.toMain.start, async (event, arg) => { - const filePaths = StartWatcher(); - // event.sender.send(ipcTypes.default.fileWatcher.toRenderer.startSuccess); + StartWatcher(); + // event.sender.send(ipcTypes.default.fileWatcher.toRenderer.startSuccess); event.sender.send( ipcTypes.default.fileWatcher.toRenderer.filepathsList, - filePaths + store.get("filePaths") ); - event.sender.send(ipcTypes.default.fileWatcher.toRenderer.startSuccess); + // event.sender.send(ipcTypes.default.fileWatcher.toRenderer.startSuccess); }); ipcMain.on(ipcTypes.default.fileWatcher.toMain.stop, async (event, arg) => { StopWatcher(); - event.sender.send(ipcTypes.default.fileWatcher.toRenderer.stopSuccess); }); ipcMain.on(ipcTypes.default.fileWatcher.toMain.addPath, async (event, arg) => { @@ -36,16 +35,36 @@ ipcMain.on(ipcTypes.default.fileWatcher.toMain.addPath, async (event, arg) => { properties: ["openDirectory"], }); - await settings.set( - "filePaths", - _.union(result.filePaths, await settings.get("filePaths")) - ); - - const newFilePaths = await settings.get("filePaths"); - console.log("newFilePaths", newFilePaths) + StopWatcher(); + store.set("filePaths", _.union(result.filePaths, store.get("filePaths"))); + const newFilePaths = store.get("filePaths"); event.sender.send( ipcTypes.default.fileWatcher.toRenderer.filepathsList, newFilePaths ); }); + +ipcMain.on( + ipcTypes.default.fileWatcher.toMain.removePath, + async (event, arg) => { + StopWatcher(); + + store.set( + "filePaths", + store.get("filePaths").filter((k) => { + const kf = path.parse(k); + const argf = path.parse(arg); + return kf.dir + kf.base !== argf.dir + argf.base; + }) + ); + + const newFilePaths = store.get("filePaths"); + console.log("newFilePaths", newFilePaths); + + event.sender.send( + ipcTypes.default.fileWatcher.toRenderer.filepathsList, + newFilePaths + ); + } +); diff --git a/electron/file-watcher/file-watcher.js b/electron/file-watcher/file-watcher.js index 52bbeca..669acf8 100644 --- a/electron/file-watcher/file-watcher.js +++ b/electron/file-watcher/file-watcher.js @@ -1,24 +1,37 @@ const chokidar = require("chokidar"); -const settings = require("electron-settings"); const ipcTypes = require("../../src/ipc.types"); const path = require("path"); const { DecodeEstimate } = require("../decoder/decoder"); -const { BrowserWindow, Notification } = require("electron"); +const { BrowserWindow } = require("electron"); const _ = require("lodash"); +const { store } = require("../electron-store"); +const { + NewNotification, +} = require("../notification-wrapper/notification-wrapper"); var watcher; function StartWatcher() { const filePaths = - settings - .getSync("filePaths") - .map((fp) => path.join(fp, "**.[eE][nN][vV]")) || []; + store.get("filePaths").map((fp) => path.join(fp, "**.[eE][nN][vV]")) || []; console.log("StartWatcher -> filePaths", filePaths); - Notification({ - title: "Watcher started!", - body: "Watcher has been started..", - }).show(); + if (filePaths.length === 0) { + NewNotification({ + title: "RPS Watcher cannot start", + body: "Please set the appropriate file paths and try again.", + }).show(); + return []; + } + + if (watcher) { + try { + console.log("Trying to close watcher - it already existed."); + watcher.close().then(); + } catch (error) { + console.log("Error trying to close Watcher.", error); + } + } watcher = chokidar.watch(filePaths, { //ignored: /[\/\\]\./, @@ -29,6 +42,7 @@ function StartWatcher() { stabilityThreshold: 2000, }, }); + watcher .on("add", async function (path) { console.log("File", path, "has been added"); @@ -62,11 +76,23 @@ function StartWatcher() { function onWatcherReady() { console.log("Ready!"); + const b = BrowserWindow.getAllWindows()[0]; + b.webContents.send(ipcTypes.default.fileWatcher.toRenderer.startSuccess); + NewNotification({ + title: "RPS Watcher Started", + body: "Newly exported estimates will be automatically uploaded.", + }).show(); } async function StopWatcher() { await watcher.close(); - console.log("Watcher Stopped!"); + console.log("Watcher stopped."); + const b = BrowserWindow.getAllWindows()[0]; + b.webContents.send(ipcTypes.default.fileWatcher.toRenderer.stopSuccess); + NewNotification({ + title: "RPS Watcher Stopped", + body: "Estimates will not be automatically uploaded.", + }).show(); } exports.StartWatcher = StartWatcher; @@ -80,14 +106,13 @@ async function HandleNewFile(path) { const newJobLow = _.transform(newJob, function (result, val, key) { result[key.toLowerCase()] = val; }); - console.log("HandleNewFile -> newJobLow", newJobLow); b.webContents.send( ipcTypes.default.estimate.toRenderer.estimateDecodeSuccess, newJobLow ); - Notification({ + NewNotification({ title: "Job Uploaded", body: "A new job has been uploaded.", }).show(); diff --git a/electron/ipc-main-handler.js b/electron/ipc-main-handler.js index eccd0a8..e1783fd 100644 --- a/electron/ipc-main-handler.js +++ b/electron/ipc-main-handler.js @@ -1,8 +1,5 @@ -const { ipcMain, dialog } = require("electron"); +const { ipcMain } = require("electron"); const { mainWindow } = require("./main"); -const settings = require("electron-settings"); -const { DecodeEstimate } = require("./decoder/decoder"); -const ipcTypes = require("../src/ipc.types"); //Import Ipc Handlers require("./file-watcher/file-watcher-ipc"); diff --git a/electron/main.js b/electron/main.js index e9e219e..d5f4545 100644 --- a/electron/main.js +++ b/electron/main.js @@ -1,16 +1,10 @@ const path = require("path"); const { app, BrowserWindow, Tray, Menu, ipcMain } = require("electron"); const isDev = require("electron-is-dev"); -const settings = require("electron-settings"); const { default: ipcTypes } = require("../src/ipc.types"); - +const { store } = require("./electron-store"); require("./ipc-main-handler"); -settings.configure({ - defaults: { - foo: "bar", - }, -}); // Conditionally include the dev tools installer to load React Dev Tools let installExtension, REACT_DEVELOPER_TOOLS; if (isDev) { @@ -76,6 +70,8 @@ function createWindow() { // mode: "detach" }); } + + mainWindow.maximize(); } exports.mainWindow = mainWindow; @@ -86,7 +82,7 @@ app.whenReady().then(() => { createWindow(); if (isDev) { - console.log(`Path to Settings File: ${settings.file()}`); + console.log(`Path to Settings File: ${store.path}`); installExtension(REACT_DEVELOPER_TOOLS) .then((name) => console.log(`Added Extension: ${name}`)) .catch((error) => console.log(`An error occurred: , ${error}`)); diff --git a/electron/notification-wrapper/notification-wrapper.js b/electron/notification-wrapper/notification-wrapper.js new file mode 100644 index 0000000..74f3880 --- /dev/null +++ b/electron/notification-wrapper/notification-wrapper.js @@ -0,0 +1,10 @@ +const { Notification } = require("electron"); +const path = require("path"); + +function NewNotification(config) { + return Notification({ + icon: path.join(__dirname, "../../src/assets/logo512.png"), + ...config, + }); +} +exports.NewNotification = NewNotification; diff --git a/package-lock.json b/package-lock.json index b6a9e68..b810aa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4019,6 +4019,11 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "atomically": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.3.2.tgz", + "integrity": "sha512-MAiqx5ir1nOoMeG2vLXJnj4oFROJYB1hMqa2aAo6GQVIkPdkIcrq9W9SR0OaRtvEowO7Y2bsXqKFuDMTO4iOAQ==" + }, "author-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", @@ -5956,6 +5961,40 @@ } } }, + "conf": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/conf/-/conf-7.1.2.tgz", + "integrity": "sha512-r8/HEoWPFn4CztjhMJaWNAe5n+gPUCSaJ0oufbqDLFKsA1V8JjAG7G+p0pgoDFAws9Bpk2VtVLLXqOBA7WxLeg==", + "requires": { + "ajv": "^6.12.2", + "atomically": "^1.3.1", + "debounce-fn": "^4.0.0", + "dot-prop": "^5.2.0", + "env-paths": "^2.2.0", + "json-schema-typed": "^7.0.3", + "make-dir": "^3.1.0", + "onetime": "^5.1.0", + "pkg-up": "^3.1.0", + "semver": "^7.3.2" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + } + } + }, "config-chain": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", @@ -6525,6 +6564,21 @@ "iconv-lite": "^0.4.24" } }, + "debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "requires": { + "mimic-fn": "^3.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==" + } + } + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -7449,37 +7503,6 @@ } } }, - "electron-settings": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/electron-settings/-/electron-settings-4.0.2.tgz", - "integrity": "sha512-WnUlrnBsO784oXcag0ym+A3ySoIwonz5GhYFsWroMHVzslzmsP+81f/Fof41T9UrRUxuPPKiZPZMwGO+yvWChg==", - "requires": { - "lodash.get": "^4.4.2", - "lodash.has": "^4.5.2", - "lodash.set": "^4.3.2", - "lodash.unset": "^4.5.2", - "mkdirp": "^1.0.4", - "write-file-atomic": "^3.0.3" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - } - } - }, "electron-squirrel-startup": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/electron-squirrel-startup/-/electron-squirrel-startup-1.0.0.tgz", @@ -7498,6 +7521,22 @@ } } }, + "electron-store": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-6.0.1.tgz", + "integrity": "sha512-8rdM0XEmDGsLuZM2oRABzsLX+XmD5x3rwxPMEPv0MrN9/BWanyy3ilb2v+tCrKtIZVF3MxUiZ9Bfqe8e0popKQ==", + "requires": { + "conf": "^7.1.2", + "type-fest": "^0.16.0" + }, + "dependencies": { + "type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==" + } + } + }, "electron-to-chromium": { "version": "1.3.578", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz", @@ -7662,8 +7701,7 @@ "env-paths": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", - "dev": true + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" }, "enzyme": { "version": "3.11.0", @@ -12640,6 +12678,11 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==" + }, "json-stable-stringify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", @@ -12961,12 +13004,8 @@ "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true }, "lodash.isequal": { "version": "4.5.0", @@ -12979,11 +13018,6 @@ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" - }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -13011,11 +13045,6 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, - "lodash.unset": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.unset/-/lodash.unset-4.5.2.tgz", - "integrity": "sha1-Nw0dPoW3Kn4bDN8tJyEhMG8j5O0=" - }, "log-symbols": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", @@ -20171,14 +20200,6 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "requires": { - "is-typedarray": "^1.0.0" - } - }, "typescript-compare": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", diff --git a/package.json b/package.json index 56b223c..1e28ad1 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "dinero.js": "^1.8.1", "dotenv": "^8.2.0", "electron-is-dev": "^1.2.0", - "electron-settings": "^4.0.2", "electron-squirrel-startup": "^1.0.0", + "electron-store": "^6.0.1", "firebase": "^7.23.0", "graphql": "^15.3.0", "lodash": "^4.17.20", diff --git a/src/App/App.jsx b/src/App/App.jsx index c624fb5..471a210 100644 --- a/src/App/App.jsx +++ b/src/App/App.jsx @@ -7,10 +7,12 @@ import { createStructuredSelector } from "reselect"; import RoutesPage from "../components/pages/routes/routes.page"; import SignInPage from "../components/pages/sign-in/sign-in.page"; import client from "../graphql/GraphQLClient"; +import ipcTypes from "../ipc.types"; import "../ipc/ipc-renderer-handler"; import { checkUserSession } from "../redux/user/user.actions"; import { selectCurrentUser } from "../redux/user/user.selectors"; import "./App.styles.scss"; +const { ipcRenderer } = window; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, @@ -24,6 +26,13 @@ export function App({ currentUser, checkUserSession }) { checkUserSession(); }, [checkUserSession]); + useEffect(() => { + // + return () => { + ipcRenderer.send(ipcTypes.default.fileWatcher.toMain.stop); + }; + }, []); + if (currentUser.authorized === null) { return ; } diff --git a/src/components/molecules/filepath-item/filepath-item.molecule.jsx b/src/components/molecules/filepath-item/filepath-item.molecule.jsx index 50c6286..72090fd 100644 --- a/src/components/molecules/filepath-item/filepath-item.molecule.jsx +++ b/src/components/molecules/filepath-item/filepath-item.molecule.jsx @@ -1,6 +1,17 @@ import { List } from "antd"; import React from "react"; +import { DeleteFilled } from "@ant-design/icons"; +import ipcTypes from "../../../ipc.types"; +const { ipcRenderer } = window; export default function FilePathMolecule(item, index) { - return {item}; + const handleClick = () => { + ipcRenderer.send(ipcTypes.default.fileWatcher.toMain.removePath, item); + }; + + return ( + ]}> + {item} + + ); } diff --git a/src/components/organisms/jobs-list/jobs-list.organism.jsx b/src/components/organisms/jobs-list/jobs-list.organism.jsx index b9a6e3c..2583451 100644 --- a/src/components/organisms/jobs-list/jobs-list.organism.jsx +++ b/src/components/organisms/jobs-list/jobs-list.organism.jsx @@ -1,5 +1,6 @@ +import { SyncOutlined } from "@ant-design/icons"; import { useQuery } from "@apollo/client"; -import { List, Space, Spin, Typography } from "antd"; +import { Button, List, Space, Spin, Typography } from "antd"; import React, { useState } from "react"; import InfiniteScroll from "react-infinite-scroller"; import { connect } from "react-redux"; @@ -10,7 +11,6 @@ import { selectSelectedJobId } from "../../../redux/application/application.sele import ErrorResultAtom from "../../atoms/error-result/error-result.atom"; import TimeAgoFormatter from "../../atoms/time-ago-formatter/time-ago-formatter.atom"; import "./jobs-list.organism.styles.scss"; - const mapStateToProps = createStructuredSelector({ selectedJobId: selectSelectedJobId, }); @@ -22,7 +22,7 @@ const limit = 20; export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) { const [state, setState] = useState({ hasMore: true }); - const { loading, error, data, fetchMore } = useQuery( + const { loading, error, data, refetch, fetchMore } = useQuery( QUERY_ALL_JOBS_PAGINATED, { variables: { @@ -76,6 +76,9 @@ export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) { return (
+
({}); diff --git a/src/graphql/jobs.queries.js b/src/graphql/jobs.queries.js index 3792762..094d56c 100644 --- a/src/graphql/jobs.queries.js +++ b/src/graphql/jobs.queries.js @@ -82,3 +82,54 @@ export const QUERY_JOB_BY_PK = gql` } } `; + +export const QUERY_JOB_BY_CLM_NO = gql` + query QUERY_JOB_BY_CLM_NO($clm_no: String!) { + jobs(where: { clm_no: { _eq: $clm_no } }) { + id + joblines { + id + act_price + db_price + line_desc + line_ind + oem_partno + part_qty + part_type + unq_seq + } + } + } +`; + +export const UPDATE_JOB = gql` + mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) { + update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { + returning { + ownr_fn + ownr_ln + v_vin + v_model_yr + v_model + v_makedesc + id + ins_co_nm + clm_no + clm_total + ro_number + updated_at + joblines(order_by: { unq_seq: asc }) { + id + act_price + db_price + line_desc + line_ind + oem_partno + part_qty + part_type + unq_seq + } + } + } + } +`; diff --git a/src/ipc.types.js b/src/ipc.types.js index 4fc5fd8..50b067e 100644 --- a/src/ipc.types.js +++ b/src/ipc.types.js @@ -10,6 +10,7 @@ exports.default = { start: "filewatcher__start", stop: "filewatcher__stop", addPath: "filewatcher__addPath", + removePath: "filewatcher__removePath", }, toRenderer: { filepathsList: "filewatcher__filepathslist", diff --git a/src/ipc/ipc-estimate-utils.js b/src/ipc/ipc-estimate-utils.js index bd51bc1..586fffd 100644 --- a/src/ipc/ipc-estimate-utils.js +++ b/src/ipc/ipc-estimate-utils.js @@ -1,16 +1,131 @@ +import gql from "graphql-tag"; +import _ from "lodash"; import client from "../graphql/GraphQLClient"; -import { INSERT_NEW_JOB } from "../graphql/jobs.queries"; +import { + INSERT_NEW_JOB, + QUERY_JOB_BY_CLM_NO, + UPDATE_JOB, +} from "../graphql/jobs.queries"; import { store } from "../redux/store"; export async function UpsertEstimate(job) { const shopId = store.getState().user.bodyshop.id; - console.log("UpsertEstimate -> shopId", shopId); - const result = await client.mutate({ - mutation: INSERT_NEW_JOB, - variables: { - job: { ...job, bodyshopid: shopId }, - }, + //Using the claim number, find out if the job exists. If it doesnt, then we need to create it. + const existingJobs = await client.query({ + query: QUERY_JOB_BY_CLM_NO, + variables: { clm_no: job.clm_no }, }); - console.log("UpsertEstimate -> result", result); + if (existingJobs.data.jobs.length === 1) { + let suppDelta = await GetSupplementDelta( + existingJobs.data.jobs[0].id, + existingJobs.data.jobs[0].joblines, + job.joblines.data + ); + await client.mutate({ + mutation: gql` + ${suppDelta} + `, + }); + delete job.joblines; + + const updatedJob = await client.mutate({ + mutation: UPDATE_JOB, + variables: { jobId: existingJobs.data.jobs[0].id, job: job }, + }); + + console.log("UpsertEstimate -> updatedJob", updatedJob); + } else { + console.log("Insert a new job recort.", job); + const result = await client.mutate({ + mutation: INSERT_NEW_JOB, + variables: { + job: { ...job, bodyshopid: shopId }, + }, + refetchQueries: ["QUERY_ALL_JOBS_PAGINATED"], + }); + console.log("UpsertEstimate -> result", result); + } } + +export const GetSupplementDelta = async (jobId, existingLinesO, newLines) => { + const existingLines = _.cloneDeep(existingLinesO); + + console.log("GetSupplementDelta -> newLines", newLines); + console.log("GetSupplementDelta -> existingLines", existingLines); + const linesToInsert = []; + const linesToUpdate = []; + + newLines.forEach((newLine) => { + const matchingIndex = existingLines.findIndex( + (eL) => eL.unq_seq === newLine.unq_seq + ); + if (matchingIndex >= 0) { + //Found a relevant matching line. Add it to lines to update. + linesToUpdate.push({ + id: existingLines[matchingIndex].id, + newData: newLine, + }); + //Splice out item we found for performance. + existingLines.splice(matchingIndex, 1); + } else { + //Didn't find a match. Must be a new line. + linesToInsert.push(newLine); + } + }); + + //Wahtever is left in the existing lines, are lines that should be removed. + + const insertQueries = linesToInsert.reduce((acc, value, idx) => { + return acc + generateInsertQuery(value, idx, jobId); + }, ""); + + const updateQueries = linesToUpdate.reduce((acc, value, idx) => { + return acc + generateUpdateQuery(value, idx); + }, ""); + + const removeQueries = existingLines.reduce((acc, value, idx) => { + return acc + generateRemoveQuery(value, idx); + }, ""); + + return new Promise((resolve, reject) => { + resolve(gql` + mutation SUPPLEMENT_EST_LINES{ + ${insertQueries + updateQueries + removeQueries} + } + `); + }); +}; + +const generateInsertQuery = (lineToInsert, index, jobId) => { + lineToInsert.jobid = jobId; + return ` + insert_joblines${index}: insert_joblines(objects: ${JSON.stringify( + lineToInsert + ).replace(/"(\w+)"\s*:/g, "$1:")}) { + returning { + id + } + }`; +}; + +const generateUpdateQuery = (lineToUpdate, index) => { + return ` + update_joblines${index}: update_joblines(where: { id: { _eq: "${ + lineToUpdate.id + }" } }, _set: ${JSON.stringify(lineToUpdate.newData).replace( + /"(\w+)"\s*:/g, + "$1:" + )}) { + returning { + id + } + }`; +}; + +const generateRemoveQuery = (lineToRemove, index) => { + return ` + delete_joblines_r${index}: delete_joblines_by_pk(id: "${lineToRemove.id}") { + id + }`; +}; diff --git a/src/ipc/ipc-renderer-handler.js b/src/ipc/ipc-renderer-handler.js index f609879..75c956f 100644 --- a/src/ipc/ipc-renderer-handler.js +++ b/src/ipc/ipc-renderer-handler.js @@ -4,7 +4,6 @@ import { setWatcherStatus, } from "../redux/application/application.actions"; import { store } from "../redux/store"; -import { message } from "antd"; import { UpsertEstimate } from "./ipc-estimate-utils"; const { ipcRenderer } = window; @@ -16,7 +15,7 @@ ipcRenderer.on("test-toRenderer", (event, obj) => { ipcRenderer.on( ipcTypes.default.fileWatcher.toRenderer.filepathsList, - (event, ...obj) => { + (event, obj) => { store.dispatch(setWatchedPaths(obj)); } ); @@ -24,37 +23,32 @@ ipcRenderer.on( //Filewatcher Status Section ipcRenderer.on( ipcTypes.default.fileWatcher.toRenderer.startSuccess, - (event, ...obj) => { - console.log("Watcher ready."); - message.success("Watcher started!"); - store.dispatch(setWatcherStatus("READY!")); + (event, obj) => { + store.dispatch(setWatcherStatus("Started")); } ); ipcRenderer.on( ipcTypes.default.fileWatcher.toRenderer.stopSuccess, - (event, ...obj) => { - store.dispatch(setWatcherStatus("STOPPED")); - } -); -ipcRenderer.on( - ipcTypes.default.fileWatcher.toRenderer.error, - (event, ...obj) => { - store.dispatch(setWatcherStatus(obj)); + (event, obj) => { + store.dispatch(setWatcherStatus("Stopped")); } ); +ipcRenderer.on(ipcTypes.default.fileWatcher.toRenderer.error, (event, obj) => { + store.dispatch(setWatcherStatus(obj)); +}); //Estimate Section ipcRenderer.on( ipcTypes.default.estimate.toRenderer.estimateDecodeStart, - (event, ...obj) => { + (event, obj) => { console.log("Decoding started!"); } ); ipcRenderer.on( ipcTypes.default.estimate.toRenderer.estimateDecodeSuccess, - async (event, ...obj) => { - console.log("Decoding success!", obj[0]); - await UpsertEstimate(obj[0]); + async (event, obj) => { + console.log("obj", obj); + await UpsertEstimate(obj); } ); diff --git a/src/redux/user/user.sagas.js b/src/redux/user/user.sagas.js index 35f88fa..f0e602c 100644 --- a/src/redux/user/user.sagas.js +++ b/src/redux/user/user.sagas.js @@ -81,6 +81,7 @@ export function* onSignOutStart() { } export function* signOutStart() { try { + ipcRenderer.send(ipcTypes.default.fileWatcher.toMain.stop); yield auth.signOut(); yield put(signOutSuccess()); localStorage.removeItem("token");