Introduced JS report and refactored some doc generation. IO-585

This commit is contained in:
Patrick Fic
2021-01-06 17:56:46 -08:00
parent 1c967ece2e
commit 2d260dceb8
33 changed files with 311 additions and 676 deletions

View File

@@ -12390,6 +12390,11 @@
"verror": "1.10.0"
}
},
"jsreport-browser-client-dist": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/jsreport-browser-client-dist/-/jsreport-browser-client-dist-1.3.0.tgz",
"integrity": "sha512-E83cVmxQ5np3rxns6dhFu15m5kZ5yXJDIyfbHjLPxO0AZXVQOuMrdvYsUCj+j/ZSaiROoBCstZRO7pa4HmZNGw=="
},
"jsx-ast-utils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz",

View File

@@ -25,6 +25,7 @@
"i18next-browser-languagedetector": "^6.0.1",
"jsoneditor": "^9.1.7",
"jsoneditor-react": "^3.1.0",
"jsreport-browser-client-dist": "^1.3.0",
"logrocket": "^1.0.13",
"moment-business-days": "^1.2.0",
"node-sass": "^4.14.1",

View File

@@ -1,58 +0,0 @@
body {
font-family: "Open Sans", sans-serif;
line-height: 1.25;
padding: 10mm 10mm 10mm 10mm !important;
}
@page {
margin: 50px;
}
table.imex-table {
border: 1px solid #ccc;
border-collapse: collapse;
margin: 0;
padding: 0;
width: 100%;
table-layout: fixed;
font-size: inherit;
page-break-inside: auto;
}
table.imex-table caption {
/* font-size: 1.5em; */
margin: 0.5em 0 0.75em;
font-size: inherit;
}
table.imex-table tr {
/* background-color: #f8f8f8; */
border: 1px solid #ddd;
padding: 0.2rem;
font-size: inherit;
page-break-inside: avoid;
page-break-after: auto;
}
table.imex-table th,
table.imex-table td {
padding: 0.3rem;
text-align: center;
font-size: inherit;
}
table.imex-table th.left,
table.imex-table td.left {
padding: 0.3rem;
text-align: left;
font-size: inherit;
}
table.imex-table th {
/* font-size: 0.85em; */
letter-spacing: 0.1em;
text-transform: uppercase;
/* display: table-header-group; */
}

View File

@@ -109,11 +109,18 @@ export function EmailOverlayContainer({
const render = async () => {
logImEXEvent("email_render_template", { template: emailConfig.template });
setLoading(true);
let html = await RenderTemplate(emailConfig.template, bodyshop);
let html = await RenderTemplate(emailConfig.template, bodyshop, true);
const response = await axios.post("/render/inlinecss", {
html: html,
url: `${window.location.protocol}://${window.location.host}/`,
});
console.log("response", response);
setMessageOptions({
...emailConfig.messageOptions,
...defaultEmailFrom,
html: html,
html: response.data,
fileList: [],
});
setLoading(false);

View File

@@ -25,14 +25,14 @@ export function PrintCenterItemComponent({
disabled,
}) {
const renderToNewWindow = async () => {
const html = await RenderTemplate(
await RenderTemplate(
{
name: item.key,
variables: { id: id },
},
bodyshop
);
displayTemplateInWindow(html);
// displayTemplateInWindow(html);
};
if (disabled) return <li className="print-center-item">{item.title} </li>;

View File

@@ -22,11 +22,7 @@ export default function ShopTemplateSaveButton({
html: data.html,
url: `${window.location.protocol}://${window.location.host}/`,
});
console.log(
"🚀 ~ file: shop-template-editor-save-button.component.jsx ~ line 25 ~ emailEditorRef.current.exportHtml ~ inlineHtml",
response
);
const result = await updateTemplate({
variables: {
templateId: templateId,

View File

@@ -2,3 +2,5 @@
[1008/114603.993:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
[1008/121110.259:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
[1008/122424.146:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
[0106/150233.556:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
[0106/152014.936:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)

View File

@@ -74,6 +74,7 @@ export const QUERY_BODYSHOP = gql`
appt_alt_transport
schedule_start_time
schedule_end_time
imexshopid
employees {
id
first_name
@@ -145,7 +146,8 @@ export const UPDATE_SHOP = gql`
appt_colors
appt_alt_transport
schedule_start_time
schedule_end_time
schedule_end_time
imexshopid
employees {
id
first_name

View File

@@ -1,43 +1,90 @@
import axios from "axios";
import gql from "graphql-tag";
import { QUERY_TEMPLATES_BY_NAME } from "../graphql/templates.queries";
import jsreport from "jsreport-browser-client-dist";
import client from "../utils/GraphQLClient";
import cleanAxios from "./CleanAxios";
export default async function RenderTemplate(templateObject, bodyshop) {
const { data: templateRecords } = await client.query({
query: QUERY_TEMPLATES_BY_NAME,
variables: { name: templateObject.name },
fetchPolicy: "network-only",
});
const server = "https://reports.bodyshop.app";
jsreport.serverUrl = server;
jsreport.headers["Authorization"] = "Basic " + btoa("admin:admin");
let templateToUse;
export default async function RenderTemplate(
templateObject,
bodyshop,
renderAsHtml = false
) {
//Template Object
// {
// name: TemplateList().parts_order_confirmation.key,
// variables: {
// id: insertResult.data.insert_parts_orders.returning[0].id,
// },
// },
if (templateRecords.templates.length === 1) {
console.log("[ITE] Using OOTB template.");
templateToUse = templateRecords.templates[0];
} else if (templateRecords.templates.length === 2) {
console.log("[ITE] Found custom template.");
templateToUse = templateRecords.templates.filter((t) => !!t.bodyshopid)[0];
console.log("templateToUse", templateToUse);
//Query assets that match the template name. Must be in format <<templateName>>.query
const jsReportQueries = await cleanAxios.get(
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`,
{ headers: { Authorization: "Basic " + btoa("admin:admin") } }
);
let templateQueryToExecute,
useShopSpecificTemplate = false;
if (jsReportQueries.data.value.length === 0) {
//We have no query to execute. Just render the template.
} else if (jsReportQueries.data.value.length === 1) {
//We're using the default template. Get the query and execute.
templateQueryToExecute = atob(jsReportQueries.data.value[0].content);
} else if (jsReportQueries.data.value.length === 2) {
//There's a custom template. Use that query instead and execute. We find it because it has a parent folder.
templateQueryToExecute = atob(
jsReportQueries.data.value.filter((v) => !!v.folder)[0].content
);
useShopSpecificTemplate = true;
} else {
//No template found.Uh oh.
alert("Error: Template key does not exist.");
throw new Error("Template key does not exist.");
//We have too many queries to choose from. Throw an error.
alert(
"There are too many queries to choose from. Please ensure there are no conflicting keys."
);
throw new Error(
"There are too many queries to choose from. Please ensure there are no conflicting keys."
);
}
const { data: contextData } = await client.query({
query: gql(templateToUse.query),
query: gql(templateQueryToExecute),
variables: { ...templateObject.variables },
fetchPolicy: "network-only",
});
const { data } = await axios.post("/render", {
view: templateToUse.html,
context: { ...contextData, bodyshop: bodyshop },
});
return new Promise((resolve, reject) => {
resolve(data);
});
let reportRequest = {
template: {
name: useShopSpecificTemplate
? `/${bodyshop.imexshopid}/${templateObject.name}`
: `/${templateObject.name}`,
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
},
data: {
...contextData,
headerpath: `/${bodyshop.imexshopid}/header.html`,
bodyshop: bodyshop,
},
};
const render = await jsreport.renderAsync(reportRequest);
if (!renderAsHtml) {
render.download();
var html =
"<html>" +
"<style>html,body {padding:0;margin:0;} iframe {width:100%;height:100%;border:0}</style>" +
"<body>" +
'<iframe type="application/pdf" src="' +
render.toDataURI() +
'"></iframe>' +
"</body></html>";
displayTemplateInWindowNoprint(html);
} else {
return new Promise((resolve, reject) => {
resolve(render.toString());
});
}
}
export const displayTemplateInWindow = (html) => {

View File

@@ -14,97 +14,5 @@ export const TemplateList = (type, object) => {
drivingId: "job",
key: "estimate_detail",
},
all_job_notes: {
title: i18n.t("printcenter.jobs.all_job_notes"),
description: "All jobs Notes including Private",
drivingId: "job",
key: "all_job_notes",
},
fippa_authorization: {
title: i18n.t("printcenter.jobs.fippa_authorization"),
description: "FIPPA Work Auth",
drivingId: "job",
key: "fippa_authorization",
},
casl_authorization: {
title: i18n.t("printcenter.jobs.casl_authorization"),
description: "CASL Auth",
drivingId: "job",
key: "casl_authorization",
},
window_tag: {
title: i18n.t("printcenter.jobs.window_tag"),
description: "Window Tag",
drivingId: "job",
key: "window_tag",
},
cover_sheet: {
title: i18n.t("printcenter.jobs.cover_sheet"),
description: "Cover Sheet",
drivingId: "job",
key: "cover_sheet",
},
time_tickets_by_employee: {
title: "Time Tickets by Employee",
description: "Time tickets for employee with date range",
drivingId: "employee",
key: "time_tickets_by_employee",
},
//Non Completed Items
appointment_reminder: {
title: "Appointment Reminder",
description:
"Sent to a customer as a reminder of an upcoming appointment.",
drivingId: "appointment",
key: "appointment_reminder",
subject: `Appointment Reminder`,
},
appointment_confirmation: {
title: "Appointment Confirmation",
description:
"Sent to a customer as a Confirmation of an upcoming appointment.",
drivingId: "appointment",
key: "appointment_confirmation",
},
parts_order_confirmation: {
title: "Parts Order Confirmation",
description: "Parts order template including part details",
drivingId: "partsorder",
key: "parts_order_confirmation",
subject: `Parts Order for ${object}`,
},
cover_sheet_landscape: {
title: "Cover Sheet - Landscape",
description: "Cover sheet landscape",
drivingId: "job",
key: "cover_sheet_landscape",
},
cover_sheet_portrait: {
title: "Cover Sheet - portrait",
description: "Cover sheet portrait",
drivingId: "job",
key: "cover_sheet_portrait",
},
parts_return_confirmation: {
title: "Parts Return Confirmation",
description: "Parts Return template including part details",
drivingId: "partsorder",
key: "parts_return_confirmation",
},
csi_invitation: {
title: "Customer Survey Invitation",
description: "Customer Survey Invitation",
drivingId: "csi",
key: "csi_invitation",
},
payment_receipt: {
title: "Payment Receipt",
description: "Receipt of payment for customer",
drivingId: "payment",
key: "payment_receipt",
},
};
};

View File

@@ -1,11 +0,0 @@
query EMAIL_APPOINTMENT_CONFIRMATION($id: uuid!) {
appointments_by_pk(id: $id) {
start
title
job {
ownr_fn
ownr_ln
ownr_ea
}
}
}

View File

@@ -1,9 +0,0 @@
<div style="font-family: Arial, Helvetica, sans-serif;">
<p style="text-align: center;">Hello {{appointments_by_pk.job.ownr_fn}},</p>
<p style="text-align: center;">
This is a confirmation that you have an appointment at
{{appointments_by_pk.start}} to bring your car in for repair. Please email
us at {{bodyshop.email}} if you can't make it.&nbsp;
</p>
</div>

View File

@@ -1,11 +0,0 @@
query EMAIL_APPOINTMENT_CONFIRMATION($id: uuid!) {
appointments_by_pk(id: $id) {
start
title
job {
ownr_fn
ownr_ln
ownr_ea
}
}
}

View File

@@ -1,8 +0,0 @@
<div style="font-family: Arial, Helvetica, sans-serif;">
<p style="text-align: center;">Hello {{appointments_by_pk.job.ownr_fn}},</p>
<p style="text-align: center;">
This is a reminder that you have an appointment at
{{appointments_by_pk.start}} to bring your car in for repair. Please email
us at {{bodyshop.email}} if you can't make it.&nbsp;
</p>
</div>

View File

@@ -1,6 +0,0 @@
query ($id: uuid!){
csi_by_pk(id: $id){
id
relateddata
}
}

View File

@@ -1,45 +0,0 @@
<div>Hi {{csi_by_pk.relateddata.job.ownr_fn}},&nbsp;</div>
<div>&nbsp;</div>
<div>
Thank you for getting your car repaired at
{{csi_by_pk.relateddata.bodyshop.shopname}}. We invite you to complete a
survey about your experience.&nbsp;
</div>
<div>&nbsp;</div>
<table border="0" width="100%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td style="text-align: center; vertical-align: middle;">
<table style="height: 47px;" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 47px;">
<td
style="border-radius: 3px; height: 47px; width: 200px;"
align="center"
bgcolor="#e9703e"
>
<a
style="
font-size: 16px;
font-family: Helvetica, Arial, sans-serif;
color: #ffffff;
text-decoration: none;
border-radius: 3px;
padding: 12px 18px;
border: 1px solid #e9703e;
display: inline-block;
"
href="https://imex.online/csi/{{csi_by_pk.id}}"
target="_blank"
rel="noopener"
>Complete Survey &rarr;</a
>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<div>&nbsp;</div>

View File

@@ -1,32 +0,0 @@
query TEMPLATE_ESTIMATE_DETAIL($id: uuid!) {
jobs_by_pk(id: $id) {
csr
ded_amt
ded_status
id
ownr_co_nm
ownr_ln
ownr_fn
plate_no
plate_st
ro_number
regie_number
tlos_ind
v_color
v_make_desc
v_model_desc
v_model_yr
v_vin
clm_no
joblines(order_by: { line_no: asc }) {
id
mod_lbr_ty
mod_lb_hrs
part_qty
oem_partno
op_code_desc
line_desc
line_no
}
}
}

View File

@@ -1,87 +0,0 @@
<div style="font-family: Arial, Helvetica, sans-serif;">
<h1 style="text-align: center;">
<span><strong>Job Detail Summary</strong></span>
</h1>
<table style="border-collapse: collapse; width: 100%;" border="1">
<tbody>
<tr>
<td style="width: 33.3333%; vertical-align: top;">
<strong>Owner:</strong> {{jobs_by_pk.ownr_fn}} {{jobs_by_pk.ownr_ln}}
{{jobs_by_pk.ownr_co_nm}}
</td>
<td style="width: 33.3333%; vertical-align: top;">
<strong>Vehicle: </strong>{{jobs_by_pk.v_model_yr}}
{{jobs_by_pk.v_color}}{{jobs_by_pk.v_make_desc}}
{{jobs_by_pk.v_model_desc}}
</td>
<td style="width: 33.3333%; vertical-align: top;">
<p>
<strong>Claim Number: </strong>{{jobs_by_pk.clm_no}}<br /><strong
>Regie Number:&lt;/strong &gt; {{jobs_by_pk.regie_number}}
</strong>
</p>
<p>
<strong>Deductible:</strong> {{jobs_by_pk.ded_amt}}
{{jobs_by_pk.ded_status}}
</p>
</td>
</tr>
</tbody>
</table>
<h2 style="text-align: center;"><span>Job Lines</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>Line No.</strong>
</td>
<td style="width: 14.2857%; text-align: center; height: 22px;">
<strong>Line Desc.</strong>
</td>
<td style="width: 14.2857%; text-align: center; height: 22px;">
<strong>OEM Part #</strong>
</td>
<td style="width: 14.2857%; text-align: center; height: 22px;">
<strong>Qty.</strong>
</td>
<td style="width: 14.2857%; text-align: center; height: 22px;">
<strong>Labor Type</strong>
</td>
<td style="width: 14.2857%; text-align: center; height: 22px;">
<strong>Hours</strong>
</td>
</tr>
<tr style="height: 22px; display: none;">
<td style="width: 100%; height: 22px;">
<span>{{#each jobs_by_pk.joblines}}</span>
</td>
</tr>
<tr style="height: 22px;">
<td style="width: 14.2857%; height: 22px; text-align: justify;">
{{this.line_no}}
</td>
<td style="width: 14.2857%; height: 22px; text-align: justify;">
{{this.line_desc}}
</td>
<td style="width: 14.2857%; height: 22px; text-align: justify;">
{{this.oem_partno}}
</td>
<td style="width: 14.2857%; height: 22px; text-align: justify;">
{{this.part_qty}}
</td>
<td style="width: 14.2857%; height: 22px; text-align: justify;">
{{this.mod_lbr_ty}}
</td>
<td style="width: 14.2857%; height: 22px; text-align: justify;">
{{this.mod_lb_hrs}}
</td>
</tr>
<tr style="height: 22px; display: none;">
<td style="width: 100%; height: 22px;"><span>{{/each}}</span></td>
</tr>
</tbody>
</table>
</div>

View File

@@ -1,3 +0,0 @@
Table Styles
<link rel='stylesheet' href='http://localhost:3000/render-styles.css'/>

View File

@@ -1,29 +0,0 @@
query REPORT_QUERY_PARTS_ORDER_BY_PK($id: uuid!) {
parts_orders_by_pk(id: $id) {
job {
id
vehicle {
id
v_model_desc
v_make_desc
v_model_yr
v_vin
}
ro_number
est_number
}
id
deliver_by
parts_order_lines {
id
db_price
act_price
line_desc
line_remarks
oem_partno
status
}
status
user_email
}
}

View File

@@ -1,122 +0,0 @@
<div style="font-family: Arial, Helvetica, sans-serif;">
<p style="text-align: center;">
<span><strong>Deliver By: {{parts_orders_by_pk.deliver_by}}</strong></span>
</p>
<table
style="
border-collapse: collapse;
width: 85%;
height: 86px;
border-color: initial;
border-style: solid;
margin-left: auto;
margin-right: auto;
"
>
<tbody>
<tr style="height: 22px;">
<td
style="
width: 33.7389%;
text-align: center;
padding: 7px;
height: 22px;
"
>
<strong>Line Description</strong>
</td>
<td
style="
width: 22.0208%;
text-align: center;
padding-top: 7px;
padding-right: 7px;
padding-bottom: 7px;
height: 22px;
"
>
<strong>Part #</strong>
</td>
<td
style="
width: 12.1897%;
text-align: center;
padding: 7px;
height: 22px;
"
>
<strong>Price</strong>
</td>
<td
style="
width: 32.0506%;
text-align: center;
padding: 7px;
height: 22px;
"
>
<strong>Line Remarks</strong>
</td>
</tr>
<tr style="height: 21px; display: none;">
<td style="height: 21px; display: none; width: 33.7389%;">
<span>{{#each parts_orders_by_pk.parts_order_lines}}</span>
</td>
</tr>
<tr style="height: 22px;">
<td
style="
width: 33.7389%;
text-align: center;
padding: 7px;
height: 22px;
border-style: solid;
border-width: 1px;
"
>
<span>{{this.line_desc}}</span>
</td>
<td
style="
width: 22.0208%;
text-align: center;
padding: 7px;
height: 22px;
border-style: solid;
border-width: 1px;
"
>
<span>{{this.oem_partno}}</span>
</td>
<td
style="
width: 12.1897%;
text-align: center;
padding: 7px;
height: 22px;
border-style: solid;
border-width: 1px;
"
>
<span>${{this.act_price}}</span>
</td>
<td
style="
width: 32.0506%;
text-align: center;
padding: 7px;
height: 22px;
border-style: solid;
border-width: 1px;
"
>
<span>{{this.line_remarks}}</span>
</td>
</tr>
<tr style="height: 21px; display: none;">
<td style="height: 21px; width: 33.7389%;"><span>{{/each}}</span></td>
</tr>
</tbody>
</table>
<p>Order Placed by {{parts_orders_by_pk.user_email}}.</p>
</div>

View File

@@ -1,26 +0,0 @@
query REPORT_PAYMENT_RECEIPT($id: uuid!) {
payments_by_pk(id: $id) {
job {
id
vehicle {
id
v_model_desc
v_make_desc
v_model_yr
v_vin
}
ro_number
est_number
clm_no
clm_total
ded_amt
}
id
amount
memo
transactionid
stripeid
payer
type
}
}

View File

@@ -1,10 +0,0 @@
<div style="font-family: Arial, Helvetica, sans-serif;">
<p style="text-align: center;"><strong>RECEIPT OF PAYMENT</strong></p>
<p style="text-align: left;"><strong>Amount</strong>: {{payments_by_pk.amount}}</p>
<p style="text-align: left;"><strong>Memo</strong>: {{payments_by_pk.memo}}</p>
<p style="text-align: left;"><strong>RO Number</strong>: {{payments_by_pk.job.ro_number}} / {{payments_by_pk.job.est_number}}</p>
<p style="text-align: left;"><strong>Payer</strong>: {{payments_by_pk.payer}}</p>
<p style="text-align: left;"><strong>StripeID</strong>: {{payments_by_pk.stripeid}}</p>
<p style="text-align: left;"><strong>Amount</strong>: {{payments_by_pk.amount}}</p>
<p style="text-align: left;">&nbsp;</p>
</div>

View File

@@ -1,25 +0,0 @@
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
}
}
}
}

View File

@@ -1,55 +0,0 @@
<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;">&nbsp;</td>
<td style="width: 31.3834%; vertical-align: top;">
<p>&nbsp;</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%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 7.14285%;">&nbsp;</td>
<td style="width: 7.14285%;">&nbsp;</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%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 14.2857%;">&nbsp;</td>
<td style="width: 7.14285%;">&nbsp;</td>
<td style="width: 7.14285%;">&nbsp;</td>
</tr>
</tbody>
</table>
</div>