Updated parts status graph and formatting. BOD-328

This commit is contained in:
Patrick Fic
2020-08-27 15:56:01 -07:00
parent 6e611fbc7c
commit cc2947c7a2
14 changed files with 150 additions and 162 deletions

View File

@@ -9860,6 +9860,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>nostatus</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>

View File

@@ -145,7 +145,7 @@ export function JobDetailCards({ setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col span={24}>
<Col {...colBreakPoints}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
@@ -163,7 +163,7 @@ export function JobDetailCards({ setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col span={24}>
<Col {...colBreakPoints}>
<JobDetailCardsDamageComponent
loading={loading}
data={data ? data.jobs_by_pk : null}

View File

@@ -1,141 +1,17 @@
import React, { useMemo, useState } from "react";
import React from "react";
import { useTranslation } from "react-i18next";
import { Pie, PieChart, Sector } from "recharts";
import PartsStatusPie from "../parts-status-pie/parts-status-pie.component";
import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsPartsComponent({ loading, data }) {
const { t } = useTranslation();
const { joblines_status } = data;
// console.log(
// "JobDetailCardsPartsComponent -> joblines_stats",
// joblines_status
// );
const memoizedData = useMemo(() => Calculatedata(joblines_status), [
joblines_status,
]);
const [state, setState] = useState({ activeIndex: 0 });
const onPieEnter = (data, index) => {
setState({
activeIndex: index,
});
};
return (
<div>
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
<PieChart width={400} height={400}>
<Pie
activeIndex={state.activeIndex}
activeShape={renderActiveShape}
data={memoizedData}
cx={200}
cy={200}
innerRadius={60}
outerRadius={80}
fill="#8884d8"
dataKey="value"
onMouseEnter={onPieEnter}
/>
</PieChart>
<PartsStatusPie joblines_status={joblines_status} />
</CardTemplate>
</div>
);
}
const Calculatedata = (data) => {
if (data.length > 0) {
const statusMapping = {};
data.map((i) => {
if (!statusMapping[i.status])
statusMapping[i.status] = { name: i.status || "No Status*", value: 0 };
statusMapping[i.status].value = statusMapping[i.status].value + i.count;
return null;
});
return Object.keys(statusMapping).map((key) => {
return statusMapping[key];
});
} else {
return [
{ name: "Group A", value: 400 },
{ name: "Group B", value: 300 },
{ name: "Group C", value: 300 },
{ name: "Group D", value: 200 },
];
}
};
const renderActiveShape = (props) => {
const RADIAN = Math.PI / 180;
const {
cx,
cy,
midAngle,
innerRadius,
outerRadius,
startAngle,
endAngle,
fill,
payload,
percent,
value,
} = props;
const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my;
const textAnchor = cos >= 0 ? "start" : "end";
return (
<g>
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>
{payload.name}
</text>
<Sector
cx={cx}
cy={cy}
innerRadius={innerRadius}
outerRadius={outerRadius}
startAngle={startAngle}
endAngle={endAngle}
fill={fill}
/>
<Sector
cx={cx}
cy={cy}
startAngle={startAngle}
endAngle={endAngle}
innerRadius={outerRadius + 6}
outerRadius={outerRadius + 10}
fill={fill}
/>
<path
d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`}
stroke={fill}
fill="none"
/>
<circle cx={ex} cy={ey} r={2} fill={fill} stroke="none" />
<text
x={ex + (cos >= 0 ? 1 : -1) * 12}
y={ey}
textAnchor={textAnchor}
fill="#333"
>{`Count: ${value}`}</text>
<text
x={ex + (cos >= 0 ? 1 : -1) * 12}
y={ey}
dy={18}
textAnchor={textAnchor}
fill="#999"
>
{`(${(percent * 100).toFixed(2)}%)`}
</text>
</g>
);
};

View File

@@ -6,22 +6,15 @@ import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsTotalsComponent({ loading, data }) {
const { t } = useTranslation();
let totals;
try {
totals = JSON.parse(data.job_totals);
} catch (error) {
console.log("Error in CardsTotal component", error);
}
return (
<CardTemplate loading={loading} title={t("jobs.labels.cards.totals")}>
{totals ? (
{data.job_totals ? (
<div className="imex-flex-row imex-flex-row__flex-space-around">
<Statistic
className="imex-flex-row__margin-large"
title={t("jobs.labels.total_repairs")}
value={Dinero(totals.totals.total_repairs).toFormat()}
value={Dinero(data.job_totals.totals.total_repairs).toFormat()}
/>
<Statistic
className="imex-flex-row__margin-large"
@@ -33,7 +26,7 @@ export default function JobDetailCardsTotalsComponent({ loading, data }) {
<Statistic
className="imex-flex-row__margin-large"
title={t("jobs.labels.net_repairs")}
value={Dinero(totals.totals.net_repairs).toFormat()}
value={Dinero(data.job_totals.totals.net_repairs).toFormat()}
/>
</div>
) : (

View File

@@ -1,25 +1,93 @@
import React from "react";
import React, { useCallback, useMemo } from "react";
import { connect } from "react-redux";
import { Cell, Pie, PieChart, ResponsiveContainer } from "recharts";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export default function PartsStatusPie({ partsList }) {
return <div>Parts Pie</div>;
//const [pieData, setPieData] = useState([]);
export function PartsStatusPie({ bodyshop, joblines_status }) {
const { t } = useTranslation();
const pieColor = useCallback(
(status) => {
if (status === bodyshop.md_order_statuses.default_ordered)
return "lightgreen";
if (status === bodyshop.md_order_statuses.default_bo) return "crimson";
// const result = partsList
// ? partsList.reduce((names, name) => {
// const val = name || "?";
// const count = names[val] || 0;
// names[val] = count + 1;
// return names;
// }, {})
// : {};
if (status === bodyshop.md_order_statuses.default_canceled)
return "dodgerblue";
// const pieData = Object.keys(result).map((i) => {
// return {
// id: i,
// label: i,
// value: result[i],
// };
// });
if (status === bodyshop.md_order_statuses.default_returned)
return "powderblue";
if (status === bodyshop.md_order_statuses.default_received)
return "seagreen";
// return <div>{JSON.stringify(pieData)}</div>;
return "slategray";
},
[
bodyshop.md_order_statuses.default_ordered,
bodyshop.md_order_statuses.default_bo,
bodyshop.md_order_statuses.default_canceled,
bodyshop.md_order_statuses.default_returned,
bodyshop.md_order_statuses.default_received,
]
);
const Calculatedata = useCallback(
(data) => {
if (data.length > 0) {
const statusMapping = {};
data.map((i) => {
if (!statusMapping[i.status])
statusMapping[i.status] = {
name: i.status || t("joblines.labels.nostatus"),
value: 0,
color: pieColor(i.status),
};
statusMapping[i.status].value =
statusMapping[i.status].value + i.count;
return null;
});
return Object.keys(statusMapping).map((key) => {
return statusMapping[key];
});
} else {
return [];
}
},
[pieColor, t]
);
const memoizedData = useMemo(() => Calculatedata(joblines_status), [
joblines_status,
Calculatedata,
]);
console.log("PartsStatusPie -> memoizedData", memoizedData);
return (
<div>
<ResponsiveContainer width="100%" height={175}>
<PieChart>
<Pie
data={memoizedData}
innerRadius={40}
outerRadius={50}
fill="#8884d8"
paddingAngle={5}
dataKey="value"
label={(entry) => `${entry.name} - ${entry.value}`}
labelLine
>
{memoizedData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
</PieChart>
</ResponsiveContainer>
</div>
);
}
export default connect(mapStateToProps, null)(PartsStatusPie);

View File

@@ -635,7 +635,8 @@
},
"labels": {
"edit": "Edit Line",
"new": "New Line"
"new": "New Line",
"nostatus": "No Status"
},
"successes": {
"created": "Job line created successfully.",

View File

@@ -635,7 +635,8 @@
},
"labels": {
"edit": "Línea de edición",
"new": "Nueva línea"
"new": "Nueva línea",
"nostatus": ""
},
"successes": {
"created": "",

View File

@@ -635,7 +635,8 @@
},
"labels": {
"edit": "Ligne d'édition",
"new": "Nouvelle ligne"
"new": "Nouvelle ligne",
"nostatus": ""
},
"successes": {
"created": "",

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,8 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE VIEW \"public\".\"joblines_status\" AS \n SELECT j.jobid,\n
\ j.status,\n count(1) AS count,\n j.part_type\n FROM joblines j\n
\ where j.part_type is not null\n GROUP BY j.jobid, j.status, j.part_type\n
\ ;"
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,8 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE VIEW \"public\".\"joblines_status\" AS \n SELECT j.jobid,\n
\ j.status,\n count(1) AS count,\n j.part_type\n FROM joblines j\n
\ where j.part_type is not null\n GROUP BY j.jobid, j.status, j.part_type\n
\ ;"
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,8 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE VIEW \"public\".\"joblines_status\" AS \n SELECT j.jobid,\n
\ j.status,\n count(1) AS count,\n j.part_type\n FROM joblines j\n
\ where j.part_type is not null and j.part_type <> 'PAE'\n GROUP BY j.jobid,
j.status, j.part_type\n ;"
type: run_sql