Compare commits

..

2 Commits

Author SHA1 Message Date
Dave
8d00fc29d1 feature/IO-3603-Production-Board-Note-Autofocus - Fix 2026-03-09 12:59:00 -04:00
Allan Carr
a873a2573a IO-3603 Production Board Note Autofocus
Signed-off-by: Allan Carr <allan@imexsystems.ca>
2026-03-06 15:32:06 -08:00
5 changed files with 96 additions and 137 deletions

View File

@@ -1,7 +1,7 @@
import Icon from "@ant-design/icons";
import { useMutation } from "@apollo/client/react";
import { Button, Input, Popover, Tooltip } from "antd";
import { useState } from "react";
import { useState, useRef } from "react";
import { useTranslation } from "react-i18next";
import { FaRegStickyNote } from "react-icons/fa";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
@@ -9,10 +9,10 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ProductionListColumnComment({ record, usePortal = false }) {
const { t } = useTranslation();
const [note, setNote] = useState(record.comment || "");
const [open, setOpen] = useState(false);
const textAreaRef = useRef(null);
const rafIdRef = useRef(null);
const [updateAlert] = useMutation(UPDATE_JOB);
@@ -38,23 +38,35 @@ export default function ProductionListColumnComment({ record, usePortal = false
};
const handleOpenChange = (flag) => {
if (rafIdRef.current) {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = null;
}
setOpen(flag);
if (flag) setNote(record.comment || "");
if (flag) {
setNote(record.comment || "");
rafIdRef.current = requestAnimationFrame(() => {
rafIdRef.current = null;
if (textAreaRef.current?.focus) {
try {
textAreaRef.current.focus({ preventScroll: true });
} catch {
textAreaRef.current.focus();
}
}
});
}
};
const content = (
<div
style={{ width: "30em" }}
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
<div style={{ width: "30em" }} onClick={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()}>
<Input.TextArea
id={`job-comment-${record.id}`}
name="comment"
rows={5}
value={note}
onChange={handleChange}
autoFocus
ref={textAreaRef}
allowClear
style={{ marginBottom: "1em" }}
/>
@@ -67,13 +79,13 @@ export default function ProductionListColumnComment({ record, usePortal = false
);
return (
<Popover
onOpenChange={handleOpenChange}
open={open}
content={content}
trigger="click"
<Popover
onOpenChange={handleOpenChange}
open={open}
content={content}
trigger="click"
destroyOnHidden
styles={{ body: { padding: '12px' } }}
styles={{ body: { padding: "12px" } }}
{...(usePortal ? { getPopupContainer: (trigger) => trigger.parentElement || document.body } : {})}
>
<div

View File

@@ -1,7 +1,7 @@
import Icon from "@ant-design/icons";
import { useMutation } from "@apollo/client/react";
import { Button, Input, Popover, Space } from "antd";
import { useCallback, useState } from "react";
import { useCallback, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { FaRegStickyNote } from "react-icons/fa";
import { logImEXEvent } from "../../firebase/firebase.utils";
@@ -20,6 +20,8 @@ function ProductionListColumnProductionNote({ record, setNoteUpsertContext, useP
const { t } = useTranslation();
const [note, setNote] = useState(record.production_vars?.note || "");
const [open, setOpen] = useState(false);
const textAreaRef = useRef(null);
const rafIdRef = useRef(null);
const [updateAlert] = useMutation(UPDATE_JOB);
@@ -52,25 +54,37 @@ function ProductionListColumnProductionNote({ record, setNoteUpsertContext, useP
const handleOpenChange = useCallback(
(flag) => {
if (rafIdRef.current) {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = null;
}
setOpen(flag);
if (flag) setNote(record.production_vars?.note || "");
if (flag) {
setNote(record.production_vars?.note || "");
rafIdRef.current = requestAnimationFrame(() => {
rafIdRef.current = null;
if (textAreaRef.current?.focus) {
try {
textAreaRef.current.focus({ preventScroll: true });
} catch {
textAreaRef.current.focus();
}
}
});
}
},
[record]
);
const content = (
<div
style={{ width: "30em" }}
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
<div style={{ width: "30em" }} onClick={(e) => e.stopPropagation()} onPointerDown={(e) => e.stopPropagation()}>
<Input.TextArea
id={`job-production-note-${record.id}`}
name="production_note"
rows={5}
value={note}
onChange={handleChange}
autoFocus
ref={textAreaRef}
allowClear
style={{ marginBottom: "1em" }}
/>
@@ -96,13 +110,13 @@ function ProductionListColumnProductionNote({ record, setNoteUpsertContext, useP
);
return (
<Popover
onOpenChange={handleOpenChange}
open={open}
content={content}
trigger="click"
<Popover
onOpenChange={handleOpenChange}
open={open}
content={content}
trigger="click"
destroyOnHidden
styles={{ body: { padding: '12px' } }}
styles={{ body: { padding: "12px" } }}
{...(usePortal ? { getPopupContainer: (trigger) => trigger.parentElement || document.body } : {})}
>
<div

View File

@@ -130,13 +130,12 @@ exports.default = async (req, res) => {
async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
try {
const url = urlBuilder(
qbo_realmId,
"query",
`select * From vendor where DisplayName = '${StandardizeName(bill.vendor.name)}'`
);
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(
qbo_realmId,
"query",
`select * From vendor where DisplayName = '${StandardizeName(bill.vendor.name)}'`
),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -151,11 +150,6 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
bodyshopid: bill.job.shopid,
email: req.user.email
});
logger.log("qbo-payables-query", "DEBUG", req.user.email, null, {
method: "QueryVendorRecord",
call: url,
result: result.json
});
return result.json?.QueryResponse?.Vendor?.[0];
} catch (error) {
@@ -173,9 +167,8 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
DisplayName: StandardizeName(bill.vendor.name)
};
try {
const url = urlBuilder(qbo_realmId, "vendor");
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(qbo_realmId, "vendor"),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -191,12 +184,6 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
bodyshopid: bill.job.shopid,
email: req.user.email
});
logger.log("qbo-payments-insert", "DEBUG", req.user.email, null, {
method: "InsertVendorRecord",
call: url,
Vendor: Vendor,
result: result.json
});
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
@@ -287,12 +274,11 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
VendorRef: {
value: vendor.Id
},
...(vendor.TermRef &&
!bill.is_credit_memo && {
SalesTermRef: {
value: vendor.TermRef.value
}
}),
...(vendor.TermRef && !bill.is_credit_memo && {
SalesTermRef: {
value: vendor.TermRef.value
}
}),
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
@@ -332,9 +318,8 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
[logKey]: logValue
});
try {
const url = urlBuilder(qbo_realmId, bill.is_credit_memo ? "vendorcredit" : "bill");
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(qbo_realmId, bill.is_credit_memo ? "vendorcredit" : "bill"),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -350,12 +335,6 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
bodyshopid: bill.job.shopid,
email: req.user.email
});
logger.log("qbo-payables-insert", "DEBUG", req.user.email, null, {
method: "InsertBill",
call: url,
postingObj: bill.is_credit_memo ? VendorCredit : billQbo,
result: result.json
});
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));

View File

@@ -82,7 +82,14 @@ exports.default = async (req, res) => {
if (isThreeTier || (!isThreeTier && twoTierPref === "name")) {
//Insert the name/owner and account for whether the source should be the ins co in 3 tier..
ownerCustomerTier = await QueryOwner(oauthClient, qbo_realmId, req, payment.job, insCoCustomerTier);
ownerCustomerTier = await QueryOwner(
oauthClient,
qbo_realmId,
req,
payment.job,
isThreeTier,
insCoCustomerTier
);
//Query for the owner itself.
if (!ownerCustomerTier) {
ownerCustomerTier = await InsertOwner(
@@ -222,9 +229,8 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef)
paymentQbo
});
try {
const url = urlBuilder(qbo_realmId, "payment");
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(qbo_realmId, "payment"),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -240,12 +246,6 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef)
bodyshopid: payment.job.shopid,
email: req.user.email
});
logger.log("qbo-payments-insert", "DEBUG", req.user.email, null, {
method: "InsertPayment",
call: url,
paymentQbo: paymentQbo,
result: result.json
});
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));
@@ -428,9 +428,8 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe
paymentQbo
});
try {
const url = urlBuilder(qbo_realmId, "creditmemo");
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(qbo_realmId, "creditmemo"),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -446,12 +445,6 @@ async function InsertCreditMemo(oauthClient, qbo_realmId, req, payment, parentRe
bodyshopid: req.user.bodyshopid,
email: req.user.email
});
logger.log("qbo-metadata-query", "DEBUG", req.user.email, null, {
method: "InsertCreditMemo",
call: url,
paymentQbo: paymentQbo,
result: result.json
});
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));

View File

@@ -213,13 +213,12 @@ exports.default = async (req, res) => {
async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
try {
const url = urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${StandardizeName(job.ins_co_nm.trim())}' and Active = true`
);
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${StandardizeName(job.ins_co_nm.trim())}' and Active = true`
),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -234,11 +233,6 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
jobid: job.id,
email: req.user.email
});
logger.log("qbo-receivables-query", "DEBUG", req.user.email, job.id, {
method: "QueryInsuranceCo",
call: url,
result: result.json
});
return result.json?.QueryResponse?.Customer?.[0];
} catch (error) {
@@ -272,9 +266,8 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
}
};
try {
const url = urlBuilder(qbo_realmId, "customer");
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(qbo_realmId, "customer"),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -290,12 +283,6 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
jobid: job.id,
email: req.user.email
});
logger.log("qbo-receivables-insert", "DEBUG", req.user.email, job.id, {
method: "InsertInsuranceCo",
call: url,
customerObj: Customer,
result: result.json
});
return result.json?.Customer;
} catch (error) {
@@ -311,13 +298,12 @@ exports.InsertInsuranceCo = InsertInsuranceCo;
async function QueryOwner(oauthClient, qbo_realmId, req, job, parentTierRef) {
const ownerName = generateOwnerTier(job, true, null);
const url = urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${StandardizeName(ownerName)}' and Active = true`
);
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${StandardizeName(ownerName)}' and Active = true`
),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -332,11 +318,6 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job, parentTierRef) {
jobid: job.id,
email: req.user.email
});
logger.log("qbo-receivables-query", "DEBUG", req.user.email, job.id, {
method: "QueryOwner",
call: url,
result: result.json
});
return result.json?.QueryResponse?.Customer?.find((x) => x.ParentRef?.value === parentTierRef?.Id);
}
@@ -366,9 +347,8 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare
: {})
};
try {
const url = urlBuilder(qbo_realmId, "customer");
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(qbo_realmId, "customer"),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -384,12 +364,6 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare
jobid: job.id,
email: req.user.email
});
logger.log("qbo-receivables-insert", "DEBUG", req.user.email, job.id, {
method: "InsertOwner",
call: url,
customerObj: Customer,
result: result.json
});
return result.json?.Customer;
} catch (error) {
@@ -404,13 +378,12 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare
exports.InsertOwner = InsertOwner;
async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
const url = urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${job.ro_number}' and Active = true`
);
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${job.ro_number}' and Active = true`
),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -425,11 +398,6 @@ async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
jobid: job.id,
email: req.user.email
});
logger.log("qbo-receivables-query", "DEBUG", req.user.email, job.id, {
method: "QueryJob",
call: url,
result: result.json
});
const customers = result.json?.QueryResponse?.Customer;
return customers && (parentTierRef ? customers.find((x) => x.ParentRef.value === parentTierRef.Id) : customers[0]);
@@ -455,9 +423,8 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
}
};
try {
const url = urlBuilder(qbo_realmId, "customer");
const result = await oauthClient.makeApiCall({
url: url,
url: urlBuilder(qbo_realmId, "customer"),
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -473,12 +440,6 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
jobid: job.id,
email: req.user.email
});
logger.log("qbo-receivables-insert", "DEBUG", req.user.email, job.id, {
method: "InsertJob",
call: url,
customerObj: Customer,
result: result.json
});
if (result.status >= 400) {
throw new Error(JSON.stringify(result.json.Fault));