Merged in master (pull request #9)
Added scatter plot on reporting RPS-52
This commit is contained in:
@@ -10,7 +10,7 @@ Nucleus.init("5f91b569b95bac34eefdb63a", {
|
||||
});
|
||||
|
||||
Nucleus.setProps({
|
||||
version: app.getVersion().toString(),
|
||||
version: app.getVersion(),
|
||||
});
|
||||
|
||||
Nucleus.onError = (type, err) => {
|
||||
@@ -24,8 +24,7 @@ ipcMain.on(ipcTypes.app.toMain.setUserName, (event, userName) => {
|
||||
});
|
||||
|
||||
ipcMain.on(ipcTypes.app.toMain.track, (e, args) => {
|
||||
console.log("args", args);
|
||||
log.log("Received Tracking Request", args);
|
||||
log.log("NUCLEUS Event", args);
|
||||
const { event, ...eventDetails } = args;
|
||||
try {
|
||||
Nucleus.track(event, eventDetails);
|
||||
|
||||
@@ -301,7 +301,7 @@ autoUpdater.on("download-progress", (ev) => {
|
||||
});
|
||||
|
||||
autoUpdater.on("update-downloaded", (ev, info) => {
|
||||
Nucleus.track("UPDATE_DOWNLOADED", info);
|
||||
Nucleus.track("UPDATE_DOWNLOADED", ev);
|
||||
// if (process.env.NODE_ENV === "production") {
|
||||
|
||||
dialog.showMessageBox(
|
||||
|
||||
@@ -133,7 +133,6 @@ export default function JobLinesTableMolecule({ loading, job }) {
|
||||
: joblines;
|
||||
|
||||
const handleChange = (pagination, filters, sorter) => {
|
||||
console.log("Various parameters", pagination, filters, sorter);
|
||||
setFilters(filters);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { queryReportingData } from "../../../redux/reporting/reporting.actions";
|
||||
import moment from "moment";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
});
|
||||
@@ -32,7 +33,24 @@ export function ReportingDatesMolecule({ queryReportingData }) {
|
||||
name="dateRange"
|
||||
rules={[{ type: "array", required: true }]}
|
||||
>
|
||||
<DatePicker.RangePicker />
|
||||
<DatePicker.RangePicker
|
||||
ranges={{
|
||||
Today: [moment(), moment()],
|
||||
"Last Month": [
|
||||
moment().startOf("month").subtract(1, "month"),
|
||||
moment().startOf("month").subtract(1, "month").endOf("month"),
|
||||
],
|
||||
"This Month": [
|
||||
moment().startOf("month"),
|
||||
moment().endOf("month"),
|
||||
],
|
||||
"Next Month": [
|
||||
moment().startOf("month").add(1, "month"),
|
||||
moment().startOf("month").add(1, "month").endOf("month"),
|
||||
],
|
||||
"Last 7 days": [moment().subtract(7, "days"), moment()],
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Run Search
|
||||
|
||||
@@ -81,7 +81,7 @@ export function ReportingJobsListMolecule({
|
||||
render: (text, record) => record.actPriceSum.toFormat(),
|
||||
},
|
||||
{
|
||||
title: "Price Diff.",
|
||||
title: "$ (Act./Target)",
|
||||
dataIndex: "jobRpsDollars",
|
||||
key: "jobRpsDollars",
|
||||
render: (text, record) => (
|
||||
@@ -95,7 +95,7 @@ export function ReportingJobsListMolecule({
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Price Diff. %",
|
||||
title: "% (Act./Target)",
|
||||
dataIndex: "price_diff_pc",
|
||||
key: "price_diff_pc",
|
||||
render: (text, record) => (
|
||||
@@ -152,7 +152,9 @@ export function ReportingJobsListMolecule({
|
||||
}}
|
||||
summary={() => (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell index={0}></Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={0}>
|
||||
<strong>Totals</strong>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={1}></Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={2}></Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={3}></Table.Summary.Cell>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Card, Skeleton, Typography, Tooltip as AntdToolTip } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
Scatter,
|
||||
ScatterChart,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
ZAxis,
|
||||
} from "recharts";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectReportLoading,
|
||||
selectScorecard,
|
||||
} from "../../../redux/reporting/reporting.selectors";
|
||||
import DataLabelAtom from "../../atoms/data-label/data-label.atom";
|
||||
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
|
||||
)(ReportingScatterChartMolecule);
|
||||
|
||||
export function ReportingScatterChartMolecule({ reportingLoading, scoreCard }) {
|
||||
if (reportingLoading) return <Skeleton active />;
|
||||
if (!scoreCard)
|
||||
return <ErrorResultAtom title="Error displaying score card data." />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
marginTop: "1rem",
|
||||
marginBottom: "1rem",
|
||||
width: "100%",
|
||||
height: "30rem",
|
||||
}}
|
||||
>
|
||||
<AntdToolTip
|
||||
placement="bottomLeft"
|
||||
title="Calculated as Actual Job RPS % less Target Job RPS %. E.g. 10% - 8% = 2%"
|
||||
>
|
||||
<Typography.Title level={4}>% Variance from Target</Typography.Title>
|
||||
</AntdToolTip>
|
||||
|
||||
<ResponsiveContainer>
|
||||
<ScatterChart margin={{ top: 20, right: 20, bottom: 10, left: 10 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="deviation"
|
||||
type="number"
|
||||
ticks={[-100, -75, -50, -25, 0, 25, 50, 75, 100]}
|
||||
domain={[-100, 100]}
|
||||
name="Deviation"
|
||||
unit="%"
|
||||
/>
|
||||
<YAxis type="number" dataKey="age" name="Age" unit="Years" />
|
||||
<ZAxis dataKey="dbPriceSumAmt" name="DB Price Total" />
|
||||
<Tooltip
|
||||
content={({ payload }) => {
|
||||
const item = payload && payload[0] && payload[0].payload;
|
||||
if (!item) return null;
|
||||
return (
|
||||
<Card title={item.clm_no}>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<DataLabelAtom label="Owner">{item.owner}</DataLabelAtom>
|
||||
<DataLabelAtom label="Vehicle">
|
||||
{item.vehicle}
|
||||
</DataLabelAtom>
|
||||
<DataLabelAtom label="Job RPS $">
|
||||
{item.jobRpsDollars.toFormat()}
|
||||
</DataLabelAtom>
|
||||
<DataLabelAtom label="Job RPS %">
|
||||
{(item.jobRpsPc * 100).toFixed(1)}%
|
||||
</DataLabelAtom>
|
||||
<DataLabelAtom label="DB Price">
|
||||
{item.dbPriceSum.toFormat()}
|
||||
</DataLabelAtom>
|
||||
</Card>
|
||||
);
|
||||
}}
|
||||
cursor={{ strokeDasharray: "2 2", stroke: "tomato" }}
|
||||
/>
|
||||
<Legend />
|
||||
{Object.keys(scoreCard.scatterChart).map((key, idx) => (
|
||||
<Scatter
|
||||
name={key}
|
||||
key={idx}
|
||||
data={scoreCard.scatterChart[key]}
|
||||
fill={colors[idx]}
|
||||
></Scatter>
|
||||
))}
|
||||
</ScatterChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const colors = [
|
||||
"#0c344d",
|
||||
"#6cc314",
|
||||
"#f5782a",
|
||||
"#f5be2a",
|
||||
"#fbff90",
|
||||
"dodgerblue",
|
||||
];
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Card } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -5,7 +6,9 @@ import { selectDates } from "../../../redux/reporting/reporting.selectors";
|
||||
import ReportingTitleAtom from "../../atoms/reporting-title/reporting-title.atom";
|
||||
import ReportingDatesMolecule from "../../molecules/reporting-dates/reporting-dates.molecule";
|
||||
import ReportingJobsListMolecule from "../../molecules/reporting-jobs-list/reporting-jobs-list.molecule";
|
||||
import ReportingScatterChartMolecule from "../../molecules/reporting-scatterchart/reporting-scatterchart.molecule";
|
||||
import ReportingTotalsStatsMolecule from "../../molecules/reporting-totals-stats/reporting-totals-stats.molecule";
|
||||
import "./reporting.page.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
dates: selectDates,
|
||||
@@ -17,13 +20,22 @@ export default connect(mapStateToProps, mapDispatchToProps)(ReportingPage);
|
||||
|
||||
export function ReportingPage({ dates }) {
|
||||
return (
|
||||
<div>
|
||||
<ReportingDatesMolecule />
|
||||
<div className="reporting-container">
|
||||
<Card>
|
||||
<ReportingDatesMolecule />
|
||||
</Card>
|
||||
{dates && dates.startDate && dates.endDate && (
|
||||
<div>
|
||||
<ReportingTitleAtom />
|
||||
<ReportingTotalsStatsMolecule />
|
||||
<ReportingJobsListMolecule />
|
||||
<div className="reporting-cards">
|
||||
<Card>
|
||||
<ReportingTitleAtom />
|
||||
<ReportingTotalsStatsMolecule />
|
||||
</Card>
|
||||
<Card>
|
||||
<ReportingJobsListMolecule />
|
||||
</Card>
|
||||
<Card>
|
||||
<ReportingScatterChartMolecule />
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.reporting-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background-color: rgb(244, 244, 244);
|
||||
& > .reporting-cards > * {
|
||||
margin: 0.7rem;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
import Dinero from "dinero.js";
|
||||
import _ from "lodash";
|
||||
import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
||||
import client from "../../graphql/GraphQLClient";
|
||||
import { REPORTING_GET_JOBS } from "../../graphql/reporting.queries";
|
||||
import ipcTypes from "../../ipc.types";
|
||||
import {
|
||||
CalculateJobRpsDollars,
|
||||
CalculateJobRpsPc
|
||||
CalculateJobRpsPc,
|
||||
} from "../../util/CalculateJobRps";
|
||||
import GetJobTarget from "../../util/GetJobTarget";
|
||||
import {
|
||||
calculateScorecard,
|
||||
setReportingData,
|
||||
setScoreCard
|
||||
setScoreCard,
|
||||
} from "./reporting.actions";
|
||||
import ReportingApplicationTypes from "./reporting.types";
|
||||
|
||||
@@ -59,6 +60,7 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
});
|
||||
|
||||
const targets = yield select((state) => state.user.bodyshop.targets);
|
||||
const groups = yield select((state) => state.user.bodyshop.groups);
|
||||
|
||||
const scoreCard = {
|
||||
shopRpsTotalDollars: Dinero(),
|
||||
@@ -69,6 +71,13 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
allJobsSumActPrice: Dinero(),
|
||||
currentRpsPc: 0,
|
||||
targetRpsPc: 0,
|
||||
scatterChart: _.sortBy(
|
||||
groups,
|
||||
[(group) => group.toLowerCase()],
|
||||
["desc"]
|
||||
).reduce((acc, val) => {
|
||||
return { ...acc, [val]: [] };
|
||||
}, {}),
|
||||
};
|
||||
|
||||
//Get the RPS on a per job basis.
|
||||
@@ -93,6 +102,19 @@ export function* handleCalculateScoreCard({ payload: jobs }) {
|
||||
actPriceSum
|
||||
);
|
||||
|
||||
scoreCard.scatterChart[job.group].push({
|
||||
deviation: Math.round((jobRpsPc - jobTarget) * 1000) / 10,
|
||||
age: job.v_age,
|
||||
dbPriceSum,
|
||||
dbPriceSumAmt: dbPriceSum.getAmount() / 100,
|
||||
id: job.id,
|
||||
owner: `${job.ownr_fn} ${job.ownr_ln}`,
|
||||
vehicle: `${job.v_model_yr} ${job.v_makedesc} ${job.v_model} (${job.v_type}) - ${job.group}`,
|
||||
clm_no: job.clm_no,
|
||||
jobRpsDollars,
|
||||
jobRpsPc,
|
||||
});
|
||||
|
||||
//sum db price * percentage expected.
|
||||
return {
|
||||
...job,
|
||||
|
||||
Reference in New Issue
Block a user