WIP Time Ticket Summary BOD-191
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { DatePicker } from "antd";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
|
||||
export default function TimeTicketsDatesSelector() {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { start, end } = searchParams;
|
||||
const history = useHistory();
|
||||
|
||||
const handleChange = (dates) => {
|
||||
const [start, end] = dates;
|
||||
|
||||
if (!!start && !!end) {
|
||||
history.push({
|
||||
search: queryString.stringify({
|
||||
start: start.format("YYYY-MM-DD"),
|
||||
end: end.format("YYYY-MM-DD"),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DatePicker.RangePicker
|
||||
defaultValue={[moment(start), moment(end)]}
|
||||
onCalendarChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import { Link } from "react-router-dom";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import moment from "moment";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
|
||||
export default function TimeTicketList({
|
||||
loading,
|
||||
@@ -37,8 +40,7 @@ export default function TimeTicketList({
|
||||
sortOrder: state.sortedInfo.columnKey === "vin" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={`/manage/employees/${record.employee.id}`}
|
||||
>{`${record.employee.first_name} ${record.employee.last_name}`}</Link>
|
||||
to={`/manage/employees/${record.employee.id}`}>{`${record.employee.first_name} ${record.employee.last_name}`}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -48,6 +50,17 @@ export default function TimeTicketList({
|
||||
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
timetickets
|
||||
.map((l) => l.cost_center)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s, //|| "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
}) || [],
|
||||
onFilter: (value, record) => value.includes(record.cost_center),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.productivehrs"),
|
||||
@@ -66,6 +79,49 @@ export default function TimeTicketList({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.clockon"),
|
||||
dataIndex: "clockon",
|
||||
key: "clockon",
|
||||
sorter: (a, b) => a.clockon - b.clockon,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clockon" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.clockon}</DateTimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.clockoff"),
|
||||
dataIndex: "clockoff",
|
||||
key: "clockoff",
|
||||
sorter: (a, b) => a.clockoff - b.clockoff,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clockoff" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.clockoff}</DateTimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.clockhours"),
|
||||
dataIndex: "clockoff",
|
||||
key: "clockoff",
|
||||
sorter: (a, b) => a.clockoff - b.clockoff,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clockoff" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
if (record.clockoff && record.clockon)
|
||||
return (
|
||||
<div>
|
||||
{moment(record.clockoff)
|
||||
.diff(moment(record.clockon), "hours", true)
|
||||
.toFixed(2)}
|
||||
</div>
|
||||
);
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
@@ -75,8 +131,7 @@ export default function TimeTicketList({
|
||||
return (
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{ id: record.id, timeticket: record }}
|
||||
>
|
||||
context={{ id: record.id, timeticket: record }}>
|
||||
{t("general.actions.edit")}
|
||||
</TimeTicketEnterButton>
|
||||
);
|
||||
@@ -91,10 +146,10 @@ export default function TimeTicketList({
|
||||
return (
|
||||
<Table
|
||||
loading={loading}
|
||||
size="small"
|
||||
size='small'
|
||||
pagination={{ position: "top" }}
|
||||
columns={columns.map((item) => ({ ...item }))}
|
||||
rowKey="id"
|
||||
rowKey='id'
|
||||
dataSource={timetickets}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
|
||||
@@ -59,7 +59,7 @@ export function TimeTicketModalContainer({
|
||||
} else {
|
||||
insertTicket({
|
||||
variables: {
|
||||
timeTicketInput: [{ ...values, bodyshop: bodyshop.id }],
|
||||
timeTicketInput: [{ ...values, bodyshopid: bodyshop.id }],
|
||||
},
|
||||
})
|
||||
.then(handleMutationSuccess)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import React from "react";
|
||||
import { Statistic, Space, List, Button, Typography } from "antd";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import moment from "moment";
|
||||
|
||||
export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
//Group everything by employee
|
||||
//Then sum the individual time TimeTicketsSummary.
|
||||
|
||||
//Calculate job based tickets.
|
||||
const jobTicketsByEmployee = {};
|
||||
timetickets
|
||||
.filter((i) => i.cost_center !== "timetickets.labels.shift")
|
||||
.map((tt) => {
|
||||
if (!!!jobTicketsByEmployee[tt.employeeid]) {
|
||||
jobTicketsByEmployee[tt.employeeid] = [];
|
||||
}
|
||||
jobTicketsByEmployee[tt.employeeid].push(tt);
|
||||
});
|
||||
const jobTickets = Object.keys(jobTicketsByEmployee).map(function (key) {
|
||||
return {
|
||||
employee: jobTicketsByEmployee[key][0].employee,
|
||||
tickets: jobTicketsByEmployee[key],
|
||||
};
|
||||
});
|
||||
|
||||
//Calculate shift based tickets.
|
||||
const shiftTicketsByEmployee = {};
|
||||
timetickets
|
||||
.filter((i) => i.cost_center === "timetickets.labels.shift")
|
||||
.map((tt) => {
|
||||
if (!!!shiftTicketsByEmployee[tt.employeeid]) {
|
||||
shiftTicketsByEmployee[tt.employeeid] = [];
|
||||
}
|
||||
shiftTicketsByEmployee[tt.employeeid].push(tt);
|
||||
});
|
||||
const shiftTickets = Object.keys(shiftTicketsByEmployee).map(function (key) {
|
||||
return {
|
||||
employee: shiftTicketsByEmployee[key][0].employee,
|
||||
tickets: shiftTicketsByEmployee[key],
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<List
|
||||
header={
|
||||
<Typography.Title level={3}>
|
||||
{t("timetickets.labels.jobhours")}
|
||||
</Typography.Title>
|
||||
}
|
||||
itemLayout='horizontal'
|
||||
dataSource={jobTickets}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button>{t("timetickets.actions.printemployee")}</Button>,
|
||||
]}>
|
||||
<LoadingSkeleton loading={loading}>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<a href='https://ant.design'>{`${item.employee.first_name} ${item.employee.last_name}`}</a>
|
||||
}
|
||||
// description='Ant Design, a design language for background applications, is refined by Ant UED Team'
|
||||
/>
|
||||
<Space>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.actualhrs")}
|
||||
precision={1}
|
||||
value={item.tickets.reduce(
|
||||
(acc, val) => acc + val.actualhrs,
|
||||
0
|
||||
)}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.productivehrs")}
|
||||
precision={1}
|
||||
value={item.tickets.reduce(
|
||||
(acc, val) => acc + val.productivehrs,
|
||||
0
|
||||
)}
|
||||
/>
|
||||
</Space>
|
||||
</LoadingSkeleton>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
<List
|
||||
header={
|
||||
<Typography.Title level={3}>
|
||||
{t("timetickets.labels.clockhours")}
|
||||
</Typography.Title>
|
||||
}
|
||||
itemLayout='horizontal'
|
||||
dataSource={shiftTickets}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button>{t("timetickets.actions.printemployee")}</Button>,
|
||||
]}>
|
||||
<LoadingSkeleton loading={loading}>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<a href='https://ant.design'>{`${item.employee.first_name} ${item.employee.last_name}`}</a>
|
||||
}
|
||||
// description='Ant Design, a design language for background applications, is refined by Ant UED Team'
|
||||
/>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.clockhours")}
|
||||
precision={2}
|
||||
value={item.tickets.reduce(
|
||||
(acc, val) =>
|
||||
acc +
|
||||
moment(item.clockoff).diff(
|
||||
moment(item.clockon),
|
||||
"hours",
|
||||
true
|
||||
),
|
||||
0
|
||||
)}
|
||||
/>
|
||||
</LoadingSkeleton>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import TimeTicketsSummaryEmployees from "../time-tickets-summary-employees/time-tickets-summary-employees.component";
|
||||
|
||||
export default function TimeTicketsSummary({ loading, timetickets }) {
|
||||
console.log("ordera ds");
|
||||
return (
|
||||
<div>
|
||||
<TimeTicketsSummaryEmployees
|
||||
loading={loading}
|
||||
timetickets={timetickets}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user