@@ -1,77 +1,78 @@
|
||||
import { Card } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import {Card} from "antd";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {
|
||||
Bar,
|
||||
CartesianGrid,
|
||||
ComposedChart,
|
||||
Legend,
|
||||
ReferenceLine,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Bar,
|
||||
CartesianGrid,
|
||||
ComposedChart,
|
||||
Legend,
|
||||
ReferenceLine,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import TimeTicketsDatesSelector from "../ticket-tickets-dates-selector/time-tickets-dates-selector.component";
|
||||
|
||||
const graphProps = {
|
||||
strokeWidth: 3,
|
||||
strokeWidth: 3,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScoreboardTicketsBar);
|
||||
|
||||
export function ScoreboardTicketsBar({ data, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card
|
||||
title={t("scoreboard.labels.productivetimeticketsoverdate")}
|
||||
extra={<TimeTicketsDatesSelector />}
|
||||
>
|
||||
<ResponsiveContainer width="100%" height={475}>
|
||||
<ComposedChart
|
||||
data={data.chartData}
|
||||
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
||||
export function ScoreboardTicketsBar({data, bodyshop}) {
|
||||
const {t} = useTranslation();
|
||||
return (
|
||||
<Card
|
||||
title={t("scoreboard.labels.productivetimeticketsoverdate")}
|
||||
extra={<TimeTicketsDatesSelector/>}
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5" />
|
||||
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
|
||||
<YAxis strokeWidth={graphProps.strokeWidth} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<ReferenceLine
|
||||
y={
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget
|
||||
}
|
||||
label="Daily Target"
|
||||
stroke="red"
|
||||
strokeDasharray="3 3"
|
||||
/>
|
||||
{data &&
|
||||
data.employees.map((e, idx) => (
|
||||
<Bar
|
||||
key={`${e}productive`}
|
||||
name={e}
|
||||
dataKey={`employees.${e}.productive`}
|
||||
stackId="productive"
|
||||
// barSize={20}
|
||||
fill={data.colors[idx]}
|
||||
>
|
||||
{/* <LabelList position="top" /> */}
|
||||
</Bar>
|
||||
))}
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
);
|
||||
<ResponsiveContainer width="100%" height={475}>
|
||||
<ComposedChart
|
||||
data={data.chartData}
|
||||
margin={{top: 20, right: 20, bottom: 20, left: 20}}
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5"/>
|
||||
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth}/>
|
||||
<YAxis strokeWidth={graphProps.strokeWidth}/>
|
||||
<Tooltip/>
|
||||
<Legend/>
|
||||
<ReferenceLine
|
||||
y={
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget
|
||||
}
|
||||
label="Daily Target"
|
||||
stroke="red"
|
||||
strokeDasharray="3 3"
|
||||
/>
|
||||
{data &&
|
||||
data.employees.map((e, idx) => (
|
||||
<Bar
|
||||
key={`${e}productive`}
|
||||
name={e}
|
||||
dataKey={`employees.${e}.productive`}
|
||||
stackId="productive"
|
||||
// barSize={20}
|
||||
fill={data.colors[idx]}
|
||||
>
|
||||
{/* <LabelList position="top" /> */}
|
||||
</Bar>
|
||||
))}
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Row } from "antd";
|
||||
import {useQuery} from "@apollo/client";
|
||||
import {Col, Row} from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
|
||||
import React, {useMemo} from "react";
|
||||
import {useLocation} from "react-router-dom";
|
||||
import {QUERY_TIME_TICKETS_IN_RANGE_SB} from "../../graphql/timetickets.queries";
|
||||
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";
|
||||
@@ -13,273 +13,273 @@ import ScoreboardTicketsBar from "./scoreboard-timetickets.bar.component";
|
||||
import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
|
||||
|
||||
export default function ScoreboardTimeTickets() {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { start, end } = searchParams;
|
||||
const startDate = start
|
||||
? dayjs(start)
|
||||
: dayjs().startOf("week").subtract(7, "day");
|
||||
const endDate = end ? dayjs(end) : dayjs().endOf("week");
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const {start, end} = searchParams;
|
||||
const startDate = start
|
||||
? dayjs(start)
|
||||
: dayjs().startOf("week").subtract(7, "day");
|
||||
const endDate = end ? dayjs(end) : dayjs().endOf("week");
|
||||
|
||||
const fixedPeriods = useMemo(() => {
|
||||
const endOfThisMonth = dayjs().endOf("month");
|
||||
const startofthisMonth = dayjs().startOf("month");
|
||||
const fixedPeriods = useMemo(() => {
|
||||
const endOfThisMonth = dayjs().endOf("month");
|
||||
const startofthisMonth = dayjs().startOf("month");
|
||||
|
||||
const endOfLastmonth = dayjs().subtract(1, "month").endOf("month");
|
||||
const startOfLastmonth = dayjs().subtract(1, "month").startOf("month");
|
||||
const endOfLastmonth = dayjs().subtract(1, "month").endOf("month");
|
||||
const startOfLastmonth = dayjs().subtract(1, "month").startOf("month");
|
||||
|
||||
const endOfThisWeek = dayjs().endOf("week");
|
||||
const startOfThisWeek = dayjs().startOf("week");
|
||||
const endOfThisWeek = dayjs().endOf("week");
|
||||
const startOfThisWeek = dayjs().startOf("week");
|
||||
|
||||
const endOfLastWeek = dayjs().subtract(1, "week").endOf("week");
|
||||
const startOfLastWeek = dayjs().subtract(1, "week").startOf("week");
|
||||
const endOfLastWeek = dayjs().subtract(1, "week").endOf("week");
|
||||
const startOfLastWeek = dayjs().subtract(1, "week").startOf("week");
|
||||
|
||||
const allDates = [
|
||||
endOfThisMonth,
|
||||
startofthisMonth,
|
||||
endOfLastmonth,
|
||||
startOfLastmonth,
|
||||
endOfThisWeek,
|
||||
startOfThisWeek,
|
||||
endOfLastWeek,
|
||||
startOfLastWeek,
|
||||
];
|
||||
const start = dayjs.min(allDates);
|
||||
const end = dayjs.max(allDates);
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
endOfThisMonth,
|
||||
startofthisMonth,
|
||||
endOfLastmonth,
|
||||
startOfLastmonth,
|
||||
endOfThisWeek,
|
||||
startOfThisWeek,
|
||||
endOfLastWeek,
|
||||
startOfLastWeek,
|
||||
};
|
||||
}, []);
|
||||
|
||||
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"),
|
||||
jobStart: startDate,
|
||||
jobEnd: endDate,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
pollInterval: 60000,
|
||||
skip: !fixedPeriods,
|
||||
});
|
||||
|
||||
const calculatedData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
const ret = {
|
||||
totalThisWeek: 0,
|
||||
totalLastWeek: 0,
|
||||
totalThisMonth: 0,
|
||||
totalLastMonth: 0,
|
||||
totalOverPeriod: 0,
|
||||
actualTotalOverPeriod: 0,
|
||||
totalEffieciencyOverPeriod: 0,
|
||||
employees: {},
|
||||
};
|
||||
data.fixedperiod.forEach((ticket) => {
|
||||
const ticketDate = dayjs(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,
|
||||
actualTotalOverPeriod: 0,
|
||||
totalEffieciencyOverPeriod: 0,
|
||||
const allDates = [
|
||||
endOfThisMonth,
|
||||
startofthisMonth,
|
||||
endOfLastmonth,
|
||||
startOfLastmonth,
|
||||
endOfThisWeek,
|
||||
startOfThisWeek,
|
||||
endOfLastWeek,
|
||||
startOfLastWeek,
|
||||
];
|
||||
const start = dayjs.min(allDates);
|
||||
const end = dayjs.max(allDates);
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
endOfThisMonth,
|
||||
startofthisMonth,
|
||||
endOfLastmonth,
|
||||
startOfLastmonth,
|
||||
endOfThisWeek,
|
||||
startOfThisWeek,
|
||||
endOfLastWeek,
|
||||
startOfLastWeek,
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
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: dayjs(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;
|
||||
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod =
|
||||
ret.employees[ticket.employee.employee_number]
|
||||
.actualTotalOverPeriod + (ticket.actualhrs || 0);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
// Add total efficiency of employees
|
||||
const totalActualAndProductive = Object.keys(ret.employees)
|
||||
.map((key) => {
|
||||
return { employee_number: key, ...ret.employees[key] };
|
||||
})
|
||||
.reduce(
|
||||
(acc, e) => {
|
||||
return {
|
||||
totalOverPeriod: acc.totalOverPeriod + e.totalOverPeriod,
|
||||
actualTotalOverPeriod:
|
||||
acc.actualTotalOverPeriod + e.actualTotalOverPeriod,
|
||||
};
|
||||
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"),
|
||||
jobStart: startDate,
|
||||
jobEnd: endDate,
|
||||
},
|
||||
{ totalOverPeriod: 0, actualTotalOverPeriod: 0 }
|
||||
);
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
pollInterval: 60000,
|
||||
skip: !fixedPeriods,
|
||||
});
|
||||
|
||||
ret.totalEffieciencyOverPeriod =
|
||||
totalActualAndProductive.actualTotalOverPeriod
|
||||
? (totalActualAndProductive.totalOverPeriod /
|
||||
totalActualAndProductive.actualTotalOverPeriod) *
|
||||
100
|
||||
: 0;
|
||||
const calculatedData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
const ret = {
|
||||
totalThisWeek: 0,
|
||||
totalLastWeek: 0,
|
||||
totalThisMonth: 0,
|
||||
totalLastMonth: 0,
|
||||
totalOverPeriod: 0,
|
||||
actualTotalOverPeriod: 0,
|
||||
totalEffieciencyOverPeriod: 0,
|
||||
employees: {},
|
||||
};
|
||||
data.fixedperiod.forEach((ticket) => {
|
||||
const ticketDate = dayjs(ticket.date);
|
||||
|
||||
roundObject(ret);
|
||||
roundObject(totals);
|
||||
roundObject(ret2);
|
||||
if (!ret.employees[ticket.employee.employee_number]) {
|
||||
ret.employees[ticket.employee.employee_number] = {
|
||||
totalThisWeek: 0,
|
||||
totalLastWeek: 0,
|
||||
totalThisMonth: 0,
|
||||
totalLastMonth: 0,
|
||||
totalOverPeriod: 0,
|
||||
actualTotalOverPeriod: 0,
|
||||
totalEffieciencyOverPeriod: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
fixed: ret,
|
||||
timeperiod: {
|
||||
totals,
|
||||
chartData: ret2,
|
||||
employees: _.uniq(employees),
|
||||
colors: getColorArray(employees.length),
|
||||
},
|
||||
};
|
||||
}, [fixedPeriods, data, startDate, endDate]);
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (loading) return <LoadingSpinner />;
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<ScoreboardTicketsStats data={calculatedData.fixed} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ScoreboardTicketsBar
|
||||
start={startDate}
|
||||
end={endDate}
|
||||
data={calculatedData.timeperiod}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
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: dayjs(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;
|
||||
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod =
|
||||
ret.employees[ticket.employee.employee_number]
|
||||
.actualTotalOverPeriod + (ticket.actualhrs || 0);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
// Add total efficiency of employees
|
||||
const totalActualAndProductive = Object.keys(ret.employees)
|
||||
.map((key) => {
|
||||
return {employee_number: key, ...ret.employees[key]};
|
||||
})
|
||||
.reduce(
|
||||
(acc, e) => {
|
||||
return {
|
||||
totalOverPeriod: acc.totalOverPeriod + e.totalOverPeriod,
|
||||
actualTotalOverPeriod:
|
||||
acc.actualTotalOverPeriod + e.actualTotalOverPeriod,
|
||||
};
|
||||
},
|
||||
{totalOverPeriod: 0, actualTotalOverPeriod: 0}
|
||||
);
|
||||
|
||||
ret.totalEffieciencyOverPeriod =
|
||||
totalActualAndProductive.actualTotalOverPeriod
|
||||
? (totalActualAndProductive.totalOverPeriod /
|
||||
totalActualAndProductive.actualTotalOverPeriod) *
|
||||
100
|
||||
: 0;
|
||||
|
||||
roundObject(ret);
|
||||
roundObject(totals);
|
||||
roundObject(ret2);
|
||||
|
||||
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 (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<ScoreboardTicketsStats data={calculatedData.fixed}/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ScoreboardTicketsBar
|
||||
start={startDate}
|
||||
end={endDate}
|
||||
data={calculatedData.timeperiod}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
//Include a filter by employee.
|
||||
@@ -290,63 +290,63 @@ export default function ScoreboardTimeTickets() {
|
||||
//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;
|
||||
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;
|
||||
}
|
||||
|
||||
function roundObject(inputObj) {
|
||||
for (var key of Object.keys(inputObj)) {
|
||||
if (typeof inputObj[key] === "number" && inputObj[key] !== 0) {
|
||||
inputObj[key] =
|
||||
inputObj[key] && inputObj[key].toFixed
|
||||
? inputObj[key].toFixed(1)
|
||||
: inputObj[key]; //Math.round(inputObj[key] * 100) / 100;
|
||||
} else if (Array.isArray(inputObj[key])) {
|
||||
inputObj[key].forEach((item) => roundObject(item));
|
||||
} else if (typeof inputObj[key] === "object") {
|
||||
roundObject(inputObj[key]);
|
||||
for (var key of Object.keys(inputObj)) {
|
||||
if (typeof inputObj[key] === "number" && inputObj[key] !== 0) {
|
||||
inputObj[key] =
|
||||
inputObj[key] && inputObj[key].toFixed
|
||||
? inputObj[key].toFixed(1)
|
||||
: inputObj[key]; //Math.round(inputObj[key] * 100) / 100;
|
||||
} else if (Array.isArray(inputObj[key])) {
|
||||
inputObj[key].forEach((item) => roundObject(item));
|
||||
} else if (typeof inputObj[key] === "object") {
|
||||
roundObject(inputObj[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,139 +1,139 @@
|
||||
import { Card, Col, Row, Statistic, Table, Typography } from "antd";
|
||||
import {Card, Col, Row, Statistic, Table, Typography} 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";
|
||||
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,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
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,
|
||||
},
|
||||
{
|
||||
title: t("scoreboard.labels.efficiencyoverperiod"),
|
||||
dataIndex: "efficiencyoverperiod",
|
||||
key: "efficiencyoverperiod",
|
||||
render: (text, record) =>
|
||||
`${(
|
||||
(record.totalOverPeriod / (record.actualTotalOverPeriod || 0.1)) *
|
||||
100
|
||||
).toFixed(1)} %`,
|
||||
},
|
||||
];
|
||||
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,
|
||||
},
|
||||
{
|
||||
title: t("scoreboard.labels.efficiencyoverperiod"),
|
||||
dataIndex: "efficiencyoverperiod",
|
||||
key: "efficiencyoverperiod",
|
||||
render: (text, record) =>
|
||||
`${(
|
||||
(record.totalOverPeriod / (record.actualTotalOverPeriod || 0.1)) *
|
||||
100
|
||||
).toFixed(1)} %`,
|
||||
},
|
||||
];
|
||||
|
||||
const tableData = data
|
||||
? Object.keys(data.employees).map((key) => {
|
||||
return { employee_number: key, ...data.employees[key] };
|
||||
})
|
||||
: [];
|
||||
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={4}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.lastweek")}
|
||||
value={data.totalLastWeek}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.lastmonth")}
|
||||
value={data.totalLastMonth}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.thisweek")}
|
||||
value={data.totalThisWeek}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.thismonth")}
|
||||
value={data.totalThisMonth}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.totaloverperiod")}
|
||||
value={data.totalOverPeriod}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.efficiencyoverperiod")}
|
||||
value={`${data.totalEffieciencyOverPeriod || 0}%`}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Typography.Text type="secondary">
|
||||
{t("scoreboard.labels.calendarperiod")}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col md={24} lg={20}>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey="employee_number"
|
||||
dataSource={tableData}
|
||||
id="employee_number"
|
||||
scroll={{ y: "300px" }}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
return (
|
||||
<Card title={t("scoreboard.labels.productivestatistics")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col md={24} lg={4}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.lastweek")}
|
||||
value={data.totalLastWeek}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.lastmonth")}
|
||||
value={data.totalLastMonth}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.thisweek")}
|
||||
value={data.totalThisWeek}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.thismonth")}
|
||||
value={data.totalThisMonth}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.totaloverperiod")}
|
||||
value={data.totalOverPeriod}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.efficiencyoverperiod")}
|
||||
value={`${data.totalEffieciencyOverPeriod || 0}%`}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Typography.Text type="secondary">
|
||||
{t("scoreboard.labels.calendarperiod")}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col md={24} lg={20}>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey="employee_number"
|
||||
dataSource={tableData}
|
||||
id="employee_number"
|
||||
scroll={{y: "300px"}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user