216 lines
7.5 KiB
JavaScript
216 lines
7.5 KiB
JavaScript
import React, { useMemo } from "react";
|
|
import { Card, Statistic } from "antd";
|
|
import { useTranslation } from "react-i18next";
|
|
import PropTypes from "prop-types";
|
|
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
|
|
|
export const StatisticType = {
|
|
HOURS: "hours",
|
|
AMOUNT: "amount",
|
|
JOBS: "jobs",
|
|
TASKS: "tasks"
|
|
};
|
|
|
|
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 tasksInProduction = useMemo(() => {
|
|
if (!data || !cardSettings.tasksInProduction) return null;
|
|
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
|
}, [data, cardSettings.tasksInProduction]);
|
|
|
|
const tasksOnBoard = useMemo(() => {
|
|
if (!reducerData || !cardSettings.tasksOnBoard) return null;
|
|
return reducerData.lanes.reduce((acc, lane) => {
|
|
return (
|
|
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata.tasks_aggregate?.aggregate?.count || 0), 0)
|
|
);
|
|
}, 0);
|
|
}, [reducerData, cardSettings.tasksOnBoard]);
|
|
|
|
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 },
|
|
{ id: 10, value: tasksOnBoard, type: StatisticType.TASKS },
|
|
{ id: 11, value: tasksInProduction, type: StatisticType.TASKS }
|
|
]),
|
|
[
|
|
totalHrs,
|
|
totalAmountInProduction,
|
|
totalLAB,
|
|
totalLAR,
|
|
jobsInProduction,
|
|
totalHrsOnBoard,
|
|
totalAmountOnBoard,
|
|
totalLABOnBoard,
|
|
totalLAROnBoard,
|
|
jobsOnBoard,
|
|
tasksOnBoard,
|
|
tasksInProduction
|
|
]
|
|
);
|
|
|
|
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.array.isRequired,
|
|
cardSettings: PropTypes.object.isRequired,
|
|
reducerData: PropTypes.object
|
|
};
|
|
|
|
export default ProductionStatistics;
|