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": "",