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
|
||||
@@ -13963,6 +13963,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>timetickets</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>vehicles</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -17771,6 +17792,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</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>
|
||||
<name>employee</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { Select, Tag } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, forwardRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
const { Option } = Select;
|
||||
//To be used as a form element only.
|
||||
|
||||
const EmployeeSearchSelect = ({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
onSelect,
|
||||
onBlur,
|
||||
}) => {
|
||||
const EmployeeSearchSelect = (
|
||||
{ value, onChange, options, onSelect, onBlur },
|
||||
ref
|
||||
) => {
|
||||
const [option, setOption] = useState(value);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
@@ -28,25 +25,23 @@ const EmployeeSearchSelect = ({
|
||||
width: 400,
|
||||
}}
|
||||
onChange={setOption}
|
||||
optionFilterProp="search"
|
||||
optionFilterProp='search'
|
||||
onSelect={onSelect}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
onBlur={onBlur}>
|
||||
{options
|
||||
? options.map((o) => (
|
||||
<Option
|
||||
key={o.id}
|
||||
value={o.id}
|
||||
search={`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
||||
discount={o.discount}
|
||||
>
|
||||
discount={o.discount}>
|
||||
<div style={{ display: "flex" }}>
|
||||
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
||||
<Tag color="blue">{o.cost_center}</Tag>
|
||||
<Tag color="red">
|
||||
<Tag color='blue'>{o.cost_center}</Tag>
|
||||
<Tag color='red'>
|
||||
<CurrencyFormatter>{o.base_rate}</CurrencyFormatter>
|
||||
</Tag>
|
||||
<Tag color="green">
|
||||
<Tag color='green'>
|
||||
{o.flat_rate
|
||||
? t("timetickets.labels.flat_rate")
|
||||
: t("timetickets.labels.straight_time")}
|
||||
@@ -58,4 +53,4 @@ const EmployeeSearchSelect = ({
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
export default EmployeeSearchSelect;
|
||||
export default forwardRef(EmployeeSearchSelect);
|
||||
|
||||
@@ -220,6 +220,11 @@ function Header({
|
||||
<Menu.Item key='invoices'>
|
||||
<Link to='/manage/invoices'>{t("menus.header.invoices")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key='timetickets'>
|
||||
<Link to='/manage/timetickets'>
|
||||
{t("menus.header.timetickets")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key='entertimetickets'
|
||||
onClick={() => {
|
||||
|
||||
@@ -25,7 +25,10 @@ export default function TimeTicketsDatesSelector() {
|
||||
return (
|
||||
<div>
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -12,17 +12,18 @@ export function TimeTicketEnterButton({
|
||||
actions,
|
||||
context,
|
||||
setTimeTicketContext,
|
||||
disabled,
|
||||
children,
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
setTimeTicketContext({
|
||||
actions,
|
||||
context,
|
||||
});
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -48,6 +48,10 @@ export default function TimeTicketList({
|
||||
dataIndex: "cost_center",
|
||||
key: "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:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
@@ -131,7 +135,8 @@ export default function TimeTicketList({
|
||||
return (
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{ id: record.id, timeticket: record }}>
|
||||
context={{ id: record.id, timeticket: record }}
|
||||
disabled={!!!record.job}>
|
||||
{t("general.actions.edit")}
|
||||
</TimeTicketEnterButton>
|
||||
);
|
||||
|
||||
@@ -169,6 +169,10 @@ export function TimeTicketModalContainer({
|
||||
timeTicketModal.context.timeticket
|
||||
? {
|
||||
...timeTicketModal.context.timeticket,
|
||||
jobid:
|
||||
(timeTicketModal.context.timeticket.job &&
|
||||
timeTicketModal.context.timeticket.job.id) ||
|
||||
null,
|
||||
date: timeTicketModal.context.timeticket.date
|
||||
? moment(timeTicketModal.context.date)
|
||||
: null,
|
||||
|
||||
@@ -3,8 +3,28 @@ 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";
|
||||
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();
|
||||
|
||||
//Group everything by employee
|
||||
@@ -19,6 +39,7 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
||||
jobTicketsByEmployee[tt.employeeid] = [];
|
||||
}
|
||||
jobTicketsByEmployee[tt.employeeid].push(tt);
|
||||
return null;
|
||||
});
|
||||
const jobTickets = Object.keys(jobTicketsByEmployee).map(function (key) {
|
||||
return {
|
||||
@@ -36,6 +57,7 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
||||
shiftTicketsByEmployee[tt.employeeid] = [];
|
||||
}
|
||||
shiftTicketsByEmployee[tt.employeeid].push(tt);
|
||||
return null;
|
||||
});
|
||||
const shiftTickets = Object.keys(shiftTicketsByEmployee).map(function (key) {
|
||||
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 (
|
||||
<div>
|
||||
<List
|
||||
@@ -54,39 +87,65 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
||||
}
|
||||
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
|
||||
)}
|
||||
renderItem={(item) => {
|
||||
const actHrs = item.tickets.reduce(
|
||||
(acc, val) => acc + val.actualhrs,
|
||||
0
|
||||
);
|
||||
|
||||
const prodHrs = item.tickets.reduce(
|
||||
(acc, val) => acc + val.productivehrs,
|
||||
0
|
||||
);
|
||||
|
||||
const clockHrs = item.tickets.reduce((acc, val) => {
|
||||
if (!!val.clockoff && !!val.clockon)
|
||||
return (
|
||||
acc +
|
||||
moment(val.clockoff).diff(moment(val.clockon), "hours", true)
|
||||
);
|
||||
return acc;
|
||||
}, 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
|
||||
title={t("timetickets.fields.productivehrs")}
|
||||
precision={1}
|
||||
value={item.tickets.reduce(
|
||||
(acc, val) => acc + val.productivehrs,
|
||||
0
|
||||
)}
|
||||
/>
|
||||
</Space>
|
||||
</LoadingSkeleton>
|
||||
</List.Item>
|
||||
)}
|
||||
<Space>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.actualhrs")}
|
||||
precision={1}
|
||||
value={actHrs}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("timetickets.fields.productivehrs")}
|
||||
precision={1}
|
||||
value={prodHrs}
|
||||
/>
|
||||
<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
|
||||
header={
|
||||
@@ -96,36 +155,42 @@ export default function TimeTicketsSummaryEmployees({ loading, timetickets }) {
|
||||
}
|
||||
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>
|
||||
)}
|
||||
renderItem={(item) => {
|
||||
const clockHrs = item.tickets.reduce((acc, val) => {
|
||||
if (!!val.clockoff && !!val.clockon)
|
||||
return (
|
||||
acc +
|
||||
moment(val.clockoff).diff(moment(val.clockon), "hours", true)
|
||||
);
|
||||
return acc;
|
||||
}, 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
|
||||
title={t("timetickets.fields.clockhours")}
|
||||
precision={2}
|
||||
value={clockHrs}
|
||||
/>
|
||||
</LoadingSkeleton>
|
||||
</List.Item>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TimeTicketsSummaryEmployees);
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
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");
|
||||
export default function TimeTicketsSummary({
|
||||
loading,
|
||||
timetickets,
|
||||
startDate,
|
||||
endDate,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<TimeTicketsSummaryEmployees
|
||||
loading={loading}
|
||||
timetickets={timetickets}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -21,7 +21,10 @@ 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 } }) {
|
||||
timetickets(
|
||||
where: { date: { _gte: $start, _lte: $end } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
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 { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
|
||||
import { setBreadcrumbs } from "../../redux/application/application.actions";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
@@ -33,13 +34,20 @@ export function TimeTicketsContainer({ bodyshop, setBreadcrumbs }) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
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, {
|
||||
variables: {
|
||||
start: start ? moment(start) : moment().startOf("week").subtract(7),
|
||||
end: end ? moment(end) : moment().endOf("week"),
|
||||
start: startDate,
|
||||
end: endDate,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TimeTicketsDatesSelector />
|
||||
@@ -51,6 +59,8 @@ export function TimeTicketsContainer({ bodyshop, setBreadcrumbs }) {
|
||||
<TimeTicketsSummary
|
||||
loading={loading}
|
||||
timetickets={data ? data.timetickets : []}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ModalsActionTypes from "./modals.types";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
|
||||
const baseModal = {
|
||||
visible: false,
|
||||
|
||||
@@ -849,6 +849,7 @@
|
||||
"shop_csi": "CSI",
|
||||
"shop_templates": "Templates",
|
||||
"shop_vendors": "Vendors",
|
||||
"timetickets": "Time Tickets",
|
||||
"vehicles": "Vehicles"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -1129,6 +1130,7 @@
|
||||
"clockon": "Clocked In",
|
||||
"cost_center": "Cost Center",
|
||||
"date": "Ticket Date",
|
||||
"efficiency": "Efficiency",
|
||||
"employee": "Employee",
|
||||
"memo": "Memo",
|
||||
"productivehrs": "Productive Hours",
|
||||
@@ -1138,12 +1140,12 @@
|
||||
"alreadyclockedon": "You are already clocked in to the following job(s):",
|
||||
"ambreak": "AM Break",
|
||||
"amshift": "AM Shift",
|
||||
"clockhours": "Clock Hours",
|
||||
"clockhours": "Shift Clock Hours Summary",
|
||||
"clockintojob": "Clock In to Job",
|
||||
"deleteconfirm": "Are you sure you want to delete this time ticket? This cannot be undone.",
|
||||
"edit": "Edit Time Ticket",
|
||||
"flat_rate": "Flat Rate",
|
||||
"jobhours": "Job Related Time Tickets",
|
||||
"jobhours": "Job Related Time Tickets Summary",
|
||||
"lunch": "Lunch",
|
||||
"new": "New Time Ticket",
|
||||
"pmbreak": "PM Break",
|
||||
|
||||
@@ -849,6 +849,7 @@
|
||||
"shop_csi": "",
|
||||
"shop_templates": "",
|
||||
"shop_vendors": "Vendedores",
|
||||
"timetickets": "",
|
||||
"vehicles": "Vehículos"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -1129,6 +1130,7 @@
|
||||
"clockon": "",
|
||||
"cost_center": "",
|
||||
"date": "",
|
||||
"efficiency": "",
|
||||
"employee": "",
|
||||
"memo": "",
|
||||
"productivehrs": "",
|
||||
|
||||
@@ -849,6 +849,7 @@
|
||||
"shop_csi": "",
|
||||
"shop_templates": "",
|
||||
"shop_vendors": "Vendeurs",
|
||||
"timetickets": "",
|
||||
"vehicles": "Véhicules"
|
||||
},
|
||||
"jobsactions": {
|
||||
@@ -1129,6 +1130,7 @@
|
||||
"clockon": "",
|
||||
"cost_center": "",
|
||||
"date": "",
|
||||
"efficiency": "",
|
||||
"employee": "",
|
||||
"memo": "",
|
||||
"productivehrs": "",
|
||||
|
||||
@@ -47,4 +47,10 @@ export const TemplateList = {
|
||||
drivingId: "Payment Id",
|
||||
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");
|
||||
const Handlebars = require("handlebars");
|
||||
|
||||
//Usage: {{moment appointments_by_pk.start format="dddd, DD MMMM YYYY"}}
|
||||
|
||||
Handlebars.registerHelper("moment", function (context, block) {
|
||||
if (context && context.hash) {
|
||||
block = _.cloneDeep(context);
|
||||
|
||||
Reference in New Issue
Block a user