IO-1911 Timetickets Scoreboard.
This commit is contained in:
@@ -40827,6 +40827,69 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>jobs</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>lastmonth</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>lastweek</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>monthlytarget</name>
|
<name>monthlytarget</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -40848,6 +40911,48 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>productivestatistics</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>productivetimeticketsoverdate</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>targets</name>
|
<name>targets</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -40869,6 +40974,69 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>thismonth</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>thisweek</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>timetickets</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>todateactual</name>
|
<name>todateactual</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -40890,6 +41058,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>totaloverperiod</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>weeklyactual</name>
|
<name>weeklyactual</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -49,11 +49,6 @@ export const ListOfDaysInCurrentMonth = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ListDaysBetween = ({ start, end }) => {
|
export const ListDaysBetween = ({ start, end }) => {
|
||||||
console.log(
|
|
||||||
"🚀 ~ file: scoreboard-targets-table.util.js ~ line 52 ~ start, end",
|
|
||||||
start,
|
|
||||||
end
|
|
||||||
);
|
|
||||||
const days = [];
|
const days = [];
|
||||||
const dateStart = moment(start);
|
const dateStart = moment(start);
|
||||||
const dateEnd = moment(end);
|
const dateEnd = moment(end);
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
import moment from "moment";
|
import React from "react";
|
||||||
import React, { useMemo } from "react";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {
|
import {
|
||||||
Area,
|
|
||||||
Bar,
|
Bar,
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
ComposedChart,
|
ComposedChart,
|
||||||
Legend,
|
Legend,
|
||||||
Line,
|
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
XAxis,
|
XAxis,
|
||||||
@@ -16,9 +14,7 @@ import {
|
|||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
import TimeTicketsDatesSelector from "../ticket-tickets-dates-selector/time-tickets-dates-selector.component";
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
const graphProps = {
|
const graphProps = {
|
||||||
strokeWidth: 3,
|
strokeWidth: 3,
|
||||||
};
|
};
|
||||||
@@ -35,86 +31,16 @@ export default connect(
|
|||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(ScoreboardTicketsBar);
|
)(ScoreboardTicketsBar);
|
||||||
|
|
||||||
export function ScoreboardTicketsBar({ start, end, timetickets, bodyshop }) {
|
export function ScoreboardTicketsBar({ data, bodyshop }) {
|
||||||
console.log(
|
const { t } = useTranslation();
|
||||||
"🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 39 ~ start, end,",
|
|
||||||
start,
|
|
||||||
end
|
|
||||||
);
|
|
||||||
const listOfBusDays = Utils.ListOfDaysInCurrentMonth();
|
|
||||||
|
|
||||||
const data = useMemo(() => {
|
|
||||||
const ticketsGroupedByDate = _.groupBy(timetickets, "date");
|
|
||||||
|
|
||||||
const listOfDays = Utils.ListDaysBetween({ start, end });
|
|
||||||
console.log(
|
|
||||||
"🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 45 ~ listOfDays",
|
|
||||||
listOfDays
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"🚀 ~ file: scoreboard-timetickets.bar.component.jsx ~ line 43 ~ groupedByDate",
|
|
||||||
ticketsGroupedByDate
|
|
||||||
);
|
|
||||||
|
|
||||||
const ret = [];
|
|
||||||
listOfDays.forEach((day) => {
|
|
||||||
const r = {
|
|
||||||
date: day,
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [timetickets]);
|
|
||||||
// const data = listOfBusDays.reduce((acc, val) => {
|
|
||||||
// //Sum up the current day.
|
|
||||||
|
|
||||||
// const groupedbyDate = _.groupBy(data, "date");
|
|
||||||
// let dayhrs;
|
|
||||||
// if (!!sbEntriesByDate[val]) {
|
|
||||||
// dayhrs = sbEntriesByDate[val].reduce(
|
|
||||||
// (dayAcc, dayVal) => {
|
|
||||||
// return {
|
|
||||||
// bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
|
|
||||||
// painthrs: dayAcc.painthrs + dayVal.painthrs,
|
|
||||||
// };
|
|
||||||
// },
|
|
||||||
// { bodyhrs: 0, painthrs: 0 }
|
|
||||||
// );
|
|
||||||
// } else {
|
|
||||||
// dayhrs = {
|
|
||||||
// bodyhrs: 0,
|
|
||||||
// painthrs: 0,
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const theValue = {
|
|
||||||
// date: moment(val).format("D ddd"),
|
|
||||||
// paintHrs: _.round(dayhrs.painthrs, 1),
|
|
||||||
// bodyHrs: _.round(dayhrs.bodyhrs, 1),
|
|
||||||
// accTargetHrs: _.round(
|
|
||||||
// Utils.AsOfDateTargetHours(
|
|
||||||
// bodyshop.scoreboard_target.dailyBodyTarget +
|
|
||||||
// bodyshop.scoreboard_target.dailyPaintTarget,
|
|
||||||
// val
|
|
||||||
// ),
|
|
||||||
// 1
|
|
||||||
// ),
|
|
||||||
// accHrs: _.round(
|
|
||||||
// acc.length > 0
|
|
||||||
// ? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs
|
|
||||||
// : dayhrs.painthrs + dayhrs.bodyhrs,
|
|
||||||
// 1
|
|
||||||
// ),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return [...acc, theValue];
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card
|
||||||
|
title={t("scoreboard.labels.productivetimeticketsoverdate")}
|
||||||
|
extra={<TimeTicketsDatesSelector />}
|
||||||
|
>
|
||||||
<ResponsiveContainer width="100%" height={475}>
|
<ResponsiveContainer width="100%" height={475}>
|
||||||
<ComposedChart
|
<ComposedChart
|
||||||
data={data}
|
data={data.chartData}
|
||||||
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
||||||
>
|
>
|
||||||
<CartesianGrid stroke="#f5f5f5" />
|
<CartesianGrid stroke="#f5f5f5" />
|
||||||
@@ -122,34 +48,32 @@ export function ScoreboardTicketsBar({ start, end, timetickets, bodyshop }) {
|
|||||||
<YAxis strokeWidth={graphProps.strokeWidth} />
|
<YAxis strokeWidth={graphProps.strokeWidth} />
|
||||||
<Tooltip />
|
<Tooltip />
|
||||||
<Legend />
|
<Legend />
|
||||||
<Area
|
{/* <Area
|
||||||
type="monotone"
|
type="monotone"
|
||||||
name="Accumulated Hours"
|
name="Accumulated Hours"
|
||||||
dataKey="accHrs"
|
dataKey="accHrs"
|
||||||
fill="lightgreen"
|
fill="lightgreen"
|
||||||
stroke="green"
|
stroke="green"
|
||||||
/>
|
/> */}
|
||||||
<Bar
|
{data &&
|
||||||
name="Body Hours"
|
data.employees.map((e, idx) => (
|
||||||
dataKey="bodyHrs"
|
<Bar
|
||||||
stackId="day"
|
key={`${e}productive`}
|
||||||
barSize={20}
|
name={e}
|
||||||
fill="darkblue"
|
dataKey={`employees.${e}.productive`}
|
||||||
/>
|
stackId="productive"
|
||||||
<Bar
|
// barSize={20}
|
||||||
name="Paint Hours"
|
fill={data.colors[idx]}
|
||||||
dataKey="paintHrs"
|
/>
|
||||||
stackId="day"
|
))}
|
||||||
barSize={20}
|
|
||||||
fill="darkred"
|
{/* <Line
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
name="Target Hours"
|
name="Target Hours"
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="accTargetHrs"
|
dataKey="accTargetHrs"
|
||||||
stroke="#ff7300"
|
stroke="#ff7300"
|
||||||
strokeWidth={graphProps.strokeWidth}
|
strokeWidth={graphProps.strokeWidth}
|
||||||
/>
|
/> */}
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import React from "react";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import queryString from "query-string";
|
|
||||||
import moment from "moment";
|
|
||||||
import { useQuery } from "@apollo/client";
|
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 { useLocation } from "react-router-dom";
|
||||||
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||||
import ScoreboardTicketsBar from "./scoreboard-timetickets.bar.component";
|
import ScoreboardTicketsBar from "./scoreboard-timetickets.bar.component";
|
||||||
|
import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
|
||||||
|
|
||||||
export default function ScoreboardTimeTickets() {
|
export default function ScoreboardTimeTickets() {
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
@@ -15,25 +19,230 @@ export default function ScoreboardTimeTickets() {
|
|||||||
? moment(start)
|
? moment(start)
|
||||||
: moment().startOf("week").subtract(7, "days");
|
: moment().startOf("week").subtract(7, "days");
|
||||||
const endDate = end ? moment(end) : moment().endOf("week");
|
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 allDates = [
|
||||||
|
endOfThisMonth,
|
||||||
|
startofthisMonth,
|
||||||
|
endOfLastmonth,
|
||||||
|
startOfLastmonth,
|
||||||
|
endOfThisWeek,
|
||||||
|
startOfThisWeek,
|
||||||
|
endOfLastWeek,
|
||||||
|
startOfLastWeek,
|
||||||
|
];
|
||||||
|
const start = moment.min(allDates);
|
||||||
|
const end = moment.max(allDates);
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
endOfThisMonth,
|
||||||
|
startofthisMonth,
|
||||||
|
endOfLastmonth,
|
||||||
|
startOfLastmonth,
|
||||||
|
endOfThisWeek,
|
||||||
|
startOfThisWeek,
|
||||||
|
endOfLastWeek,
|
||||||
|
startOfLastWeek,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
|
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
|
||||||
variables: {
|
variables: {
|
||||||
start: startDate,
|
start: startDate.format("YYYY-MM-DD"),
|
||||||
end: endDate,
|
end: endDate.format("YYYY-MM-DD"),
|
||||||
|
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
|
||||||
|
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
|
||||||
},
|
},
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
|
pollInterval: 60000,
|
||||||
|
skip: !fixedPeriods,
|
||||||
});
|
});
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
|
||||||
|
|
||||||
|
const calculatedData = useMemo(() => {
|
||||||
|
if (!data) return [];
|
||||||
|
const ret = {
|
||||||
|
totalThisWeek: 0,
|
||||||
|
totalLastWeek: 0,
|
||||||
|
totalThisMonth: 0,
|
||||||
|
totalLastMonth: 0,
|
||||||
|
totalOverPeriod: 0,
|
||||||
|
employees: {},
|
||||||
|
};
|
||||||
|
data.fixedperiod.forEach((ticket) => {
|
||||||
|
const ticketDate = moment(ticket.date);
|
||||||
|
|
||||||
|
if (!ret.employees[ticket.employee.employee_number]) {
|
||||||
|
ret.employees[ticket.employee.employee_number] = {
|
||||||
|
totalThisWeek: 0,
|
||||||
|
totalLastWeek: 0,
|
||||||
|
totalThisMonth: 0,
|
||||||
|
totalLastMonth: 0,
|
||||||
|
totalOverPeriod: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ticketDate.isBetween(
|
||||||
|
fixedPeriods.startOfThisWeek,
|
||||||
|
fixedPeriods.endOfThisWeek,
|
||||||
|
undefined,
|
||||||
|
"[]"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
|
||||||
|
ret.employees[ticket.employee.employee_number].totalThisWeek =
|
||||||
|
ret.employees[ticket.employee.employee_number].totalThisWeek +
|
||||||
|
ticket.productivehrs;
|
||||||
|
} else if (
|
||||||
|
ticketDate.isBetween(
|
||||||
|
fixedPeriods.startOfLastWeek,
|
||||||
|
fixedPeriods.endOfLastWeek,
|
||||||
|
undefined,
|
||||||
|
"[]"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
|
||||||
|
ret.employees[ticket.employee.employee_number].totalLastWeek =
|
||||||
|
ret.employees[ticket.employee.employee_number].totalLastWeek +
|
||||||
|
ticket.productivehrs;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
ticketDate.isBetween(
|
||||||
|
fixedPeriods.startofthisMonth,
|
||||||
|
fixedPeriods.endOfThisMonth,
|
||||||
|
undefined,
|
||||||
|
"[]"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
|
||||||
|
ret.employees[ticket.employee.employee_number].totalThisMonth =
|
||||||
|
ret.employees[ticket.employee.employee_number].totalThisMonth +
|
||||||
|
ticket.productivehrs;
|
||||||
|
} else if (
|
||||||
|
ticketDate.isBetween(
|
||||||
|
fixedPeriods.startOfLastmonth,
|
||||||
|
fixedPeriods.endOfLastmonth,
|
||||||
|
undefined,
|
||||||
|
"[]"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
|
||||||
|
ret.employees[ticket.employee.employee_number].totalLastMonth =
|
||||||
|
ret.employees[ticket.employee.employee_number].totalLastMonth +
|
||||||
|
ticket.productivehrs;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
|
||||||
|
const listOfDays = Utils.ListDaysBetween({
|
||||||
|
start: startDate,
|
||||||
|
end: endDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const employees = [];
|
||||||
|
const ret2 = [];
|
||||||
|
let totals = {
|
||||||
|
totalproductive: 0,
|
||||||
|
totalactual: 0,
|
||||||
|
employees: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
listOfDays.forEach((day) => {
|
||||||
|
const r = {
|
||||||
|
date: moment(day).format("MM/DD"),
|
||||||
|
actualtotal: 0,
|
||||||
|
productivetotal: 0,
|
||||||
|
employees: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ticketsGroupedByDate[day]) {
|
||||||
|
ticketsGroupedByDate[day].forEach((ticket) => {
|
||||||
|
r.actualtotal = r.actualtotal + ticket.actualhrs;
|
||||||
|
r.productivetotal = r.productivetotal + ticket.productivehrs;
|
||||||
|
totals.totalactual = totals.totalactual + ticket.actualhrs;
|
||||||
|
totals.totalproductive =
|
||||||
|
totals.totalproductive + ticket.productivehrs;
|
||||||
|
|
||||||
|
employees.push(ticket.employee.employee_number);
|
||||||
|
//Add to table data.
|
||||||
|
ret.employees[ticket.employee.employee_number].totalOverPeriod =
|
||||||
|
ret.employees[ticket.employee.employee_number].totalOverPeriod +
|
||||||
|
ticket.productivehrs;
|
||||||
|
|
||||||
|
if (!totals.employees[ticket.employee.employee_number])
|
||||||
|
totals.employees[ticket.employee.employee_number] = {
|
||||||
|
totalactual: 0,
|
||||||
|
totalproductive: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!r.employees[ticket.employee.employee_number])
|
||||||
|
r.employees[ticket.employee.employee_number] = {
|
||||||
|
actual: 0,
|
||||||
|
productive: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
//Add to totals.
|
||||||
|
totals.employees[ticket.employee.employee_number].totalproductive =
|
||||||
|
totals.employees[ticket.employee.employee_number].totalproductive +
|
||||||
|
ticket.productivehrs;
|
||||||
|
|
||||||
|
totals.employees[ticket.employee.employee_number].totalactual =
|
||||||
|
totals.employees[ticket.employee.employee_number].totalactual +
|
||||||
|
ticket.actualhrs;
|
||||||
|
//Add to dailys.
|
||||||
|
r.employees[ticket.employee.employee_number].productive =
|
||||||
|
r.employees[ticket.employee.employee_number].productive +
|
||||||
|
ticket.productivehrs;
|
||||||
|
|
||||||
|
r.employees[ticket.employee.employee_number].actual =
|
||||||
|
r.employees[ticket.employee.employee_number].actual +
|
||||||
|
ticket.actualhrs;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ret2.push(r);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
fixed: ret,
|
||||||
|
timeperiod: {
|
||||||
|
totals,
|
||||||
|
chartData: ret2,
|
||||||
|
employees: _.uniq(employees),
|
||||||
|
colors: getColorArray(employees.length),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [fixedPeriods, data, startDate, endDate]);
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
if (loading) return <LoadingSpinner />;
|
||||||
return (
|
return (
|
||||||
<div>
|
<Row gutter={[16, 16]}>
|
||||||
<TimeTicketsDatesSelector />
|
<Col span={24}>
|
||||||
<ScoreboardTicketsBar
|
<ScoreboardTicketsStats data={calculatedData.fixed} />
|
||||||
start={startDate}
|
</Col>
|
||||||
end={endDate}
|
<Col span={24}>
|
||||||
timetickets={data ? data.timetickets : []}
|
<ScoreboardTicketsBar
|
||||||
/>
|
start={startDate}
|
||||||
</div>
|
end={endDate}
|
||||||
|
data={calculatedData.timeperiod}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,3 +252,50 @@ export default function ScoreboardTimeTickets() {
|
|||||||
//Hours produced in last 7 days
|
//Hours produced in last 7 days
|
||||||
//Hours produced for time period by day
|
//Hours produced for time period by day
|
||||||
//Hours produced by employee by day for time period.
|
//Hours produced by employee by day for time period.
|
||||||
|
|
||||||
|
function getColorArray(num) {
|
||||||
|
return [
|
||||||
|
"#3366cc",
|
||||||
|
"#dc3912",
|
||||||
|
"#ff9900",
|
||||||
|
"#109618",
|
||||||
|
"#990099",
|
||||||
|
"#0099c6",
|
||||||
|
"#dd4477",
|
||||||
|
"#66aa00",
|
||||||
|
"#b82e2e",
|
||||||
|
"#316395",
|
||||||
|
"#3366cc",
|
||||||
|
"#994499",
|
||||||
|
"#22aa99",
|
||||||
|
"#aaaa11",
|
||||||
|
"#6633cc",
|
||||||
|
"#e67300",
|
||||||
|
"#8b0707",
|
||||||
|
"#651067",
|
||||||
|
"#329262",
|
||||||
|
"#5574a6",
|
||||||
|
"#3b3eac",
|
||||||
|
"#b77322",
|
||||||
|
"#16d620",
|
||||||
|
"#b91383",
|
||||||
|
"#f4359e",
|
||||||
|
"#9c5935",
|
||||||
|
"#a9c413",
|
||||||
|
"#2a778d",
|
||||||
|
"#668d1c",
|
||||||
|
"#bea413",
|
||||||
|
"#0c5922",
|
||||||
|
"#743411",
|
||||||
|
];
|
||||||
|
// var result = [];
|
||||||
|
// for (var i = 0; i < num; i += 1) {
|
||||||
|
// var letters = "0123456789ABCDEF".split("");
|
||||||
|
// var color = "#";
|
||||||
|
// for (var j = 0; j < 6; j += 1) {
|
||||||
|
// color += letters[Math.floor(Math.random() * 16)];
|
||||||
|
// }
|
||||||
|
// result.push(color);
|
||||||
|
// }
|
||||||
|
// return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { Card, Col, Row, Space, Statistic, Table } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ScoreboardTicketsStats);
|
||||||
|
|
||||||
|
export function ScoreboardTicketsStats({ data, bodyshop }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("employees.fields.employee_number"),
|
||||||
|
dataIndex: "employee_number",
|
||||||
|
key: "employee_number",
|
||||||
|
sorter: (a, b) => a.employee_number - b.employee_number,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("scoreboard.labels.thisweek"),
|
||||||
|
dataIndex: "totalThisWeek",
|
||||||
|
key: "totalThisWeek",
|
||||||
|
sorter: (a, b) => a.totalThisWeek - b.totalThisWeek,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("scoreboard.labels.lastweek"),
|
||||||
|
dataIndex: "totalLastWeek",
|
||||||
|
key: "totalLastWeek",
|
||||||
|
sorter: (a, b) => a.totalLastWeek - b.totalLastWeek,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("scoreboard.labels.thismonth"),
|
||||||
|
dataIndex: "totalThisMonth",
|
||||||
|
key: "totalThisMonth",
|
||||||
|
sorter: (a, b) => a.totalThisMonth - b.totalThisMonth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("scoreboard.labels.lastmonth"),
|
||||||
|
dataIndex: "totalLastMonth",
|
||||||
|
key: "totalLastMonth",
|
||||||
|
sorter: (a, b) => a.totalLastMonth - b.totalLastMonth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("scoreboard.labels.totaloverperiod"),
|
||||||
|
dataIndex: "totalOverPeriod",
|
||||||
|
key: "totalOverPeriod",
|
||||||
|
sorter: (a, b) => a.totalOverPeriod - b.totalOverPeriod,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const tableData = data
|
||||||
|
? Object.keys(data.employees).map((key) => {
|
||||||
|
return { employee_number: key, ...data.employees[key] };
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title={t("scoreboard.labels.productivestatistics")}>
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
<Col md={24} lg={6}>
|
||||||
|
<Space wrap>
|
||||||
|
<Statistic
|
||||||
|
title={t("scoreboard.labels.lastweek")}
|
||||||
|
value={data.totalLastWeek.toFixed(1)}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("scoreboard.labels.thisweek")}
|
||||||
|
value={data.totalThisWeek.toFixed(1)}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("scoreboard.labels.lastmonth")}
|
||||||
|
value={data.totalLastMonth.toFixed(1)}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("scoreboard.labels.thismonth")}
|
||||||
|
value={data.totalThisMonth.toFixed(1)}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
<Col md={24} lg={18}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={tableData}
|
||||||
|
id="employee_number"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ export default function TimeTicketsDatesSelector() {
|
|||||||
if (!!start && !!end) {
|
if (!!start && !!end) {
|
||||||
history.push({
|
history.push({
|
||||||
search: queryString.stringify({
|
search: queryString.stringify({
|
||||||
|
...searchParams,
|
||||||
start: start.format("YYYY-MM-DD"),
|
start: start.format("YYYY-MM-DD"),
|
||||||
end: end.format("YYYY-MM-DD"),
|
end: end.format("YYYY-MM-DD"),
|
||||||
}),
|
}),
|
||||||
@@ -25,6 +26,7 @@ export default function TimeTicketsDatesSelector() {
|
|||||||
} else {
|
} else {
|
||||||
history.push({
|
history.push({
|
||||||
search: queryString.stringify({
|
search: queryString.stringify({
|
||||||
|
...searchParams,
|
||||||
start: null,
|
start: null,
|
||||||
end: null,
|
end: null,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
|||||||
flat_rate
|
flat_rate
|
||||||
clockon
|
clockon
|
||||||
clockoff
|
clockoff
|
||||||
|
rate
|
||||||
employee {
|
employee {
|
||||||
id
|
id
|
||||||
first_name
|
first_name
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ export const QUERY_TICKETS_BY_JOBID = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||||
query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) {
|
query QUERY_TIME_TICKETS_IN_RANGE(
|
||||||
|
$start: date!
|
||||||
|
$end: date!
|
||||||
|
$fixedStart: date!
|
||||||
|
$fixedEnd: date!
|
||||||
|
) {
|
||||||
timetickets(
|
timetickets(
|
||||||
where: { date: { _gte: $start, _lte: $end } }
|
where: { date: { _gte: $start, _lte: $end } }
|
||||||
order_by: { date: desc_nulls_first }
|
order_by: { date: desc_nulls_first }
|
||||||
@@ -56,6 +61,35 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
|||||||
last_name
|
last_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fixedperiod: timetickets(
|
||||||
|
where: { date: { _gte: $fixedStart, _lte: $fixedEnd } }
|
||||||
|
order_by: { date: desc_nulls_first }
|
||||||
|
) {
|
||||||
|
actualhrs
|
||||||
|
ciecacode
|
||||||
|
clockoff
|
||||||
|
clockon
|
||||||
|
cost_center
|
||||||
|
created_at
|
||||||
|
date
|
||||||
|
id
|
||||||
|
rate
|
||||||
|
productivehrs
|
||||||
|
memo
|
||||||
|
jobid
|
||||||
|
flat_rate
|
||||||
|
job {
|
||||||
|
id
|
||||||
|
ro_number
|
||||||
|
}
|
||||||
|
employeeid
|
||||||
|
employee {
|
||||||
|
id
|
||||||
|
employee_number
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon, { BarsOutlined } from "@ant-design/icons";
|
||||||
import { Tabs } from "antd";
|
import { Tabs } from "antd";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FaShieldAlt } from "react-icons/fa";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
import ScoreboardDisplay from "../../components/scoreboard-display/scoreboard-display.component";
|
import ScoreboardDisplay from "../../components/scoreboard-display/scoreboard-display.component";
|
||||||
|
import ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component";
|
||||||
import {
|
import {
|
||||||
setBreadcrumbs,
|
setBreadcrumbs,
|
||||||
setSelectedHeader,
|
setSelectedHeader,
|
||||||
} from "../../redux/application/application.actions";
|
} from "../../redux/application/application.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { FaHardHat, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
|
import queryString from "query-string";
|
||||||
import ScoreboardTimeTickets from "../../components/scoreboard-timetickets/scoreboard-timetickets.component";
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -26,7 +28,9 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
|
|
||||||
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
|
const { tab } = searchParams;
|
||||||
|
const history = useHistory();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = t("titles.scoreboard");
|
document.title = t("titles.scoreboard");
|
||||||
setSelectedHeader("scoreboard");
|
setSelectedHeader("scoreboard");
|
||||||
@@ -41,12 +45,21 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
return (
|
return (
|
||||||
<FeatureWrapper featureName="scoreboard">
|
<FeatureWrapper featureName="scoreboard">
|
||||||
<RbacWrapper action="scoreboard:view">
|
<RbacWrapper action="scoreboard:view">
|
||||||
<Tabs>
|
<Tabs
|
||||||
|
activeKey={tab || "sb"}
|
||||||
|
destroyInactiveTabPane
|
||||||
|
onChange={(key) => {
|
||||||
|
searchParams.tab = key;
|
||||||
|
history.push({
|
||||||
|
search: queryString.stringify(searchParams),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
tab={
|
tab={
|
||||||
<span>
|
<span>
|
||||||
<Icon component={FaShieldAlt} />
|
<Icon component={FaShieldAlt} />
|
||||||
{t("menus.jobsdetail.general")}
|
{t("scoreboard.labels.jobs")}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
destroyInactiveTabPane
|
destroyInactiveTabPane
|
||||||
@@ -57,8 +70,8 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
tab={
|
tab={
|
||||||
<span>
|
<span>
|
||||||
<Icon component={FaShieldAlt} />
|
<BarsOutlined />
|
||||||
{t("menus.jobsdetail.general")}
|
{t("scoreboard.labels.timetickets")}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
destroyInactiveTabPane
|
destroyInactiveTabPane
|
||||||
|
|||||||
@@ -2426,9 +2426,18 @@
|
|||||||
"asoftodaytarget": "As of Today",
|
"asoftodaytarget": "As of Today",
|
||||||
"dailyactual": "Actual (D)",
|
"dailyactual": "Actual (D)",
|
||||||
"dailytarget": "Daily",
|
"dailytarget": "Daily",
|
||||||
|
"jobs": "Jobs",
|
||||||
|
"lastmonth": "Last Month",
|
||||||
|
"lastweek": "Last Week",
|
||||||
"monthlytarget": "Monthly",
|
"monthlytarget": "Monthly",
|
||||||
|
"productivestatistics": "Productive Hours Statistics",
|
||||||
|
"productivetimeticketsoverdate": "Productive Hours over Selected Dates",
|
||||||
"targets": "Targets",
|
"targets": "Targets",
|
||||||
|
"thismonth": "This Month",
|
||||||
|
"thisweek": "This Week",
|
||||||
|
"timetickets": "Timetickets",
|
||||||
"todateactual": "Actual (MTD)",
|
"todateactual": "Actual (MTD)",
|
||||||
|
"totaloverperiod": "Total Period",
|
||||||
"weeklyactual": "Actual (W)",
|
"weeklyactual": "Actual (W)",
|
||||||
"weeklytarget": "Weekly",
|
"weeklytarget": "Weekly",
|
||||||
"workingdays": "Working Days / Month"
|
"workingdays": "Working Days / Month"
|
||||||
|
|||||||
@@ -2426,9 +2426,18 @@
|
|||||||
"asoftodaytarget": "",
|
"asoftodaytarget": "",
|
||||||
"dailyactual": "",
|
"dailyactual": "",
|
||||||
"dailytarget": "",
|
"dailytarget": "",
|
||||||
|
"jobs": "",
|
||||||
|
"lastmonth": "",
|
||||||
|
"lastweek": "",
|
||||||
"monthlytarget": "",
|
"monthlytarget": "",
|
||||||
|
"productivestatistics": "",
|
||||||
|
"productivetimeticketsoverdate": "",
|
||||||
"targets": "",
|
"targets": "",
|
||||||
|
"thismonth": "",
|
||||||
|
"thisweek": "",
|
||||||
|
"timetickets": "",
|
||||||
"todateactual": "",
|
"todateactual": "",
|
||||||
|
"totaloverperiod": "",
|
||||||
"weeklyactual": "",
|
"weeklyactual": "",
|
||||||
"weeklytarget": "",
|
"weeklytarget": "",
|
||||||
"workingdays": ""
|
"workingdays": ""
|
||||||
|
|||||||
@@ -2426,9 +2426,18 @@
|
|||||||
"asoftodaytarget": "",
|
"asoftodaytarget": "",
|
||||||
"dailyactual": "",
|
"dailyactual": "",
|
||||||
"dailytarget": "",
|
"dailytarget": "",
|
||||||
|
"jobs": "",
|
||||||
|
"lastmonth": "",
|
||||||
|
"lastweek": "",
|
||||||
"monthlytarget": "",
|
"monthlytarget": "",
|
||||||
|
"productivestatistics": "",
|
||||||
|
"productivetimeticketsoverdate": "",
|
||||||
"targets": "",
|
"targets": "",
|
||||||
|
"thismonth": "",
|
||||||
|
"thisweek": "",
|
||||||
|
"timetickets": "",
|
||||||
"todateactual": "",
|
"todateactual": "",
|
||||||
|
"totaloverperiod": "",
|
||||||
"weeklyactual": "",
|
"weeklyactual": "",
|
||||||
"weeklytarget": "",
|
"weeklytarget": "",
|
||||||
"workingdays": ""
|
"workingdays": ""
|
||||||
|
|||||||
Reference in New Issue
Block a user