diff --git a/src/components/molecules/close-date-display/close-date-display.molecule.jsx b/src/components/molecules/close-date-display/close-date-display.molecule.jsx
index d9a29ed..1f54701 100644
--- a/src/components/molecules/close-date-display/close-date-display.molecule.jsx
+++ b/src/components/molecules/close-date-display/close-date-display.molecule.jsx
@@ -29,7 +29,7 @@ export default function CloseDateDisplayMolecule({ jobId, close_date }) {
return (
setEditMode(false)}>
{loading &&
}
@@ -38,7 +38,7 @@ export default function CloseDateDisplayMolecule({ jobId, close_date }) {
return (
setEditMode(true)}>
- {value.isValid() ? value.format("MM/DD/yyyy") : "No date set"}
+ {value && value.isValid() ? value.format("MM/DD/yyyy") : "No date set"}
);
}
diff --git a/src/components/molecules/jobs-search-fields/jobs-search-fields.molecule.jsx b/src/components/molecules/jobs-search-fields/jobs-search-fields.molecule.jsx
index 45765d0..bd03303 100644
--- a/src/components/molecules/jobs-search-fields/jobs-search-fields.molecule.jsx
+++ b/src/components/molecules/jobs-search-fields/jobs-search-fields.molecule.jsx
@@ -7,7 +7,7 @@ export default function JobsSearchFieldsMolecule({ callSearchQuery }) {
const handleFinish = (values) => {
callSearchQuery({
variables: {
- search: values.search,
+ search: values.search || "",
startDate: (values.dateRange && values.dateRange[0]) || null,
endDate: (values.dateRange && values.dateRange[1]) || null,
},
diff --git a/src/components/molecules/jobs-targets-stats/jobs-targets-stats.molecule.jsx b/src/components/molecules/jobs-targets-stats/jobs-targets-stats.molecule.jsx
index 2c7008e..f7a7ccc 100644
--- a/src/components/molecules/jobs-targets-stats/jobs-targets-stats.molecule.jsx
+++ b/src/components/molecules/jobs-targets-stats/jobs-targets-stats.molecule.jsx
@@ -53,10 +53,9 @@ export function JobsTargetsStatsMolecule({
currentRpsPc ? "tomato" : "seagreen",
+ color: selectedJobTargetPc > currentRpsPc ? "tomato" : "seagreen",
}}
- value={currentRpsPc}
+ value={(currentRpsPc * 100).toFixed(1)}
suffix="%"
/>
diff --git a/src/components/molecules/reporting-jobs-list/reporting-jobs-list.molecule.jsx b/src/components/molecules/reporting-jobs-list/reporting-jobs-list.molecule.jsx
new file mode 100644
index 0000000..829ed73
--- /dev/null
+++ b/src/components/molecules/reporting-jobs-list/reporting-jobs-list.molecule.jsx
@@ -0,0 +1,140 @@
+import { Input, Table } from "antd";
+import React, { useState } from "react";
+import CurrencyFormatterAtom from "../../atoms/currency-formatter/currency-formatter.atom";
+import IgnoreJobLine from "../../atoms/ignore-job-line/ignore-job-line.atom";
+import partTypeConverterAtom from "../../atoms/part-type-converter/part-type-converter.atom";
+import PriceDiffPcFormatterAtom from "../../atoms/price-diff-pc-formatter/price-diff-pc-formatter.atom";
+
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import {
+ selectReportData,
+ selectReportLoading,
+} from "../../../redux/reporting/reporting.selectors";
+const mapStateToProps = createStructuredSelector({
+ reportingLoading: selectReportLoading,
+ reportData: selectReportData,
+});
+const mapDispatchToProps = (dispatch) => ({
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ReportingJobsListMolecule);
+
+export function ReportingJobsListMolecule({ reportingLoading, reportData }) {
+ const [searchText, setSearchText] = useState("");
+
+ const columns = [
+ {
+ title: "Claim No.",
+ dataIndex: "clm_no",
+ key: "clm_no",
+ },
+ {
+ title: "Ins Co.",
+ dataIndex: "ins_co_nm",
+ key: "ins_co_nm",
+ },
+ {
+ title: "First Name",
+ dataIndex: "ownr_fn",
+ key: "ownr_fn",
+ },
+ {
+ title: "Last Name",
+ dataIndex: "ownr_ln",
+ key: "ownr_ln",
+ },
+ {
+ title: "Vehicle",
+ dataIndex: "vehicle",
+ key: "vehicle",
+ render: (text, record) =>
+ `${record.v_model_yr} ${record.v_makedesc} ${record.v_model} (${
+ record.v_type
+ }) - ${record.group} @ ${
+ record.v_age === 1 ? `${record.v_age} year` : `${record.v_age} years`
+ }`,
+ },
+ {
+ title: "Database Price Sum",
+ dataIndex: "dbPriceSum",
+ key: "dbPriceSum",
+ render: (text, record) => record.dbPriceSum.toFormat(),
+ },
+ {
+ title: "Actual Price Sum ",
+ dataIndex: "actPriceSum",
+ key: "actPriceSum",
+ render: (text, record) => record.actPriceSum.toFormat(),
+ },
+ {
+ title: "Price Diff.",
+ dataIndex: "jobRpsDollars",
+ key: "jobRpsDollars",
+ render: (text, record) => (
+ record.jobTarget ? "seagreen" : "tomato",
+ }}
+ >
+ {`${record.jobRpsDollars.toFormat()} / ${record.expectedRpsDollars.toFormat()}`}
+
+ ),
+ },
+ {
+ title: "Price Diff. %",
+ dataIndex: "price_diff_pc",
+ key: "price_diff_pc",
+ render: (text, record) => (
+ record.jobTarget ? "seagreen" : "tomato",
+ }}
+ >
+ {`${(record.jobRpsPc * 100).toFixed(1)}% / ${(
+ record.jobTarget * 100
+ ).toFixed(1)}%`}
+
+ ),
+ },
+ ];
+
+ const data =
+ searchText !== ""
+ ? reportData.filter(
+ (j) =>
+ j.ownr_fn.toLowerCase().includes(searchText.toLowerCase()) ||
+ j.ownr_ln.toLowerCase().includes(searchText.toLowerCase()) ||
+ j.ownr_clm_no.toLowerCase().includes(searchText.toLowerCase())
+ )
+ : reportData;
+
+ return (
+
+
(
+ {
+ setSearchText(val);
+ }}
+ enterButton
+ allowClear
+ />
+ )}
+ columns={columns}
+ rowKey="id"
+ loading={reportingLoading}
+ size="small"
+ pagination={false}
+ dataSource={data}
+ scroll={{
+ x: true,
+ }}
+ />
+
+ );
+}
diff --git a/src/components/molecules/reporting-totals-stats/reporting-totals-stats.molecule.jsx b/src/components/molecules/reporting-totals-stats/reporting-totals-stats.molecule.jsx
new file mode 100644
index 0000000..63adb2c
--- /dev/null
+++ b/src/components/molecules/reporting-totals-stats/reporting-totals-stats.molecule.jsx
@@ -0,0 +1,73 @@
+import { Skeleton, Statistic } from "antd";
+import React, { useCallback } from "react";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { selectSelectedJobTargetPc } from "../../../redux/application/application.selectors";
+import {
+ selectReportLoading,
+ selectScorecard,
+} from "../../../redux/reporting/reporting.selectors";
+import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
+
+const mapStateToProps = createStructuredSelector({
+ reportingLoading: selectReportLoading,
+ scoreCard: selectScorecard,
+});
+const mapDispatchToProps = (dispatch) => ({
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
+});
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ReportingTotalsStatsMolecule);
+
+export function ReportingTotalsStatsMolecule({ reportingLoading, scoreCard }) {
+ if (reportingLoading) return ;
+ if (!scoreCard)
+ return ;
+
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/pages/reporting/reporting.page.jsx b/src/components/pages/reporting/reporting.page.jsx
index 73a57d9..f6e6949 100644
--- a/src/components/pages/reporting/reporting.page.jsx
+++ b/src/components/pages/reporting/reporting.page.jsx
@@ -1,10 +1,29 @@
import React from "react";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { selectDates } from "../../../redux/reporting/reporting.selectors";
import ReportingDatesMolecule from "../../molecules/reporting-dates/reporting-dates.molecule";
+import ReportingJobsListMolecule from "../../molecules/reporting-jobs-list/reporting-jobs-list.molecule";
+import ReportingTotalsStatsMolecule from "../../molecules/reporting-totals-stats/reporting-totals-stats.molecule";
-export default function ReportingPage() {
+const mapStateToProps = createStructuredSelector({
+ dates: selectDates,
+});
+const mapDispatchToProps = (dispatch) => ({
+ //setUserLanguage: language => dispatch(setUserLanguage(language))
+});
+export default connect(mapStateToProps, mapDispatchToProps)(ReportingPage);
+
+export function ReportingPage({ dates }) {
return (
+ {dates && dates.startDate && dates.endDate && (
+
+
+
+
+ )}
);
}
diff --git a/src/graphql/reporting.queries.js b/src/graphql/reporting.queries.js
index a7bbfb5..9625ca4 100644
--- a/src/graphql/reporting.queries.js
+++ b/src/graphql/reporting.queries.js
@@ -25,6 +25,8 @@ export const REPORTING_GET_JOBS = gql`
v_makedesc
v_model
v_model_yr
+ v_vin
+ v_type
joblines {
act_price
db_price
diff --git a/src/redux/reporting/reporting.sagas.js b/src/redux/reporting/reporting.sagas.js
index 5d02944..5624d96 100644
--- a/src/redux/reporting/reporting.sagas.js
+++ b/src/redux/reporting/reporting.sagas.js
@@ -1,8 +1,19 @@
import { all, call, takeLatest, select, put } from "redux-saga/effects";
-import { calculateScorecard, setReportingData } from "./reporting.actions";
+import {
+ calculateScorecard,
+ setReportingData,
+ setScoreCard,
+} from "./reporting.actions";
import ReportingApplicationTypes from "./reporting.types";
import client from "../../graphql/GraphQLClient";
import { REPORTING_GET_JOBS } from "../../graphql/reporting.queries";
+import Dinero from "dinero.js";
+import {
+ CalculateJobRpsDollars,
+ CalculateJobRpsPc,
+} from "../../util/CalculateJobRps";
+import GetJobTarget from "../../util/GetJobTarget";
+
const { log } = window;
export function* onQueryReportData() {
@@ -20,7 +31,7 @@ export function* queryReportingData({ payload: { startDate, endDate } }) {
log.error("Error fetching report data.", result.errors);
yield put(setReportingData(null));
} else {
- yield put(setReportingData(result.data.jobs));
+ yield put(calculateScorecard(result.data.jobs));
}
}
@@ -31,7 +42,7 @@ export function* onSetReportData() {
);
}
export function* handleSetReportData({ payload: jobs }) {
- yield put(calculateScorecard(jobs));
+ // yield put(calculateScorecard(jobs));
}
export function* onCalculateScoreCard() {
@@ -42,10 +53,70 @@ export function* onCalculateScoreCard() {
}
export function* handleCalculateScoreCard({ payload: jobs }) {
console.log("jobs", jobs);
- // yield put(calculateScorecard(jobs));
+ const targets = yield select((state) => state.user.bodyshop.targets);
- //Get the RPS on a per job basis.
+ const scoreCard = {
+ shopRpsTotalDollars: Dinero(),
+ shopRpsExpectedDollars: Dinero(),
+ varianceDollars: null,
+ variancePc: 0,
+ allJobsSumDbPrice: Dinero(),
+ allJobsSumActPrice: Dinero(),
+ currentRpsPc: 0,
+ targetRpsPc: 0,
+ };
+ //Get the RPS on a per job basis.
+ jobs = jobs.map((job) => {
+ const { actPriceSum, jobRpsDollars } = CalculateJobRpsDollars(job, true);
+ const { dbPriceSum, jobRpsPc } = CalculateJobRpsPc(
+ job,
+ jobRpsDollars,
+ true
+ );
+ const jobTarget = GetJobTarget(job.group, job.v_age, targets);
+ scoreCard.shopRpsTotalDollars = scoreCard.shopRpsTotalDollars.add(
+ jobRpsDollars
+ );
+ const expectedRpsDollars = dbPriceSum.percentage(jobTarget * 100);
+ scoreCard.shopRpsExpectedDollars = scoreCard.shopRpsExpectedDollars.add(
+ expectedRpsDollars
+ );
+
+ scoreCard.allJobsSumDbPrice = scoreCard.allJobsSumDbPrice.add(dbPriceSum);
+ scoreCard.allJobsSumActPrice = scoreCard.allJobsSumActPrice.add(
+ actPriceSum
+ );
+
+ //sum db price * percentage expected.
+ return {
+ ...job,
+ actPriceSum,
+ jobRpsDollars,
+ dbPriceSum,
+ jobRpsPc,
+ jobTarget,
+ expectedRpsDollars,
+ };
+ });
+
+ scoreCard.varianceDollars = scoreCard.shopRpsTotalDollars.subtract(
+ scoreCard.shopRpsExpectedDollars
+ );
+
+ scoreCard.variancePc =
+ scoreCard.varianceDollars.getAmount() /
+ scoreCard.shopRpsExpectedDollars.getAmount();
+
+ scoreCard.currentRpsPc =
+ scoreCard.shopRpsTotalDollars.getAmount() /
+ scoreCard.allJobsSumDbPrice.getAmount();
+ scoreCard.targetRpsPc =
+ scoreCard.shopRpsExpectedDollars.getAmount() /
+ scoreCard.allJobsSumDbPrice.getAmount();
+ //Set the data.
+ yield put(setScoreCard(scoreCard));
+ yield put(setReportingData(jobs));
}
export function* reportingSagas() {
diff --git a/src/util/CalculateJobRps.js b/src/util/CalculateJobRps.js
index f31aa09..c97aeda 100644
--- a/src/util/CalculateJobRps.js
+++ b/src/util/CalculateJobRps.js
@@ -1,12 +1,16 @@
import Dinero from "dinero.js";
-export function CalculateJobRpsDollars(job) {
+export function CalculateJobRpsDollars(job, returnSumActPrice) {
if (!job) {
return 0;
}
- return job.joblines
+ let actPriceSum = Dinero();
+ const jobRpsDollars = job.joblines
.filter((j) => !j.ignore)
.reduce((acc, val) => {
+ actPriceSum = actPriceSum.add(
+ Dinero({ amount: Math.round((val.act_price || 0) * 100) })
+ );
if (val.price_diff > 0) {
return acc.add(
Dinero({ amount: Math.round((val.price_diff || 0) * 100) })
@@ -15,9 +19,14 @@ export function CalculateJobRpsDollars(job) {
return acc;
}
}, Dinero());
+ return returnSumActPrice ? { actPriceSum, jobRpsDollars } : jobRpsDollars;
}
-export function CalculateJobRpsPc(job, currentRpsDollars) {
+export function CalculateJobRpsPc(
+ job,
+ currentRpsDollars,
+ returnSumDbPrice = false
+) {
//TODO Redo this to do total of db price - act price / db price
if (!job) {
return 0;
@@ -25,7 +34,9 @@ export function CalculateJobRpsPc(job, currentRpsDollars) {
const dbPriceSum = job.joblines
.filter((j) => !j.ignore)
.reduce((acc, val) => {
- return acc + val.db_price;
- }, 0);
- return (currentRpsDollars.getAmount() / dbPriceSum).toFixed(1);
+ return acc.add(Dinero({ amount: Math.round((val.db_price || 0) * 100) }));
+ }, Dinero());
+
+ const jobRpsPc = currentRpsDollars.getAmount() / dbPriceSum.getAmount();
+ return returnSumDbPrice ? { dbPriceSum, jobRpsPc } : jobRpsPc;
}
diff --git a/src/util/GetJobTarget.js b/src/util/GetJobTarget.js
index 4919eaa..df93f1a 100644
--- a/src/util/GetJobTarget.js
+++ b/src/util/GetJobTarget.js
@@ -4,9 +4,9 @@ export default function GetJobTarget(group, v_age, targets) {
const targetPc = targetsForGroup.filter(
(t) => t.ageGte <= v_age && (t.ageLt ? t.ageLt > v_age : true)
);
- if (targetPc.length === 0) return 100;
+ if (targetPc.length === 0) return 1;
else if (targetPc.length === 1) return targetPc[0].target;
else {
- return 100;
+ return 1;
}
}