Additional Changes for calculations of RPS %.
This commit is contained in:
@@ -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);
|
||||
@@ -0,0 +1,9 @@
|
||||
.blink_me {
|
||||
animation: blinker 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user