Add impact and JSON results showing for ES.
This commit is contained in:
@@ -255,7 +255,7 @@ async function DecodeAd2File(extensionlessFilePath) {
|
||||
}
|
||||
}
|
||||
|
||||
async function DecodeVehFile(extensionlessFilePath) {
|
||||
async function DecodePfhFile(extensionlessFilePath) {
|
||||
let dbf;
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFH`);
|
||||
@@ -270,7 +270,7 @@ async function DecodeVehFile(extensionlessFilePath) {
|
||||
}
|
||||
}
|
||||
|
||||
async function DecodePfhFile(extensionlessFilePath) {
|
||||
async function DecodeVehFile(extensionlessFilePath) {
|
||||
let dbf;
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}V.VEH`);
|
||||
@@ -282,8 +282,8 @@ async function DecodePfhFile(extensionlessFilePath) {
|
||||
if (!dbf) return {};
|
||||
let records = await dbf.readRecords(1);
|
||||
return _.pick(records[0], [
|
||||
// "IMPACT_1",
|
||||
// "IMPACT_2",
|
||||
"IMPACT_1",
|
||||
"IMPACT_2",
|
||||
// "DB_V_CODE",
|
||||
// "PLATE_NO",
|
||||
// "PLATE_ST",
|
||||
|
||||
@@ -82,7 +82,7 @@ async function ScrubEstimate({ job }) {
|
||||
return lowercasedTotal;
|
||||
});
|
||||
}
|
||||
console.log("*** ~ ScrubEstimate ~ job:", job);
|
||||
|
||||
const fileName = `RPSTest-${job.id}-${Date.now()}`;
|
||||
|
||||
// Write job object to logs subfolder
|
||||
@@ -104,7 +104,6 @@ async function ScrubEstimate({ job }) {
|
||||
},
|
||||
headers: formData.getHeaders ? formData.getHeaders() : {}
|
||||
});
|
||||
console.log("*** ~ ScrubEstimate ~ result:", result);
|
||||
|
||||
const resultPDFUrl = result?.data?.report_link
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ insert_permissions:
|
||||
- group_verified
|
||||
- id
|
||||
- id_pro_nam
|
||||
- impact_1
|
||||
- impact_2
|
||||
- ins_co_nm
|
||||
- loss_date
|
||||
- loss_desc
|
||||
@@ -71,6 +73,8 @@ select_permissions:
|
||||
- group_verified
|
||||
- id
|
||||
- id_pro_nam
|
||||
- impact_1
|
||||
- impact_2
|
||||
- ins_co_nm
|
||||
- loss_date
|
||||
- loss_desc
|
||||
@@ -115,6 +119,8 @@ update_permissions:
|
||||
- group_verified
|
||||
- id
|
||||
- id_pro_nam
|
||||
- impact_1
|
||||
- impact_2
|
||||
- ins_co_nm
|
||||
- loss_date
|
||||
- loss_desc
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "impact1" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "impact1" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "impact2" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "impact2" text
|
||||
null;
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."jobs" rename column "impact_1" to "impact1";
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."jobs" rename column "impact1" to "impact_1";
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."jobs" rename column "impact_2" to "impact2";
|
||||
@@ -0,0 +1 @@
|
||||
alter table "public"."jobs" rename column "impact2" to "impact_2";
|
||||
@@ -3,7 +3,7 @@
|
||||
"productName": "ImEX RPS",
|
||||
"author": "ImEX Systems Inc. <support@thinkimex.com>",
|
||||
"description": "ImEX RPS",
|
||||
"version": "1.4.2-alpha.7",
|
||||
"version": "1.4.2-alpha.8",
|
||||
"main": "electron/main.js",
|
||||
"homepage": "./",
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
import { InfoCircleOutlined, LinkOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import { Badge, Collapse, Input, Space, Table, Tag, Typography } from "antd";
|
||||
import _ from "lodash";
|
||||
import { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectEsResults } from "../../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
esResults: selectEsResults
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EstimateScrubberResults);
|
||||
|
||||
const { Panel } = Collapse;
|
||||
const { Title, Text, Link } = Typography;
|
||||
|
||||
export function EstimateScrubberResults({ bodyshop, jobid, job, esResults }) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
// Filter items based on search text
|
||||
const filteredItems = esResults?.items
|
||||
? esResults.items.filter(item => {
|
||||
if (!searchText.trim()) return true;
|
||||
const searchLower = searchText.toLowerCase();
|
||||
return (
|
||||
(item.L && item.L.toLowerCase().includes(searchLower)) ||
|
||||
(item.R && item.R.toLowerCase().includes(searchLower))
|
||||
);
|
||||
})
|
||||
: [];
|
||||
|
||||
// Group filtered items by category
|
||||
const groupedItems = filteredItems.length
|
||||
? _.groupBy(
|
||||
filteredItems.filter((item) => item.Category !== "Display Group"),
|
||||
"Category"
|
||||
)
|
||||
: {};
|
||||
|
||||
// Define category colors and priorities
|
||||
const categoryConfig = {
|
||||
"Association Items": { color: "blue", priority: 1, icon: "🔗" },
|
||||
"Guidelines Items": { color: "orange", priority: 2, icon: "📋" }
|
||||
//"Display Group": { color: "green", priority: 3, icon: "📊" }
|
||||
};
|
||||
|
||||
// Sort categories by priority
|
||||
const sortedCategories = Object.keys(groupedItems).sort((a, b) => {
|
||||
const priorityA = categoryConfig[a]?.priority || 999;
|
||||
const priorityB = categoryConfig[b]?.priority || 999;
|
||||
return priorityA - priorityB;
|
||||
});
|
||||
|
||||
// Define table columns
|
||||
const columns = [
|
||||
{
|
||||
title: "Item",
|
||||
dataIndex: "L",
|
||||
key: "item",
|
||||
width: "25%",
|
||||
render: (text, record) => (
|
||||
<Space direction="vertical" size="small">
|
||||
<Text strong>{text}</Text>
|
||||
{record.LinkText && record.Anchor && (
|
||||
<Link href={record.Anchor} target="_blank" rel="noopener noreferrer">
|
||||
<LinkOutlined /> Learn more
|
||||
</Link>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Description",
|
||||
dataIndex: "R",
|
||||
key: "description",
|
||||
width: "75%",
|
||||
render: (text) => <Text style={{}}>{text}</Text>
|
||||
}
|
||||
];
|
||||
|
||||
if (!esResults?.items?.length || job?.id !== esResults?.jobid) {
|
||||
return (
|
||||
<div style={{ padding: "24px", textAlign: "center" }}>
|
||||
<InfoCircleOutlined style={{ fontSize: "48px", color: "#d9d9d9", marginBottom: "16px" }} />
|
||||
<Title level={4} type="secondary">
|
||||
Estimate not yet scrubbed.
|
||||
</Title>
|
||||
<Text type="secondary">Run the estimate scrubber to see results here.</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show empty search results state
|
||||
if (searchText && !filteredItems.length) {
|
||||
return (
|
||||
<div style={{ padding: "16px" }}>
|
||||
<Input
|
||||
placeholder="Search items and descriptions..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
style={{ maxWidth: 400, marginBottom: "24px" }}
|
||||
/>
|
||||
<div style={{ padding: "24px", textAlign: "center" }}>
|
||||
<SearchOutlined style={{ fontSize: "48px", color: "#d9d9d9", marginBottom: "16px" }} />
|
||||
<Title level={4} type="secondary">
|
||||
No items match your search
|
||||
</Title>
|
||||
<Text type="secondary">Try different keywords or clear the search to see all items.</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: "16px" }}>
|
||||
<Space direction="vertical" size="large" style={{ width: "100%" }}>
|
||||
{/* Search Input */}
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Search items and descriptions..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
style={{ maxWidth: 400 }}
|
||||
/>
|
||||
{searchText && (
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<Text type="secondary">
|
||||
Showing {filteredItems.length} of {esResults.items.length} items
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Grouped Results */}
|
||||
<Collapse defaultActiveKey={sortedCategories} expandIconPosition="right" size="large">
|
||||
{sortedCategories.map((category) => {
|
||||
const items = groupedItems[category];
|
||||
const config = categoryConfig[category] || { color: "default", icon: "📄" };
|
||||
|
||||
return (
|
||||
<Panel
|
||||
header={
|
||||
<Space>
|
||||
<span style={{ fontSize: "18px" }}>{config.icon}</span>
|
||||
<Text strong>{category}</Text>
|
||||
<Badge
|
||||
count={items.length}
|
||||
style={{
|
||||
backgroundColor:
|
||||
config.color === "blue" ? "#1890ff" : config.color === "orange" ? "#fa8c16" : "#52c41a"
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
key={category}
|
||||
>
|
||||
<Table
|
||||
dataSource={items}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
size="middle"
|
||||
rowKey={(record, index) => `${category}-${index}`}
|
||||
style={{ marginTop: "12px" }}
|
||||
scroll={{}}
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
})}
|
||||
</Collapse>
|
||||
|
||||
{/* Summary */}
|
||||
|
||||
<Title level={5}>Summary</Title>
|
||||
<Space wrap>
|
||||
{Object.entries(groupedItems).map(([category, items]) => {
|
||||
const config = categoryConfig[category] || { color: "default" };
|
||||
return (
|
||||
<Tag key={category} color={config.color}>
|
||||
{category}: {items.length}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Card, Result } from "antd";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_JOB_BY_PK } from "../../../graphql/jobs.queries";
|
||||
@@ -8,12 +8,12 @@ import { setSelectedJobTargetPc } from "../../../redux/application/application.a
|
||||
import { selectSelectedJobId } from "../../../redux/application/application.selectors";
|
||||
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
|
||||
import JobsPartsGraphAtom from "../../atoms/jobs-parts-graph/jobs-parts-graph.atom";
|
||||
import EstimateScrubberButton from "../../molecules/estimate-scrubber-button/estimate-scrubber-button.molecule";
|
||||
import EstimateScrubberResultsMolecule from "../../molecules/estimate-scruber-results/estimate-scrubber-results.molecule";
|
||||
import JobsDetailDescriptionMolecule from "../../molecules/jobs-detail-description/jobs-detail-description.molecule";
|
||||
import JobsLinesTableMolecule from "../../molecules/jobs-lines-table/jobs-lines-table.molecule";
|
||||
import JobsTargetsStatsMolecule from "../../molecules/jobs-targets-stats/jobs-targets-stats.molecule";
|
||||
import "./jobs-detail.organism.styles.scss";
|
||||
import JobsClaimClerk from "../../molecules/jobs-claims-clerk/jobs-claims-clerk.molecule";
|
||||
import { EstimateScrubberButton } from "../../molecules/estimate-scrubber-button/estimate-scrubber-button.molecule";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -93,6 +93,18 @@ export function JobsDetailOrganism({ selectedJobId, setSelectedJobTargetPc }) {
|
||||
>
|
||||
<JobsLinesTableMolecule loading={loading} job={data ? data.jobs_by_pk : {}} />
|
||||
</Card>
|
||||
<Card
|
||||
title="Estimate Scrubber Results"
|
||||
extra={[
|
||||
<EstimateScrubberButton
|
||||
key="es"
|
||||
jobid={data ? data.jobs_by_pk?.id : null}
|
||||
job={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
]}
|
||||
>
|
||||
<EstimateScrubberResultsMolecule loading={loading} job={data ? data.jobs_by_pk : {}} />
|
||||
</Card>
|
||||
<Card title="Parts Breakdown">
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -235,6 +235,8 @@ export const QUERY_JOB_ESTIMATE_SCRUBBER = gql`
|
||||
ownr_ph1
|
||||
v_stage
|
||||
id_pro_nam
|
||||
impact_1
|
||||
impact_2
|
||||
supp_amt
|
||||
g_bett_amt
|
||||
bodyshop {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ipcTypes from "../ipc.types";
|
||||
import {
|
||||
setReleaseNotes,
|
||||
setScrubResults,
|
||||
setSettings,
|
||||
setUpdateAvailable,
|
||||
setUpdateProgress,
|
||||
@@ -84,3 +85,6 @@ ipcRenderer.on(ipcTypes.audit.toRenderer.auditClaimsArray, async (event, claimsA
|
||||
ipcRenderer.on(ipcTypes.audit.toRenderer.auditError, async (event, error) => {
|
||||
store.dispatch(setAuditError(error));
|
||||
});
|
||||
ipcRenderer.on(ipcTypes.app.toRenderer.scrubResults, async (event, results) => {
|
||||
store.dispatch(setScrubResults(results));
|
||||
});
|
||||
|
||||
@@ -57,3 +57,7 @@ export const setReleaseNotes = (releaseNotes) => ({
|
||||
type: ApplicationActionTypes.SET_RELEASE_NOTES,
|
||||
payload: releaseNotes
|
||||
});
|
||||
export const setScrubResults = (scrubResults) => ({
|
||||
type: ApplicationActionTypes.SET_ES_RESULTS,
|
||||
payload: scrubResults
|
||||
});
|
||||
|
||||
@@ -10,6 +10,10 @@ const INITIAL_STATE = {
|
||||
updateAvailable: false,
|
||||
updateProgress: null,
|
||||
releaseNotes: null,
|
||||
esResults: {
|
||||
jobid: null,
|
||||
identified_items: []
|
||||
}
|
||||
};
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
@@ -69,6 +73,8 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
return { ...state, updateProgress: action.payload };
|
||||
case ApplicationActionTypes.SET_RELEASE_NOTES:
|
||||
return { ...state, releaseNotes: action.payload };
|
||||
case ApplicationActionTypes.SET_ES_RESULTS:
|
||||
return { ...state, esResults: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -46,3 +46,7 @@ export const selectReleaseNotes = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.releaseNotes
|
||||
);
|
||||
export const selectEsResults = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.esResults
|
||||
);
|
||||
|
||||
@@ -11,5 +11,6 @@ const ApplicationActionTypes = {
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
|
||||
SET_UPDATE_PROGRESS: "SET_UPDATE_PROGRESS",
|
||||
SET_RELEASE_NOTES: "SET_RELEASE_NOTES",
|
||||
SET_ES_RESULTS: "SET_ES_RESULTS"
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -186,6 +186,10 @@ export function* handleCalculateScoreCard({ payload: queriedJobs }) {
|
||||
const simpleJobAlerts = [];
|
||||
|
||||
job.joblines.forEach((jobline) => {
|
||||
|
||||
if (jobline.ignore) {
|
||||
return;
|
||||
}
|
||||
if (jobline.part_qty > 1) {
|
||||
simpleJobAlerts.push({
|
||||
key: `line-${jobline.id}-q`,
|
||||
@@ -206,7 +210,7 @@ export function* handleCalculateScoreCard({ payload: queriedJobs }) {
|
||||
}
|
||||
});
|
||||
|
||||
const jobAlerts = [...simpleJobAlerts,
|
||||
const jobAlerts = [...simpleJobAlerts,
|
||||
// ...job.joblines
|
||||
// .map((jobline) =>
|
||||
// jobline.alerts?.map((alert, idx) => ({
|
||||
|
||||
Reference in New Issue
Block a user