diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 446075c77..abc4f8118 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -3339,6 +3339,53 @@ + + jc_hourly_rates + + + mapa + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + mash + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + lastnumberworkingdays false @@ -10757,6 +10804,27 @@ + + updating + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -12947,6 +13015,48 @@ + + nointernet + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + nointernet_sub + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + none false @@ -22179,6 +22289,27 @@ + + prt_dsmk_total + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + rates false @@ -27859,6 +27990,48 @@ labels + + noneselected + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + onenamerequired + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + vendorcategory false @@ -30292,6 +30465,32 @@ + + successes + + + updated + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index c8e619c05..1f5298da2 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -14,6 +14,7 @@ if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp"); export default function AppContainer() { const { t } = useTranslation(); + return ( const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, + online: selectOnline, }); const mapDispatchToProps = (dispatch) => ({ checkUserSession: () => dispatch(checkUserSession()), + setOnline: (isOnline) => dispatch(setOnline(isOnline)), }); -export function App({ checkUserSession, currentUser }) { +export function App({ checkUserSession, currentUser, online, setOnline }) { useEffect(() => { checkUserSession(); }, [checkUserSession]); @@ -42,10 +47,38 @@ export function App({ checkUserSession, currentUser }) { const { t } = useTranslation(); + window.addEventListener("offline", function (e) { + console.log("Internet connection lost."); + setOnline(false); + }); + + window.addEventListener("online", function (e) { + setOnline(true); + }); + if (currentUser.authorized === null) { return ; } + if (!online) + return ( + { + window.location.reload(); + }} + > + {t("general.actions.refresh")} + + } + /> + ); + return ( }> diff --git a/client/src/components/documents-upload/documents-upload.utility.js b/client/src/components/documents-upload/documents-upload.utility.js index ad5083e95..5856d985e 100644 --- a/client/src/components/documents-upload/documents-upload.utility.js +++ b/client/src/components/documents-upload/documents-upload.utility.js @@ -128,7 +128,7 @@ export const uploadToCloudinary = async ( let takenat; if (fileType.includes("image")) { const exif = await exifr.parse(file); - console.log(`exif`, exif); + takenat = exif && exif.DateTimeOriginal; } const documentInsert = await client.mutate({ @@ -141,7 +141,7 @@ export const uploadToCloudinary = async ( uploaded_by: uploaded_by, key: key, type: fileType, - extension: extension, + extension: cloudinaryUploadResponse.data.format || extension, bodyshopid: bodyshop.id, size: cloudinaryUploadResponse.data.bytes || file.size, takenat, @@ -176,6 +176,7 @@ export const uploadToCloudinary = async ( } }; +//Also needs to be updated in media JS and mobile app. export function DetermineFileType(filetype) { if (!filetype) return "auto"; else if (filetype.startsWith("image")) return "image"; 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 02c73382a..cd88da672 100644 --- a/client/src/components/job-detail-lines/job-lines.component.jsx +++ b/client/src/components/job-detail-lines/job-lines.component.jsx @@ -155,7 +155,14 @@ export function JobLinesComponent({ state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, ellipsis: true, render: (text, record) => ( - {record.act_price} + <> + {record.act_price} + {record.prt_dsmk_p !== 0 && ( + {`(${record.prt_dsmk_p}%)`} + )} + ), }, { 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 index 7f1b884d6..93ed6eea6 100644 --- 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 @@ -69,17 +69,28 @@ export default function JobTotalsTableParts({ job }) { x: true, }} summary={() => ( - - - {t("jobs.labels.partstotal")} - + <> + + + {t("jobs.labels.prt_dsmk_total")} + - - - {Dinero(job.job_totals.parts.parts.total).toFormat()} - - - + + {Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()} + + + + + {t("jobs.labels.partstotal")} + + + + + {Dinero(job.job_totals.parts.parts.total).toFormat()} + + + + )} /> ); diff --git a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx index d467fe8c1..4f36cd850 100644 --- a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx +++ b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx @@ -32,7 +32,7 @@ mutation UNVOID_JOB($jobId: uuid!) { } insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${ currentUser.email - }", text: "${t("jobs.labels.unvoidnote", { email: currentUser.email })}"}) { + }", text: "${t("jobs.labels.unvoidnote")}"}) { returning { id } diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx index 345981c08..1f68838c3 100644 --- a/client/src/components/jobs-available-table/jobs-available-table.container.jsx +++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx @@ -124,10 +124,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) { data: { created_by: currentUser.email, audit: true, - text: t("jobs.labels.importnote", { - date: moment().format("MM/DD/yyy"), - time: moment().format("hh:mm a"), - }), + text: t("jobs.labels.importnote"), }, }, queued_for_parts: true, @@ -278,10 +275,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) { jobid: selectedJob, created_by: currentUser.email, audit: true, - text: t("jobs.labels.supplementnote", { - date: moment().format("MM/DD/yyy"), - time: moment().format("hh:mm a"), - }), + text: t("jobs.labels.supplementnote"), }, ], }, diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index 16bcc94ff..1b31a210d 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -1,7 +1,6 @@ import { DownCircleFilled } from "@ant-design/icons"; import { useApolloClient, useMutation } from "@apollo/client"; import { Button, Dropdown, Menu, notification, Popconfirm } from "antd"; -import moment from "moment"; import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -385,10 +384,7 @@ export function JobsDetailHeaderActions({ jobid: job.id, created_by: currentUser.email, audit: true, - text: t("jobs.labels.voidnote", { - date: moment().format("MM/DD/yyy"), - time: moment().format("hh:mm a"), - }), + text: t("jobs.labels.voidnote"), }, ], }, 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 363fc889b..8beebc60d 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 @@ -151,17 +151,21 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) { - {`${job.v_model_yr || ""} ${job.v_color || ""} + job.vehicle ? ( + + {`${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`} - + + ) : ( + + ) } >
diff --git a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx index 2c50c9672..e4c3d0eba 100644 --- a/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx +++ b/client/src/components/jobs-documents-gallery/jobs-documents-gallery.component.jsx @@ -31,7 +31,9 @@ function JobsDocumentsComponent({ acc.images.push({ src: `${ process.env.REACT_APP_CLOUDINARY_ENDPOINT - }/${DetermineFileType(value.type)}/upload/${value.key}`, + }/${DetermineFileType(value.type)}/upload/${value.key}${ + value.extension ? `.${value.extension}` : "" + }`, thumbnail: `${ process.env.REACT_APP_CLOUDINARY_ENDPOINT }/${DetermineFileType(value.type)}/upload/${ @@ -51,13 +53,17 @@ function JobsDocumentsComponent({ let thumb; switch (fileType) { case "video": - thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/c_fill,f_png,h_250,w_250/${value.key}`; + thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`; break; case "raw": thumb = `${window.location.origin}/file.png`; break; default: - thumb = `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${fileType}/upload/${process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS}/${value.key}`; + thumb = `${ + process.env.REACT_APP_CLOUDINARY_ENDPOINT + }/${fileType}/upload/${ + process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS + }/${value.key}${value.extension ? `.${value.extension}` : ""}`; break; } @@ -65,9 +71,9 @@ function JobsDocumentsComponent({ acc.other.push({ src: `${ process.env.REACT_APP_CLOUDINARY_ENDPOINT - }/${fileType}/upload/${fileType === "video" ? "q_auto/" : ""}${ - value.key - }${fileType === "raw" ? `.${value.extension}` : ""}`, + }/${fileType}/upload/${value.key}${ + value.extension ? `.${value.extension}` : "" + }`, thumbnail: thumb, tags: [ { diff --git a/client/src/components/phonebook-form/phonebook-form.component.jsx b/client/src/components/phonebook-form/phonebook-form.component.jsx index e962a34c6..e40096ce9 100644 --- a/client/src/components/phonebook-form/phonebook-form.component.jsx +++ b/client/src/components/phonebook-form/phonebook-form.component.jsx @@ -82,7 +82,31 @@ export function PhonebookFormComponent({ - + ({ + validator(rule, value) { + const { firstname, lastname, company } = getFieldsValue([ + "firstname", + "lastname", + "company", + ]); + + if ( + (firstname && firstname.trim() !== "") || + (lastname && lastname.trim() !== "") || + (company && company.trim() !== "") + ) { + return Promise.resolve(); + } + return Promise.reject(t("phonebook.labels.onenamerequired")); + }, + }), + ]} + > diff --git a/client/src/components/shop-info/shop-info.general.component.jsx b/client/src/components/shop-info/shop-info.general.component.jsx index 00c6225a6..74170bf3c 100644 --- a/client/src/components/shop-info/shop-info.general.component.jsx +++ b/client/src/components/shop-info/shop-info.general.component.jsx @@ -16,7 +16,7 @@ import PhoneFormItem, { } from "../form-items-formatted/phone-form-item.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; - +import CurrencyInput from "../form-items-formatted/currency-form-item.component"; export default function ShopInfoGeneral({ form }) { const { t } = useTranslation(); return ( @@ -425,6 +425,18 @@ export default function ShopInfoGeneral({ form }) { > + + + + + + diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index 5f456e88a..5bbea1ef5 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -87,6 +87,7 @@ export const QUERY_BODYSHOP = gql` md_ccc_rates enforce_referral website + jc_hourly_rates employees { id active @@ -171,6 +172,7 @@ export const UPDATE_SHOP = gql` md_ccc_rates enforce_referral website + jc_hourly_rates employees { id first_name diff --git a/client/src/graphql/documents.queries.js b/client/src/graphql/documents.queries.js index 8959e49a9..d0c11f914 100644 --- a/client/src/graphql/documents.queries.js +++ b/client/src/graphql/documents.queries.js @@ -20,6 +20,7 @@ export const GET_DOCUMENTS_BY_JOB = gql` type size takenat + extension bill { id invoice_number diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 0f1d23141..9d1dbea66 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -528,6 +528,7 @@ export const GET_JOB_BY_PK = gql` tax_part db_ref manual_line + prt_dsmk_p billlines(limit: 1, order_by: { bill: { date: desc } }) { id quantity diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 9563b5bfd..364520735 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -385,7 +385,7 @@ export function Manage({ match, conflict, bodyshop }) {