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"; import Dinero from "dinero.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.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 })); }; const calculateReducerTotalAmount = (lanes, key) => { return lanes.reduce( (acc, lane) => { return acc.add( lane.cards.reduce( (laneAcc, card) => laneAcc.add(Dinero(card.metadata[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 }) ) ); }, Dinero({ amount: 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 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 total.toFormat("$0,0.00"); }, [data, cardSettings.totalAmountInProduction]); const totalAmountOnBoard = useMemo(() => { if (!reducerData || !cardSettings.totalAmountOnBoard) return null; const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals"); return total.toFormat("$0,0.00"); }, [reducerData, cardSettings.totalAmountOnBoard]); 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 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 (