WIP Time Ticket Summary BOD-191
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<babeledit_project be_version="2.6.1" version="1.2">
|
<babeledit_project version="1.2" be_version="2.6.1">
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
BabelEdit project file
|
BabelEdit project file
|
||||||
@@ -17509,6 +17509,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>printemployee</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>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
<folder_node>
|
<folder_node>
|
||||||
@@ -17556,6 +17577,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>creating</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>deleting</name>
|
<name>deleting</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -17624,6 +17666,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>clockhours</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>clockoff</name>
|
<name>clockoff</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -17860,6 +17923,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>clockhours</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>clockintojob</name>
|
<name>clockintojob</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -17944,6 +18028,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>jobhours</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>lunch</name>
|
<name>lunch</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -18138,6 +18243,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>created</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>deleted</name>
|
<name>deleted</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -18799,6 +18925,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>vehicle-details</name>
|
<name>vehicle-details</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -19431,6 +19578,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</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>vehicledetail</name>
|
<name>vehicledetail</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -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 { alphaSort } from "../../utils/sorters";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
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({
|
export default function TimeTicketList({
|
||||||
loading,
|
loading,
|
||||||
@@ -37,8 +40,7 @@ export default function TimeTicketList({
|
|||||||
sortOrder: state.sortedInfo.columnKey === "vin" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "vin" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/manage/employees/${record.employee.id}`}
|
to={`/manage/employees/${record.employee.id}`}>{`${record.employee.first_name} ${record.employee.last_name}`}</Link>
|
||||||
>{`${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),
|
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
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"),
|
title: t("timetickets.fields.productivehrs"),
|
||||||
@@ -66,6 +79,49 @@ export default function TimeTicketList({
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "actualhrs" && state.sortedInfo.order,
|
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"),
|
title: t("general.labels.actions"),
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
@@ -75,8 +131,7 @@ export default function TimeTicketList({
|
|||||||
return (
|
return (
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton
|
||||||
actions={{ refetch }}
|
actions={{ refetch }}
|
||||||
context={{ id: record.id, timeticket: record }}
|
context={{ id: record.id, timeticket: record }}>
|
||||||
>
|
|
||||||
{t("general.actions.edit")}
|
{t("general.actions.edit")}
|
||||||
</TimeTicketEnterButton>
|
</TimeTicketEnterButton>
|
||||||
);
|
);
|
||||||
@@ -91,10 +146,10 @@ export default function TimeTicketList({
|
|||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="small"
|
size='small'
|
||||||
pagination={{ position: "top" }}
|
pagination={{ position: "top" }}
|
||||||
columns={columns.map((item) => ({ ...item }))}
|
columns={columns.map((item) => ({ ...item }))}
|
||||||
rowKey="id"
|
rowKey='id'
|
||||||
dataSource={timetickets}
|
dataSource={timetickets}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function TimeTicketModalContainer({
|
|||||||
} else {
|
} else {
|
||||||
insertTicket({
|
insertTicket({
|
||||||
variables: {
|
variables: {
|
||||||
timeTicketInput: [{ ...values, bodyshop: bodyshop.id }],
|
timeTicketInput: [{ ...values, bodyshopid: bodyshop.id }],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(handleMutationSuccess)
|
.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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -47,8 +47,8 @@ try {
|
|||||||
|
|
||||||
export { messaging };
|
export { messaging };
|
||||||
|
|
||||||
export const logImEXEvent = (eventName, additionalParams) => {
|
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||||
const state = store.getState();
|
const state = stateProp || store.getState();
|
||||||
const eventParams = {
|
const eventParams = {
|
||||||
shop:
|
shop:
|
||||||
(state.user && state.user.bodyshop && state.user.bodyshop.shopname) ||
|
(state.user && state.user.bodyshop && state.user.bodyshop.shopname) ||
|
||||||
|
|||||||
@@ -19,6 +19,36 @@ export const QUERY_TICKETS_BY_JOBID = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||||
|
query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) {
|
||||||
|
timetickets(where: { date: { _gte: $start, _lte: $end } }) {
|
||||||
|
actualhrs
|
||||||
|
ciecacode
|
||||||
|
clockoff
|
||||||
|
clockon
|
||||||
|
cost_center
|
||||||
|
created_at
|
||||||
|
date
|
||||||
|
id
|
||||||
|
rate
|
||||||
|
productivehrs
|
||||||
|
memo
|
||||||
|
job {
|
||||||
|
id
|
||||||
|
ro_number
|
||||||
|
}
|
||||||
|
employeeid
|
||||||
|
employee {
|
||||||
|
id
|
||||||
|
employee_number
|
||||||
|
first_name
|
||||||
|
cost_center
|
||||||
|
last_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const INSERT_NEW_TIME_TICKET = gql`
|
export const INSERT_NEW_TIME_TICKET = gql`
|
||||||
mutation INSERT_NEW_TIME_TICKET(
|
mutation INSERT_NEW_TIME_TICKET(
|
||||||
$timeTicketInput: [timetickets_insert_input!]!
|
$timeTicketInput: [timetickets_insert_input!]!
|
||||||
@@ -72,7 +102,7 @@ export const QUERY_ACTIVE_TIME_TICKETS = gql`
|
|||||||
clockoff: { _is_null: true }
|
clockoff: { _is_null: true }
|
||||||
employeeid: { _eq: $employeeId }
|
employeeid: { _eq: $employeeId }
|
||||||
clockon: { _is_null: false }
|
clockon: { _is_null: false }
|
||||||
jobid: {_is_null: false}
|
jobid: { _is_null: false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -102,7 +132,7 @@ export const QUERY_ACTIVE_SHIFT_TIME_TICKETS = gql`
|
|||||||
clockoff: { _is_null: true }
|
clockoff: { _is_null: true }
|
||||||
employeeid: { _eq: $employeeId }
|
employeeid: { _eq: $employeeId }
|
||||||
clockon: { _is_null: false }
|
clockon: { _is_null: false }
|
||||||
jobid: {_is_null: true}
|
jobid: { _is_null: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -118,7 +118,10 @@ const PaymentsAll = lazy(() =>
|
|||||||
);
|
);
|
||||||
const ShiftClock = lazy(() => import("../shift-clock/shift-clock.page"));
|
const ShiftClock = lazy(() => import("../shift-clock/shift-clock.page"));
|
||||||
const Scoreboard = lazy(() =>
|
const Scoreboard = lazy(() =>
|
||||||
import("../scoreboard/scoreboard.page.container.jsx")
|
import("../scoreboard/scoreboard.page.container")
|
||||||
|
);
|
||||||
|
const TimeTicketsAll = lazy(() =>
|
||||||
|
import("../time-tickets/time-tickets.container")
|
||||||
);
|
);
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
@@ -329,6 +332,12 @@ export function Manage({ match, conflict }) {
|
|||||||
path={`${match.path}/scoreboard`}
|
path={`${match.path}/scoreboard`}
|
||||||
component={Scoreboard}
|
component={Scoreboard}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}/timetickets`}
|
||||||
|
component={TimeTicketsAll}
|
||||||
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
62
client/src/pages/time-tickets/time-tickets.container.jsx
Normal file
62
client/src/pages/time-tickets/time-tickets.container.jsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { useQuery } from "@apollo/react-hooks";
|
||||||
|
import moment from "moment";
|
||||||
|
import queryString from "query-string";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
|
||||||
|
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component";
|
||||||
|
import TimeTicketsSummary from "../../components/time-tickets-summary/time-tickets-summary.component";
|
||||||
|
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||||
|
import { setBreadcrumbs } from "../../redux/application/application.actions";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function TimeTicketsContainer({ bodyshop, setBreadcrumbs }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = t("titles.timetickets");
|
||||||
|
setBreadcrumbs([
|
||||||
|
{
|
||||||
|
link: "/manage/timetickets",
|
||||||
|
label: t("titles.bc.timetickets"),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [t, setBreadcrumbs]);
|
||||||
|
|
||||||
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
|
const { start, end } = searchParams;
|
||||||
|
|
||||||
|
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
|
||||||
|
variables: {
|
||||||
|
start: start ? moment(start) : moment().startOf("week").subtract(7),
|
||||||
|
end: end ? moment(end) : moment().endOf("week"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TimeTicketsDatesSelector />
|
||||||
|
|
||||||
|
<TimeTicketList
|
||||||
|
loading={loading}
|
||||||
|
timetickets={data ? data.timetickets : []}
|
||||||
|
/>
|
||||||
|
<TimeTicketsSummary
|
||||||
|
loading={loading}
|
||||||
|
timetickets={data ? data.timetickets : []}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TimeTicketsContainer);
|
||||||
@@ -25,7 +25,7 @@ const INITIAL_STATE = {
|
|||||||
const modalsReducer = (state = INITIAL_STATE, action) => {
|
const modalsReducer = (state = INITIAL_STATE, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ModalsActionTypes.TOGGLE_MODAL_VISIBLE:
|
case ModalsActionTypes.TOGGLE_MODAL_VISIBLE:
|
||||||
logImEXEvent("redux_toggle_modal_visible", { modal: action.payload });
|
//logImEXEvent("redux_toggle_modal_visible", { modal: action.payload });
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
[action.payload]: {
|
[action.payload]: {
|
||||||
@@ -34,7 +34,7 @@ const modalsReducer = (state = INITIAL_STATE, action) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
case ModalsActionTypes.SET_MODAL_CONTEXT:
|
case ModalsActionTypes.SET_MODAL_CONTEXT:
|
||||||
logImEXEvent("redux_set_modal_context", { modal: action.payload.modal });
|
// logImEXEvent("redux_set_modal_context", { modal: action.payload.modal });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@@ -1112,16 +1112,19 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"clockin": "Clock In",
|
"clockin": "Clock In",
|
||||||
"clockout": "Clock Out",
|
"clockout": "Clock Out",
|
||||||
"enter": "Enter New Time Ticket"
|
"enter": "Enter New Time Ticket",
|
||||||
|
"printemployee": "Print Time Tickets"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"clockingin": "Error while clocking in. {{message}}",
|
"clockingin": "Error while clocking in. {{message}}",
|
||||||
"clockingout": "Error while clocking out. {{message}}",
|
"clockingout": "Error while clocking out. {{message}}",
|
||||||
|
"creating": "Error creating time ticket. {{message}}",
|
||||||
"deleting": "Error deleting time ticket. {{message}}"
|
"deleting": "Error deleting time ticket. {{message}}"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"actualhrs": "Actual Hours",
|
"actualhrs": "Actual Hours",
|
||||||
"ciecacode": "CIECA Code",
|
"ciecacode": "CIECA Code",
|
||||||
|
"clockhours": "Clock Hours",
|
||||||
"clockoff": "Clock Off",
|
"clockoff": "Clock Off",
|
||||||
"clockon": "Clocked In",
|
"clockon": "Clocked In",
|
||||||
"cost_center": "Cost Center",
|
"cost_center": "Cost Center",
|
||||||
@@ -1135,10 +1138,12 @@
|
|||||||
"alreadyclockedon": "You are already clocked in to the following job(s):",
|
"alreadyclockedon": "You are already clocked in to the following job(s):",
|
||||||
"ambreak": "AM Break",
|
"ambreak": "AM Break",
|
||||||
"amshift": "AM Shift",
|
"amshift": "AM Shift",
|
||||||
|
"clockhours": "Clock Hours",
|
||||||
"clockintojob": "Clock In to Job",
|
"clockintojob": "Clock In to Job",
|
||||||
"deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.",
|
"deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.",
|
||||||
"edit": "Edit Time Ticket",
|
"edit": "Edit Time Ticket",
|
||||||
"flat_rate": "Flat Rate",
|
"flat_rate": "Flat Rate",
|
||||||
|
"jobhours": "Job Related Time Tickets",
|
||||||
"lunch": "Lunch",
|
"lunch": "Lunch",
|
||||||
"new": "New Time Ticket",
|
"new": "New Time Ticket",
|
||||||
"pmbreak": "PM Break",
|
"pmbreak": "PM Break",
|
||||||
@@ -1150,6 +1155,7 @@
|
|||||||
"successes": {
|
"successes": {
|
||||||
"clockedin": "Clocked in successfully.",
|
"clockedin": "Clocked in successfully.",
|
||||||
"clockedout": "Clocked out successfully.",
|
"clockedout": "Clocked out successfully.",
|
||||||
|
"created": "Time ticket entered successfully.",
|
||||||
"deleted": "Time ticket deleted successfully."
|
"deleted": "Time ticket deleted successfully."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1185,6 +1191,7 @@
|
|||||||
"shop": "Manage my Shop ({{shopname}})",
|
"shop": "Manage my Shop ({{shopname}})",
|
||||||
"shop-csi": "CSI Responses",
|
"shop-csi": "CSI Responses",
|
||||||
"shop-templates": "Shop Templates",
|
"shop-templates": "Shop Templates",
|
||||||
|
"timetickets": "Time Tickets",
|
||||||
"vehicle-details": "Vehicle: {{vehicle}}",
|
"vehicle-details": "Vehicle: {{vehicle}}",
|
||||||
"vehicles": "Vehicles"
|
"vehicles": "Vehicles"
|
||||||
},
|
},
|
||||||
@@ -1216,6 +1223,7 @@
|
|||||||
"shop-csi": "CSI Responses | $t(titles.app)",
|
"shop-csi": "CSI Responses | $t(titles.app)",
|
||||||
"shop-templates": "Shop Templates | $t(titles.app)",
|
"shop-templates": "Shop Templates | $t(titles.app)",
|
||||||
"shop_vendors": "Vendors | $t(titles.app)",
|
"shop_vendors": "Vendors | $t(titles.app)",
|
||||||
|
"timetickets": "Time Tickets | $t(titles.app)",
|
||||||
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
|
"vehicledetail": "Vehicle Details {{vehicle}} | $t(titles.app)",
|
||||||
"vehicles": "All Vehicles | $t(titles.app)"
|
"vehicles": "All Vehicles | $t(titles.app)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1112,16 +1112,19 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"clockin": "",
|
"clockin": "",
|
||||||
"clockout": "",
|
"clockout": "",
|
||||||
"enter": ""
|
"enter": "",
|
||||||
|
"printemployee": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"clockingin": "",
|
"clockingin": "",
|
||||||
"clockingout": "",
|
"clockingout": "",
|
||||||
|
"creating": "",
|
||||||
"deleting": ""
|
"deleting": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"actualhrs": "",
|
"actualhrs": "",
|
||||||
"ciecacode": "",
|
"ciecacode": "",
|
||||||
|
"clockhours": "",
|
||||||
"clockoff": "",
|
"clockoff": "",
|
||||||
"clockon": "",
|
"clockon": "",
|
||||||
"cost_center": "",
|
"cost_center": "",
|
||||||
@@ -1135,10 +1138,12 @@
|
|||||||
"alreadyclockedon": "",
|
"alreadyclockedon": "",
|
||||||
"ambreak": "",
|
"ambreak": "",
|
||||||
"amshift": "",
|
"amshift": "",
|
||||||
|
"clockhours": "",
|
||||||
"clockintojob": "",
|
"clockintojob": "",
|
||||||
"deleteconfirm": "",
|
"deleteconfirm": "",
|
||||||
"edit": "",
|
"edit": "",
|
||||||
"flat_rate": "",
|
"flat_rate": "",
|
||||||
|
"jobhours": "",
|
||||||
"lunch": "",
|
"lunch": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
"pmbreak": "",
|
"pmbreak": "",
|
||||||
@@ -1150,6 +1155,7 @@
|
|||||||
"successes": {
|
"successes": {
|
||||||
"clockedin": "",
|
"clockedin": "",
|
||||||
"clockedout": "",
|
"clockedout": "",
|
||||||
|
"created": "",
|
||||||
"deleted": ""
|
"deleted": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1185,6 +1191,7 @@
|
|||||||
"shop": "",
|
"shop": "",
|
||||||
"shop-csi": "",
|
"shop-csi": "",
|
||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
|
"timetickets": "",
|
||||||
"vehicle-details": "",
|
"vehicle-details": "",
|
||||||
"vehicles": ""
|
"vehicles": ""
|
||||||
},
|
},
|
||||||
@@ -1216,6 +1223,7 @@
|
|||||||
"shop-csi": "",
|
"shop-csi": "",
|
||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
"shop_vendors": "Vendedores | $t(titles.app)",
|
"shop_vendors": "Vendedores | $t(titles.app)",
|
||||||
|
"timetickets": "",
|
||||||
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)",
|
"vehicledetail": "Detalles del vehículo {{vehicle}} | $t(titles.app)",
|
||||||
"vehicles": "Todos los vehiculos | $t(titles.app)"
|
"vehicles": "Todos los vehiculos | $t(titles.app)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1112,16 +1112,19 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"clockin": "",
|
"clockin": "",
|
||||||
"clockout": "",
|
"clockout": "",
|
||||||
"enter": ""
|
"enter": "",
|
||||||
|
"printemployee": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"clockingin": "",
|
"clockingin": "",
|
||||||
"clockingout": "",
|
"clockingout": "",
|
||||||
|
"creating": "",
|
||||||
"deleting": ""
|
"deleting": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"actualhrs": "",
|
"actualhrs": "",
|
||||||
"ciecacode": "",
|
"ciecacode": "",
|
||||||
|
"clockhours": "",
|
||||||
"clockoff": "",
|
"clockoff": "",
|
||||||
"clockon": "",
|
"clockon": "",
|
||||||
"cost_center": "",
|
"cost_center": "",
|
||||||
@@ -1135,10 +1138,12 @@
|
|||||||
"alreadyclockedon": "",
|
"alreadyclockedon": "",
|
||||||
"ambreak": "",
|
"ambreak": "",
|
||||||
"amshift": "",
|
"amshift": "",
|
||||||
|
"clockhours": "",
|
||||||
"clockintojob": "",
|
"clockintojob": "",
|
||||||
"deleteconfirm": "",
|
"deleteconfirm": "",
|
||||||
"edit": "",
|
"edit": "",
|
||||||
"flat_rate": "",
|
"flat_rate": "",
|
||||||
|
"jobhours": "",
|
||||||
"lunch": "",
|
"lunch": "",
|
||||||
"new": "",
|
"new": "",
|
||||||
"pmbreak": "",
|
"pmbreak": "",
|
||||||
@@ -1150,6 +1155,7 @@
|
|||||||
"successes": {
|
"successes": {
|
||||||
"clockedin": "",
|
"clockedin": "",
|
||||||
"clockedout": "",
|
"clockedout": "",
|
||||||
|
"created": "",
|
||||||
"deleted": ""
|
"deleted": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1185,6 +1191,7 @@
|
|||||||
"shop": "",
|
"shop": "",
|
||||||
"shop-csi": "",
|
"shop-csi": "",
|
||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
|
"timetickets": "",
|
||||||
"vehicle-details": "",
|
"vehicle-details": "",
|
||||||
"vehicles": ""
|
"vehicles": ""
|
||||||
},
|
},
|
||||||
@@ -1216,6 +1223,7 @@
|
|||||||
"shop-csi": "",
|
"shop-csi": "",
|
||||||
"shop-templates": "",
|
"shop-templates": "",
|
||||||
"shop_vendors": "Vendeurs | $t(titles.app)",
|
"shop_vendors": "Vendeurs | $t(titles.app)",
|
||||||
|
"timetickets": "",
|
||||||
"vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)",
|
"vehicledetail": "Détails du véhicule {{vehicle} | $t(titles.app)",
|
||||||
"vehicles": "Tous les véhicules | $t(titles.app)"
|
"vehicles": "Tous les véhicules | $t(titles.app)"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user