Added pie to job costinng, combined columns on job costing IO-569

This commit is contained in:
Patrick Fic
2021-01-11 12:13:50 -08:00
parent ae7f67461b
commit 32cbe95d0b
8 changed files with 161 additions and 35 deletions

View File

@@ -1,10 +1,13 @@
import { Typography } from "antd";
import Dinero from "dinero.js";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobCostingPartsTable from "../job-costing-parts-table/job-costing-parts-table.component";
import JobCostingStatistics from "../job-costing-statistics/job-costing-statistics.component";
import JobCostingPie from "./job-costing-modal.pie.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -16,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
export function JobCostingModalComponent({ bodyshop, job }) {
const defaultProfits = bodyshop.md_responsibility_centers.defaults.profits;
// const defaultCosts = bodyshop.md_responsibility_centers.defaults.costs;
const { t } = useTranslation();
const jobLineTotalsByProfitCenter =
job &&
job.joblines.reduce(
@@ -115,11 +118,11 @@ export function JobCostingModalComponent({ bodyshop, job }) {
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost_parts = billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 });
const cost = (billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })).add(
ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
);
const costs = (
billTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 })
).add(ticketTotalsByProfitCenter[ccVal] || Dinero({ amount: 0 }));
const totalSales = sale_labor.add(sale_parts);
const gpdollars = totalSales.subtract(cost);
const gpdollars = totalSales.subtract(costs);
const gppercent = (
(gpdollars.getAmount() / totalSales.getAmount()) *
100
@@ -139,16 +142,19 @@ export function JobCostingModalComponent({ bodyshop, job }) {
.add(sale_parts);
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
summaryData.totalCost = summaryData.totalCost.add(cost);
summaryData.totalCost = summaryData.totalCost.add(costs);
return {
id: idx,
cost_center: ccVal,
sale_labor: sale_labor && sale_labor.toFormat(),
sale_parts: sale_parts && sale_parts.toFormat(),
sales: sale_labor.add(sale_parts).toFormat(),
sales_dinero: sale_labor.add(sale_parts),
cost_parts: cost_parts && cost_parts.toFormat(),
cost_labor: cost_labor && cost_labor.toFormat(),
cost: cost && cost.toFormat(),
costs: cost_parts.add(cost_labor).toFormat(),
costs_dinero: cost_parts.add(cost_labor),
gpdollars: gpdollars.toFormat(),
gppercent: gppercentFormatted,
};
@@ -173,6 +179,18 @@ export function JobCostingModalComponent({ bodyshop, job }) {
<div>
<JobCostingStatistics job={job} summaryData={summaryData} />
<JobCostingPartsTable job={job} data={costCenterData} />
<div className="imex-flex-row">
<div style={{ flex: 1 }}>
<Typography.Title level={4}>
{t("jobs.labels.sales")}
</Typography.Title>
<JobCostingPie type="sales" costCenterData={costCenterData} />
</div>
<div style={{ flex: 1 }}>
<Typography.Title level={4}>{t("jobs.labels.cost")}</Typography.Title>
<JobCostingPie type="cost" costCenterData={costCenterData} />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,69 @@
import React, { useCallback, useMemo } from "react";
import { Cell, Pie, PieChart, ResponsiveContainer } from "recharts";
export default function JobCostingPieComponent({
type = "sales",
costCenterData,
}) {
const Calculatedata = useCallback(
(data) => {
if (data && data.length > 0) {
return data.reduce((acc, i) => {
const value =
type === "sales"
? i.sales_dinero.getAmount()
: i.costs_dinero.getAmount();
if (value > 0) {
acc.push({
name: i.cost_center,
color: "#" + Math.floor(Math.random() * 16777215).toString(16),
label: `${i.cost_center} - ${
type === "sales"
? i.sales_dinero.toFormat()
: i.costs_dinero.toFormat()
}`,
value:
type === "sales"
? i.sales_dinero.getAmount()
: i.costs_dinero.getAmount(),
});
}
return acc;
}, []);
} else {
return [];
}
},
[type]
);
const memoizedData = useMemo(() => Calculatedata(costCenterData), [
costCenterData,
Calculatedata,
]);
console.log(type, memoizedData);
return (
<ResponsiveContainer width="100%" height={175}>
<PieChart>
<Pie
data={memoizedData}
innerRadius={40}
outerRadius={50}
fill="#8884d8"
paddingAngle={5}
dataKey="value"
label={(entry) => entry.label}
labelLine
>
{memoizedData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
</PieChart>
</ResponsiveContainer>
);
}

View File

@@ -24,37 +24,23 @@ export default function JobCostingPartsTable({ job, data }) {
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
},
{
title: t("jobs.labels.sale_labor"),
dataIndex: "sale_labor",
key: "sale_labor",
sorter: (a, b) => alphaSort(a.sale_labor, b.sale_labor),
title: t("jobs.labels.sales"),
dataIndex: "sales",
key: "sales",
sorter: (a, b) => alphaSort(a.sales, b.sales),
sortOrder:
state.sortedInfo.columnKey === "sale_labor" && state.sortedInfo.order,
state.sortedInfo.columnKey === "sales" && state.sortedInfo.order,
},
{
title: t("jobs.labels.sale_parts"),
dataIndex: "sale_parts",
key: "sale_parts",
sorter: (a, b) => alphaSort(a.sale_parts, b.sale_parts),
title: t("jobs.labels.costs"),
dataIndex: "costs",
key: "costs",
sorter: (a, b) => a.costs - b.costs,
sortOrder:
state.sortedInfo.columnKey === "sale_parts" && state.sortedInfo.order,
},
{
title: t("jobs.labels.cost_labor"),
dataIndex: "cost_labor",
key: "cost_labor",
sorter: (a, b) => a.cost_labor - b.cost_labor,
sortOrder:
state.sortedInfo.columnKey === "cost_labor" && state.sortedInfo.order,
},
{
title: t("jobs.labels.cost_parts"),
dataIndex: "cost_parts",
key: "cost_parts",
sorter: (a, b) => a.cost_parts - b.cost_parts,
sortOrder:
state.sortedInfo.columnKey === "cost_parts" && state.sortedInfo.order,
state.sortedInfo.columnKey === "costs" && state.sortedInfo.order,
},
{
title: t("jobs.labels.gpdollars"),
dataIndex: "gpdollars",

View File

@@ -1112,6 +1112,7 @@
"reconciliationheader": "Parts & Sublet Reconciliation",
"sale_labor": "Sales - Labor",
"sale_parts": "Sales - Parts",
"sales": "Sales",
"state_tax_amt": "State/Provincial Taxes",
"subletstotal": "Sublets Total",
"subtotal": "Subtotal",

View File

@@ -1112,6 +1112,7 @@
"reconciliationheader": "",
"sale_labor": "",
"sale_parts": "",
"sales": "",
"state_tax_amt": "",
"subletstotal": "",
"subtotal": "",

View File

@@ -1112,6 +1112,7 @@
"reconciliationheader": "",
"sale_labor": "",
"sale_parts": "",
"sales": "",
"state_tax_amt": "",
"subletstotal": "",
"subtotal": "",