Added additional stats and ticket printing to all time tickets screen BOD-191
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<babeledit_project version="1.2" be_version="2.6.1">
|
<babeledit_project be_version="2.6.1" version="1.2">
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
BabelEdit project file
|
BabelEdit project file
|
||||||
@@ -13963,6 +13963,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>vehicles</name>
|
<name>vehicles</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -17771,6 +17792,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>efficiency</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>employee</name>
|
<name>employee</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { Select, Tag } from "antd";
|
import { Select, Tag } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState, forwardRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
//To be used as a form element only.
|
//To be used as a form element only.
|
||||||
|
|
||||||
const EmployeeSearchSelect = ({
|
const EmployeeSearchSelect = (
|
||||||
value,
|
{ value, onChange, options, onSelect, onBlur },
|
||||||
onChange,
|
ref
|
||||||
options,
|
) => {
|
||||||
onSelect,
|
|
||||||
onBlur,
|
|
||||||
}) => {
|
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -28,25 +25,23 @@ const EmployeeSearchSelect = ({
|
|||||||
width: 400,
|
width: 400,
|
||||||
}}
|
}}
|
||||||
onChange={setOption}
|
onChange={setOption}
|
||||||
optionFilterProp="search"
|
optionFilterProp='search'
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}>
|
||||||
>
|
|
||||||
{options
|
{options
|
||||||
? options.map((o) => (
|
? options.map((o) => (
|
||||||
<Option
|
<Option
|
||||||
key={o.id}
|
key={o.id}
|
||||||
value={o.id}
|
value={o.id}
|
||||||
search={`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
search={`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
||||||
discount={o.discount}
|
discount={o.discount}>
|
||||||
>
|
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
||||||
<Tag color="blue">{o.cost_center}</Tag>
|
<Tag color='blue'>{o.cost_center}</Tag>
|
||||||
<Tag color="red">
|
<Tag color='red'>
|
||||||
<CurrencyFormatter>{o.base_rate}</CurrencyFormatter>
|
<CurrencyFormatter>{o.base_rate}</CurrencyFormatter>
|
||||||
</Tag>
|
</Tag>
|
||||||
<Tag color="green">
|
<Tag color='green'>
|
||||||
{o.flat_rate
|
{o.flat_rate
|
||||||
? t("timetickets.labels.flat_rate")
|
? t("timetickets.labels.flat_rate")
|
||||||
: t("timetickets.labels.straight_time")}
|
: t("timetickets.labels.straight_time")}
|
||||||
@@ -58,4 +53,4 @@ const EmployeeSearchSelect = ({
|
|||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default EmployeeSearchSelect;
|
export default forwardRef(EmployeeSearchSelect);
|
||||||
|
|||||||
@@ -220,6 +220,11 @@ function Header({
|
|||||||
<Menu.Item key='invoices'>
|
<Menu.Item key='invoices'>
|
||||||
<Link to='/manage/invoices'>{t("menus.header.invoices")}</Link>
|
<Link to='/manage/invoices'>{t("menus.header.invoices")}</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<Menu.Item key='timetickets'>
|
||||||
|
<Link to='/manage/timetickets'>
|
||||||
|
{t("menus.header.timetickets")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key='entertimetickets'
|
key='entertimetickets'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ export default function TimeTicketsDatesSelector() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<DatePicker.RangePicker
|
<DatePicker.RangePicker
|
||||||
defaultValue={[moment(start), moment(end)]}
|
defaultValue={[
|
||||||
|
start ? moment(start) : moment().startOf("week").subtract(7, "days"),
|
||||||
|
end ? moment(end) : moment().endOf("week"),
|
||||||
|
]}
|
||||||
onCalendarChange={handleChange}
|
onCalendarChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,17 +12,18 @@ export function TimeTicketEnterButton({
|
|||||||
actions,
|
actions,
|
||||||
context,
|
context,
|
||||||
setTimeTicketContext,
|
setTimeTicketContext,
|
||||||
|
disabled,
|
||||||
children,
|
children,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTimeTicketContext({
|
setTimeTicketContext({
|
||||||
actions,
|
actions,
|
||||||
context,
|
context,
|
||||||
});
|
});
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ export default function TimeTicketList({
|
|||||||
dataIndex: "cost_center",
|
dataIndex: "cost_center",
|
||||||
key: "cost_center",
|
key: "cost_center",
|
||||||
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
|
||||||
|
render: (text, record) =>
|
||||||
|
record.cost_center === "timetickets.labels.shift"
|
||||||
|
? t(record.cost_center)
|
||||||
|
: record.cost_center,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
filters:
|
filters:
|
||||||
@@ -131,7 +135,8 @@ export default function TimeTicketList({
|
|||||||
return (
|
return (
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton
|
||||||
actions={{ refetch }}
|
actions={{ refetch }}
|
||||||
context={{ id: record.id, timeticket: record }}>
|
context={{ id: record.id, timeticket: record }}
|
||||||
|
disabled={!!!record.job}>
|
||||||
{t("general.actions.edit")}
|
{t("general.actions.edit")}
|
||||||
</TimeTicketEnterButton>
|
</TimeTicketEnterButton>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -169,6 +169,10 @@ export function TimeTicketModalContainer({
|
|||||||
timeTicketModal.context.timeticket
|
timeTicketModal.context.timeticket
|
||||||
? {
|
? {
|
||||||
...timeTicketModal.context.timeticket,
|
...timeTicketModal.context.timeticket,
|
||||||
|
jobid:
|
||||||
|
(timeTicketModal.context.timeticket.job &&
|
||||||
|
timeTicketModal.context.timeticket.job.id) ||
|
||||||
|
null,
|
||||||
date: timeTicketModal.context.timeticket.date
|
date: timeTicketModal.context.timeticket.date
|
||||||
? moment(timeTicketModal.context.date)
|
? moment(timeTicketModal.context.date)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -3,8 +3,28 @@ import { Statistic, Space, List, Button, Typography } from "antd";
|
|||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import RenderTemplate, {
|
||||||
|
displayTemplateInWindow,
|
||||||
|
} from "../../utils/RenderTemplate";
|
||||||
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
|
||||||
|
export function TimeTicketsSummaryEmployees({
|
||||||
|
bodyshop,
|
||||||
|
loading,
|
||||||
|
timetickets,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
//Group everything by employee
|
//Group everything by employee
|
||||||
@@ -19,6 +39,7 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
|||||||
jobTicketsByEmployee[tt.employeeid] = [];
|
jobTicketsByEmployee[tt.employeeid] = [];
|
||||||
}
|
}
|
||||||
jobTicketsByEmployee[tt.employeeid].push(tt);
|
jobTicketsByEmployee[tt.employeeid].push(tt);
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
const jobTickets = Object.keys(jobTicketsByEmployee).map(function (key) {
|
const jobTickets = Object.keys(jobTicketsByEmployee).map(function (key) {
|
||||||
return {
|
return {
|
||||||
@@ -36,6 +57,7 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
|||||||
shiftTicketsByEmployee[tt.employeeid] = [];
|
shiftTicketsByEmployee[tt.employeeid] = [];
|
||||||
}
|
}
|
||||||
shiftTicketsByEmployee[tt.employeeid].push(tt);
|
shiftTicketsByEmployee[tt.employeeid].push(tt);
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
const shiftTickets = Object.keys(shiftTicketsByEmployee).map(function (key) {
|
const shiftTickets = Object.keys(shiftTicketsByEmployee).map(function (key) {
|
||||||
return {
|
return {
|
||||||
@@ -44,6 +66,17 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handlePrintEmployeeTicket = async (empId) => {
|
||||||
|
const html = await RenderTemplate(
|
||||||
|
{
|
||||||
|
name: TemplateList.time_tickets_by_employee.key,
|
||||||
|
variables: { id: empId, start: startDate, end: endDate },
|
||||||
|
},
|
||||||
|
bodyshop
|
||||||
|
);
|
||||||
|
displayTemplateInWindow(html);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<List
|
<List
|
||||||
@@ -54,39 +87,65 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
|||||||
}
|
}
|
||||||
itemLayout='horizontal'
|
itemLayout='horizontal'
|
||||||
dataSource={jobTickets}
|
dataSource={jobTickets}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => {
|
||||||
<List.Item
|
const actHrs = item.tickets.reduce(
|
||||||
actions={[
|
(acc, val) => acc + val.actualhrs,
|
||||||
<Button>{t("timetickets.actions.printemployee")}</Button>,
|
0
|
||||||
]}>
|
);
|
||||||
<LoadingSkeleton loading={loading}>
|
|
||||||
<List.Item.Meta
|
const prodHrs = item.tickets.reduce(
|
||||||
title={
|
(acc, val) => acc + val.productivehrs,
|
||||||
<a href='https://ant.design'>{`${item.employee.first_name} ${item.employee.last_name}`}</a>
|
0
|
||||||
}
|
);
|
||||||
// description='Ant Design, a design language for background applications, is refined by Ant UED Team'
|
|
||||||
/>
|
const clockHrs = item.tickets.reduce((acc, val) => {
|
||||||
<Space>
|
if (!!val.clockoff && !!val.clockon)
|
||||||
<Statistic
|
return (
|
||||||
title={t("timetickets.fields.actualhrs")}
|
acc +
|
||||||
precision={1}
|
moment(val.clockoff).diff(moment(val.clockon), "hours", true)
|
||||||
value={item.tickets.reduce(
|
);
|
||||||
(acc, val) => acc + val.actualhrs,
|
return acc;
|
||||||
0
|
}, 0);
|
||||||
)}
|
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
onClick={() => handlePrintEmployeeTicket(item.employee.id)}>
|
||||||
|
{t("timetickets.actions.printemployee")}
|
||||||
|
</Button>,
|
||||||
|
]}>
|
||||||
|
<LoadingSkeleton loading={loading}>
|
||||||
|
<List.Item.Meta
|
||||||
|
title={`${item.employee.first_name} ${item.employee.last_name}`}
|
||||||
/>
|
/>
|
||||||
<Statistic
|
<Space>
|
||||||
title={t("timetickets.fields.productivehrs")}
|
<Statistic
|
||||||
precision={1}
|
title={t("timetickets.fields.actualhrs")}
|
||||||
value={item.tickets.reduce(
|
precision={1}
|
||||||
(acc, val) => acc + val.productivehrs,
|
value={actHrs}
|
||||||
0
|
/>
|
||||||
)}
|
<Statistic
|
||||||
/>
|
title={t("timetickets.fields.productivehrs")}
|
||||||
</Space>
|
precision={1}
|
||||||
</LoadingSkeleton>
|
value={prodHrs}
|
||||||
</List.Item>
|
/>
|
||||||
)}
|
<Statistic
|
||||||
|
title={t("timetickets.fields.efficiency")}
|
||||||
|
precision={1}
|
||||||
|
value={(prodHrs / actHrs) * 100}
|
||||||
|
suffix={"%"}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("timetickets.fields.clockhours")}
|
||||||
|
precision={1}
|
||||||
|
value={clockHrs}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</LoadingSkeleton>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<List
|
<List
|
||||||
header={
|
header={
|
||||||
@@ -96,36 +155,42 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
|||||||
}
|
}
|
||||||
itemLayout='horizontal'
|
itemLayout='horizontal'
|
||||||
dataSource={shiftTickets}
|
dataSource={shiftTickets}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => {
|
||||||
<List.Item
|
const clockHrs = item.tickets.reduce((acc, val) => {
|
||||||
actions={[
|
if (!!val.clockoff && !!val.clockon)
|
||||||
<Button>{t("timetickets.actions.printemployee")}</Button>,
|
return (
|
||||||
]}>
|
acc +
|
||||||
<LoadingSkeleton loading={loading}>
|
moment(val.clockoff).diff(moment(val.clockon), "hours", true)
|
||||||
<List.Item.Meta
|
);
|
||||||
title={
|
return acc;
|
||||||
<a href='https://ant.design'>{`${item.employee.first_name} ${item.employee.last_name}`}</a>
|
}, 0);
|
||||||
}
|
|
||||||
// description='Ant Design, a design language for background applications, is refined by Ant UED Team'
|
return (
|
||||||
/>
|
<List.Item
|
||||||
<Statistic
|
actions={[
|
||||||
title={t("timetickets.fields.clockhours")}
|
<Button
|
||||||
precision={2}
|
onClick={() => handlePrintEmployeeTicket(item.employee.id)}>
|
||||||
value={item.tickets.reduce(
|
{t("timetickets.actions.printemployee")}
|
||||||
(acc, val) =>
|
</Button>,
|
||||||
acc +
|
]}>
|
||||||
moment(item.clockoff).diff(
|
<LoadingSkeleton loading={loading}>
|
||||||
moment(item.clockon),
|
<List.Item.Meta
|
||||||
"hours",
|
title={`${item.employee.first_name} ${item.employee.last_name}`}
|
||||||
true
|
/>
|
||||||
),
|
<Statistic
|
||||||
0
|
title={t("timetickets.fields.clockhours")}
|
||||||
)}
|
precision={2}
|
||||||
/>
|
value={clockHrs}
|
||||||
</LoadingSkeleton>
|
/>
|
||||||
</List.Item>
|
</LoadingSkeleton>
|
||||||
)}
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(TimeTicketsSummaryEmployees);
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import TimeTicketsSummaryEmployees from "../time-tickets-summary-employees/time-tickets-summary-employees.component";
|
import TimeTicketsSummaryEmployees from "../time-tickets-summary-employees/time-tickets-summary-employees.component";
|
||||||
|
|
||||||
export default function TimeTicketsSummary({ loading, timetickets }) {
|
export default function TimeTicketsSummary({
|
||||||
console.log("ordera ds");
|
loading,
|
||||||
|
timetickets,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TimeTicketsSummaryEmployees
|
<TimeTicketsSummaryEmployees
|
||||||
loading={loading}
|
loading={loading}
|
||||||
timetickets={timetickets}
|
timetickets={timetickets}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ 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!) {
|
||||||
timetickets(where: { date: { _gte: $start, _lte: $end } }) {
|
timetickets(
|
||||||
|
where: { date: { _gte: $start, _lte: $end } }
|
||||||
|
order_by: { date: desc_nulls_first }
|
||||||
|
) {
|
||||||
actualhrs
|
actualhrs
|
||||||
ciecacode
|
ciecacode
|
||||||
clockoff
|
clockoff
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.c
|
|||||||
import TimeTicketsSummary from "../../components/time-tickets-summary/time-tickets-summary.component";
|
import TimeTicketsSummary from "../../components/time-tickets-summary/time-tickets-summary.component";
|
||||||
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||||
import { setBreadcrumbs } from "../../redux/application/application.actions";
|
import { setBreadcrumbs } from "../../redux/application/application.actions";
|
||||||
|
import AlertComponent from "../../components/alert/alert.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
|
||||||
@@ -33,13 +34,20 @@ export function TimeTicketsContainer({ bodyshop, setBreadcrumbs }) {
|
|||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
const { start, end } = searchParams;
|
const { start, end } = searchParams;
|
||||||
|
|
||||||
|
const startDate = start
|
||||||
|
? moment(start)
|
||||||
|
: moment().startOf("week").subtract(7, "days");
|
||||||
|
const endDate = end ? moment(end) : moment().endOf("week");
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
|
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
|
||||||
variables: {
|
variables: {
|
||||||
start: start ? moment(start) : moment().startOf("week").subtract(7),
|
start: startDate,
|
||||||
end: end ? moment(end) : moment().endOf("week"),
|
end: endDate,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TimeTicketsDatesSelector />
|
<TimeTicketsDatesSelector />
|
||||||
@@ -51,6 +59,8 @@ export function TimeTicketsContainer({ bodyshop, setBreadcrumbs }) {
|
|||||||
<TimeTicketsSummary
|
<TimeTicketsSummary
|
||||||
loading={loading}
|
loading={loading}
|
||||||
timetickets={data ? data.timetickets : []}
|
timetickets={data ? data.timetickets : []}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import ModalsActionTypes from "./modals.types";
|
import ModalsActionTypes from "./modals.types";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
|
|
||||||
const baseModal = {
|
const baseModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
|
|||||||
@@ -849,6 +849,7 @@
|
|||||||
"shop_csi": "CSI",
|
"shop_csi": "CSI",
|
||||||
"shop_templates": "Templates",
|
"shop_templates": "Templates",
|
||||||
"shop_vendors": "Vendors",
|
"shop_vendors": "Vendors",
|
||||||
|
"timetickets": "Time Tickets",
|
||||||
"vehicles": "Vehicles"
|
"vehicles": "Vehicles"
|
||||||
},
|
},
|
||||||
"jobsactions": {
|
"jobsactions": {
|
||||||
@@ -1129,6 +1130,7 @@
|
|||||||
"clockon": "Clocked In",
|
"clockon": "Clocked In",
|
||||||
"cost_center": "Cost Center",
|
"cost_center": "Cost Center",
|
||||||
"date": "Ticket Date",
|
"date": "Ticket Date",
|
||||||
|
"efficiency": "Efficiency",
|
||||||
"employee": "Employee",
|
"employee": "Employee",
|
||||||
"memo": "Memo",
|
"memo": "Memo",
|
||||||
"productivehrs": "Productive Hours",
|
"productivehrs": "Productive Hours",
|
||||||
@@ -1138,12 +1140,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",
|
"clockhours": "Shift Clock Hours Summary",
|
||||||
"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",
|
"jobhours": "Job Related Time Tickets Summary",
|
||||||
"lunch": "Lunch",
|
"lunch": "Lunch",
|
||||||
"new": "New Time Ticket",
|
"new": "New Time Ticket",
|
||||||
"pmbreak": "PM Break",
|
"pmbreak": "PM Break",
|
||||||
|
|||||||
@@ -849,6 +849,7 @@
|
|||||||
"shop_csi": "",
|
"shop_csi": "",
|
||||||
"shop_templates": "",
|
"shop_templates": "",
|
||||||
"shop_vendors": "Vendedores",
|
"shop_vendors": "Vendedores",
|
||||||
|
"timetickets": "",
|
||||||
"vehicles": "Vehículos"
|
"vehicles": "Vehículos"
|
||||||
},
|
},
|
||||||
"jobsactions": {
|
"jobsactions": {
|
||||||
@@ -1129,6 +1130,7 @@
|
|||||||
"clockon": "",
|
"clockon": "",
|
||||||
"cost_center": "",
|
"cost_center": "",
|
||||||
"date": "",
|
"date": "",
|
||||||
|
"efficiency": "",
|
||||||
"employee": "",
|
"employee": "",
|
||||||
"memo": "",
|
"memo": "",
|
||||||
"productivehrs": "",
|
"productivehrs": "",
|
||||||
|
|||||||
@@ -849,6 +849,7 @@
|
|||||||
"shop_csi": "",
|
"shop_csi": "",
|
||||||
"shop_templates": "",
|
"shop_templates": "",
|
||||||
"shop_vendors": "Vendeurs",
|
"shop_vendors": "Vendeurs",
|
||||||
|
"timetickets": "",
|
||||||
"vehicles": "Véhicules"
|
"vehicles": "Véhicules"
|
||||||
},
|
},
|
||||||
"jobsactions": {
|
"jobsactions": {
|
||||||
@@ -1129,6 +1130,7 @@
|
|||||||
"clockon": "",
|
"clockon": "",
|
||||||
"cost_center": "",
|
"cost_center": "",
|
||||||
"date": "",
|
"date": "",
|
||||||
|
"efficiency": "",
|
||||||
"employee": "",
|
"employee": "",
|
||||||
"memo": "",
|
"memo": "",
|
||||||
"productivehrs": "",
|
"productivehrs": "",
|
||||||
|
|||||||
@@ -47,4 +47,10 @@ export const TemplateList = {
|
|||||||
drivingId: "Payment Id",
|
drivingId: "Payment Id",
|
||||||
key: "payment_receipt",
|
key: "payment_receipt",
|
||||||
},
|
},
|
||||||
|
time_tickets_by_employee: {
|
||||||
|
title: "Time Tickets by Employee",
|
||||||
|
description: "Time tickets for employee with date range",
|
||||||
|
drivingId: "Employee ID with start and end date",
|
||||||
|
key: "time_tickets_by_employee",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
query REPORT_TIME_TICKETS_IN_RANGE($id: uuid!, $start: date!, $end: date!) {
|
||||||
|
employees_by_pk(id: $id) {
|
||||||
|
id
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
employee_number
|
||||||
|
timetickets(where: { date: { _gte: $start, _lte: $end } }) {
|
||||||
|
actualhrs
|
||||||
|
ciecacode
|
||||||
|
clockoff
|
||||||
|
clockon
|
||||||
|
cost_center
|
||||||
|
created_at
|
||||||
|
date
|
||||||
|
id
|
||||||
|
rate
|
||||||
|
productivehrs
|
||||||
|
memo
|
||||||
|
job {
|
||||||
|
id
|
||||||
|
ro_number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<div style="font-family: Arial, Helvetica, sans-serif;">
|
||||||
|
<h1 style="text-align: center;"><span><strong>Employee Time Tickets</strong></span></h1>
|
||||||
|
<table style="border-collapse: collapse; width: 100%;" border="1">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 33.3333%; vertical-align: top;"><strong>Employee:</strong> {{employees_by_pk.first_name}} {{employees_by_pk.last_name}}</td>
|
||||||
|
<td style="width: 35.2832%; vertical-align: top;"> </td>
|
||||||
|
<td style="width: 31.3834%; vertical-align: top;">
|
||||||
|
<p> </p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h2 style="text-align: center;"><span>Time Tickets</span></h2>
|
||||||
|
<table style="border-collapse: collapse; width: 100%; height: 88px;" border="1">
|
||||||
|
<tbody>
|
||||||
|
<tr style="height: 22px;">
|
||||||
|
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Date</strong></td>
|
||||||
|
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Cost Center</strong></td>
|
||||||
|
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Actual Hrs</strong></td>
|
||||||
|
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Productive Hrs</strong></td>
|
||||||
|
<td style="width: 14.2857%; text-align: center; height: 22px;"><strong>Shift Clock On</strong></td>
|
||||||
|
<td style="width: 7.14285%; text-align: center; height: 22px;"><strong>Shift Clock Off</strong></td>
|
||||||
|
<td style="width: 7.14285%; text-align: center;"><strong>Shift Time</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="height: 22px; display: none;">
|
||||||
|
<td style="width: 14.2857%; height: 22px;"><span>{{#each employees_by_pk.timetickets}}</span></td>
|
||||||
|
<td style="width: 14.2857%;"> </td>
|
||||||
|
<td style="width: 14.2857%;"> </td>
|
||||||
|
<td style="width: 14.2857%;"> </td>
|
||||||
|
<td style="width: 14.2857%;"> </td>
|
||||||
|
<td style="width: 7.14285%;"> </td>
|
||||||
|
<td style="width: 7.14285%;"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr style="height: 22px;">
|
||||||
|
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{this.date}}</td>
|
||||||
|
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{this.cost_center}}</td>
|
||||||
|
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{this.actualhrs}}</td>
|
||||||
|
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{this.productivehrs}}</td>
|
||||||
|
<td style="width: 14.2857%; height: 22px; text-align: justify;">{{moment this.clockon format="MM/DD/YYYY @ hh:mm:ss"}}</td>
|
||||||
|
<td style="width: 7.14285%; height: 22px; text-align: justify;">{{moment this.clockoff format="MM/DD/YYYY @ hh:mm:ss"}}</td>
|
||||||
|
<td style="width: 7.14285%; text-align: justify;">{{moment this.clockoff diff=this.clockon }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="height: 22px; display: none;">
|
||||||
|
<td style="width: 14.2857%; height: 22px;"><span>{{/each}}</span></td>
|
||||||
|
<td style="width: 14.2857%;"> </td>
|
||||||
|
<td style="width: 14.2857%;"> </td>
|
||||||
|
<td style="width: 14.2857%;"> </td>
|
||||||
|
<td style="width: 14.2857%;"> </td>
|
||||||
|
<td style="width: 7.14285%;"> </td>
|
||||||
|
<td style="width: 7.14285%;"> </td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -9,6 +9,8 @@ require("dotenv").config({
|
|||||||
var _ = require("lodash");
|
var _ = require("lodash");
|
||||||
const Handlebars = require("handlebars");
|
const Handlebars = require("handlebars");
|
||||||
|
|
||||||
|
//Usage: {{moment appointments_by_pk.start format="dddd, DD MMMM YYYY"}}
|
||||||
|
|
||||||
Handlebars.registerHelper("moment", function (context, block) {
|
Handlebars.registerHelper("moment", function (context, block) {
|
||||||
if (context && context.hash) {
|
if (context && context.hash) {
|
||||||
block = _.cloneDeep(context);
|
block = _.cloneDeep(context);
|
||||||
|
|||||||
Reference in New Issue
Block a user