feature/IO-3702-ESPD-UI-AND-FIXES - Stage 4

This commit is contained in:
Dave
2026-05-27 13:27:54 -04:00
parent c806ef67a0
commit 1e7e13ff32
11 changed files with 141 additions and 27 deletions

File diff suppressed because one or more lines are too long

21
package-lock.json generated
View File

@@ -27,6 +27,7 @@
"@electron-toolkit/tsconfig": "^2.0.0",
"@playwright/test": "^1.58.2",
"@reduxjs/toolkit": "^2.11.2",
"@types/archiver": "^7.0.0",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/lodash": "^4.17.24",
@@ -5089,6 +5090,16 @@
"node": ">=10"
}
},
"node_modules/@types/archiver": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz",
"integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/readdir-glob": "*"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -5375,6 +5386,16 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/readdir-glob": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz",
"integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/redux-logger": {
"version": "3.0.13",
"resolved": "https://registry.npmjs.org/@types/redux-logger/-/redux-logger-3.0.13.tgz",

View File

@@ -41,6 +41,7 @@
"@electron-toolkit/tsconfig": "^2.0.0",
"@playwright/test": "^1.58.2",
"@reduxjs/toolkit": "^2.11.2",
"@types/archiver": "^7.0.0",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/lodash": "^4.17.24",

View File

@@ -5,6 +5,7 @@ import fs from "fs";
import _ from "lodash";
import path from "path";
import errorTypeCheck from "../../util/errorTypeCheck";
import ipcTypes from "../../util/ipcTypes.json";
import { ScrubEstimate } from "../estimate-scrubber/estimate-scrubber";
import store from "../store/store";
import setAppProgressbar from "../util/setAppProgressBar";
@@ -138,7 +139,9 @@ async function ImportJob(filepath: string): Promise<void> {
console.log("Available Job record to upload;", newAvailableJob);
//Scrub the estimate
const scrubPdfURL = await ScrubEstimate({ job: jobObject });
const scrubResult = await ScrubEstimate({ job: jobObject });
const scrubPdfURL = scrubResult?.pdfUrl;
const scrubHistoryJobId = scrubResult?.jobId;
setAppProgressbar(0.95);
const esApiKey = store.get("settings.esApiKey") as string;
@@ -160,11 +163,27 @@ async function ImportJob(filepath: string): Promise<void> {
],
});
const openScrubHistoryItem = (): void => {
const mainWindow = getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) return;
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
mainWindow.focus();
if (scrubHistoryJobId) {
mainWindow.webContents.send(
ipcTypes.toRenderer.scrub.openHistoryItem,
{ jobId: scrubHistoryJobId },
);
}
};
uploadNotification.on("click", openScrubHistoryItem);
uploadNotification.on("action", (e) => {
// e.actionIndex
if (e.actionIndex === 0) {
const mainWindow = getMainWindow();
mainWindow?.show();
openScrubHistoryItem();
} else if (e.actionIndex === 1) {
if (scrubPdfURL) {
newWindow(scrubPdfURL);

View File

@@ -10,6 +10,11 @@ import { insertScrubRun } from "../db/scrub-history-db";
import getMainWindow from "../../util/getMainWindow";
import { showNotification } from "../util/notification";
export type ScrubEstimateResult = {
pdfUrl?: string;
jobId?: string;
};
function getErrorMessage(responseMessage: string | undefined): string | null {
if (!responseMessage) {
return null;
@@ -57,7 +62,7 @@ async function ScrubEstimate({
job,
}: {
job: RawJobDataObject;
}): Promise<string | undefined> {
}): Promise<ScrubEstimateResult | undefined> {
// No transformation here - send raw job to Lambda
const currentChannel = autoUpdater.channel;
let estimateScrubberUrl: string;
@@ -129,14 +134,20 @@ async function ScrubEstimate({
const { report_issue_url, identified_item, pdf_url } = result?.data ?? {};
let scrubHistoryJobId: string | undefined;
try {
insertScrubRun({
const identifiedItems = Array.isArray(identified_item)
? identified_item.slice(1, identified_item.length)
: [];
const scrubRun = insertScrubRun({
job,
identifiedItems: identified_item.slice(1, identified_item.length), // Remove first item which is the result metadata
identifiedItems, // Remove first item which is the result metadata
pdf_url: typeof pdf_url === "string" ? pdf_url : null,
report_issue_url:
typeof report_issue_url === "string" ? report_issue_url : null,
});
scrubHistoryJobId = scrubRun.jobId;
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send(ipcTypes.toRenderer.scrub.historyUpdated);
}
@@ -153,7 +164,10 @@ async function ScrubEstimate({
// report_issue_url,
// });
return pdf_url;
return {
pdfUrl: typeof pdf_url === "string" ? pdf_url : undefined,
jobId: scrubHistoryJobId,
};
} catch (error) {
const err = error as AxiosError;
log.error("Error while scrubbing estimate:", err.message, err.stack);
@@ -187,7 +201,7 @@ async function ScrubEstimate({
scrubErrorMessage || "Authentication with Estimate Scrubber failed.",
});
}
return "Error: Unable to scrub estimate.";
return undefined;
}
}

View File

@@ -564,6 +564,9 @@ app.whenReady().then(async () => {
const mainWindow = getMainWindow();
mainWindow?.webContents.send(ipcTypes.toRenderer.updates.checking);
});
autoUpdater.on("error", (error) => {
console.error("Auto updater error:", error);
});
autoUpdater.on("update-available", (info) => {
log.info("Update available.", info);
const mainWindow = getMainWindow();

View File

@@ -23,7 +23,9 @@ async function checkForAppUpdates(channel?: string | null): Promise<void> {
store.set("app.channel", channel);
}
log.debug("Checking for app updates on channel:", autoUpdater.channel);
autoUpdater.checkForUpdates();
autoUpdater.checkForUpdates().catch((error) => {
console.error("Error checking for app updates:", error);
});
}
export { checkForAppUpdates, checkForAppUpdatesContinuously };

View File

@@ -88,14 +88,18 @@ async function StartWatcher(
watcher
.on("add", async function (path) {
console.log("File", path, "has been added");
HandleNewFile(path);
HandleNewFile(path).catch((err) => {
console.error("Something went wrong in watcher -> add", err);
});
})
// .on("addDir", function (path) {
// console.log("Directory", path, "has been added");
// })
.on("change", async function (path) {
console.log("File", path, "has been changed");
HandleNewFile(path);
HandleNewFile(path).catch((err) => {
console.error("Something went wrong in watcher -> change", err);
});
})
// .on("unlink", function (path) {
// console.log("File", path, "has been removed");

View File

@@ -1,14 +1,38 @@
import { ConfigProvider, Layout } from "antd";
import { FC } from "react";
import { FC, useEffect } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Provider } from "react-redux";
import { HashRouter, Route, Routes } from "react-router";
import { HashRouter, Route, Routes, useNavigate } from "react-router";
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback/ErrorBoundaryFallback";
import Home from "./components/Home/Home";
import Settings from "./components/Settings/Settings";
import UpdateAvailable from "./components/UpdateAvailable/UpdateAvailable";
import reduxStore from "./redux/redux-store";
import { NotificationProvider } from "./util/notificationContext";
import ipcTypes from "../../util/ipcTypes.json";
const ScrubHistoryNavigationBridge: FC = () => {
const navigate = useNavigate();
useEffect(() => {
const removeListener = window.electron.ipcRenderer.on(
ipcTypes.toRenderer.scrub.openHistoryItem,
(_event: Electron.IpcRendererEvent, payload?: { jobId?: unknown }) => {
const jobId =
typeof payload?.jobId === "string" ? payload.jobId.trim() : "";
if (!jobId) return;
navigate(`/?jobId=${encodeURIComponent(jobId)}&openedAt=${Date.now()}`);
},
);
return () => {
removeListener();
};
}, [navigate]);
return null;
};
const App: FC = () => {
return (
@@ -28,6 +52,7 @@ const App: FC = () => {
<HashRouter>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<NotificationProvider>
<ScrubHistoryNavigationBridge />
<Layout
style={{
height: "100vh",

View File

@@ -12,8 +12,8 @@ import {
SettingOutlined,
} from "@ant-design/icons";
import {
Button,
Badge,
Button,
Card,
Col,
Empty,
@@ -25,13 +25,13 @@ import {
Spin,
Statistic,
Table,
theme,
Tooltip,
Typography,
theme,
} from "antd";
import { FC, UIEvent, useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
import { useNavigate, useSearchParams } from "react-router";
import { selectWatcherStatus } from "@renderer/redux/app.slice";
import { useAppSelector } from "@renderer/redux/reduxHooks";
import ipcTypes from "../../../../util/ipcTypes.json";
@@ -88,6 +88,7 @@ function isScrubHistoryPage(value: unknown): value is ScrubHistoryPage {
const Home: FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { token } = theme.useToken();
const ipcRenderer = window.electron.ipcRenderer;
@@ -99,8 +100,31 @@ const Home: FC = () => {
const [totalResults, setTotalResults] = useState<number>(0);
const [lastProcessed, setLastProcessed] = useState<number | null>(null);
const [loadedPage, setLoadedPage] = useState<number>(1);
const [selectedJobId, setSelectedJobId] = useState<string | null>(null);
const [manualSelectedJobId, setManualSelectedJobId] = useState<string | null>(
null,
);
const [loadingMore, setLoadingMore] = useState<boolean>(false);
const routeSelectedJobId = searchParams.get("jobId")?.trim() || null;
const selectedJobId = routeSelectedJobId ?? manualSelectedJobId;
const clearRouteSelection = useCallback(() => {
if (routeSelectedJobId) {
navigate("/", { replace: true });
}
}, [navigate, routeSelectedJobId]);
const selectJob = useCallback(
(jobId: string) => {
setManualSelectedJobId(jobId);
clearRouteSelection();
},
[clearRouteSelection],
);
const clearSelectedJob = useCallback(() => {
setManualSelectedJobId(null);
clearRouteSelection();
}, [clearRouteSelection]);
const loadScrubHistoryPage = useCallback(
async (page: number, size: number): Promise<unknown> =>
@@ -238,19 +262,19 @@ const Home: FC = () => {
async (jobId: string) => {
await ipcRenderer.invoke(ipcTypes.toMain.scrubHistory.deleteJob, jobId);
if (selectedJobId === jobId) {
setSelectedJobId(null);
clearSelectedJob();
}
await refresh(1);
},
[ipcRenderer, refresh, selectedJobId],
[clearSelectedJob, ipcRenderer, refresh, selectedJobId],
);
const clearAll = useCallback(async () => {
await ipcRenderer.invoke(ipcTypes.toMain.scrubHistory.clearAll);
setLoadedPage(1);
setSelectedJobId(null);
clearSelectedJob();
await refresh(1);
}, [ipcRenderer, refresh]);
}, [clearSelectedJob, ipcRenderer, refresh]);
const openScrubReport = useCallback((anchor: string | null) => {
if (!anchor) return;
@@ -554,11 +578,11 @@ const Home: FC = () => {
<div
role="button"
tabIndex={0}
onClick={() => setSelectedJobId(record.id)}
onClick={() => selectJob(record.id)}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
setSelectedJobId(record.id);
selectJob(record.id);
}
}}
style={{
@@ -738,7 +762,7 @@ const Home: FC = () => {
icon={<ReloadOutlined />}
loading={loading}
onClick={() => {
setSelectedJobId(null);
clearSelectedJob();
refresh(1).catch(() => undefined);
}}
>

View File

@@ -64,7 +64,8 @@
},
"scrub": {
"scrubError": "toRenderer_scrubError",
"historyUpdated": "toRenderer_scrub_historyUpdated"
"historyUpdated": "toRenderer_scrub_historyUpdated",
"openHistoryItem": "toRenderer_scrub_openHistoryItem"
},
"updates": {
"checking": "toRenderer_updates_checking",