diff --git a/package.json b/package.json index 69c4101..57b752f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bodyshop-desktop", - "version": "0.0.1-alpha.4", + "version": "0.0.1-alpha.5", "description": "Shop Management System Partner", "main": "./out/main/index.js", "author": "Convenient Brands, LLC", diff --git a/src/main/http-server/http-server.ts b/src/main/http-server/http-server.ts index 8223246..7329160 100644 --- a/src/main/http-server/http-server.ts +++ b/src/main/http-server/http-server.ts @@ -6,9 +6,11 @@ import http from "http"; import errorTypeCheck from "../../util/errorTypeCheck"; import ImportJob from "../decoder/decoder"; import folderScan from "../decoder/folder-scan"; +import { handleEMSPartsOrder } from "../ems-parts-order/ems-parts-order-handler"; +import { handleShopMetaDataFetch } from "../ipc/ipcMainHandler.user"; import { handlePartsPariceChangeRequest } from "../ppc/ppc-handler"; import { handleQuickBookRequest } from "../quickbooks-desktop/quickbooks-desktop"; -import { handleEMSPartsOrder } from "../ems-parts-order/ems-parts-order-handler"; +import { c } from "vite/dist/node/moduleRunnerTransport.d-CXw_Ws6P"; export default class LocalServer { private app: express.Application; @@ -146,6 +148,26 @@ export default class LocalServer { } }, ); + this.app.post( + "/refresh", + async (req: express.Request, res: express.Response) => { + log.debug("[HTTP Server] Refresh request received"); + try { + await handleShopMetaDataFetch(true); + res.status(200).json({ success: true }); + } catch (error) { + log.error( + "[HTTP Server] Error refreshing shop metadata", + errorTypeCheck(error), + ); + res.status(500).json({ + success: false, + error: "Error importing file", + ...errorTypeCheck(error), + }); + } + }, + ); // Add more routes as needed } diff --git a/src/main/index.ts b/src/main/index.ts index 0fc0bd2..6185f7b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,4 +1,4 @@ -import { is, optimizer } from "@electron-toolkit/utils"; +import { is, optimizer, platform } from "@electron-toolkit/utils"; import Sentry from "@sentry/electron/main"; import { app, @@ -88,7 +88,10 @@ function createWindow(): void { // { role: 'fileMenu' } { label: "File", - submenu: [isMac ? { role: "close" } : { role: "quit" }], + submenu: [ + ...(!isMac ? [{ role: "about" }] : []), + isMac ? { role: "close" } : { role: "quit" }, + ], }, // { role: 'editMenu' } { @@ -149,7 +152,7 @@ function createWindow(): void { }, }, { - label: "Check for Updates", + label: `Check for Updates (${app.getVersion()})`, click: (): void => { checkForAppUpdates(); }, @@ -169,6 +172,7 @@ function createWindow(): void { store.set("app.isTest", !currentSetting); log.info("Setting isTest to: ", !currentSetting); app.relaunch(); // Relaunch the app + preQuitMethods(); //Quitting handlers aren't called. Manually execute to clean up the app. app.exit(0); // Exit the current instance }, }, @@ -321,6 +325,10 @@ app.whenReady().then(async () => { // Default open or close DevTools by F12 in development // and ignore CommandOrControl + R in production. // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils + if (platform.isWindows) { + app.setAppUserModelId(app.name); + } + app.on("browser-window-created", (_, window) => { optimizer.watchWindowShortcuts(window); }); diff --git a/src/main/ipc/ipcMainHandler.user.ts b/src/main/ipc/ipcMainHandler.user.ts index 8a4a18f..46ef6df 100644 --- a/src/main/ipc/ipcMainHandler.user.ts +++ b/src/main/ipc/ipcMainHandler.user.ts @@ -13,7 +13,7 @@ import { } from "../graphql/queries"; import { default as Store, default as store } from "../store/store"; import { checkForAppUpdatesContinuously } from "../util/checkForAppUpdates"; -import { sendIpcToRenderer } from "../util/toRenderer"; +import { getMainWindow, sendIpcToRenderer } from "../util/toRenderer"; const ipcMainHandleAuthStateChanged = async ( _event: IpcMainEvent, @@ -23,27 +23,10 @@ const ipcMainHandleAuthStateChanged = async ( try { //Need to query the currently active shop, and store the metadata as well. //Also need to query the OP Codes for decoding reference. - const activeBodyshop: ActiveBodyshopQueryResult = await client.request( - QUERY_ACTIVE_BODYSHOP_TYPED, - ); - - Store.set("app.bodyshop", activeBodyshop.bodyshops[0]); - - const OpCodes: MasterdataQueryResult = await client.request( - QUERY_MASTERDATA_TYPED, - { - key: `${activeBodyshop.bodyshops[0].region_config}_ciecaopcodes`, - }, - ); - Store.set( - "app.masterdata.opcodes", - JSON.parse(OpCodes.masterdata[0]?.value), - ); log.debug("Received authentication state change from Renderer.", user); - log.debug("Requery shop information & master data."); - + handleShopMetaDataFetch(); //Check for updates - const convCo = activeBodyshop.bodyshops[0].convenient_company; + const convCo = Store.get("app.bodyshop"); if (convCo === "alpha") { autoUpdater.channel = "alpha"; log.debug("Setting update channel to ALPHA channel."); @@ -64,6 +47,39 @@ const ipcMainHandleAuthStateChanged = async ( checkForAppUpdatesContinuously(); }; +const handleShopMetaDataFetch = async ( + reloadWindow: boolean, +): Promise => { + try { + log.debug("Requery shop information & master data."); + const activeBodyshop: ActiveBodyshopQueryResult = await client.request( + QUERY_ACTIVE_BODYSHOP_TYPED, + ); + + Store.set("app.bodyshop", activeBodyshop.bodyshops[0]); + + const OpCodes: MasterdataQueryResult = await client.request( + QUERY_MASTERDATA_TYPED, + { + key: `${activeBodyshop.bodyshops[0].region_config}_ciecaopcodes`, + }, + ); + Store.set( + "app.masterdata.opcodes", + JSON.parse(OpCodes.masterdata[0]?.value), + ); + if (reloadWindow) { + const mainWindow = getMainWindow(); + if (mainWindow) { + mainWindow.reload(); + } + } + } catch (error) { + log.error("Error while fetching shop metadata", errorTypeCheck(error)); + throw error; + } +}; + const ipMainHandleResetPassword = async (): Promise => { shell.openExternal( store.get("app.isTest") @@ -72,4 +88,8 @@ const ipMainHandleResetPassword = async (): Promise => { ); }; -export { ipcMainHandleAuthStateChanged, ipMainHandleResetPassword }; +export { + ipcMainHandleAuthStateChanged, + ipMainHandleResetPassword, + handleShopMetaDataFetch, +}; diff --git a/src/renderer/src/components/Settings/Settings.Watcher.tsx b/src/renderer/src/components/Settings/Settings.Watcher.tsx index 1d3312d..136b561 100644 --- a/src/renderer/src/components/Settings/Settings.Watcher.tsx +++ b/src/renderer/src/components/Settings/Settings.Watcher.tsx @@ -134,16 +134,18 @@ const SettingsWatcher: React.FC = () => { checkedChildren={t("settings.labels.watchermoderealtime")} unCheckedChildren={t("settings.labels.watchermodepolling")} /> - - {t("settings.labels.pollinginterval")} - - + {pollingState.enabled && ( + + {t("settings.labels.pollinginterval")} + + + )} {watcherError && } diff --git a/src/renderer/src/components/UpdateAvailable/UpdateAvailable.tsx b/src/renderer/src/components/UpdateAvailable/UpdateAvailable.tsx index caea38e..b808854 100644 --- a/src/renderer/src/components/UpdateAvailable/UpdateAvailable.tsx +++ b/src/renderer/src/components/UpdateAvailable/UpdateAvailable.tsx @@ -8,6 +8,7 @@ import { useAppSelector } from "@renderer/redux/reduxHooks"; import { Affix, Button, Card, Progress, Space, Statistic } from "antd"; import { useTranslation } from "react-i18next"; import ipcTypes from "../../../../util/ipcTypes.json"; +import { useState } from "react"; const UpdateAvailable: React.FC = () => { const { t } = useTranslation(); @@ -16,12 +17,14 @@ const UpdateAvailable: React.FC = () => { const updateSpeed = useAppSelector(selectAppUpdateSpeed); const updateProgress = useAppSelector(selectAppUpdateProgress); const isUpdateComplete = useAppSelector(selectAppUpdateCompleted); + const [applyingUpdate, setApplyingUpdate] = useState(false); const handleDownload = (): void => { window.electron.ipcRenderer.send(ipcTypes.toMain.updates.download); }; const handleApply = (): void => { + setApplyingUpdate(true); window.electron.ipcRenderer.send(ipcTypes.toMain.updates.apply); }; @@ -30,28 +33,45 @@ const UpdateAvailable: React.FC = () => { } return ( - - {updateProgress === 0 && ( - - )} - - {!isUpdateComplete && formatSpeed(updateSpeed)} - {isUpdateComplete && ( - - - - window.electron.ipcRenderer.send(ipcTypes.toMain.updates.apply) - } - /> - - )} + + + {updateProgress === 0 && ( + + )} + + {!isUpdateComplete && formatSpeed(updateSpeed)} + {isUpdateComplete && ( + <> + + handleApply()} + /> + + )} + ); diff --git a/vscode-profile-2025-04-14-15-12-44.heapprofile b/vscode-profile-2025-04-14-15-12-44.heapprofile new file mode 100644 index 0000000..ad89ee8 --- /dev/null +++ b/vscode-profile-2025-04-14-15-12-44.heapprofile @@ -0,0 +1 @@ +{"head":{"callFrame":{"functionName":"(root)","scriptId":"0","url":"","lineNumber":-1,"columnNumber":-1},"selfSize":0,"id":1,"children":[{"callFrame":{"functionName":"callbackTrampoline","scriptId":"11","url":"node:internal/async_hooks","lineNumber":117,"columnNumber":27},"selfSize":0,"id":6,"children":[{"callFrame":{"functionName":"onStreamRead","scriptId":"98","url":"node:internal/stream_base_commons","lineNumber":165,"columnNumber":21},"selfSize":0,"id":7,"children":[{"callFrame":{"functionName":"Readable.push","scriptId":"88","url":"node:internal/streams/readable","lineNumber":386,"columnNumber":34},"selfSize":0,"id":8,"children":[{"callFrame":{"functionName":"readableAddChunkPushByteMode","scriptId":"88","url":"node:internal/streams/readable","lineNumber":462,"columnNumber":37},"selfSize":0,"id":9,"children":[{"callFrame":{"functionName":"addChunk","scriptId":"88","url":"node:internal/streams/readable","lineNumber":549,"columnNumber":17},"selfSize":0,"id":10,"children":[{"callFrame":{"functionName":"emit","scriptId":"22","url":"node:events","lineNumber":470,"columnNumber":43},"selfSize":0,"id":11,"children":[{"callFrame":{"functionName":"readFromStdout","scriptId":"240","url":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","lineNumber":584,"columnNumber":23},"selfSize":0,"id":12,"children":[{"callFrame":{"functionName":"handleIncomingPacket","scriptId":"240","url":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","lineNumber":661,"columnNumber":29},"selfSize":0,"id":13,"children":[{"callFrame":{"functionName":"handleRequest","scriptId":"240","url":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","lineNumber":634,"columnNumber":22},"selfSize":32800,"id":14,"children":[],"locationId":9}],"locationId":8}],"locationId":7}],"locationId":6}],"locationId":5}],"locationId":4}],"locationId":3}],"locationId":2}],"locationId":1}],"locationId":0},"samples":[{"size":32800,"nodeId":14,"ordinal":4}],"$vscode":{"rootPath":"/Users/pfic/Development/bodyshop-desktop","locations":[{"callFrame":{"functionName":"(root)","scriptId":"0","url":"","lineNumber":-1,"columnNumber":-1},"locations":[]},{"callFrame":{"functionName":"callbackTrampoline","scriptId":"11","url":"node:internal/async_hooks","lineNumber":117,"columnNumber":27},"locations":[{"lineNumber":118,"columnNumber":28,"source":{"name":"/internal/async_hooks","path":"/internal/async_hooks","sourceReference":1760071345,"presentationHint":"deemphasize","origin":"Skipped by skipFiles"}}]},{"callFrame":{"functionName":"onStreamRead","scriptId":"98","url":"node:internal/stream_base_commons","lineNumber":165,"columnNumber":21},"locations":[{"lineNumber":166,"columnNumber":22,"source":{"name":"/internal/stream_base_commons","path":"/internal/stream_base_commons","sourceReference":733809357,"presentationHint":"deemphasize","origin":"Skipped by skipFiles"}}]},{"callFrame":{"functionName":"Readable.push","scriptId":"88","url":"node:internal/streams/readable","lineNumber":386,"columnNumber":34},"locations":[{"lineNumber":387,"columnNumber":35,"source":{"name":"/internal/streams/readable","path":"/internal/streams/readable","sourceReference":860984053,"presentationHint":"deemphasize","origin":"Skipped by skipFiles"}}]},{"callFrame":{"functionName":"readableAddChunkPushByteMode","scriptId":"88","url":"node:internal/streams/readable","lineNumber":462,"columnNumber":37},"locations":[{"lineNumber":463,"columnNumber":38,"source":{"name":"/internal/streams/readable","path":"/internal/streams/readable","sourceReference":860984053,"presentationHint":"deemphasize","origin":"Skipped by skipFiles"}}]},{"callFrame":{"functionName":"addChunk","scriptId":"88","url":"node:internal/streams/readable","lineNumber":549,"columnNumber":17},"locations":[{"lineNumber":550,"columnNumber":18,"source":{"name":"/internal/streams/readable","path":"/internal/streams/readable","sourceReference":860984053,"presentationHint":"deemphasize","origin":"Skipped by skipFiles"}}]},{"callFrame":{"functionName":"emit","scriptId":"22","url":"node:events","lineNumber":470,"columnNumber":43},"locations":[{"lineNumber":471,"columnNumber":44,"source":{"name":"/events","path":"/events","sourceReference":838564717,"presentationHint":"deemphasize","origin":"Skipped by skipFiles"}}]},{"callFrame":{"functionName":"readFromStdout","scriptId":"240","url":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","lineNumber":584,"columnNumber":23},"locations":[{"lineNumber":585,"columnNumber":24,"source":{"name":"node_modules/esbuild/lib/main.js","path":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","sourceReference":0}}]},{"callFrame":{"functionName":"handleIncomingPacket","scriptId":"240","url":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","lineNumber":661,"columnNumber":29},"locations":[{"lineNumber":662,"columnNumber":30,"source":{"name":"node_modules/esbuild/lib/main.js","path":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","sourceReference":0}}]},{"callFrame":{"functionName":"handleRequest","scriptId":"240","url":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","lineNumber":634,"columnNumber":22},"locations":[{"lineNumber":635,"columnNumber":23,"source":{"name":"node_modules/esbuild/lib/main.js","path":"/Users/pfic/Development/bodyshop-desktop/node_modules/esbuild/lib/main.js","sourceReference":0}}]}]}} \ No newline at end of file