Additional Changes for calculations of RPS %.

This commit is contained in:
Patrick Fic
2020-10-16 15:14:25 -07:00
parent c94f525a3e
commit 584f43bc4e
27 changed files with 504 additions and 115 deletions

View File

@@ -0,0 +1,52 @@
import React, { useMemo } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../../redux/user/user.selectors";
import "./price-diff-pc-formatter.styles.scss";
import { AlertFilled } from "@ant-design/icons";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function PriceDiffPcFormatterAtom({
bodyshop,
price_diff_pc,
group,
v_age,
}) {
const metTarget = useMemo(() => {
const targetsForGroup = bodyshop.targets[group];
if (!targetsForGroup) return 0;
const targetPc = targetsForGroup.filter(
(t) => t.ageGte <= v_age && (t.ageLt ? t.ageLt > v_age : true)
);
if (targetPc.length === 0) return false;
else if (targetPc.length === 1) return price_diff_pc >= targetPc[0].target;
else {
alert("Multiple targets match.");
return false;
}
}, [bodyshop, group, price_diff_pc, v_age]);
return (
<div
style={{
color: metTarget ? "green" : "red",
display: "flex",
alignItems: "center",
}}
>
{(price_diff_pc * 100).toFixed(1)}%
{price_diff_pc === 1 ? (
<AlertFilled style={{ color: "tomato" }} className="blink_me" />
) : null}
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(PriceDiffPcFormatterAtom);

View File

@@ -0,0 +1,9 @@
.blink_me {
animation: blinker 1s linear infinite;
}
@keyframes blinker {
50% {
opacity: 0;
}
}

View File

@@ -0,0 +1,39 @@
import { Statistic } from "antd";
import React, { useMemo } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function PriceDiffPcFormatterAtom({ bodyshop, group, v_age }) {
const metTarget = useMemo(() => {
const targetsForGroup = bodyshop.targets[group];
if (!targetsForGroup) return 0;
const targetPc = targetsForGroup.filter(
(t) => t.ageGte <= v_age && (t.ageLt ? t.ageLt > v_age : true)
);
if (targetPc.length === 0) return 0;
else if (targetPc.length === 1) return targetPc[0].target;
else {
return 0;
}
}, [bodyshop, group, v_age]);
return (
<Statistic
title="Target RPS %"
value={(metTarget * 100).toFixed(1)}
suffix="%"
/>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(PriceDiffPcFormatterAtom);

View File

@@ -1,4 +1,4 @@
import { Descriptions, Skeleton } from "antd";
import { Descriptions, PageHeader, Skeleton } from "antd";
import React from "react";
import CurrencyFormatterAtom from "../../atoms/currency-formatter/currency-formatter.atom";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
@@ -10,24 +10,17 @@ export default function JobsDetailDescriptionMolecule({ loading, job }) {
return (
<div>
<Descriptions
title={`${job.clm_no}${job.ins_co_nm ? ` | ${job.ins_co_nm}` : ""}`}
bordered
layout="vertical"
column={{ xxl: 5, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}
>
<Descriptions.Item label="Claim No.">{job.clm_no}</Descriptions.Item>
<Descriptions.Item label="Ins Co. Nm.">
{job.ins_co_nm}
</Descriptions.Item>
<Descriptions.Item label="Owner">{`${job.ownr_fn} ${job.ownr_ln}`}</Descriptions.Item>
<Descriptions.Item label="Vehicle">{`${job.v_model_yr} ${job.v_makedesc} ${job.v_model}`}</Descriptions.Item>
<Descriptions.Item label="Claim Total">
<CurrencyFormatterAtom>{job.clm_total}</CurrencyFormatterAtom>
</Descriptions.Item>
<Descriptions.Item label="Group">{job.group}</Descriptions.Item>
<Descriptions.Item label="Age">{job.v_age}</Descriptions.Item>
</Descriptions>
<PageHeader ghost={false} title={job.clm_no} subTitle={job.ins_co_nm}>
<Descriptions column={{ xxl: 5, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}>
<Descriptions.Item label="Owner">{`${job.ownr_fn} ${job.ownr_ln}`}</Descriptions.Item>
<Descriptions.Item label="Vehicle">{`${job.v_model_yr} ${job.v_makedesc} ${job.v_model}`}</Descriptions.Item>
<Descriptions.Item label="Claim Total">
<CurrencyFormatterAtom>{job.clm_total}</CurrencyFormatterAtom>
</Descriptions.Item>
<Descriptions.Item label="Group">{job.group}</Descriptions.Item>
<Descriptions.Item label="Age">{job.v_age}</Descriptions.Item>
</Descriptions>
</PageHeader>
</div>
);
}

View File

@@ -1,8 +1,10 @@
import { Table } from "antd";
import React from "react";
import CurrencyFormatterAtom from "../../atoms/currency-formatter/currency-formatter.atom";
import PriceDiffPcFormatterAtom from "../../atoms/price-diff-pc-formatter/price-diff-pc-formatter.atom";
export default function JobLinesTableMolecule({ loading, jobLines }) {
export default function JobLinesTableMolecule({ loading, job }) {
const { joblines } = job;
const columns = [
{
title: "#",
@@ -48,9 +50,26 @@ export default function JobLinesTableMolecule({ loading, jobLines }) {
),
},
{
title: "Qty.",
dataIndex: "part_qty",
key: "part_qty",
title: "Price Diff.",
dataIndex: "price_diff",
key: "price_diff",
render: (text, record) => (
<CurrencyFormatterAtom>{record.price_diff}</CurrencyFormatterAtom>
),
},
{
title: "Price Diff. %",
dataIndex: "price_diff_pc",
key: "price_diff_pc",
render: (text, record) => (
<PriceDiffPcFormatterAtom
price_diff_pc={record.price_diff_pc}
v_age={job.v_age}
group={job.group}
/>
),
},
];
@@ -62,7 +81,7 @@ export default function JobLinesTableMolecule({ loading, jobLines }) {
loading={loading}
size="small"
pagination={false}
dataSource={jobLines}
dataSource={joblines}
scroll={{
x: true,
//y: "40rem"

View File

@@ -0,0 +1,52 @@
import { Skeleton, Space, Statistic } from "antd";
import React, { useMemo } from "react";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import TargetPriceDiffPcAtom from "../../atoms/target-price-diff/target-price-diff-pc.atom";
import _ from "lodash";
import Dinero from "dinero.js";
export default function JobsTargetsStatsMolecule({ loading, job }) {
const currentRpsPc = useMemo(() => {
if (!job) {
return 0;
}
return (
(_.sum(job.joblines.map((jl) => jl.price_diff_pc)) /
job.joblines.length) *
100
).toFixed(1);
}, [job]);
const currentRpsDollars = useMemo(() => {
if (!job) {
return 0;
}
return job.joblines.reduce((acc, val) => {
console.log("val.price_diff :>> ", val.price_diff);
if (val.price_diff > 0) {
return acc.add(
Dinero({ amount: Math.round((val.price_diff || 0) * 100) })
);
} else {
return acc;
}
}, Dinero());
}, [job]);
if (loading) return <Skeleton active />;
if (!job) return <ErrorResultAtom title="Error displaying job data." />;
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-around",
}}
>
<TargetPriceDiffPcAtom v_age={job.v_age} group={job.group} />
<Statistic title="Current RPS %" value={currentRpsPc} suffix="%" />
<Statistic title="Current RPS $" value={currentRpsDollars.toFormat()} />
</div>
);
}

View File

@@ -8,6 +8,7 @@ import { selectSelectedJobId } from "../../../redux/application/application.sele
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
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";
const mapStateToProps = createStructuredSelector({
@@ -32,17 +33,21 @@ export function JobsDetailOrganism({ selectedJobId }) {
errorMessage={JSON.stringify(error)}
/>
);
return (
<div className="jobs-detail-container">
<JobsDetailDescriptionMolecule
loading={loading}
job={data ? data.jobs_by_pk : null}
/>
<JobsTargetsStatsMolecule
loading={loading}
job={data ? data.jobs_by_pk : null}
/>
<JobsLinesTableMolecule
loading={loading}
jobLines={data ? data.jobs_by_pk.joblines : []}
job={data ? data.jobs_by_pk : {}}
/>
{selectedJobId}
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, List, Space, Spin, Typography } from "antd";
import { Button, Divider, List, Space, Spin } from "antd";
import React, { useState } from "react";
import InfiniteScroll from "react-infinite-scroller";
import { connect } from "react-redux";
@@ -87,6 +87,7 @@ export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) {
useWindow={false}
>
<List
bordered
dataSource={data ? data.jobs : []}
renderItem={(item) => (
<List.Item
@@ -101,22 +102,24 @@ export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) {
: ""
}`}
>
<div style={{ display: "flex" }}>
<Typography.Title level={4} style={{ flex: 1 }}>
{`${item.clm_no}${
item.ins_co_nm ? ` | ${item.ins_co_nm}` : ""
}`}
</Typography.Title>
<span className="job-list-last-updated-time">
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<strong>{item.clm_no || "No Claim Number"}</strong>
<span style={{ fontStyle: "italic" }}>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</span>
</div>
<Space>
<span>{`${item.ownr_fn} ${item.ownr_ln}`}</span>
<span>
{`${item.v_model_yr} ${item.v_makedesc} ${item.v_model} ${item.v_vin}`}
</span>
</Space>
<div>{item.ins_co_nm || "No Insurance Co."}</div>
<div>{`${item.ownr_fn} ${item.ownr_ln}`}</div>
<div>
{`${item.v_model_yr} ${item.v_makedesc} ${item.v_model} ${item.v_vin}`}
</div>
</div>
</List.Item>
)}

View File

@@ -2,8 +2,8 @@ import { Col, Row } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import JobsListOrganism from "../../organisms/jobs-list/jobs-list.organism";
import JobsDetailOrganism from "../../organisms/jobs-detail/jobs-detail.organism";
import JobsListOrganism from "../../organisms/jobs-list/jobs-list.organism";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({});
@@ -12,11 +12,10 @@ export function JobsPage() {
return (
<div style={{ height: "100%" }}>
<Row gutter={[16, 16]} style={{ height: "100%" }}>
<Col span={10} style={{ height: "100%" }}>
<Col span={6} style={{ height: "100%" }}>
<JobsListOrganism />
</Col>
<Col span={14} style={{ height: "100%" }}>
<Col span={18} style={{ height: "100%" }}>
<JobsDetailOrganism />
</Col>
</Row>