diff --git a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js index 9f925a1a3..c69493108 100644 --- a/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js +++ b/client/src/components/scoreboard-targets-table/scoreboard-targets-table.util.js @@ -8,18 +8,45 @@ export const CalculateWorkingDaysThisMonth = () => { return moment().endOf("month").businessDaysIntoMonth(); }; +export const CalculateWorkingDaysInPeriod = (start, end) => { + return moment(start).businessDiff(moment(end)); +}; + export const CalculateWorkingDaysAsOfToday = () => { return moment().businessDaysIntoMonth(); }; +export const CalculateWorkingDaysLastMonth = () => { + return moment().subtract(1, "month").endOf("month").businessDaysIntoMonth(); +}; + export const WeeklyTargetHrs = (dailyTargetHrs, bodyshop) => { - return dailyTargetHrs * 5; + return ( + dailyTargetHrs * + CalculateWorkingDaysInPeriod( + moment().startOf("week"), + moment().endOf("week") + ) + ); +}; + +export const WeeklyTargetHrsInPeriod = ( + dailyTargetHrs, + start, + end, + bodyshop +) => { + return dailyTargetHrs * CalculateWorkingDaysInPeriod(start, end); }; export const MonthlyTargetHrs = (dailyTargetHrs, bodyshop) => { return dailyTargetHrs * CalculateWorkingDaysThisMonth(); }; +export const LastMonthTargetHrs = (dailyTargetHrs, bodyshop) => { + return dailyTargetHrs * CalculateWorkingDaysLastMonth(); +}; + export const AsOfTodayTargetHrs = (dailyTargetHrs, bodyshop) => { return dailyTargetHrs * CalculateWorkingDaysAsOfToday(); }; diff --git a/client/src/components/scoreboard-timetickets-stats/chart-custom-tooltip.jsx b/client/src/components/scoreboard-timetickets-stats/chart-custom-tooltip.jsx new file mode 100644 index 000000000..b351a60db --- /dev/null +++ b/client/src/components/scoreboard-timetickets-stats/chart-custom-tooltip.jsx @@ -0,0 +1,26 @@ +const CustomTooltip = ({ active, payload, label }) => { + if (active && payload && payload.length) { + return ( +
+

{label}

+ {payload.map((data, index) => { + return ( +

{`${ + data.name + } : ${data.value.toFixed(1)}`}

+ ); + })} +
+ ); + } + + return null; +}; + +export default CustomTooltip; diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.chart.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.chart.component.jsx new file mode 100644 index 000000000..36c193c33 --- /dev/null +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.chart.component.jsx @@ -0,0 +1,54 @@ +import { Card } from "antd"; +import React from "react"; +import { + Area, + CartesianGrid, + ComposedChart, + Legend, + Line, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; +import CustomTooltip from "./chart-custom-tooltip"; + +const graphProps = { + strokeWidth: 3, +}; + +export default function ScoreboardTimeTicketsChart({ data, chartTitle }) { + return ( + + + + + + + } /> + + + + + + + + ); +} diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx new file mode 100644 index 000000000..d2ced5334 --- /dev/null +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.component.jsx @@ -0,0 +1,399 @@ +import { useQuery } from "@apollo/client"; +import { Col, Row } from "antd"; +import _ from "lodash"; +import moment from "moment"; +import queryString from "query-string"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import AlertComponent from "../alert/alert.component"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util"; +import ScoreboardTimeTicketsChart from "./scoreboard-timetickets.chart.component"; +import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component"; +import ScoreboardTimeticketsTargetsTable from "./scoreboard-timetickets.targets-table.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(ScoreboardTimeTicketsStats); + +export function ScoreboardTimeTicketsStats({ bodyshop }) { + const { t } = useTranslation(); + const searchParams = queryString.parse(useLocation().search); + const { start, end } = searchParams; + const startDate = start + ? moment(start) + : moment().startOf("week").subtract(7, "days"); + const endDate = end ? moment(end) : moment().endOf("week"); + + const fixedPeriods = useMemo(() => { + const endOfThisMonth = moment().endOf("month"); + const startofthisMonth = moment().startOf("month"); + + const endOfLastmonth = moment().subtract(1, "month").endOf("month"); + const startOfLastmonth = moment().subtract(1, "month").startOf("month"); + + const endOfThisWeek = moment().endOf("week"); + const startOfThisWeek = moment().startOf("week"); + + const endOfLastWeek = moment().subtract(1, "week").endOf("week"); + const startOfLastWeek = moment().subtract(1, "week").startOf("week"); + + const endOfPriorWeek = moment().subtract(2, "week").endOf("week"); + const startOfPriorWeek = moment().subtract(2, "week").startOf("week"); + + const allDates = [ + endOfThisMonth, + startofthisMonth, + endOfLastmonth, + startOfLastmonth, + endOfThisWeek, + startOfThisWeek, + endOfLastWeek, + startOfLastWeek, + endOfPriorWeek, + startOfPriorWeek, + ]; + const start = moment.min(allDates); + const end = moment.max(allDates); + return { + start, + end, + endOfThisMonth, + startofthisMonth, + endOfLastmonth, + startOfLastmonth, + endOfThisWeek, + startOfThisWeek, + endOfLastWeek, + startOfLastWeek, + endOfPriorWeek, + startOfPriorWeek, + }; + }, []); + + const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, { + variables: { + start: startDate.format("YYYY-MM-DD"), + end: endDate.format("YYYY-MM-DD"), + fixedStart: fixedPeriods.start.format("YYYY-MM-DD"), + fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"), + }, + fetchPolicy: "network-only", + nextFetchPolicy: "network-only", + pollInterval: 60000, + skip: !fixedPeriods, + }); + + const calculatedData = useMemo(() => { + if (!data) return []; + const ret = { + totalThisWeek: 0, + totalThisWeekLAB: 0, + totalThisWeekLAR: 0, + totalLastWeek: 0, + totalLastWeekLAB: 0, + totalLastWeekLAR: 0, + totalPriorWeek: 0, + totalPriorWeekLAB: 0, + totalPriorWeekLAR: 0, + totalThisMonth: 0, + totalThisMonthLAB: 0, + totalThisMonthLAR: 0, + totalLastMonth: 0, + totalLastMonthLAB: 0, + totalLastMonthLAR: 0, + actualTotalOverPeriod: 0, + actualTotalOverPeriodLAB: 0, + actualTotalOverPeriodLAR: 0, + totalEffieciencyOverPeriod: 0, + totalEffieciencyOverPeriodLAB: 0, + totalEffieciencyOverPeriodLAR: 0, + seperatedThisWeek: { + sunday: { + total: 0, + lab: 0, + lar: 0, + }, + monday: { + total: 0, + lab: 0, + lar: 0, + }, + tuesday: { + total: 0, + lab: 0, + lar: 0, + }, + wednesday: { + total: 0, + lab: 0, + lar: 0, + }, + thursday: { + total: 0, + lab: 0, + lar: 0, + }, + friday: { + total: 0, + lab: 0, + lar: 0, + }, + saturday: { + total: 0, + lab: 0, + lar: 0, + }, + }, + }; + + data.fixedperiod.forEach((ticket) => { + const ticketDate = moment(ticket.date); + if ( + ticketDate.isBetween( + fixedPeriods.startOfThisWeek, + fixedPeriods.endOfThisWeek, + undefined, + "[]" + ) + ) { + ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") + ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs; + if (ticket.ciecacode === "LAR") + ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs; + + //Seperate out to Day of Week + ret.seperatedThisWeek[ + moment(ticket.date).format("dddd").toLowerCase() + ].total = + ret.seperatedThisWeek[ + moment(ticket.date).format("dddd").toLowerCase() + ].total + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") + ret.seperatedThisWeek[ + moment(ticket.date).format("dddd").toLowerCase() + ].lab = + ret.seperatedThisWeek[ + moment(ticket.date).format("dddd").toLowerCase() + ].lab + ticket.productivehrs; + if (ticket.ciecacode === "LAR") + ret.seperatedThisWeek[ + moment(ticket.date).format("dddd").toLowerCase() + ].lar = + ret.seperatedThisWeek[ + moment(ticket.date).format("dddd").toLowerCase() + ].lar + ticket.productivehrs; + } else if ( + ticketDate.isBetween( + fixedPeriods.startOfLastWeek, + fixedPeriods.endOfLastWeek, + undefined, + "[]" + ) + ) { + ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") + ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs; + if (ticket.ciecacode === "LAR") + ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs; + } else if ( + ticketDate.isBetween( + fixedPeriods.startOfPriorWeek, + fixedPeriods.endOfPriorWeek, + undefined, + "[]" + ) + ) { + ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") + ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs; + if (ticket.ciecacode === "LAR") + ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs; + } + if ( + ticketDate.isBetween( + fixedPeriods.startofthisMonth, + fixedPeriods.endOfThisMonth, + undefined, + "[]" + ) + ) { + ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs; + ret.actualTotalOverPeriod = + ret.actualTotalOverPeriod + (ticket.actualhrs || 0); + if (ticket.ciecacode !== "LAR") { + ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs; + ret.actualTotalOverPeriodLAB = + ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0); + } + if (ticket.ciecacode === "LAR") { + ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs; + ret.actualTotalOverPeriodLAR = + ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0); + } + } else if ( + ticketDate.isBetween( + fixedPeriods.startOfLastmonth, + fixedPeriods.endOfLastmonth, + undefined, + "[]" + ) + ) { + ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs; + if (ticket.ciecacode !== "LAR") + ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs; + if (ticket.ciecacode === "LAR") + ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs; + } + }); + + ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod + ? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100 + : 0; + ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB + ? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100 + : 0; + ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR + ? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100 + : 0; + + roundObject(ret); + + const ticketsGroupedByDate = _.groupBy(data.timetickets, "date"); + + const listOfDays = Utils.ListOfDaysInCurrentMonth(); + + const combinedData = [], + labData = [], + larData = []; + var acc_comb = 0; + var acc_lab = 0; + var acc_lar = 0; + + listOfDays.forEach((day) => { + const r = { + date: moment(day).format("MM/DD"), + actualhrs: 0, + productivehrs: 0, + }; + + const combined = { + accTargetHrs: _.round( + Utils.AsOfDateTargetHours( + bodyshop.scoreboard_target.dailyBodyTarget + + bodyshop.scoreboard_target.dailyPaintTarget, + day + ) + + (bodyshop.scoreboard_target.dailyBodyTarget + + bodyshop.scoreboard_target.dailyPaintTarget), + 1 + ), + accHrs: 0, + }; + const lab = { + accTargetHrs: _.round( + Utils.AsOfDateTargetHours( + bodyshop.scoreboard_target.dailyBodyTarget, + day + ) + bodyshop.scoreboard_target.dailyBodyTarget, + 1 + ), + accHrs: 0, + }; + const lar = { + accTargetHrs: _.round( + Utils.AsOfDateTargetHours( + bodyshop.scoreboard_target.dailyPaintTarget, + day + ) + bodyshop.scoreboard_target.dailyPaintTarget, + 1 + ), + accHrs: 0, + }; + + if (ticketsGroupedByDate[day]) { + ticketsGroupedByDate[day].forEach((ticket) => { + r.actualhrs = r.actualhrs + ticket.actualhrs; + r.productivehrs = r.productivehrs + ticket.productivehrs; + acc_comb = acc_comb + ticket.productivehrs; + + if (ticket.ciecacode !== "LAR") + acc_lab = acc_lab + ticket.productivehrs; + if (ticket.ciecacode === "LAR") + acc_lar = acc_lar + ticket.productivehrs; + }); + } + combined.accHrs = acc_comb; + lab.accHrs = acc_lab; + lar.accHrs = acc_lar; + + combinedData.push({ ...r, ...combined }); + labData.push({ ...r, ...lab }); + larData.push({ ...r, ...lar }); + }); + + return { + fixed: ret, + combinedData: combinedData, + labData: labData, + larData: larData, + }; + }, [fixedPeriods, data, bodyshop]); + + if (error) return ; + if (loading) return ; + return ( + + + + + + + + + + + + + + + + + + ); +} + +function roundObject(inputObj) { + for (var key of Object.keys(inputObj)) { + if (typeof inputObj[key] === "number") { + inputObj[key] = inputObj[key].toFixed(1); + } else if (Array.isArray(inputObj[key])) { + inputObj[key].forEach((item) => roundObject(item)); + } else if (typeof inputObj[key] === "object") { + roundObject(inputObj[key]); + } + } +} diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.stats.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.stats.component.jsx new file mode 100644 index 000000000..016bd137e --- /dev/null +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.stats.component.jsx @@ -0,0 +1,617 @@ +import { + Card, + Col, + Form, + Row, + Space, + Statistic, + Switch, + Typography, +} from "antd"; +import moment from "moment"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); +export default connect( + mapStateToProps, + mapDispatchToProps +)(ScoreboardTicketsStats); + +function useLocalStorage(key, initialValue) { + const [storedValue, setStoredValue] = useState(() => { + const item = localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + }); + + useEffect(() => { + localStorage.setItem(key, JSON.stringify(storedValue)); + }, [key, storedValue]); + + return [storedValue, setStoredValue]; +} + +export function ScoreboardTicketsStats({ data, bodyshop }) { + const { t } = useTranslation(); + const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false); + + const statisticSize = isLarge ? 36 : 24; + const statisticWeight = isLarge ? 550 : "normal"; + const daySpan = + Util.CalculateWorkingDaysInPeriod( + moment().startOf("week"), + moment().endOf("week") + ) > 5 + ? 3 + : 4; + + return ( + + setIsLarge(!isLarge)} + defaultChecked={isLarge} + /> + + } + > + + + {/* Daily Stats */} + + + {[ + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + ].map((day) => { + if (bodyshop.workingdays[day] === true) { + return ( + + + + + = + bodyshop.scoreboard_target.dailyBodyTarget + + bodyshop.scoreboard_target.dailyPaintTarget + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + + {t("scoreboard.labels.body")} + + } + value={data.seperatedThisWeek[day].lab} + valueStyle={{ + color: + parseFloat(data.seperatedThisWeek[day].lab) >= + bodyshop.scoreboard_target.dailyBodyTarget + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + {t("scoreboard.labels.refinish")} + + } + value={data.seperatedThisWeek[day].lar} + valueStyle={{ + color: + parseFloat(data.seperatedThisWeek[day].lar) >= + bodyshop.scoreboard_target.dailyPaintTarget + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + ); + } else { + return null; + } + })} + + {/* Weekly Stats */} + + {/* This Week */} + + + + + = + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + moment().startOf("week"), + moment().endOf("week"), + bodyshop + ) + + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + moment().startOf("week"), + moment().endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + + {t("scoreboard.labels.body")} + + } + value={data.totalThisWeekLAB} + valueStyle={{ + color: + parseFloat(data.totalThisWeekLAB) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + moment().startOf("week"), + moment().endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + {t("scoreboard.labels.refinish")} + + } + value={data.totalThisWeekLAR} + valueStyle={{ + color: + parseFloat(data.totalThisWeekLAR) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + moment().startOf("week"), + moment().endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + {/* Last Week */} + + + + + = + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + moment().subtract(1, "week").startOf("week"), + moment().subtract(1, "week").endOf("week"), + bodyshop + ) + + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + moment().subtract(1, "week").startOf("week"), + moment().subtract(1, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + + {t("scoreboard.labels.body")} + + } + value={data.totalLastWeekLAB} + valueStyle={{ + color: + parseFloat(data.totalLastWeekLAB) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + moment().subtract(1, "week").startOf("week"), + moment().subtract(1, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + {t("scoreboard.labels.refinish")} + + } + value={data.totalLastWeekLAR} + valueStyle={{ + color: + parseFloat(data.totalLastWeekLAR) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + moment().subtract(1, "week").startOf("week"), + moment().subtract(1, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + {/* Prior Week */} + + + + + = + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + moment().subtract(2, "week").startOf("week"), + moment().subtract(2, "week").endOf("week"), + bodyshop + ) + + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + moment().subtract(2, "week").startOf("week"), + moment().subtract(2, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + + {t("scoreboard.labels.body")} + + } + value={data.totalPriorWeekLAB} + valueStyle={{ + color: + parseFloat(data.totalPriorWeekLAB) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyBodyTarget, + moment().subtract(2, "week").startOf("week"), + moment().subtract(2, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + {t("scoreboard.labels.refinish")} + + } + value={data.totalPriorWeekLAR} + valueStyle={{ + color: + parseFloat(data.totalPriorWeekLAR) >= + Util.WeeklyTargetHrsInPeriod( + bodyshop.scoreboard_target.dailyPaintTarget, + moment().subtract(2, "week").startOf("week"), + moment().subtract(2, "week").endOf("week"), + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + + {/* Monthly Stats */} + + {/* This Month */} + + + + + = + Util.MonthlyTargetHrs( + bodyshop.scoreboard_target.dailyBodyTarget, + bodyshop + ) + + Util.MonthlyTargetHrs( + bodyshop.scoreboard_target.dailyPaintTarget, + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + + {t("scoreboard.labels.body")} + + } + value={data.totalThisMonthLAB} + valueStyle={{ + color: + parseFloat(data.totalThisMonthLAB) >= + Util.MonthlyTargetHrs( + bodyshop.scoreboard_target.dailyBodyTarget, + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + {t("scoreboard.labels.refinish")} + + } + value={data.totalThisMonthLAR} + valueStyle={{ + color: + parseFloat(data.totalThisMonthLAR) >= + Util.MonthlyTargetHrs( + bodyshop.scoreboard_target.dailyPaintTarget, + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + {/* Last Month */} + + + + + = + Util.LastMonthTargetHrs( + bodyshop.scoreboard_target.dailyBodyTarget, + bodyshop + ) + + Util.LastMonthTargetHrs( + bodyshop.scoreboard_target.dailyPaintTarget, + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + + {t("scoreboard.labels.body")} + + } + value={data.totalLastMonthLAB} + valueStyle={{ + color: + parseFloat(data.totalLastMonthLAB) >= + Util.LastMonthTargetHrs( + bodyshop.scoreboard_target.dailyBodyTarget, + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + {t("scoreboard.labels.refinish")} + + } + value={data.totalLastMonthLAR} + valueStyle={{ + color: + parseFloat(data.totalLastMonthLAR) >= + Util.LastMonthTargetHrs( + bodyshop.scoreboard_target.dailyPaintTarget, + bodyshop + ) + ? "green" + : "red", + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + {/* Efficiency Over Period */} + + + + + + + + + + + {t("scoreboard.labels.body")} + + } + value={`${data.totalEffieciencyOverPeriodLAB || 0}%`} + valueStyle={{ + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + {t("scoreboard.labels.refinish")} + + } + value={`${data.totalEffieciencyOverPeriodLAR || 0}%`} + valueStyle={{ + fontSize: statisticSize, + fontWeight: statisticWeight, + }} + /> + + + + + + + {/* Disclaimer */} + + *{t("scoreboard.labels.calendarperiod")} + + + + + ); +} diff --git a/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.targets-table.component.jsx b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.targets-table.component.jsx new file mode 100644 index 000000000..d7bab48ad --- /dev/null +++ b/client/src/components/scoreboard-timetickets-stats/scoreboard-timetickets.targets-table.component.jsx @@ -0,0 +1,285 @@ +import { CalendarOutlined } from "@ant-design/icons"; +import { Card, Col, Divider, Row, Statistic } from "antd"; +import moment from "moment"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +const rowGutter = [16, 16]; +const statSpans = { xs: 24, sm: 3 }; + +export function ScoreboardTimeticketsTargetsTable({ bodyshop }) { + const { t } = useTranslation(); + + return ( + + + + } + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} +export default connect( + mapStateToProps, + mapDispatchToProps +)(ScoreboardTimeticketsTargetsTable); diff --git a/client/src/graphql/timetickets.queries.js b/client/src/graphql/timetickets.queries.js index 985e491cc..bb90c4e9d 100644 --- a/client/src/graphql/timetickets.queries.js +++ b/client/src/graphql/timetickets.queries.js @@ -145,7 +145,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql` $fixedEnd: date! ) { timetickets( - where: { date: { _gte: $start, _lte: $end } } + where: { date: { _gte: $start, _lte: $end }, cost_center: {_neq: "timetickets.labels.shift"} } order_by: { date: desc_nulls_first } ) { actualhrs @@ -176,7 +176,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql` } } fixedperiod: timetickets( - where: { date: { _gte: $fixedStart, _lte: $fixedEnd } } + where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: {_neq: "timetickets.labels.shift"} } order_by: { date: desc_nulls_first } ) { actualhrs diff --git a/client/src/pages/scoreboard/scoreboard.page.container.jsx b/client/src/pages/scoreboard/scoreboard.page.container.jsx index ac35b6885..1ef4b273f 100644 --- a/client/src/pages/scoreboard/scoreboard.page.container.jsx +++ b/client/src/pages/scoreboard/scoreboard.page.container.jsx @@ -1,21 +1,22 @@ import Icon, { FieldTimeOutlined } from "@ant-design/icons"; import { Tabs } from "antd"; +import queryString from "query-string"; import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { FaShieldAlt } from "react-icons/fa"; import { connect } from "react-redux"; +import { useHistory, useLocation } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import ScoreboardDisplay from "../../components/scoreboard-display/scoreboard-display.component"; +import ScoreboardTimeTicketsStats from "../../components/scoreboard-timetickets-stats/scoreboard-timetickets.component"; import ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component"; import { setBreadcrumbs, setSelectedHeader, } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; -import queryString from "query-string"; -import { useHistory, useLocation } from "react-router-dom"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -71,7 +72,7 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) { tab={ - {t("scoreboard.labels.timetickets")} + {t("scoreboard.labels.timeticketsemployee")} } destroyInactiveTabPane @@ -79,6 +80,18 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) { > + + + {t("scoreboard.labels.allemployeetimetickets")} + + } + destroyInactiveTabPane + key="ticketsstats" + > + + diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 4950b5944..81be8f10c 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1114,6 +1114,7 @@ "total": "Total", "totals": "Totals", "tuesday": "Tuesday", + "tvmode": "TV Mode", "unknown": "Unknown", "username": "Username", "view": "View", @@ -2672,8 +2673,12 @@ "painthrs": "Paint Hours" }, "labels": { + "allemployeetimetickets": "All Employee Time Tickets", "asoftodaytarget": "As of Today", + "body": "Body", + "bodycharttitle": "Body Targets vs Actual", "calendarperiod": "Periods based on calendar weeks/months.", + "combinedcharttitle": "Combined Targets vs Actual", "dailyactual": "Actual (D)", "dailytarget": "Daily", "efficiencyoverperiod": "Efficiency over Selected Dates", @@ -2682,12 +2687,16 @@ "lastmonth": "Last Month", "lastweek": "Last Week", "monthlytarget": "Monthly", + "priorweek": "Prior Week", "productivestatistics": "Productive Hours Statistics", "productivetimeticketsoverdate": "Productive Hours over Selected Dates", + "refinish": "Refinish", + "refinishcharttitle": "Refinish Targets vs Actual", "targets": "Targets", "thismonth": "This Month", "thisweek": "This Week", - "timetickets": "Timetickets", + "timetickets": "Time Tickets", + "timeticketsemployee": "Time Tickets by Employee", "todateactual": "Actual (MTD)", "totaloverperiod": "Total over Selected Dates", "weeklyactual": "Actual (W)", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 2f3fca133..cc29a804e 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1114,6 +1114,7 @@ "total": "", "totals": "", "tuesday": "", + "tvmode": "", "unknown": "Desconocido", "username": "", "view": "", @@ -2672,8 +2673,12 @@ "painthrs": "" }, "labels": { + "allemployeetimetickets": "", "asoftodaytarget": "", + "body": "", + "bodycharttitle": "", "calendarperiod": "", + "combinedcharttitle": "", "dailyactual": "", "dailytarget": "", "efficiencyoverperiod": "", @@ -2682,12 +2687,16 @@ "lastmonth": "", "lastweek": "", "monthlytarget": "", + "priorweek": "", "productivestatistics": "", "productivetimeticketsoverdate": "", + "refinish": "", + "refinishcharttitle": "", "targets": "", "thismonth": "", "thisweek": "", "timetickets": "", + "timeticketsemployee": "", "todateactual": "", "totaloverperiod": "", "weeklyactual": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 4dba2e141..9fd7f94da 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1114,6 +1114,7 @@ "total": "", "totals": "", "tuesday": "", + "tvmode": "", "unknown": "Inconnu", "username": "", "view": "", @@ -2672,8 +2673,12 @@ "painthrs": "" }, "labels": { + "allemployeetimetickets": "", "asoftodaytarget": "", + "body": "", + "bodycharttitle": "", "calendarperiod": "", + "combinedcharttitle": "", "dailyactual": "", "dailytarget": "", "efficiencyoverperiod": "", @@ -2682,12 +2687,16 @@ "lastmonth": "", "lastweek": "", "monthlytarget": "", + "priorweek": "", "productivestatistics": "", "productivetimeticketsoverdate": "", + "refinish": "", + "refinishcharttitle": "", "targets": "", "thismonth": "", "thisweek": "", "timetickets": "", + "timeticketsemployee": "", "todateactual": "", "totaloverperiod": "", "weeklyactual": "",