Merged in development (pull request #42) IO-21 IO-586 IO-798 IO-808

This commit is contained in:
Patrick Fic
2021-03-25 00:08:29 +00:00
18 changed files with 28856 additions and 167 deletions

View File

@@ -10759,6 +10759,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>copylink</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>create</name> <name>create</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -14292,6 +14313,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>generatecsi</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>gotojob</name> <name>gotojob</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -21932,6 +21974,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>rescueme</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>schedule</name> <name>schedule</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

23190
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@ export function ChatMediaSelector({
conversation, conversation,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
console.log("conversation", conversation);
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, { const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
variables: { variables: {
jobId: jobId:

View File

@@ -334,6 +334,13 @@ function Header({
<Menu.Item danger onClick={() => signOutStart()}> <Menu.Item danger onClick={() => signOutStart()}>
{t("user.actions.signout")} {t("user.actions.signout")}
</Menu.Item> </Menu.Item>
<Menu.Item
onClick={() => {
window.open("https://imexrescue.com/", "_blank");
}}
>
{t("menus.header.rescueme")}
</Menu.Item>
<Menu.Item key="shiftclock"> <Menu.Item key="shiftclock">
<Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link> <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
</Menu.Item> </Menu.Item>

View File

@@ -1,4 +1,4 @@
import { Button, Card, Input, Space } from "antd"; import { Button, Input, Space } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -9,45 +9,50 @@ export default function HelpRescue() {
const handleClick = async () => { const handleClick = async () => {
var bodyFormData = new FormData(); var bodyFormData = new FormData();
bodyFormData.append("Code", code); bodyFormData.append("Code", code);
bodyFormData.append("hostederrorhandling", 1);
const res1 = await fetch( const res1 = await fetch(
"https://secure.logmeinrescue.com/Customer/Code.aspx", "https://secure.logmeinrescue.com/Customer/Code.aspx",
{ {
mode: "no-cors",
method: "POST", method: "POST",
body: bodyFormData, body: bodyFormData,
} }
); );
console.log("handleClick -> res1", res1); console.log("handleClick -> res1", await res1.text());
}; };
return ( return (
<Card title={t("help.labels.rescuetitle")}> <div style={{ display: "flex", justifyContent: "center" }}>
<div style={{ display: "flex", justifyContent: "center" }}> <Space direction="vertical" align="center">
<Space direction="vertical" align="center"> <div>{t("help.labels.rescuedesc")}</div>
<div>{t("help.labels.rescuedesc")}</div> <Input
<form size="large"
name="logmeinsupport" style={{ width: "10rem" }}
action="https://secure.logmeinrescue.com/Customer/Code.aspx" onChange={(e) => setCode(e.target.value)}
method="post" value={code}
> placeholder={t("help.labels.codeplacholder")}
<span> />
Enter your six-digit code, then click the Start Download button <Button onClick={handleClick}>{t("help.actions.connect")}</Button>
below
</span> <form
<input type="text" name="Code" /> name="logmeinsupport"
<br /> action="https://secure.logmeinrescue.com/Customer/Code.aspx"
<input type="submit" value="Connect to technician" /> method="post"
</form> id="logmeinsupport"
<Input onSubmit={(...props) => {
size="large" console.log(`props`, props);
style={{ width: "10rem" }} alert();
onChange={(e) => setCode(e.target.value)} }}
value={code} >
placeholder={t("help.labels.codeplacholder")} <span>Enter your 6-digit code: </span>
/> <input type="text" name="Code" />
<Button onClick={handleClick}>{t("help.actions.connect")}</Button> <br />
</Space> <input type="submit" value="Connect to technician" />
</div> <input type="hidden" name="tracking0" maxlength="64" />
</Card> <input type="hidden" name="language" maxlength="5" />
<input type="hidden" name="hostederrorhandling" value="1" />
</form>
</Space>
</div>
); );
} }

View File

@@ -19,6 +19,7 @@ const JobSearchSelect = (
disabled, disabled,
convertedOnly = false, convertedOnly = false,
notExported = true, notExported = true,
clm_no = false,
}, },
ref ref
) => { ) => {
@@ -55,7 +56,7 @@ const JobSearchSelect = (
useEffect(() => { useEffect(() => {
if (value === option && value) { if (value === option && value) {
callIdSearch({ variables: { id: value } }); callIdSearch({ variables: { id: value } }); // Sometimes results in a no-op. Not sure how to fix.
} }
}, [value, option, callIdSearch]); }, [value, option, callIdSearch]);
@@ -66,10 +67,13 @@ const JobSearchSelect = (
} }
}; };
const theOptions = [ const theOptions = _.uniqBy(
...(idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []), [
...(data && data.search_jobs ? data.search_jobs : []), ...(idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []),
]; ...(data && data.search_jobs ? data.search_jobs : []),
],
"id"
);
return ( return (
<div> <div>
@@ -93,9 +97,9 @@ const JobSearchSelect = (
{theOptions {theOptions
? theOptions.map((o) => ( ? theOptions.map((o) => (
<Option key={o.id} value={o.id}> <Option key={o.id} value={o.id}>
{`${o.ro_number || t("general.labels.na")} | ${ {`${clm_no ? `${o.clm_no} | ` : ""}${
o.ownr_ln || "" o.ro_number || t("general.labels.na")
} ${o.ownr_fn || ""} ${ } | ${o.ownr_ln || ""} ${o.ownr_fn || ""} ${
o.ownr_co_nm ? ` ${o.ownr_co_num}` : "" o.ownr_co_nm ? ` ${o.ownr_co_num}` : ""
}| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${ }| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
o.v_model_desc || "" o.v_model_desc || ""

View File

@@ -46,7 +46,7 @@ export function JobsDetailHeaderCsi({
logImEXEvent("job_create_csi"); logImEXEvent("job_create_csi");
//Is tehre already a CSI? //Is tehre already a CSI?
if (job.csi_invites.length === 0) { if (!job.csiinvites || job.csiinvites.length === 0) {
const questionSetResult = await client.query({ const questionSetResult = await client.query({
query: GET_CURRENT_QUESTIONSET_ID, query: GET_CURRENT_QUESTIONSET_ID,
}); });
@@ -110,10 +110,10 @@ export function JobsDetailHeaderCsi({
if (e.key === "text") { if (e.key === "text") {
const p = parsePhoneNumber(job.ownr_ph1, "CA"); const p = parsePhoneNumber(job.ownr_ph1, "CA");
if (p && p.isValid()) { if (p && p.isValid()) {
// openChatByPhone({ openChatByPhone({
// phone_num: p.formatInternational(), phone_num: p.formatInternational(),
// jobid: job.id, jobid: job.id,
// }); });
setMessage( setMessage(
`${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}` `${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}`
); );
@@ -123,6 +123,12 @@ export function JobsDetailHeaderCsi({
}); });
} }
} }
if (e.key === "generate") {
//copy it to clipboard.
navigator.clipboard.writeText(
`${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}`
);
}
} else { } else {
notification["error"]({ notification["error"]({
message: t("csi.errors.notconfigured"), message: t("csi.errors.notconfigured"),
@@ -138,7 +144,7 @@ export function JobsDetailHeaderCsi({
template: { template: {
name: TemplateList("job").csi_invitation.key, name: TemplateList("job").csi_invitation.key,
variables: { variables: {
id: job.csi_invites[0].id, id: job.csiinvites[0].id,
}, },
}, },
}); });
@@ -146,12 +152,12 @@ export function JobsDetailHeaderCsi({
if (e.key === "text") { if (e.key === "text") {
const p = parsePhoneNumber(job.ownr_ph1, "CA"); const p = parsePhoneNumber(job.ownr_ph1, "CA");
if (p && p.isValid()) { if (p && p.isValid()) {
// openChatByPhone({ openChatByPhone({
// phone_num: p.formatInternational(), phone_num: p.formatInternational(),
// jobid: job.id, jobid: job.id,
// }); });
setMessage( setMessage(
`${window.location.protocol}//${window.location.host}/csi/${job.csi_invites[0].id}` `${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`
); );
} else { } else {
notification["error"]({ notification["error"]({
@@ -159,6 +165,13 @@ export function JobsDetailHeaderCsi({
}); });
} }
} }
if (e.key === "generate") {
//copy it to clipboard.
navigator.clipboard.writeText(
`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`
);
}
} }
}; };
@@ -178,6 +191,13 @@ export function JobsDetailHeaderCsi({
> >
{t("general.labels.text")} {t("general.labels.text")}
</Menu.Item> </Menu.Item>
<Menu.Item
onClick={handleCreateCsi}
key="generate"
disabled={job.csiinvites && job.csiinvites.length > 0}
>
{t("jobs.actions.generatecsi")}
</Menu.Item>
<Menu.Divider /> <Menu.Divider />
{job.csiinvites.map((item, idx) => { {job.csiinvites.map((item, idx) => {
return item.completedon ? ( return item.completedon ? (

View File

@@ -1,4 +1,4 @@
import { Button, Form, Popover, notification } from "antd"; import { Button, Form, Popover, notification, Space } from "antd";
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import JobSearchSelect from "../job-search-select/job-search-select.component"; import JobSearchSelect from "../job-search-select/job-search-select.component";
@@ -100,9 +100,14 @@ export default function JobsDocumentsGalleryReassign({ galleryImages }) {
<JobSearchSelect /> <JobSearchSelect />
</Form.Item> </Form.Item>
</Form> </Form>
<Button onClick={() => form.submit()}> <Space>
{t("general.actions.submit")} <Button type="primary" onClick={() => form.submit()}>
</Button> {t("general.actions.submit")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</div> </div>
); );

View File

@@ -9,6 +9,7 @@ import Alert from "../alert/alert.component";
import DatePickerFormItem from "../form-date-picker/form-date-picker.component"; import DatePickerFormItem from "../form-date-picker/form-date-picker.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobSearchSelect from "../job-search-select/job-search-select.component"; import JobSearchSelect from "../job-search-select/job-search-select.component";
import PaymentFormTotalPayments from "./payment-form.totalpayments.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -39,8 +40,18 @@ export function PaymentFormComponent({
}, },
]} ]}
> >
<JobSearchSelect disabled={disabled} notExported={false} /> <JobSearchSelect disabled={disabled} notExported={false} clm_no />
</Form.Item> </Form.Item>
<Form.Item
shouldUpdate={(prev, cur) => cur.jobid && prev.jobid !== cur.jobid}
>
{() => {
return (
<PaymentFormTotalPayments jobid={form.getFieldValue("jobid")} />
);
}}
</Form.Item>
<Form.Item <Form.Item
label={t("payments.fields.amount")} label={t("payments.fields.amount")}
name="amount" name="amount"

View File

@@ -0,0 +1,43 @@
import { useQuery } from "@apollo/client";
import { Statistic } from "antd";
import Dinero from "dinero.js";
import React from "react";
import { useTranslation } from "react-i18next";
import { QUERY_JOB_PAYMENT_TOTALS } from "../../graphql/payments.queries";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
export default function PaymentFormTotalPayments({ jobid }) {
const { t } = useTranslation();
const { loading, error, data } = useQuery(QUERY_JOB_PAYMENT_TOTALS, {
variables: { id: jobid },
skip: !jobid,
});
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!data) return <div>Select a job</div>;
const totalPayments = data.jobs_by_pk.payments.reduce((acc, val) => {
return acc.add(Dinero({ amount: (val.amount || 0) * 100 }));
}, Dinero());
const balance = Dinero(
data.jobs_by_pk.job_totals.totals.total_repairs
).subtract(totalPayments);
return (
<div style={{ display: "flex", justifyContent: "space-evenly" }}>
<Statistic
title={t("payments.labels.totalpayments")}
value={totalPayments.toFormat()}
/>
<Statistic
title={t("payments.labels.balance")}
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
value={balance.toFormat()}
/>
</div>
);
}

View File

@@ -949,7 +949,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
ownr_fn ownr_fn
ownr_ln ownr_ln
ro_number ro_number
clm_no
vehicleid vehicleid
v_make_desc v_make_desc
v_model_desc v_model_desc
@@ -964,7 +964,7 @@ export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql`
ownr_fn ownr_fn
ownr_ln ownr_ln
ro_number ro_number
clm_no
vehicleid vehicleid
v_make_desc v_make_desc
v_model_desc v_model_desc

View File

@@ -95,3 +95,17 @@ export const UPDATE_PAYMENTS = gql`
} }
} }
`; `;
export const QUERY_JOB_PAYMENT_TOTALS = gql`
query QUERY_JOB_PAYMENT_TOTALS($id: uuid!) {
jobs_by_pk(id: $id) {
id
job_totals
payments {
id
amount
date
}
}
}
`;

View File

@@ -21,22 +21,22 @@ require("dotenv").config();
// Dinero.defaultCurrency = "CAD"; // Dinero.defaultCurrency = "CAD";
// Dinero.globalLocale = "en-CA"; // Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_UP"; Dinero.globalRoundingMode = "HALF_EVEN";
//if (process.env.NODE_ENV !== "development") { if (process.env.NODE_ENV !== "development") {
Sentry.init({ Sentry.init({
dsn: dsn:
"https://fd7e89369b6b4bdc9c6c4c9f22fa4ee4@o492140.ingest.sentry.io/5651027", "https://fd7e89369b6b4bdc9c6c4c9f22fa4ee4@o492140.ingest.sentry.io/5651027",
integrations: [ integrations: [
// new Integrations.BrowserTracing(), // new Integrations.BrowserTracing(),
// new Sentry.Integrations.Breadcrumbs({ console: true }), // new Sentry.Integrations.Breadcrumbs({ console: true }),
], ],
environment: process.env.NODE_ENV, environment: process.env.NODE_ENV,
// We recommend adjusting this value in production, or using tracesSampler // We recommend adjusting this value in production, or using tracesSampler
// for finer control // for finer control
// tracesSampleRate: 0.5, // tracesSampleRate: 0.5,
}); });
//} }
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>

View File

@@ -707,6 +707,7 @@
"calculate": "Calculate", "calculate": "Calculate",
"cancel": "Cancel", "cancel": "Cancel",
"close": "Close", "close": "Close",
"copylink": "Copy Link",
"create": "Create", "create": "Create",
"delete": "Delete", "delete": "Delete",
"deleteall": "Delete All", "deleteall": "Delete All",
@@ -914,6 +915,7 @@
"export": "Export", "export": "Export",
"exportselected": "Export Selected", "exportselected": "Export Selected",
"filterpartsonly": "Filter Parts Only", "filterpartsonly": "Filter Parts Only",
"generatecsi": "Generate CSI & Copy Link",
"gotojob": "Go to Job", "gotojob": "Go to Job",
"intake": "Intake", "intake": "Intake",
"manualnew": "Create New Job Manually", "manualnew": "Create New Job Manually",
@@ -1306,6 +1308,7 @@
"productionlist": "Production Board - List", "productionlist": "Production Board - List",
"recent": "Recent Items", "recent": "Recent Items",
"reportcenter": "Report Center", "reportcenter": "Report Center",
"rescueme": "Rescue me!",
"schedule": "Schedule", "schedule": "Schedule",
"scoreboard": "Scoreboard", "scoreboard": "Scoreboard",
"search": { "search": {

View File

@@ -707,6 +707,7 @@
"calculate": "", "calculate": "",
"cancel": "", "cancel": "",
"close": "", "close": "",
"copylink": "",
"create": "", "create": "",
"delete": "Borrar", "delete": "Borrar",
"deleteall": "", "deleteall": "",
@@ -914,6 +915,7 @@
"export": "", "export": "",
"exportselected": "", "exportselected": "",
"filterpartsonly": "", "filterpartsonly": "",
"generatecsi": "",
"gotojob": "", "gotojob": "",
"intake": "", "intake": "",
"manualnew": "", "manualnew": "",
@@ -1306,6 +1308,7 @@
"productionlist": "", "productionlist": "",
"recent": "", "recent": "",
"reportcenter": "", "reportcenter": "",
"rescueme": "",
"schedule": "Programar", "schedule": "Programar",
"scoreboard": "", "scoreboard": "",
"search": { "search": {

View File

@@ -707,6 +707,7 @@
"calculate": "", "calculate": "",
"cancel": "", "cancel": "",
"close": "", "close": "",
"copylink": "",
"create": "", "create": "",
"delete": "Effacer", "delete": "Effacer",
"deleteall": "", "deleteall": "",
@@ -914,6 +915,7 @@
"export": "", "export": "",
"exportselected": "", "exportselected": "",
"filterpartsonly": "", "filterpartsonly": "",
"generatecsi": "",
"gotojob": "", "gotojob": "",
"intake": "", "intake": "",
"manualnew": "", "manualnew": "",
@@ -1306,6 +1308,7 @@
"productionlist": "", "productionlist": "",
"recent": "", "recent": "",
"reportcenter": "", "reportcenter": "",
"rescueme": "",
"schedule": "Programme", "schedule": "Programme",
"scoreboard": "", "scoreboard": "",
"search": { "search": {

5504
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ const GraphQLClient = require("graphql-request").GraphQLClient;
// Dinero.defaultCurrency = "USD"; // Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA"; // Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_UP"; Dinero.globalRoundingMode = "HALF_EVEN";
exports.totalsSsu = async function (req, res) { exports.totalsSsu = async function (req, res) {
const BearerToken = req.headers.authorization; const BearerToken = req.headers.authorization;