diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 735dea603..e0338811e 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -19723,6 +19723,27 @@ + + estimatelines + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + existing_jobs false diff --git a/client/src/App/App.styles.scss b/client/src/App/App.styles.scss index ae6596dd5..e9c7200ca 100644 --- a/client/src/App/App.styles.scss +++ b/client/src/App/App.styles.scss @@ -67,11 +67,6 @@ // background-color: #188fff; // } -.ant-table-cell { - // background-color: red; - //padding: 0.2rem !important; -} - .ant-input-number-input, .ant-input-number, .ant-picker-input, diff --git a/client/src/components/data-label/data-label.component.jsx b/client/src/components/data-label/data-label.component.jsx index ebb4bd252..902a254cb 100644 --- a/client/src/components/data-label/data-label.component.jsx +++ b/client/src/components/data-label/data-label.component.jsx @@ -7,6 +7,7 @@ export default function DataLabel({ children, vertical, visible = true, + valueStyle = {}, ...props }) { if (!visible || (hideIfNull && !!!children)) return null; @@ -30,7 +31,7 @@ export default function DataLabel({ }} > {typeof children === "string" ? ( - {children} + {children} ) : ( children )} diff --git a/client/src/components/form-fields-changed-alert/form-fields-changed-alert.component.jsx b/client/src/components/form-fields-changed-alert/form-fields-changed-alert.component.jsx index b040db770..bf9836d77 100644 --- a/client/src/components/form-fields-changed-alert/form-fields-changed-alert.component.jsx +++ b/client/src/components/form-fields-changed-alert/form-fields-changed-alert.component.jsx @@ -11,7 +11,7 @@ export default function FormsFieldChanged({ form }) { form.resetFields(); }; const loc = useLocation(); - + if (!form.isFieldsTouched()) return <>; return ( {() => { diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx index 211ebb909..9baf80363 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -1,6 +1,6 @@ import { DeleteFilled, FilterFilled, SyncOutlined } from "@ant-design/icons"; import { useMutation } from "@apollo/client"; -import { Button, Dropdown, Input, Menu, Space, Table } from "antd"; +import { Button, Dropdown, Input, Menu, PageHeader, Space, Table } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -39,35 +39,13 @@ export function JobLinesComponent({ refetch, jobLines, setSearchText, - selectedLines, - setSelectedLines, job, setJobLineEditContext, form, }) { const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK); - // const { - // loading: billLinesLoading, - // error: billLinesError, - // data: billLinesData, - // } = useQuery(QUERY_BILLS_BY_JOB_REF, { - // variables: { jobId: job && job.id }, - // skip: loading || !job, - // }); - - // const billLinesDataObj = useMemo(() => { - // if (!billLinesData) return {}; - // const ret = {}; - // billLinesData.billlines.map((b) => { - // if (b.joblineid) { - // ret[b.joblineid] = { ...b, total: b.actual_price * b.quantity }; - // } - // return null; - // }); - // return ret; - // }, [billLinesData]); - + const [selectedLines, setSelectedLines] = useState([]); const [state, setState] = useState({ sortedInfo: {}, filteredInfo: {}, @@ -80,6 +58,7 @@ export function JobLinesComponent({ dataIndex: "line_no", key: "line_no", sorter: (a, b) => a.line_no - b.line_no, + fixed: "left", sortOrder: state.sortedInfo.columnKey === "line_no" && state.sortedInfo.order, }, @@ -87,13 +66,16 @@ export function JobLinesComponent({ title: t("joblines.fields.line_ind"), dataIndex: "line_ind", key: "line_ind", + fixed: "left", sorter: (a, b) => alphaSort(a.line_ind, b.line_ind), sortOrder: state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order, + responsive: ["md"], }, { title: t("joblines.fields.line_desc"), dataIndex: "line_desc", + fixed: "left", key: "line_desc", sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), sortOrder: @@ -231,6 +213,7 @@ export function JobLinesComponent({ dataIndex: "billref", key: "billref", render: (text, record) => , + responsive: ["md"], }, { title: t("joblines.fields.status"), @@ -258,35 +241,6 @@ export function JobLinesComponent({ ), }, - // { - // title: t("allocations.fields.employee"), - // dataIndex: "employee", - // key: "employee", - // sorter: (a, b) => - // alphaSort( - // a.allocations[0] && - // a.allocations[0].employee.first_name + - // a.allocations[0].employee.last_name, - // b.allocations[0] && - // b.allocations[0].employee.first_name + - // b.allocations[0].employee.last_name - // ), - // sortOrder: - // state.sortedInfo.columnKey === "employee" && state.sortedInfo.order, - // render: (text, record) => ( - // - // {record.allocations && record.allocations.length > 0 - // ? record.allocations.map((item) => ( - // - // )) - // : null} - // - // ), - // }, { title: t("general.labels.actions"), dataIndex: "actions", @@ -330,14 +284,6 @@ export function JobLinesComponent({ )} - { - // - } ), }, @@ -366,109 +312,82 @@ export function JobLinesComponent({ return (
+ + + + + + + + + + + { + e.preventDefault(); + setSearchText(e.target.value); + }} + /> + + } + /> { - return ( -
- - - - - - - { - // - } - -
- { - e.preventDefault(); - setSearchText(e.target.value); - }} - /> -
-
- ); - }} - // expandedRowRender={(record) => ( - //
- // {t("parts_orders.labels.orderhistory")} - // {record.parts_order_lines.map((item) => ( - //
- // - // {item.parts_order.order_number || ""} - // - // - - // - // {item.parts_order.vendor.name || ""} - // - // {` on ${item.parts_order.order_date || ""}`} - //
- // ))} - //
- // )} - rowClassName="table-small-margin" rowSelection={{ selectedRowKeys: selectedLines.map((item) => item.id), onSelectAll: (selected, selectedRows, changeRows) => { diff --git a/client/src/components/job-detail-lines/job-lines.container.jsx b/client/src/components/job-detail-lines/job-lines.container.jsx index ea1906a9b..053b984cf 100644 --- a/client/src/components/job-detail-lines/job-lines.container.jsx +++ b/client/src/components/job-detail-lines/job-lines.container.jsx @@ -1,43 +1,43 @@ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import JobLinesComponent from "./job-lines.component"; - -function JobLinesContainer({ job, joblines, refetch, form }) { +function JobLinesContainer({ job, joblines, refetch, form, ...rest }) { const [searchText, setSearchText] = useState(""); - const [selectedLines, setSelectedLines] = useState([]); - const jobLines = joblines - ? searchText - ? joblines.filter( - (jl) => - (jl.unq_seq || "") - .toString() - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.line_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.part_type || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.oem_partno || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.op_code_desc || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (jl.db_price || "").toString().includes(searchText.toLowerCase()) || - (jl.act_price || "").toString().includes(searchText.toLowerCase()) - ) - : joblines - : null; + const jobLines = useMemo(() => { + return joblines + ? searchText + ? joblines.filter( + (jl) => + (jl.unq_seq || "") + .toString() + .toLowerCase() + .includes(searchText.toLowerCase()) || + (jl.line_desc || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (jl.part_type || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (jl.oem_partno || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (jl.op_code_desc || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (jl.db_price || "") + .toString() + .includes(searchText.toLowerCase()) || + (jl.act_price || "").toString().includes(searchText.toLowerCase()) + ) + : joblines + : []; + }, [joblines, searchText]); return ( diff --git a/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx b/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx index a0b1888d5..86eadafd7 100644 --- a/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx +++ b/client/src/components/job-employee-assignments/job-employee-assignments.component.jsx @@ -1,12 +1,13 @@ +import { DeleteFilled, PlusCircleFilled } from "@ant-design/icons"; +import { Button, Popover, Select, Spin } from "antd"; import React, { useState } from "react"; -import DataLabel from "../data-label/data-label.component"; import { useTranslation } from "react-i18next"; -import { PlusCircleFilled, MinusOutlined } from "@ant-design/icons"; -import { Select, Button, Popover } from "antd"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import DataLabel from "../data-label/data-label.component"; + const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, jobRO: selectJobReadOnly, @@ -15,6 +16,8 @@ const mapDispatchToProps = (dispatch) => ({ //setUserLanguage: language => dispatch(setUserLanguage(language)) }); +const iconStyle = { marginLeft: ".3rem" }; + export function JobEmployeeAssignments({ bodyshop, jobRO, @@ -23,6 +26,7 @@ export function JobEmployeeAssignments({ prep, handleAdd, handleRemove, + loading, }) { const { t } = useTranslation(); const [assignment, setAssignment] = useState({ @@ -68,93 +72,84 @@ export function JobEmployeeAssignments({ ); return ( -
- -
- - {body ? ( -
- {`${body.first_name || ""} ${ - body.last_name || "" - }`} - !jobRO && handleRemove("body")} - /> -
- ) : ( - + + + {body ? ( +
+ {`${body.first_name || ""} ${body.last_name || ""}`} + { - if (!jobRO) { - setAssignment({ operation: "body" }); - setVisibility(true); - } - }} + style={iconStyle} + onClick={() => !jobRO && handleRemove("body")} /> - )} - - - {prep ? ( -
- {`${prep.first_name || ""} ${ - prep.last_name || "" - }`} - !jobRO && handleRemove("prep")} - /> -
- ) : ( - + ) : ( + { + if (!jobRO) { + setAssignment({ operation: "body" }); + setVisibility(true); + } + }} + /> + )} +
+ + {prep ? ( +
+ {`${prep.first_name || ""} ${prep.last_name || ""}`} + { - if (!jobRO) { - setAssignment({ operation: "prep" }); - setVisibility(true); - } - }} + style={iconStyle} + operation="prep" + onClick={() => !jobRO && handleRemove("prep")} /> - )} - - - {refinish ? ( -
- {`${refinish.first_name || ""} ${ - refinish.last_name || "" - }`} - !jobRO && handleRemove("refinish")} - /> -
- ) : ( - + ) : ( + { + if (!jobRO) { + setAssignment({ operation: "prep" }); + setVisibility(true); + } + }} + /> + )} +
+ + {refinish ? ( +
+ {`${refinish.first_name || ""} ${ + refinish.last_name || "" + }`} + { - if (!jobRO) { - setAssignment({ operation: "refinish" }); - setVisibility(true); - } - }} + style={iconStyle} + operation="refinish" + onClick={() => !jobRO && handleRemove("refinish")} /> - )} - -
- -
+
+ ) : ( + { + if (!jobRO) { + setAssignment({ operation: "refinish" }); + setVisibility(true); + } + }} + /> + )} +
+
+ ); } export default connect( diff --git a/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx b/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx index 9ac170096..510bb77c9 100644 --- a/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx +++ b/client/src/components/job-employee-assignments/job-employee-assignments.container.jsx @@ -1,16 +1,18 @@ import { useMutation } from "@apollo/client"; import { notification } from "antd"; -import React from "react"; +import React, { useState } from "react"; import { useTranslation } from "react-i18next"; +import { logImEXEvent } from "../../firebase/firebase.utils"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; import JobEmployeeAssignmentsComponent from "./job-employee-assignments.component"; -import { logImEXEvent } from "../../firebase/firebase.utils"; export default function JobEmployeeAssignmentsContainer({ job, refetch }) { const { t } = useTranslation(); const [updateJob] = useMutation(UPDATE_JOB); + const [loading, setLoading] = useState(false); const handleAdd = async (assignment) => { + setLoading(true); const { operation, employeeid } = assignment; logImEXEvent("job_assign_employee", { operation }); @@ -30,8 +32,10 @@ export default function JobEmployeeAssignmentsContainer({ job, refetch }) { }), }); } + setLoading(false); }; const handleRemove = async (operation) => { + setLoading(true); logImEXEvent("job_unassign_employee", { operation }); let empAssignment = determineFieldName(operation); @@ -48,6 +52,7 @@ export default function JobEmployeeAssignmentsContainer({ job, refetch }) { }), }); } + setLoading(false); }; return ( @@ -58,6 +63,7 @@ export default function JobEmployeeAssignmentsContainer({ job, refetch }) { prep={job.employee_prep_rel} handleAdd={handleAdd} handleRemove={handleRemove} + loading={loading} />
); diff --git a/client/src/components/job-payments/job-payments.component.jsx b/client/src/components/job-payments/job-payments.component.jsx new file mode 100644 index 000000000..eae8bc76e --- /dev/null +++ b/client/src/components/job-payments/job-payments.component.jsx @@ -0,0 +1,205 @@ +import { Button, Card, Space, Table } from "antd"; +import Dinero from "dinero.js"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import { DateTimeFormatter } from "../../utils/DateFormatter"; +import { alphaSort } from "../../utils/sorters"; +import { TemplateList } from "../../utils/TemplateConstants"; +import DataLabel from "../data-label/data-label.component"; +import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, + jobRO: selectJobReadOnly, +}); + +const mapDispatchToProps = (dispatch) => ({ + setPaymentContext: (context) => + dispatch(setModalContext({ context: context, modal: "payment" })), +}); + +const stripeTestEnv = process.env.REACT_APP_STRIPE_PUBLIC_KEY; //.includes("test"); + +export function JobPayments({ + job, + jobRO, + bodyshop, + setPaymentContext, + refetch, +}) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {}, + }); + const columns = [ + { + title: t("payments.fields.created_at"), + dataIndex: "created_at", + key: "created_at", + sortOrder: + state.sortedInfo.columnKey === "created_at" && state.sortedInfo.order, + render: (text, record) => ( + {record.created_at} + ), + }, + { + title: t("payments.fields.payer"), + dataIndex: "payer", + key: "payer", + sorter: (a, b) => alphaSort(a.payer, b.payer), + sortOrder: + state.sortedInfo.columnKey === "payer" && state.sortedInfo.order, + }, + { + title: t("payments.fields.amount"), + dataIndex: "amount", + key: "amount", + sorter: (a, b) => a.amount - b.amount, + sortOrder: + state.sortedInfo.columnKey === "amount" && state.sortedInfo.order, + render: (text, record) => ( + {record.amount} + ), + }, + { + title: t("payments.fields.memo"), + dataIndex: "memo", + key: "memo", + sorter: (a, b) => alphaSort(a.memo, b.memo), + sortOrder: + state.sortedInfo.columnKey === "memo" && state.sortedInfo.order, + }, + { + title: t("payments.fields.type"), + dataIndex: "type", + key: "type", + sorter: (a, b) => alphaSort(a.type, b.type), + sortOrder: + state.sortedInfo.columnKey === "type" && state.sortedInfo.order, + }, + { + title: t("payments.fields.transactionid"), + dataIndex: "transactionid", + key: "transactionid", + sorter: (a, b) => alphaSort(a.transactionid, b.transactionid), + sortOrder: + state.sortedInfo.columnKey === "transactionid" && + state.sortedInfo.order, + }, + { + title: t("payments.fields.stripeid"), + dataIndex: "stripeid", + key: "stripeid", + render: (text, record) => + record.stripeid ? ( + + {record.stripeid} + + ) : null, + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( + + ), + }, + ]; + + const total = useMemo(() => { + return ( + job.payments && + job.payments.reduce((acc, val) => { + acc = acc.add(Dinero({ amount: Math.round(val.amount * 100) })); + return acc; + }, Dinero()) + ); + }, [job.payments]); + + const balance = useMemo(() => { + if (job && job.job_totals && job.job_totals.totals.total_repairs) + return Dinero(job.job_totals.totals.total_repairs).subtract(total); + return Dinero({ amount: 0 }).subtract(total); + }, [job, total]); + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + return ( + + + + {balance.toFormat()} + + + } + > +
( + <> + + + {t("payments.labels.totalpayments")} + + + + {total.toFormat()} + + + + + + + )} + /> + + ); +} +export default connect(mapStateToProps, mapDispatchToProps)(JobPayments); diff --git a/client/src/components/job-totals-table/job-totals-table.component.jsx b/client/src/components/job-totals-table/job-totals-table.component.jsx index 7da4b2bfb..2159339f6 100644 --- a/client/src/components/job-totals-table/job-totals-table.component.jsx +++ b/client/src/components/job-totals-table/job-totals-table.component.jsx @@ -1,5 +1,4 @@ -import { Col, Collapse, Result, Row, Typography } from "antd"; -import Dinero from "dinero.js"; +import { Card, Col, Collapse, Result, Row } from "antd"; //import { JsonEditor as Editor } from "jsoneditor-react"; //import "jsoneditor-react/es/editor.min.css"; import React from "react"; @@ -7,14 +6,16 @@ import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectJobReadOnly } from "../../redux/application/application.selectors"; -import { selectBodyshop } from "../../redux/user/user.selectors"; import JobCalculateTotals from "../job-calculate-totals/job-calculate-totals.component"; import "./job-totals-table.styles.scss"; +import JobTotalsTableLabor from "./job-totals.table.labor.component"; +import JobTotalsTableOther from "./job-totals.table.other.component"; +import JobTotalsTableParts from "./job-totals.table.parts.component"; +import JobTotalsTableTotals from "./job-totals.table.totals.component"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser jobRO: selectJobReadOnly, - bodyshop: selectBodyshop, }); const colSpan = { @@ -22,15 +23,17 @@ const colSpan = { lg: { span: 12 }, }; -export function JobsTotalsTableComponent({ bodyshop, jobRO, job }) { +export function JobsTotalsTableComponent({ jobRO, job }) { const { t } = useTranslation(); if (!!!job.job_totals) { return ( - } - /> + + } + /> + ); } @@ -38,334 +41,50 @@ export function JobsTotalsTableComponent({ bodyshop, jobRO, job }) {
-
- - {t("jobs.labels.labortotals")} - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{t("jobs.fields.rate_laa")} - {Dinero(job.job_totals.rates.laa.total).toFormat()} - {`(${job.job_totals.rates.laa.hours.toFixed(2)} @ ${ - job.job_totals.rates.laa.rate - })`}
{t("jobs.fields.rate_lab")} - {Dinero(job.job_totals.rates.lab.total).toFormat()} - {`(${job.job_totals.rates.lab.hours.toFixed(2)} @ ${ - job.job_totals.rates.lab.rate - })`}
{t("jobs.fields.rate_lad")} - {Dinero(job.job_totals.rates.lad.total).toFormat()} - {`(${job.job_totals.rates.lad.hours.toFixed(2)} @ ${ - job.job_totals.rates.lad.rate - })`}
{t("jobs.fields.rate_lae")} - {Dinero(job.job_totals.rates.lae.total).toFormat()} - {`(${job.job_totals.rates.lae.hours.toFixed(2)} @ ${ - job.job_totals.rates.lae.rate - })`}
{t("jobs.fields.rate_laf")} - {Dinero(job.job_totals.rates.laf.total).toFormat()} - {`(${job.job_totals.rates.laf.hours.toFixed(2)} @ ${ - job.job_totals.rates.laf.rate - })`}
{t("jobs.fields.rate_lag")} - {Dinero(job.job_totals.rates.lag.total).toFormat()} - {`(${job.job_totals.rates.lag.hours.toFixed(2)} @ ${ - job.job_totals.rates.lag.rate - })`}
{t("jobs.fields.rate_lam")} - {Dinero(job.job_totals.rates.lam.total).toFormat()} - {`(${job.job_totals.rates.lam.hours.toFixed(2)} @ ${ - job.job_totals.rates.lam.rate - })`}
{t("jobs.fields.rate_lar")} - {Dinero(job.job_totals.rates.lar.total).toFormat()} - {`(${job.job_totals.rates.lar.hours.toFixed(2)} @ ${ - job.job_totals.rates.lar.rate - })`}
{t("jobs.fields.rate_las")} - {Dinero(job.job_totals.rates.las.total).toFormat()} - {`(${job.job_totals.rates.las.hours.toFixed(2)} @ ${ - job.job_totals.rates.las.rate - })`}
{t("jobs.fields.rate_lau")} - {Dinero(job.job_totals.rates.lau.total).toFormat()} - {`(${job.job_totals.rates.lau.hours.toFixed(2)} @ ${ - job.job_totals.rates.lau.rate - })`}
{t("jobs.fields.rate_la1")} - {Dinero(job.job_totals.rates.la1.total).toFormat()} - {`(${job.job_totals.rates.la1.hours.toFixed(2)} @ ${ - job.job_totals.rates.la1.rate - })`}
{t("jobs.fields.rate_la2")} - {Dinero(job.job_totals.rates.la2.total).toFormat()} - {`(${job.job_totals.rates.la2.hours.toFixed(2)} @ ${ - job.job_totals.rates.la2.rate - })`}
{t("jobs.fields.rate_la3")} - {Dinero(job.job_totals.rates.la3.total).toFormat()} - {`(${job.job_totals.rates.la3.hours.toFixed(2)} @ ${ - job.job_totals.rates.la3.rate - })`}
{t("jobs.fields.rate_la4")} - {Dinero(job.job_totals.rates.la4.total).toFormat()} - {`(${job.job_totals.rates.la4.hours.toFixed(2)} @ ${ - job.job_totals.rates.la4.rate - })`}
{t("jobs.labels.labor_rates_subtotal")} - - {Dinero(job.job_totals.rates.rates_subtotal).toFormat()} - -
{t("jobs.labels.mapa")} - {Dinero(job.job_totals.rates.mapa.total).toFormat()} - {`(${job.job_totals.rates.mapa.hours.toFixed(2)} @ ${ - job.job_totals.rates.mapa.rate - })`}
{t("jobs.labels.mash")} - {Dinero(job.job_totals.rates.mash.total).toFormat()} - {`(${job.job_totals.rates.mash.hours.toFixed(2)} @ ${ - job.job_totals.rates.mash.rate - })`}
{t("jobs.labels.rates_subtotal")} - - {Dinero(job.job_totals.rates.subtotal).toFormat()} - -
-
+ + + -
- - {t("jobs.labels.partstotal")} - - - - {Object.keys(job.job_totals.parts.parts.list).map( - (key, idx) => ( - - - - - ) - )} - - - - - -
{t(`jobs.fields.${key.toLowerCase()}`)} - {Dinero( - job.job_totals.parts.parts.list[key].total - ).toFormat()} -
{t("jobs.labels.partstotal")} - - {Dinero(job.job_totals.parts.parts.total).toFormat()} - -
- - - {t("jobs.labels.othertotal")} - - - - - - - - - - - - - -
{t("jobs.labels.subletstotal")} - {Dinero(job.job_totals.parts.sublets.total).toFormat()} -
{t("jobs.labels.additionaltotal")} - {Dinero(job.job_totals.additional).toFormat()} -
- - - {t("jobs.labels.jobtotals")} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{t("jobs.labels.subtotal")} - - {Dinero(job.job_totals.totals.subtotal).toFormat()} - -
{t("jobs.labels.local_tax_amt")} - {Dinero(job.job_totals.totals.local_tax).toFormat()} -
{t("jobs.labels.state_tax_amt")} - {Dinero(job.job_totals.totals.state_tax).toFormat()} -
{t("jobs.labels.federal_tax_amt")} - {Dinero(job.job_totals.totals.federal_tax).toFormat()} -
{t("jobs.fields.ded_amt")} - {Dinero( - job.job_totals.totals.custPayable.deductible - ).toFormat()} -
{t("jobs.fields.federal_tax_payable")} - {Dinero( - job.job_totals.totals.custPayable.federal_tax - ).toFormat()} -
{t("jobs.fields.other_amount_payable")} - {Dinero( - job.job_totals.totals.custPayable.other_customer_amount - ).toFormat()} -
{t("jobs.fields.depreciation_taxes")} - {Dinero( - job.job_totals.totals.custPayable.dep_taxes - ).toFormat()} -
{t("jobs.labels.total_repairs")} - {Dinero(job.job_totals.totals.total_repairs).toFormat()} -
{t("jobs.labels.total_cust_payable")} - {Dinero(job.job_totals.totals.custPayable.total).toFormat()} -
{t("jobs.labels.net_repairs")} - - {Dinero(job.job_totals.totals.net_repairs).toFormat()} - -
- - - - -
-
-                    {JSON.stringify(
-                      {
-                        CIECA: job.cieca_ttl && job.cieca_ttl.data,
-                        ImEXCalc: job.job_totals,
-                      },
-                      null,
-                      2
-                    )}
-                  
-
-
-
-
+ + + + + + + + + + + + + + + + + + + + + +
+
+                        {JSON.stringify(
+                          {
+                            CIECA: job.cieca_ttl && job.cieca_ttl.data,
+                            CIECASTL: job.cieca_stl && job.cieca_stl.data,
+                            ImEXCalc: job.job_totals,
+                          },
+                          null,
+                          2
+                        )}
+                      
+
+
+
+
+ +
diff --git a/client/src/components/job-totals-table/job-totals-table.styles.scss b/client/src/components/job-totals-table/job-totals-table.styles.scss index 8a68d651b..062f2ac6b 100644 --- a/client/src/components/job-totals-table/job-totals-table.styles.scss +++ b/client/src/components/job-totals-table/job-totals-table.styles.scss @@ -1,38 +1,38 @@ -.job-totals-half { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; +// .job-totals-half { +// flex: 1; +// display: flex; +// flex-direction: column; +// align-items: center; - table { - border: 1px solid #ccc; - border-collapse: collapse; - margin: 0; - padding: 0; - width: 80%; - table-layout: fixed; - } +// table { +// border: 1px solid #ccc; +// border-collapse: collapse; +// margin: 0; +// padding: 0; +// width: 80%; +// table-layout: fixed; +// } - table tr { - //background-color: #f8f8f8; - border: 1px solid #ddd; - padding: 0.35em; - } +// table tr { +// //background-color: #f8f8f8; +// border: 1px solid #ddd; +// padding: 0.35em; +// } - table th, - table td { - padding: 0.625em; - //text-align: center; - } - table td.currency { - text-align: right; - } -} +// table th, +// table td { +// padding: 0.625em; +// //text-align: center; +// } +// table td.currency { +// text-align: right; +// } +// } -.job-totals-stats { - margin: 1rem; - display: flex; - width: 100%; - //flex-direction: column; - justify-content: space-evenly; -} +// .job-totals-stats { +// margin: 1rem; +// display: flex; +// width: 100%; +// //flex-direction: column; +// justify-content: space-evenly; +// } diff --git a/client/src/components/job-totals-table/job-totals.table.labor.component.jsx b/client/src/components/job-totals-table/job-totals.table.labor.component.jsx new file mode 100644 index 000000000..10097f743 --- /dev/null +++ b/client/src/components/job-totals-table/job-totals.table.labor.component.jsx @@ -0,0 +1,157 @@ +import { Table } from "antd"; +import Dinero from "dinero.js"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import { alphaSort } from "../../utils/sorters"; + +export default function JobTotalsTableLabor({ job }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: { + columnKey: "profitcenter_labor", + field: "profitcenter_labor", + order: "ascend", + }, + filteredInfo: {}, + }); + + const data = useMemo(() => { + return Object.keys(job.job_totals.rates) + .filter( + (key) => + key !== "mapa" && + key !== "mash" && + key !== "subtotal" && + key !== "rates_subtotal" + ) + .map((key) => { + return { + id: key, + ...job.job_totals.rates[key], + }; + }); + }, [job.job_totals.rates]); + + const columns = [ + { + title: t("joblines.fields.profitcenter_labor"), + dataIndex: "profitcenter_labor", + key: "profitcenter_labor", + defaultSortOrder: "ascend", + sorter: (a, b) => + alphaSort( + t(`jobs.fields.rate_${a.id.toLowerCase()}`), + t(`jobs.fields.rate_${b.id.toLowerCase()}`) + ), + sortOrder: + state.sortedInfo.columnKey === "profitcenter_labor" && + state.sortedInfo.order, + render: (text, record) => + t(`jobs.fields.rate_${record.id.toLowerCase()}`), + }, + { + title: t("jobs.labels.rates"), + dataIndex: "rate", + key: "rate", + align: "right", + sorter: (a, b) => a.rate - b.rate, + sortOrder: + state.sortedInfo.columnKey === "rate" && state.sortedInfo.order, + render: (text, record) => ( + {record.rate} + ), + }, + { + title: t("joblines.fields.mod_lb_hrs"), + dataIndex: "mod_lb_hrs", + + key: "mod_lb_hrs", + sorter: (a, b) => a.mod_lb_hrs - b.mod_lb_hrs, + sortOrder: + state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order, + + render: (text, record) => record.hours.toFixed(1), + }, + { + title: t("joblines.fields.total"), + dataIndex: "total", + key: "total", + align: "right", + sorter: (a, b) => a.total.amount - b.total.amount, + sortOrder: + state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + + render: (text, record) => Dinero(record.total).toFormat(), + }, + ]; + + const handleTableChange = (pagination, filters, sorter) => { + console.log("sorter :>> ", sorter); + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + return ( + ( + <> + + + {t("jobs.labels.labor_rates_subtotal")} + + + + + + {Dinero(job.job_totals.rates.rates_subtotal).toFormat()} + + + + + {t("jobs.labels.mapa")} + + {job.job_totals.rates.mapa.rate} + + + {job.job_totals.rates.mapa.hours.toFixed(2)} + + + {Dinero(job.job_totals.rates.mapa.total).toFormat()} + + + + {t("jobs.labels.mash")} + + {job.job_totals.rates.mash.rate} + + + {job.job_totals.rates.mash.hours.toFixed(2)} + + + {Dinero(job.job_totals.rates.mash.total).toFormat()} + + + + + {t("jobs.labels.labor_rates_subtotal")} + + + + + + {Dinero(job.job_totals.rates.rates_subtotal).toFormat()} + + + + + )} + /> + ); +} diff --git a/client/src/components/job-totals-table/job-totals.table.other.component.jsx b/client/src/components/job-totals-table/job-totals.table.other.component.jsx new file mode 100644 index 000000000..c3160e866 --- /dev/null +++ b/client/src/components/job-totals-table/job-totals.table.other.component.jsx @@ -0,0 +1,77 @@ +import { Table } from "antd"; +import Dinero from "dinero.js"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; + +export default function JobTotalsTableOther({ job }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {}, + }); + + const data = useMemo(() => { + return [ + { + key: t("jobs.labels.subletstotal"), + total: job.job_totals.parts.sublets.total, + }, + { + key: t("jobs.labels.additionaltotal"), + total: job.job_totals.additional, + }, + ]; + }, [job.job_totals, t]); + + const columns = [ + { + //title: t("joblines.fields.part_type"), + dataIndex: "key", + key: "key", + sorter: (a, b) => alphaSort(a.key, b.key), + sortOrder: state.sortedInfo.columnKey === "key" && state.sortedInfo.order, + width: "0%", + }, + { + title: t("joblines.fields.total"), + dataIndex: "total", + key: "total", + sorter: (a, b) => a.total.amount - b.total.amount, + sortOrder: + state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + width: "20%", + align: "right", + render: (text, record) => Dinero(record.total).toFormat(), + }, + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + return ( +
( + + + {t("jobs.labels.additionaltotal")} + + + + + {Dinero(job.job_totals.parts.parts.total).toFormat()} + + + + )} + /> + ); +} diff --git a/client/src/components/job-totals-table/job-totals.table.parts.component.jsx b/client/src/components/job-totals-table/job-totals.table.parts.component.jsx new file mode 100644 index 000000000..ca1eef5b3 --- /dev/null +++ b/client/src/components/job-totals-table/job-totals.table.parts.component.jsx @@ -0,0 +1,84 @@ +import { Table } from "antd"; +import Dinero from "dinero.js"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { alphaSort } from "../../utils/sorters"; + +export default function JobTotalsTableParts({ job }) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: {}, + }); + + const data = useMemo(() => { + return Object.keys(job.job_totals.parts.parts.list) + .filter( + (key) => + key !== "mapa" && + key !== "mash" && + key !== "subtotal" && + key !== "rates_subtotal" + ) + .map((key) => { + return { + id: key, + ...job.job_totals.parts.parts.list[key], + }; + }); + }, [job.job_totals.parts.parts.list]); + + const columns = [ + { + title: t("joblines.fields.part_type"), + dataIndex: "id", + key: "id", + sorter: (a, b) => + alphaSort( + t(`jobs.fields.${a.id.toLowerCase()}`), + t(`jobs.fields.${b.id.toLowerCase()}`) + ), + width: "80%", + sortOrder: state.sortedInfo.columnKey === "id" && state.sortedInfo.order, + render: (text, record) => t(`jobs.fields.${record.id.toLowerCase()}`), + }, + { + title: t("joblines.fields.total"), + dataIndex: "total", + key: "total", + sorter: (a, b) => a.total.amount - b.total.amount, + sortOrder: + state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + width: "20%", + align: "right", + render: (text, record) => Dinero(record.total).toFormat(), + }, + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + return ( +
( + + {t("jobs.labels.partstotal")} + + + + {Dinero(job.job_totals.parts.parts.total).toFormat()} + + + + )} + /> + ); +} diff --git a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx new file mode 100644 index 000000000..d2d0ab7f0 --- /dev/null +++ b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx @@ -0,0 +1,96 @@ +import { Table } from "antd"; +import Dinero from "dinero.js"; +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; + +export default function JobTotalsTableTotals({ job }) { + const { t } = useTranslation(); + + const data = useMemo(() => { + return [ + { + key: t("jobs.labels.subtotal"), + total: job.job_totals.totals.subtotal, + bold: true, + }, + { + key: t("jobs.labels.local_tax_amt"), + total: job.job_totals.totals.local_tax, + }, + { + key: t("jobs.labels.state_tax_amt"), + total: job.job_totals.totals.state_tax, + }, + { + key: t("jobs.labels.federal_tax_amt"), + total: job.job_totals.totals.federal_tax, + }, + { + key: t("jobs.fields.ded_amt"), + total: job.job_totals.totals.custPayable.deductible, + }, + { + key: t("jobs.fields.federal_tax_payable"), + total: job.job_totals.totals.custPayable.federal_tax, + }, + { + key: t("jobs.fields.other_amount_payable"), + total: job.job_totals.totals.custPayable.other_customer_amount, + }, + { + key: t("jobs.fields.depreciation_taxes"), + total: job.job_totals.totals.custPayable.dep_taxes, + }, + { + key: t("jobs.labels.total_repairs"), + total: job.job_totals.totals.total_repairs, + bold: true, + }, + { + key: t("jobs.labels.total_cust_payable"), + total: job.job_totals.totals.custPayable.total, + }, + { + key: t("jobs.labels.net_repairs"), + total: job.job_totals.totals.net_repairs, + bold: true, + }, + ]; + }, [job.job_totals, t]); + + const columns = [ + { + //title: t("joblines.fields.part_type"), + dataIndex: "key", + key: "key", + width: "80%", + onCell: (record, rowIndex) => { + return { style: { fontWeight: record.bold && "bold" } }; + }, + }, + { + title: t("joblines.fields.total"), + dataIndex: "total", + key: "total", + align: "right", + render: (text, record) => Dinero(record.total).toFormat(), + width: "20%", + onCell: (record, rowIndex) => { + return { style: { fontWeight: record.bold && "bold" } }; + }, + }, + ]; + + return ( +
+ ); +} diff --git a/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx b/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx index 385fd21bd..25a6c66ec 100644 --- a/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx +++ b/client/src/components/jobs-detail-general/jobs-detail-general.component.jsx @@ -27,7 +27,6 @@ const lossColDamage = { sm: { span: 24 }, md: { span: 6 }, lg: { span: 4 } }; export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) { const { getFieldValue } = form; const { t } = useTranslation(); - return (
diff --git a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx index 81f71d1e9..ceff24182 100644 --- a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx +++ b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx @@ -10,6 +10,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import DataLabel from "../data-label/data-label.component"; +import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container"; +import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component"; import "./jobs-detail-header.styles.scss"; const mapStateToProps = createStructuredSelector({ @@ -22,15 +24,25 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setModalContext({ context: context, modal: "printCenter" })), }); -export function JobsDetailHeader({ - setPrintCenterContext, - jobRO, - job, - refetch, - loading, - form, - bodyshop, -}) { +const colSpan = { + xs: { + span: 24, + }, + sm: { + span: 24, + }, + md: { + span: 12, + }, + lg: { + span: 6, + }, + xl: { + span: 6, + }, +}; + +export function JobsDetailHeader({ job, bodyshop }) { const { t } = useTranslation(); const jobInPostProduction = useMemo(() => { @@ -39,15 +51,10 @@ export function JobsDetailHeader({ ); }, [job.status, bodyshop.md_ro_statuses.post_production_statuses]); - const gridStyle = { - flex: 1, - //textAlign: "center", - }; - return ( - -
- + + +
{job.status} @@ -68,10 +75,15 @@ export function JobsDetailHeader({ / {job.owner_owing} + {(job.inproduction || jobInPostProduction) && ( + + + + )}
- + - + + + +
+ +
+
+ ); @@ -156,7 +178,7 @@ export function JobsDetailHeader({ // // )} - // + // // // // ); diff --git a/client/src/components/jobs-detail-rates-change-button/jobs-detail-rates-change-button.component.jsx b/client/src/components/jobs-detail-rates-change-button/jobs-detail-rates-change-button.component.jsx index d0332480c..b3bce1462 100644 --- a/client/src/components/jobs-detail-rates-change-button/jobs-detail-rates-change-button.component.jsx +++ b/client/src/components/jobs-detail-rates-change-button/jobs-detail-rates-change-button.component.jsx @@ -15,14 +15,9 @@ export function JobsDetailRatesChangeButton({ disabled, form, bodyshop }) { const handleClick = ({ item, key, keyPath }) => { const rate = item.props.value; - console.log("handleClick -> rate", rate); form.setFieldsValue(rate); }; - console.log( - "🚀 ~ file: jobs-detail-rates-change-button.component.jsx ~ line 26 ~ bodyshop.md_labor_rates", - bodyshop.md_labor_rates - ); const menu = (
diff --git a/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx b/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx index ce006a974..a067493a0 100644 --- a/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx +++ b/client/src/components/jobs-detail-rates/jobs-detail-rates.component.jsx @@ -14,9 +14,8 @@ const mapStateToProps = createStructuredSelector({ jobRO: selectJobReadOnly, }); -export function JobsDetailRates({ job, jobRO, form }) { +export function JobsDetailRates({ jobRO, form }) { const { t } = useTranslation(); - return (
diff --git a/client/src/components/jobs-detail-totals/jobs-detail-totals.component.jsx b/client/src/components/jobs-detail-totals/jobs-detail-totals.component.jsx index b5763b60a..40e49733b 100644 --- a/client/src/components/jobs-detail-totals/jobs-detail-totals.component.jsx +++ b/client/src/components/jobs-detail-totals/jobs-detail-totals.component.jsx @@ -1,143 +1,15 @@ -import { Button, Divider, Space, Statistic, Typography } from "antd"; -import Dinero from "dinero.js"; -import React, { useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectJobReadOnly } from "../../redux/application/application.selectors"; -import { setModalContext } from "../../redux/modals/modals.actions"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import { DateTimeFormatter } from "../../utils/DateFormatter"; -import { TemplateList } from "../../utils/TemplateConstants"; +import { Divider } from "antd"; +import React from "react"; +import JobPayments from "../job-payments/job-payments.component"; import JobTotalsTable from "../job-totals-table/job-totals-table.component"; -import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; - -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, - jobRO: selectJobReadOnly, -}); - -const mapDispatchToProps = (dispatch) => ({ - setPaymentContext: (context) => - dispatch(setModalContext({ context: context, modal: "payment" })), -}); - -const stripeTestEnv = process.env.REACT_APP_STRIPE_PUBLIC_KEY; //.includes("test"); - -export function JobsDetailTotals({ - job, - jobRO, - bodyshop, - setPaymentContext, - refetch, -}) { - const { t } = useTranslation(); - - const total = useMemo(() => { - return ( - job.payments && - job.payments.reduce((acc, val) => { - acc = acc.add(Dinero({ amount: Math.round(val.amount * 100) })); - return acc; - }, Dinero()) - ); - }, [job.payments]); - - const balance = useMemo(() => { - if (job && job.job_totals && job.job_totals.totals.total_repairs) - return Dinero(job.job_totals.totals.total_repairs).subtract(total); - return Dinero({ amount: 0 }).subtract(total); - }, [job, total]); +export function JobsDetailTotals({ job, refetch }) { return (
- - {t("payments.labels.title")} - - -
-
- - - - - - - - - - - - - - {job.payments.map((p, idx) => ( - - - - - - - - - - - ))} - -
{t("payments.fields.created_at")}{t("payments.fields.payer")}{t("payments.fields.amount")}{t("payments.fields.memo")}{t("payments.fields.type")}{t("payments.fields.transactionid")}{t("payments.fields.stripeid")}{t("general.labels.actions")}
- {p.created_at} - {p.payer} - {p.amount} - {p.memo}{p.type}{p.transactionid} - {p.stripeid ? ( - - {p.stripeid} - - ) : null} - - -
- - - - - - + - ); } -export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailTotals); +export default JobsDetailTotals; diff --git a/client/src/index.js b/client/src/index.js index 6d63c917f..7eb7b6985 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -84,4 +84,4 @@ const onServiceWorkerUpdate = (registration) => { }; serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate }); -reportWebVitals(console.log); +reportWebVitals(); diff --git a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx index 72952c226..2ecbe961f 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.component.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.component.jsx @@ -6,7 +6,15 @@ import Icon, { PrinterFilled, ToolFilled, } from "@ant-design/icons"; -import { Button, Form, notification, PageHeader, Space, Tabs } from "antd"; +import { + Button, + Divider, + Form, + notification, + PageHeader, + Space, + Tabs, +} from "antd"; import Axios from "axios"; import moment from "moment"; import queryString from "query-string"; @@ -148,13 +156,15 @@ export function JobsDetailPage({ extra={menuExtra} /> - + history.push({ search: `?tab=${key}` })} + tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }} > @@ -166,6 +176,7 @@ export function JobsDetailPage({ @@ -182,6 +193,7 @@ export function JobsDetailPage({ /> @@ -226,6 +238,7 @@ export function JobsDetailPage({ diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 4331073ae..a7675e0c5 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1069,7 +1069,7 @@ "policy_no": "Policy #", "ponumber": "PO Number", "production_vars": { - "note": "Production Note:" + "note": "Production Note" }, "rate_la1": "LA1", "rate_la2": "LA2", @@ -1192,6 +1192,7 @@ "documents-other": "Other Documents", "duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.", "employeeassignments": "Employee Assignments", + "estimatelines": "Estimate Lines", "existing_jobs": "Existing Jobs", "federal_tax_amt": "Federal Taxes", "gpdollars": "$ G.P.", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index f65be0f01..979c96b54 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1192,6 +1192,7 @@ "documents-other": "", "duplicateconfirm": "", "employeeassignments": "", + "estimatelines": "", "existing_jobs": "Empleos existentes", "federal_tax_amt": "", "gpdollars": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 326d033bf..7dc306750 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1192,6 +1192,7 @@ "documents-other": "", "duplicateconfirm": "", "employeeassignments": "", + "estimatelines": "", "existing_jobs": "Emplois existants", "federal_tax_amt": "", "gpdollars": "", diff --git a/client/src/utils/usetraceupdate.jsx b/client/src/utils/usetraceupdate.jsx index acf37bbcb..90e63c2d1 100644 --- a/client/src/utils/usetraceupdate.jsx +++ b/client/src/utils/usetraceupdate.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; function useTraceUpdate(props) { const prev = useRef(props); useEffect(() => {