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
4 changed files with 104 additions and 61 deletions

View File

@@ -144,11 +144,18 @@ export default function JobTotalsTableLabor({ job }) {
{t("jobs.labels.mapa")}
{InstanceRenderManager({
imex:
(job.materials?.mapa ?? job.materials?.MAPA)?.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", { amount: (job.materials.mapa ?? job.materials.MAPA).cal_maxdlr }),
job.materials?.mapa &&
job.materials.mapa.cal_maxdlr &&
job.materials.mapa.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.mapa.cal_maxdlr
}),
rome:
job.materials?.MAPA?.cal_maxdlr !== undefined &&
t("jobs.labels.threshhold", { amount: job.materials.MAPA.cal_maxdlr })
job.materials?.MAPA &&
job.materials.MAPA.cal_maxdlr !== undefined &&
t("jobs.labels.threshhold", {
amount: job.materials.MAPA.cal_maxdlr
})
})}
</Space>
</ResponsiveTable.Summary.Cell>
@@ -183,11 +190,18 @@ export default function JobTotalsTableLabor({ job }) {
{t("jobs.labels.mash")}
{InstanceRenderManager({
imex:
(job.materials?.mash ?? job.materials?.MASH)?.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", { amount: (job.materials.mash ?? job.materials.MASH).cal_maxdlr }),
job.materials?.mash &&
job.materials.mash.cal_maxdlr &&
job.materials.mash.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.mash.cal_maxdlr
}),
rome:
job.materials?.MASH?.cal_maxdlr !== undefined &&
t("jobs.labels.threshhold", { amount: job.materials.MASH.cal_maxdlr })
job.materials?.MASH &&
job.materials.MASH.cal_maxdlr !== undefined &&
t("jobs.labels.threshhold", {
amount: job.materials.MASH.cal_maxdlr
})
})}
</Space>
</ResponsiveTable.Summary.Cell>

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

@@ -315,12 +315,7 @@ function CalculateRatesTotals(ratesList) {
if (item.mod_lbr_ty) {
//Check to see if it has 0 hours and a price instead.
//Extend for when there are hours and a price.
if (
item.lbr_op === "OP14" &&
item.act_price > 0 &&
(!item.part_type || item.mod_lb_hrs === 0) &&
!IsAdditionalCost(item)
) {
if (item.lbr_op === "OP14" && item.act_price > 0 && (!item.part_type || item.mod_lb_hrs === 0) && !IsAdditionalCost(item)) {
//Scenario where SGI may pay out hours using a part price.
if (!ret[item.mod_lbr_ty.toLowerCase()].total) {
ret[item.mod_lbr_ty.toLowerCase()].total = Dinero();
@@ -344,30 +339,38 @@ function CalculateRatesTotals(ratesList) {
let subtotal = Dinero({ amount: 0 });
let rates_subtotal = Dinero({ amount: 0 });
for (const [property, values] of Object.entries(ret)) {
for (const property in ret) {
//Skip calculating mapa and mash if we got the amounts.
const shouldSkipCalculation = (property === "mapa" && hasMapaLine) || (property === "mash" && hasMashLine);
if (!shouldSkipCalculation) {
values.total ??= Dinero();
//Check if there is a max for this type and apply it.
const maxDollar =
ratesList.materials?.[property]?.cal_maxdlr || ratesList.materials?.[property.toUpperCase()]?.cal_maxdlr;
const threshold = maxDollar > 0 ? Dinero({ amount: Math.round(maxDollar * 100) }) : null;
if (!((property === "mapa" && hasMapaLine) || (property === "mash" && hasMashLine))) {
if (!ret[property].total) {
ret[property].total = Dinero();
}
let threshold;
//Check if there is a max for this type.
if (ratesList.materials && ratesList.materials[property]) {
//
if (ratesList.materials[property].cal_maxdlr && ratesList.materials[property].cal_maxdlr > 0) {
//It has an upper threshhold.
threshold = Dinero({
amount: Math.round(ratesList.materials[property].cal_maxdlr * 100)
});
}
}
const total = Dinero({
amount: Math.round((values.rate || 0) * 100)
}).multiply(values.hours);
amount: Math.round((ret[property].rate || 0) * 100)
}).multiply(ret[property].hours);
values.total = values.total.add(threshold && total.greaterThanOrEqual(threshold) ? threshold : total);
if (threshold && total.greaterThanOrEqual(threshold)) {
ret[property].total = ret[property].total.add(threshold);
} else {
ret[property].total = ret[property].total.add(total);
}
}
subtotal = subtotal.add(values.total);
subtotal = subtotal.add(ret[property].total);
if (property !== "mapa" && property !== "mash") {
rates_subtotal = rates_subtotal.add(values.total);
}
if (property !== "mapa" && property !== "mash") rates_subtotal = rates_subtotal.add(ret[property].total);
}
ret.subtotal = subtotal;