Files
bodyshop/client/src/components/production-board-kanban/production-board-kanban.statistics.jsx
2024-08-02 19:52:49 -04:00

224 lines
7.4 KiB
JavaScript

import React, { useMemo } from "react";
import { Card, Statistic } from "antd";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
import { statisticsItems, defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
export const StatisticType = {
HOURS: "hours",
AMOUNT: "amount",
JOBS: "jobs"
};
const mergeStatistics = (items, values) => {
const valuesMap = values.reduce((acc, value) => {
acc[value.id] = value;
return acc;
}, {});
return items.map((item) => ({
...item,
value: valuesMap[item.id]?.value,
type: valuesMap[item.id]?.type
}));
};
const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
const { t } = useTranslation();
const calculateTotal = (items, key, subKey) => {
return items.reduce((acc, item) => acc + (item[key]?.aggregate?.sum?.[subKey] || 0), 0);
};
const calculateTotalAmount = (items, key) => {
return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0);
};
const calculateReducerTotal = (lanes, key, subKey) => {
return lanes.reduce((acc, lane) => {
return (
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.aggregate?.sum?.[subKey] || 0), 0)
);
}, 0);
};
const calculateReducerTotalAmount = (lanes, key) => {
return lanes.reduce((acc, lane) => {
return (
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.totals?.subtotal?.amount || 0), 0)
);
}, 0);
};
const formatValue = (value, type) => {
if (type === StatisticType.JOBS) {
return value.toFixed(0);
}
if (type === StatisticType.HOURS) {
return value.toFixed(2);
}
return value;
};
const totalHrs = useMemo(() => {
if (!cardSettings.totalHrs) return null;
const total = calculateTotal(data, "labhrs", "mod_lb_hrs") + calculateTotal(data, "larhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [data, cardSettings.totalHrs]);
const totalLAB = useMemo(() => {
if (!cardSettings.totalLAB) return null;
const total = calculateTotal(data, "labhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [data, cardSettings.totalLAB]);
const totalLAR = useMemo(() => {
if (!cardSettings.totalLAR) return null;
const total = calculateTotal(data, "larhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [data, cardSettings.totalLAR]);
const jobsInProduction = useMemo(
() => (cardSettings.jobsInProduction ? data.length : null),
[data, cardSettings.jobsInProduction]
);
const totalAmountInProduction = useMemo(() => {
if (!cardSettings.totalAmountInProduction) return null;
const total = calculateTotalAmount(data, "job_totals");
return parseFloat(total.toFixed(2));
}, [data, cardSettings.totalAmountInProduction]);
const totalHrsOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
const total =
calculateReducerTotal(reducerData.lanes, "labhrs", "mod_lb_hrs") +
calculateReducerTotal(reducerData.lanes, "larhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalHrsOnBoard]);
const totalLABOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalLABOnBoard) return null;
const total = calculateReducerTotal(reducerData.lanes, "labhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalLABOnBoard]);
const totalLAROnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalLAROnBoard) return null;
const total = calculateReducerTotal(reducerData.lanes, "larhrs", "mod_lb_hrs");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalLAROnBoard]);
const jobsOnBoard = useMemo(
() =>
reducerData && cardSettings.jobsOnBoard
? reducerData.lanes.reduce((acc, lane) => acc + lane.cards.length, 0)
: null,
[reducerData, cardSettings.jobsOnBoard]
);
const totalAmountOnBoard = useMemo(() => {
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
return parseFloat(total.toFixed(2));
}, [reducerData, cardSettings.totalAmountOnBoard]);
const statistics = useMemo(
() =>
mergeStatistics(statisticsItems, [
{ id: 0, value: totalHrs, type: StatisticType.HOURS },
{ id: 1, value: totalAmountInProduction, type: StatisticType.AMOUNT },
{ id: 2, value: totalLAB, type: StatisticType.HOURS },
{ id: 3, value: totalLAR, type: StatisticType.HOURS },
{ id: 4, value: jobsInProduction, type: StatisticType.JOBS },
{ id: 5, value: totalHrsOnBoard, type: StatisticType.HOURS },
{ id: 6, value: totalAmountOnBoard, type: StatisticType.AMOUNT },
{ id: 7, value: totalLABOnBoard, type: StatisticType.HOURS },
{ id: 8, value: totalLAROnBoard, type: StatisticType.HOURS },
{ id: 9, value: jobsOnBoard, type: StatisticType.JOBS }
]),
[
totalHrs,
totalAmountInProduction,
totalLAB,
totalLAR,
jobsInProduction,
totalHrsOnBoard,
totalAmountOnBoard,
totalLABOnBoard,
totalLAROnBoard,
jobsOnBoard
]
);
const sortedStatistics = useMemo(() => {
const statisticsMap = new Map(statistics.map((stat) => [stat.id, stat]));
return (
cardSettings?.statisticsOrder ? cardSettings.statisticsOrder : defaultKanbanSettings.statisticsOrder
).reduce((sorted, orderId) => {
const value = statisticsMap.get(orderId);
if (value && value.value !== null) {
sorted.push(value);
}
return sorted;
}, []);
}, [statistics, cardSettings.statisticsOrder]);
return (
<div style={{ display: "flex", gap: "5px", flexWrap: "wrap", marginBottom: "5px" }}>
{sortedStatistics.map((stat) => (
<Card styles={{ body: { padding: "8px" } }} key={stat.id}>
<Statistic
title={t(`production.statistics.${stat.label}`)}
value={formatValue(stat.value, stat.type)}
prefix={stat.type === StatisticType.AMOUNT ? t("production.statistics.currency_symbol") : undefined}
suffix={
stat.type === StatisticType.HOURS
? t("production.statistics.hours")
: stat.type === StatisticType.JOBS
? t("production.statistics.jobs")
: undefined
}
/>
</Card>
))}
</div>
);
};
ProductionStatistics.propTypes = {
data: PropTypes.arrayOf(
PropTypes.shape({
labhrs: PropTypes.object,
larhrs: PropTypes.object,
job_totals: PropTypes.object
})
).isRequired,
cardSettings: PropTypes.shape({
totalHrs: PropTypes.bool,
totalLAB: PropTypes.bool,
totalLAR: PropTypes.bool,
jobsInProduction: PropTypes.bool,
totalAmountInProduction: PropTypes.bool,
totalHrsOnBoard: PropTypes.bool,
totalLABOnBoard: PropTypes.bool,
totalLAROnBoard: PropTypes.bool,
jobsOnBoard: PropTypes.bool,
totalAmountOnBoard: PropTypes.bool,
statisticsOrder: PropTypes.arrayOf(PropTypes.number)
}).isRequired,
reducerData: PropTypes.shape({
lanes: PropTypes.arrayOf(
PropTypes.shape({
cards: PropTypes.arrayOf(
PropTypes.shape({
metadata: PropTypes.object
})
).isRequired
})
).isRequired
})
};
export default ProductionStatistics;