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> </translation>
</translations> </translations>
</concept_node> </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> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>

View File

@@ -145,7 +145,7 @@ export function JobDetailCards({ setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null} data={data ? data.jobs_by_pk : null}
/> />
</Col> </Col>
<Col span={24}> <Col {...colBreakPoints}>
<JobDetailCardsPartsComponent <JobDetailCardsPartsComponent
loading={loading} loading={loading}
data={data ? data.jobs_by_pk : null} data={data ? data.jobs_by_pk : null}
@@ -163,7 +163,7 @@ export function JobDetailCards({ setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null} data={data ? data.jobs_by_pk : null}
/> />
</Col> </Col>
<Col span={24}> <Col {...colBreakPoints}>
<JobDetailCardsDamageComponent <JobDetailCardsDamageComponent
loading={loading} loading={loading}
data={data ? data.jobs_by_pk : null} 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 { 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"; import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsPartsComponent({ loading, data }) { export default function JobDetailCardsPartsComponent({ loading, data }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { joblines_status } = data; 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 ( return (
<div> <div>
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}> <CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
<PieChart width={400} height={400}> <PartsStatusPie joblines_status={joblines_status} />
<Pie
activeIndex={state.activeIndex}
activeShape={renderActiveShape}
data={memoizedData}
cx={200}
cy={200}
innerRadius={60}
outerRadius={80}
fill="#8884d8"
dataKey="value"
onMouseEnter={onPieEnter}
/>
</PieChart>
</CardTemplate> </CardTemplate>
</div> </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 }) { export default function JobDetailCardsTotalsComponent({ loading, data }) {
const { t } = useTranslation(); const { t } = useTranslation();
let totals;
try {
totals = JSON.parse(data.job_totals);
} catch (error) {
console.log("Error in CardsTotal component", error);
}
return ( return (
<CardTemplate loading={loading} title={t("jobs.labels.cards.totals")}> <CardTemplate loading={loading} title={t("jobs.labels.cards.totals")}>
{totals ? ( {data.job_totals ? (
<div className="imex-flex-row imex-flex-row__flex-space-around"> <div className="imex-flex-row imex-flex-row__flex-space-around">
<Statistic <Statistic
className="imex-flex-row__margin-large" className="imex-flex-row__margin-large"
title={t("jobs.labels.total_repairs")} title={t("jobs.labels.total_repairs")}
value={Dinero(totals.totals.total_repairs).toFormat()} value={Dinero(data.job_totals.totals.total_repairs).toFormat()}
/> />
<Statistic <Statistic
className="imex-flex-row__margin-large" className="imex-flex-row__margin-large"
@@ -33,7 +26,7 @@ export default function JobDetailCardsTotalsComponent({ loading, data }) {
<Statistic <Statistic
className="imex-flex-row__margin-large" className="imex-flex-row__margin-large"
title={t("jobs.labels.net_repairs")} title={t("jobs.labels.net_repairs")}
value={Dinero(totals.totals.net_repairs).toFormat()} value={Dinero(data.job_totals.totals.net_repairs).toFormat()}
/> />
</div> </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 }) { export function PartsStatusPie({ bodyshop, joblines_status }) {
return <div>Parts Pie</div>; const { t } = useTranslation();
//const [pieData, setPieData] = useState([]); 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 if (status === bodyshop.md_order_statuses.default_canceled)
// ? partsList.reduce((names, name) => { return "dodgerblue";
// const val = name || "?";
// const count = names[val] || 0;
// names[val] = count + 1;
// return names;
// }, {})
// : {};
// const pieData = Object.keys(result).map((i) => { if (status === bodyshop.md_order_statuses.default_returned)
// return { return "powderblue";
// id: i, if (status === bodyshop.md_order_statuses.default_received)
// label: i, return "seagreen";
// value: result[i],
// };
// });
// 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": { "labels": {
"edit": "Edit Line", "edit": "Edit Line",
"new": "New Line" "new": "New Line",
"nostatus": "No Status"
}, },
"successes": { "successes": {
"created": "Job line created successfully.", "created": "Job line created successfully.",

View File

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

View File

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