feature/IO-3725-RPS-Changes - Deprecations / Bug fixes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import { ConfigProvider, theme } from "antd";
|
||||
import { App as AntdApp, ConfigProvider, theme } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import React, { useEffect } from "react";
|
||||
import { connect } from "react-redux";
|
||||
@@ -13,6 +13,7 @@ import "../ipc/ipc-renderer-handler";
|
||||
import { checkUserSession } from "../redux/user/user.actions";
|
||||
import { selectCurrentUser, selectDarkMode } from "../redux/user/user.selectors";
|
||||
import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";
|
||||
import { configureAntdFeedback } from "../util/antdFeedback";
|
||||
import "./App.styles.scss";
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
@@ -30,6 +31,16 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession())
|
||||
});
|
||||
|
||||
function AntdFeedbackBridge() {
|
||||
const { message, notification } = AntdApp.useApp();
|
||||
|
||||
useEffect(() => {
|
||||
configureAntdFeedback({ message, notification });
|
||||
}, [message, notification]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function App({ currentUser, checkUserSession, darkMode }) {
|
||||
useEffect(() => {
|
||||
checkUserSession();
|
||||
@@ -62,7 +73,10 @@ export function App({ currentUser, checkUserSession, darkMode }) {
|
||||
input={{ autoComplete: "new-password" }}
|
||||
locale={enLocale}
|
||||
>
|
||||
<div>{currentUser.authorized ? <RoutesPage /> : <SignInPage />}</div>
|
||||
<AntdApp>
|
||||
<AntdFeedbackBridge />
|
||||
<div>{currentUser.authorized ? <RoutesPage /> : <SignInPage />}</div>
|
||||
</AntdApp>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, message, Popconfirm } from "antd";
|
||||
import { Button, Popconfirm } from "antd";
|
||||
import { antdMessage as message } from "../../../util/antdFeedback";
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { message, Switch } from "antd";
|
||||
import { Switch } from "antd";
|
||||
import { antdMessage as message } from "../../../util/antdFeedback";
|
||||
import React, { useState } from "react";
|
||||
import { UPDATE_JOB_LINE } from "../../../graphql/joblines.queries";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { WarningOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { DatePicker, message, notification, Space, Spin } from "antd";
|
||||
import { DatePicker, Space, Spin } from "antd";
|
||||
import { antdMessage as message, antdNotification as notification } from "../../../util/antdFeedback";
|
||||
import React, { useState } from "react";
|
||||
import { UPDATE_JOB } from "../../../graphql/jobs.queries";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, message, Tooltip } from "antd";
|
||||
import { Button, Tooltip } from "antd";
|
||||
import { antdMessage as message } from "../../../util/antdFeedback";
|
||||
import { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
@@ -18,7 +18,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EstimateScrubberResults);
|
||||
|
||||
const { Panel } = Collapse;
|
||||
const { Title, Text, Link } = Typography;
|
||||
|
||||
export function EstimateScrubberResults({ bodyshop, jobid, job, esResults }) {
|
||||
@@ -26,8 +25,15 @@ export function EstimateScrubberResults({ bodyshop, jobid, job, esResults }) {
|
||||
const buttonDisabled = job?.g_bett_amt == null;
|
||||
|
||||
// Filter items based on search text
|
||||
const filteredItems = esResults?.items
|
||||
? esResults.items.filter((item) => {
|
||||
const itemsWithKeys = esResults?.items
|
||||
? esResults.items.map((item, itemIndex) => ({
|
||||
...item,
|
||||
scrubberRowKey: [item.SubCategory, item.L, item.R, item.Anchor, itemIndex].filter(Boolean).join("|")
|
||||
}))
|
||||
: [];
|
||||
|
||||
const filteredItems = itemsWithKeys.length
|
||||
? itemsWithKeys.filter((item) => {
|
||||
if (!searchText.trim()) return true;
|
||||
const searchLower = searchText.toLowerCase();
|
||||
return (
|
||||
@@ -89,6 +95,38 @@ export function EstimateScrubberResults({ bodyshop, jobid, job, esResults }) {
|
||||
}
|
||||
];
|
||||
|
||||
const collapseItems = sortedCategories.map((category) => {
|
||||
const items = groupedItems[category];
|
||||
const config = categoryConfig[category] || { color: "default", icon: "📄" };
|
||||
|
||||
return {
|
||||
key: category,
|
||||
label: (
|
||||
<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>
|
||||
),
|
||||
children: (
|
||||
<Table
|
||||
dataSource={items}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
size="middle"
|
||||
rowKey="scrubberRowKey"
|
||||
style={{ marginTop: "12px" }}
|
||||
scroll={{}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
});
|
||||
|
||||
if (!esResults?.items?.length || job?.id !== esResults?.jobid) {
|
||||
return (
|
||||
<Result
|
||||
@@ -189,41 +227,7 @@ export function EstimateScrubberResults({ bodyshop, jobid, job, esResults }) {
|
||||
})}
|
||||
</Space>
|
||||
|
||||
<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>
|
||||
<Collapse defaultActiveKey={sortedCategories} expandIconPosition="end" size="large" items={collapseItems} />
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CheckOutlined, CloseOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { message, Switch } from "antd";
|
||||
import { Switch } from "antd";
|
||||
import { antdMessage as message } from "../../../util/antdFeedback";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { AlertFilled, DownOutlined, LoadingOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Dropdown, Menu, message, Space, Tooltip } from "antd";
|
||||
import { Dropdown, Space, Tooltip } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_JOB } from "../../../graphql/jobs.queries";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
import { selectBodyshop } from "../../../redux/user/user.selectors";
|
||||
import { antdMessage as message } from "../../../util/antdFeedback";
|
||||
import GroupVerifySwitch from "../group-verify-switch/group-verify-switch.component";
|
||||
import JobsGroupModalMolecule from "../jobs-group-modal/jobs-group-modal.molecule";
|
||||
import { WhichMPIRulesetToApply } from "../../../util/constants";
|
||||
@@ -48,41 +49,38 @@ export function JobGroupMolecule({ bodyshop, jobId, group, job }) {
|
||||
|
||||
const MPIRulesetToApply = WhichMPIRulesetToApply(job.close_date);
|
||||
const MPImenu =
|
||||
MPIRulesetToApply === "V3" ? (
|
||||
<Menu
|
||||
onClick={handleMenuClick}
|
||||
//disabled
|
||||
items={[
|
||||
{ label: "Group 1", key: "Group 1", value: "Group 1" },
|
||||
{ label: "Group 2", key: "Group 2", value: "Group 2" },
|
||||
{ label: "Group 3", key: "Group 3", value: "Group 3" },
|
||||
{ label: "Group 4", key: "Group 4", value: "Group 4" },
|
||||
{ label: "Group 5", key: "Group 5", value: "Group 5" },
|
||||
{ label: "Group 6", key: "Group 6", value: "Group 6" },
|
||||
{ label: "Group 7", key: "Group 7", value: "Group 7" },
|
||||
{ label: "Group 8", key: "Group 8", value: "Group 8" },
|
||||
{ label: "Group 9", key: "Group 9", value: "Group 9" },
|
||||
{ label: "Group 10", key: "Group 10", value: "Group 10" },
|
||||
{ label: "Group 11", key: "Group 11", value: "Group 11" },
|
||||
{ label: "Group 12", key: "Group 12", value: "Group 12" },
|
||||
{ label: "Default", key: "Default", value: "Default" }
|
||||
]}
|
||||
></Menu>
|
||||
) : (
|
||||
<Menu onClick={handleMenuClick} items={bodyshop.groups.map((g, idx) => ({ key: g, title: g, label: g }))}></Menu>
|
||||
);
|
||||
const SGImenu = (
|
||||
<Menu
|
||||
onClick={handleMenuClick}
|
||||
//disabled
|
||||
items={[{ label: "Default", key: "Default", value: "Default" }]}
|
||||
></Menu>
|
||||
);
|
||||
MPIRulesetToApply === "V3"
|
||||
? {
|
||||
onClick: handleMenuClick,
|
||||
items: [
|
||||
{ label: "Group 1", key: "Group 1", value: "Group 1" },
|
||||
{ label: "Group 2", key: "Group 2", value: "Group 2" },
|
||||
{ label: "Group 3", key: "Group 3", value: "Group 3" },
|
||||
{ label: "Group 4", key: "Group 4", value: "Group 4" },
|
||||
{ label: "Group 5", key: "Group 5", value: "Group 5" },
|
||||
{ label: "Group 6", key: "Group 6", value: "Group 6" },
|
||||
{ label: "Group 7", key: "Group 7", value: "Group 7" },
|
||||
{ label: "Group 8", key: "Group 8", value: "Group 8" },
|
||||
{ label: "Group 9", key: "Group 9", value: "Group 9" },
|
||||
{ label: "Group 10", key: "Group 10", value: "Group 10" },
|
||||
{ label: "Group 11", key: "Group 11", value: "Group 11" },
|
||||
{ label: "Group 12", key: "Group 12", value: "Group 12" },
|
||||
{ label: "Default", key: "Default", value: "Default" }
|
||||
]
|
||||
}
|
||||
: {
|
||||
onClick: handleMenuClick,
|
||||
items: bodyshop.groups.map((g) => ({ key: g, title: g, label: g }))
|
||||
};
|
||||
const SGImenu = {
|
||||
onClick: handleMenuClick,
|
||||
items: [{ label: "Default", key: "Default", value: "Default" }]
|
||||
};
|
||||
|
||||
const menuToUse = bodyshop.ins_rule_set === "MPI" ? MPImenu : SGImenu;
|
||||
return (
|
||||
<Space align="center" wrap>
|
||||
<Dropdown overlay={menuToUse} trigger={["click"]}>
|
||||
<Dropdown menu={menuToUse} trigger={["click"]}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
{group}
|
||||
<DownOutlined style={{ marginLeft: ".2rem" }} />
|
||||
|
||||
@@ -5,7 +5,6 @@ import { createStructuredSelector } from "reselect";
|
||||
import { queryReportingData } from "../../../redux/reporting/reporting.actions";
|
||||
import dayjs from "../../../util/day.js";
|
||||
import ipcTypes from "../../../ipc.types.js";
|
||||
import { range } from "lodash";
|
||||
const { ipcRenderer } = window;
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -15,8 +14,49 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ReportingDatesMolecule);
|
||||
|
||||
const getReportingDatePresets = () => [
|
||||
{
|
||||
label: "Last Month",
|
||||
value: [
|
||||
dayjs().startOf("month").subtract(1, "month"),
|
||||
dayjs().startOf("month").subtract(1, "month").endOf("month")
|
||||
]
|
||||
},
|
||||
{ label: "This Month", value: [dayjs().startOf("month"), dayjs().endOf("month")] },
|
||||
{
|
||||
label: "Next Month",
|
||||
value: [
|
||||
dayjs().startOf("month").add(1, "month"),
|
||||
dayjs().startOf("month").add(1, "month").endOf("month")
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Last Quarter",
|
||||
value: [
|
||||
dayjs().startOf("quarter").subtract(1, "quarter"),
|
||||
dayjs().startOf("quarter").subtract(1, "day")
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "This Quarter",
|
||||
value: [dayjs().startOf("quarter"), dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day")]
|
||||
},
|
||||
{
|
||||
label: "Last 3 Months (Exlcusive)",
|
||||
value: [
|
||||
dayjs().startOf("month").subtract(3, "month"),
|
||||
dayjs().startOf("month").subtract(1, "month").endOf("month")
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Last 3 Months (Inclusive)",
|
||||
value: [dayjs().startOf("month").subtract(2, "month"), dayjs().endOf("month")]
|
||||
}
|
||||
];
|
||||
|
||||
export function ReportingDatesMolecule({ queryReportingData }) {
|
||||
const [form] = Form.useForm();
|
||||
const reportingDatePresets = getReportingDatePresets();
|
||||
|
||||
const handleFinish = (values) => {
|
||||
queryReportingData({
|
||||
@@ -49,34 +89,7 @@ export function ReportingDatesMolecule({ queryReportingData }) {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<DatePicker.RangePicker
|
||||
format="MM/DD/YYYY"
|
||||
ranges={{
|
||||
"Last Month": [
|
||||
dayjs().startOf("month").subtract(1, "month"),
|
||||
dayjs().startOf("month").subtract(1, "month").endOf("month")
|
||||
],
|
||||
|
||||
"This Month": [dayjs().startOf("month"), dayjs().endOf("month")],
|
||||
"Next Month": [
|
||||
dayjs().startOf("month").add(1, "month"),
|
||||
dayjs().startOf("month").add(1, "month").endOf("month")
|
||||
],
|
||||
"Last Quarter": [
|
||||
dayjs().startOf("quarter").subtract(1, "quarter"),
|
||||
dayjs().startOf("quarter").subtract(1, "day")
|
||||
],
|
||||
"This Quarter": [
|
||||
dayjs().startOf("quarter"),
|
||||
dayjs().startOf("quarter").add(1, "quarter").subtract(1, "day")
|
||||
],
|
||||
"Last 3 Months (Exlcusive)": [
|
||||
dayjs().startOf("month").subtract(3, "month"),
|
||||
dayjs().startOf("month").subtract(1, "month").endOf("month")
|
||||
],
|
||||
"Last 3 Months (Inclusive)": [dayjs().startOf("month").subtract(2, "month"), dayjs().endOf("month")]
|
||||
}}
|
||||
/>
|
||||
<DatePicker.RangePicker format="MM/DD/YYYY" presets={reportingDatePresets} />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Run Search
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button, Input, message, Table } from "antd";
|
||||
import { Button, Input, Table } from "antd";
|
||||
import { antdMessage as message } from "../../../util/antdFeedback";
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CheckOutlined, CloseOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { message, Switch } from "antd";
|
||||
import { Switch } from "antd";
|
||||
import { antdMessage as message } from "../../../util/antdFeedback";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Badge, Button, Card, Result } from "antd";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Badge, Button, Card, Result, Skeleton } from "antd";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_JOB_BY_PK } from "../../../graphql/jobs.queries";
|
||||
@@ -16,6 +16,8 @@ import JobsTargetsStatsMolecule from "../../molecules/jobs-targets-stats/jobs-ta
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
import "./jobs-detail.organism.styles.scss";
|
||||
|
||||
const JOB_SELECTION_DEBOUNCE_MS = 120;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
selectedJobId: selectSelectedJobId,
|
||||
@@ -26,23 +28,58 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setSelectedJobTargetPc: (job) => dispatch(setSelectedJobTargetPc(job))
|
||||
});
|
||||
|
||||
export function JobsDetailOrganism({ bodyshop, selectedJobId, setSelectedJobTargetPc }) {
|
||||
const { loading, error, data } = useQuery(QUERY_JOB_BY_PK, {
|
||||
variables: { jobId: selectedJobId },
|
||||
skip: !selectedJobId
|
||||
});
|
||||
const jobDetailRef = useRef();
|
||||
function useDebouncedSelectedJobId(selectedJobId) {
|
||||
const [debouncedSelectedJobId, setDebouncedSelectedJobId] = useState(selectedJobId);
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data.jobs_by_pk)
|
||||
if (!selectedJobId) {
|
||||
setDebouncedSelectedJobId(selectedJobId);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
setDebouncedSelectedJobId(selectedJobId);
|
||||
}, JOB_SELECTION_DEBOUNCE_MS);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [selectedJobId]);
|
||||
|
||||
return debouncedSelectedJobId;
|
||||
}
|
||||
|
||||
function JobDetailSkeleton() {
|
||||
return (
|
||||
<div className="jobs-detail-container">
|
||||
<Card>
|
||||
<Skeleton active paragraph={{ rows: 4 }} />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function JobsDetailOrganism({ bodyshop, selectedJobId, setSelectedJobTargetPc }) {
|
||||
const debouncedSelectedJobId = useDebouncedSelectedJobId(selectedJobId);
|
||||
const { loading, error, data } = useQuery(QUERY_JOB_BY_PK, {
|
||||
variables: { jobId: debouncedSelectedJobId },
|
||||
skip: !debouncedSelectedJobId
|
||||
});
|
||||
const jobDetailRef = useRef();
|
||||
const job = data?.jobs_by_pk;
|
||||
const isSelectionPending = selectedJobId !== debouncedSelectedJobId;
|
||||
const hasSelectedJobData = job?.id === debouncedSelectedJobId;
|
||||
const isMissingSelectedJob = !isSelectionPending && !loading && debouncedSelectedJobId && !job;
|
||||
const isJobDetailLoading = isSelectionPending || loading || (job && !hasSelectedJobData);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSelectedJobData)
|
||||
setSelectedJobTargetPc({
|
||||
group: data.jobs_by_pk && data.jobs_by_pk.group,
|
||||
v_age: data.jobs_by_pk && data.jobs_by_pk.v_age,
|
||||
close_date: data.jobs_by_pk && data.jobs_by_pk.close_date,
|
||||
v_mileage: data.jobs_by_pk && data.jobs_by_pk.v_mileage,
|
||||
job: data.jobs_by_pk
|
||||
group: job.group,
|
||||
v_age: job.v_age,
|
||||
close_date: job.close_date,
|
||||
v_mileage: job.v_mileage,
|
||||
job
|
||||
});
|
||||
}, [data, setSelectedJobTargetPc]);
|
||||
}, [hasSelectedJobData, job, setSelectedJobTargetPc]);
|
||||
const isSgi = bodyshop?.ins_rule_set === "SGI";
|
||||
if (!selectedJobId)
|
||||
return (
|
||||
@@ -57,7 +94,10 @@ export function JobsDetailOrganism({ bodyshop, selectedJobId, setSelectedJobTarg
|
||||
<Result title="No job selected." />
|
||||
</div>
|
||||
);
|
||||
if (isJobDetailLoading) return <JobDetailSkeleton />;
|
||||
if (error) return <ErrorResultAtom title="Error fetching Job details.." errorMessage={JSON.stringify(error)} />;
|
||||
if (isMissingSelectedJob)
|
||||
return <ErrorResultAtom title="Error displaying job details." errorMessage="It looks like this job doesn't exist." />;
|
||||
|
||||
return (
|
||||
<div ref={jobDetailRef} className="jobs-detail-container">
|
||||
@@ -70,13 +110,13 @@ export function JobsDetailOrganism({ bodyshop, selectedJobId, setSelectedJobTarg
|
||||
</style>
|
||||
<Card>
|
||||
<JobsDetailDescriptionMolecule
|
||||
loading={loading}
|
||||
job={data ? data.jobs_by_pk : null}
|
||||
loading={false}
|
||||
job={job}
|
||||
jobDetailRef={jobDetailRef}
|
||||
/>
|
||||
</Card>
|
||||
<Card title="Job Targets">
|
||||
<JobsTargetsStatsMolecule loading={loading} job={data ? data.jobs_by_pk : null} />
|
||||
<JobsTargetsStatsMolecule loading={false} job={job} />
|
||||
</Card>
|
||||
|
||||
{/* <JobsClaimClerk loading={loading} job={data ? data.jobs_by_pk : null} />
|
||||
@@ -84,12 +124,12 @@ export function JobsDetailOrganism({ bodyshop, selectedJobId, setSelectedJobTarg
|
||||
*/}
|
||||
|
||||
<Card title="Estimate Lines">
|
||||
<JobsLinesTableMolecule loading={loading} job={data ? data.jobs_by_pk : {}} />
|
||||
<JobsLinesTableMolecule loading={false} job={job} />
|
||||
</Card>
|
||||
{bodyshop.es_api_key ? (
|
||||
<Badge.Ribbon text="BETA" color="red">
|
||||
<Card id="es-results-card" title="Estimate Scrubber Results" extra={[]}>
|
||||
<EstimateScrubberResultsMolecule loading={loading} job={data ? data.jobs_by_pk : {}} />
|
||||
<EstimateScrubberResultsMolecule loading={false} job={job} />
|
||||
</Card>
|
||||
</Badge.Ribbon>
|
||||
) : (
|
||||
@@ -127,8 +167,8 @@ export function JobsDetailOrganism({ bodyshop, selectedJobId, setSelectedJobTarg
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<JobsPartsGraphAtom job={data ? data.jobs_by_pk : null} loading={loading} price="db_price" />
|
||||
<JobsPartsGraphAtom job={data ? data.jobs_by_pk : null} loading={loading} />
|
||||
<JobsPartsGraphAtom job={job} loading={false} price="db_price" />
|
||||
<JobsPartsGraphAtom job={job} loading={false} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -27,19 +27,23 @@ export default function JobsTableOrganism() {
|
||||
},
|
||||
|
||||
updateQuery: (prev, { fetchMoreResult }) => {
|
||||
if (!fetchMoreResult) {
|
||||
const previousJobs = prev?.jobs || [];
|
||||
const incomingJobs = fetchMoreResult?.jobs || [];
|
||||
|
||||
if (!incomingJobs.length) {
|
||||
console.log("No more results. Fetch More was empty.");
|
||||
setState({ ...state, hasMore: false });
|
||||
setState((current) => ({ ...current, hasMore: false }));
|
||||
return prev;
|
||||
}
|
||||
|
||||
const newCache = Object.assign({}, prev, {
|
||||
jobs: [...prev.jobs, ...fetchMoreResult.jobs]
|
||||
jobs: [...previousJobs, ...incomingJobs],
|
||||
jobs_aggregate: fetchMoreResult.jobs_aggregate || prev?.jobs_aggregate
|
||||
});
|
||||
|
||||
if (newCache.jobs.length >= (data && data.jobs_aggregate.aggregate.count)) {
|
||||
console.log("No more results.");
|
||||
setState({ ...state, hasMore: false });
|
||||
setState((current) => ({ ...current, hasMore: false }));
|
||||
}
|
||||
|
||||
return newCache;
|
||||
@@ -56,7 +60,14 @@ export default function JobsTableOrganism() {
|
||||
|
||||
return (
|
||||
<div className="jobs-list-container">
|
||||
<Button onClick={() => refetch()} style={{ marginBottom: ".5rem" }} size="large">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setState({ hasMore: true });
|
||||
refetch();
|
||||
}}
|
||||
style={{ marginBottom: ".5rem" }}
|
||||
size="large"
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
<div className="jobs-list-infinite-container">
|
||||
|
||||
@@ -9,7 +9,7 @@ import JobsSearchFieldsMolecule from "../../molecules/jobs-search-fields/jobs-se
|
||||
|
||||
const limit = 50;
|
||||
export default function JobsTableOrganism() {
|
||||
const [state, setState] = useState({ hasMore: true });
|
||||
const [state, setState] = useState({ hasMore: false });
|
||||
|
||||
const [callSearch, { loading, error, data, fetchMore }] = useLazyQuery(
|
||||
SEARCH_JOBS_PAGINATED,
|
||||
@@ -21,8 +21,13 @@ export default function JobsTableOrganism() {
|
||||
}
|
||||
);
|
||||
|
||||
const handleSearch = (options) => {
|
||||
setState({ hasMore: true });
|
||||
callSearch(options);
|
||||
};
|
||||
|
||||
const handleInfiniteOnLoad = async (page) => {
|
||||
if (fetchMore) {
|
||||
if (fetchMore && data?.search_jobs) {
|
||||
// ipcRenderer.send(ipcTypes.app.toMain.track, {
|
||||
// event: "FETCH_MORE_JOBS",
|
||||
// });
|
||||
@@ -31,14 +36,18 @@ export default function JobsTableOrganism() {
|
||||
offset: limit * page,
|
||||
},
|
||||
updateQuery: (prev, { fetchMoreResult }) => {
|
||||
if (!fetchMoreResult) {
|
||||
const previousJobs = prev?.search_jobs || [];
|
||||
const incomingJobs = fetchMoreResult?.search_jobs || [];
|
||||
|
||||
if (!incomingJobs.length) {
|
||||
console.log("No more results. Fetch More was empty.");
|
||||
setState({ ...state, hasMore: false });
|
||||
setState((current) => ({ ...current, hasMore: false }));
|
||||
return prev;
|
||||
}
|
||||
|
||||
const newCache = Object.assign({}, prev, {
|
||||
search_jobs: [...prev.search_jobs, ...fetchMoreResult.search_jobs],
|
||||
search_jobs: [...previousJobs, ...incomingJobs],
|
||||
search_jobs_aggregate: fetchMoreResult.search_jobs_aggregate || prev?.search_jobs_aggregate
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -46,7 +55,7 @@ export default function JobsTableOrganism() {
|
||||
(data && data.search_jobs_aggregate.aggregate.count)
|
||||
) {
|
||||
console.log("No more results.");
|
||||
setState({ ...state, hasMore: false });
|
||||
setState((current) => ({ ...current, hasMore: false }));
|
||||
}
|
||||
|
||||
return newCache;
|
||||
@@ -65,7 +74,7 @@ export default function JobsTableOrganism() {
|
||||
|
||||
return (
|
||||
<div className="jobs-list-container">
|
||||
<JobsSearchFieldsMolecule callSearchQuery={callSearch} />
|
||||
<JobsSearchFieldsMolecule callSearchQuery={handleSearch} />
|
||||
<div className="jobs-list-infinite-container">
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Form, notification, Skeleton } from "antd";
|
||||
import { Form, Skeleton } from "antd";
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -7,6 +7,7 @@ import { debounce } from "lodash";
|
||||
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../../graphql/bodyshop.queries";
|
||||
import ipcTypes from "../../../ipc.types";
|
||||
import { setBodyshop } from "../../../redux/user/user.actions";
|
||||
import { antdNotification as notification } from "../../../util/antdFeedback";
|
||||
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
|
||||
import ShopSettingsFormMolecule from "../../molecules/shop-settings-form/shop-settings-form.molecule";
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
@@ -17,6 +17,38 @@ import { setSelectedJobId } from "../../../redux/application/application.actions
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
const getAuditDatePresets = () => [
|
||||
{
|
||||
label: "2 Months ago",
|
||||
value: [
|
||||
dayjs().startOf("month").subtract(2, "month"),
|
||||
dayjs().startOf("month").subtract(2, "month").endOf("month")
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Last Month",
|
||||
value: [
|
||||
dayjs().startOf("month").subtract(1, "month"),
|
||||
dayjs().startOf("month").subtract(1, "month").endOf("month")
|
||||
]
|
||||
},
|
||||
{ label: "This Month", value: [dayjs().startOf("month"), dayjs().endOf("month")] },
|
||||
{
|
||||
label: "Last Quarter",
|
||||
value: [
|
||||
dayjs().startOf("quarter").subtract(1, "quarter"),
|
||||
dayjs().startOf("quarter").subtract(1, "day")
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Last 3 Months",
|
||||
value: [
|
||||
dayjs().startOf("month").subtract(3, "month"),
|
||||
dayjs().startOf("month").subtract(1, "month").endOf("month")
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -31,6 +63,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(AuditPage);
|
||||
|
||||
export function AuditPage({ auditError, queryReportingData, bodyshop, reportingError, setSelectedJobId }) {
|
||||
const [form] = Form.useForm();
|
||||
const auditDatePresets = getAuditDatePresets();
|
||||
const [sheets, setSheets] = useState([]);
|
||||
useEffect(() => {
|
||||
ipcRenderer.on(ipcTypes.audit.toRenderer.auditFilePath, async (event, { filePath, sheets }) => {
|
||||
@@ -104,28 +137,7 @@ export function AuditPage({ auditError, queryReportingData, bodyshop, reportingE
|
||||
}
|
||||
]}
|
||||
>
|
||||
<DatePicker.RangePicker
|
||||
format="MM/DD/YYYY"
|
||||
ranges={{
|
||||
"2 Months ago": [
|
||||
dayjs().startOf("month").subtract(2, "month"),
|
||||
dayjs().startOf("month").subtract(2, "month").endOf("month")
|
||||
],
|
||||
"Last Month": [
|
||||
dayjs().startOf("month").subtract(1, "month"),
|
||||
dayjs().startOf("month").subtract(1, "month").endOf("month")
|
||||
],
|
||||
"This Month": [dayjs().startOf("month"), dayjs().endOf("month")],
|
||||
"Last Quarter": [
|
||||
dayjs().startOf("quarter").subtract(1, "quarter"),
|
||||
dayjs().startOf("quarter").subtract(1, "day")
|
||||
],
|
||||
"Last 3 Months": [
|
||||
dayjs().startOf("month").subtract(3, "month"),
|
||||
dayjs().startOf("month").subtract(1, "month").endOf("month")
|
||||
]
|
||||
}}
|
||||
/>
|
||||
<DatePicker.RangePicker format="MM/DD/YYYY" presets={auditDatePresets} />
|
||||
</Form.Item>
|
||||
|
||||
<Space align="middle" wrap>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Alert, Layout, notification } from "antd";
|
||||
import { Alert, Layout } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Routes } from "react-router-dom";
|
||||
|
||||
@@ -26,6 +26,21 @@ class ErrorBoundary extends React.Component {
|
||||
|
||||
render() {
|
||||
if (this.state.hasErrored === true) {
|
||||
const errorItems = [
|
||||
{
|
||||
key: "error-details",
|
||||
label: "Error Details",
|
||||
children: (
|
||||
<>
|
||||
<div>
|
||||
<strong>{this.state.error.message}</strong>
|
||||
</div>
|
||||
<div>{this.state.error.stack}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Result
|
||||
@@ -47,14 +62,7 @@ class ErrorBoundary extends React.Component {
|
||||
/>
|
||||
<Row>
|
||||
<Col offset={6} span={12}>
|
||||
<Collapse bordered={false}>
|
||||
<Collapse.Panel header="Error Details">
|
||||
<div>
|
||||
<strong>{this.state.error.message}</strong>
|
||||
</div>
|
||||
<div>{this.state.error.stack}</div>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
<Collapse bordered={false} items={errorItems} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
@@ -104,7 +104,33 @@ if (import.meta.env.DEV) {
|
||||
|
||||
middlewares.push(sentryLink.concat(retryLink.concat(errorLink.concat(authLink.concat(httpLink)))));
|
||||
|
||||
const cache = new InMemoryCache({});
|
||||
const mergeByOffset = (existing = [], incoming = [], { args }) => {
|
||||
const merged = existing ? existing.slice(0) : [];
|
||||
const offset = args?.offset || 0;
|
||||
|
||||
incoming.forEach((item, index) => {
|
||||
merged[offset + index] = item;
|
||||
});
|
||||
|
||||
return merged;
|
||||
};
|
||||
|
||||
const cache = new InMemoryCache({
|
||||
typePolicies: {
|
||||
Query: {
|
||||
fields: {
|
||||
jobs: {
|
||||
keyArgs: ["order"],
|
||||
merge: mergeByOffset
|
||||
},
|
||||
search_jobs: {
|
||||
keyArgs: ["args", "where"],
|
||||
merge: mergeByOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default new ApolloClient({
|
||||
link: ApolloLink.from(middlewares),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { message } from "antd";
|
||||
import { antdMessage as message } from "../util/antdFeedback";
|
||||
import gql from "graphql-tag";
|
||||
import _ from "lodash";
|
||||
import client from "../graphql/GraphQLClient";
|
||||
@@ -8500,4 +8500,4 @@ export const SgiGroupsV12026April = [
|
||||
"ageDesc": "Over 7 years",
|
||||
"name": "SGIV1"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { notification } from "antd";
|
||||
import { antdNotification as notification } from "../util/antdFeedback";
|
||||
import ipcTypes from "../ipc.types";
|
||||
import {
|
||||
setReleaseNotes,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createStore, applyMiddleware, compose } from "redux";
|
||||
import { legacy_createStore as createStore, applyMiddleware, compose } from "redux";
|
||||
import { persistStore } from "redux-persist";
|
||||
import { createLogger } from "redux-logger";
|
||||
import createSagaMiddleware from "redux-saga";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { message } from "antd";
|
||||
import { antdMessage as message } from "../../util/antdFeedback";
|
||||
import dayjs from "../../util/day.js";
|
||||
//import LogRocket from "logrocket";
|
||||
import * as Sentry from "@sentry/electron/renderer";
|
||||
|
||||
Reference in New Issue
Block a user