diff --git a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
index 9f7e5b8e4..3897d3674 100644
--- a/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
+++ b/client/src/components/time-ticket-modal/time-ticket-modal.component.jsx
@@ -1,7 +1,7 @@
import { useLazyQuery } from "@apollo/client/react";
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import { Card, Form, Input, InputNumber, Select, Space, Switch } from "antd";
-import { useEffect } from "react";
+import { useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -40,6 +40,7 @@ export function TimeTicketModalComponent({
isOpen
}) {
const { t } = useTranslation();
+
const {
treatments: { Enhanced_Payroll }
} = useTreatmentsWithConfig({
@@ -48,49 +49,68 @@ export function TimeTicketModalComponent({
splitKey: bodyshop.imexshopid
});
- const [loadLineTicketData, { called, loading, data: lineTicketData }] = useLazyQuery(GET_LINE_TICKET_BY_PK, {
+ const [loadLineTicketData, { loading, data: lineTicketData }] = useLazyQuery(GET_LINE_TICKET_BY_PK, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
- // Watch the jobid field so we can refetch the bottom section without relying on jobid changes
const watchedJobId = Form.useWatch("jobid", form);
+ // Local mutable memory: dedupe jobid-driven fetches without re-rendering.
+ const lastFetchedJobIdRef = useRef(null);
+
+ // Reset + fetch when job changes (never during render)
+ useEffect(() => {
+ if (!isOpen) {
+ lastFetchedJobIdRef.current = null;
+ return;
+ }
+
+ if (!watchedJobId) {
+ lastFetchedJobIdRef.current = null;
+ return;
+ }
+
+ if (lastFetchedJobIdRef.current === watchedJobId) return;
+
+ lastFetchedJobIdRef.current = watchedJobId;
+ loadLineTicketData({ variables: { id: watchedJobId } });
+ }, [isOpen, watchedJobId, loadLineTicketData]);
+
+ // Force refresh (e.g., after Save / external refresh bump)
useEffect(() => {
if (!isOpen) return;
if (!watchedJobId) return;
- if (!lineTicketRefreshKey) return;
+ if (lineTicketRefreshKey === 0) return;
- loadLineTicketData({ id: watchedJobId });
- }, [lineTicketRefreshKey, watchedJobId, isOpen]);
+ loadLineTicketData({ variables: { id: watchedJobId } });
+ }, [lineTicketRefreshKey, isOpen, watchedJobId, loadLineTicketData]);
- const CostCenterSelect = ({ emps, value, ...props }) => {
- return (
-
- );
- };
+ const CostCenterSelect = ({ emps, value, ...props }) => (
+
+ );
- const MemoInput = ({ value, ...props }) => {
- return ;
- };
+ const MemoInput = ({ value, ...props }) => (
+
+ );
return (
@@ -102,8 +122,7 @@ export function TimeTicketModalComponent({
label={t("timetickets.fields.ro_number")}
rules={[
{
- required: !(form.getFieldValue("cost_center") === "timetickets.labels.shift")
- //message: t("general.validation.required"),
+ required: form.getFieldValue("cost_center") !== "timetickets.labels.shift"
}
]}
>
@@ -115,25 +134,25 @@ export function TimeTicketModalComponent({
)}
+
+
@@ -141,29 +160,23 @@ export function TimeTicketModalComponent({
disabled={employeeSelectDisabled || disabled}
options={employeeAutoCompleteOptions}
onSelect={(value) => {
- const emps = employeeAutoCompleteOptions && employeeAutoCompleteOptions.filter((e) => e.id === value)[0];
-
+ const emps = employeeAutoCompleteOptions?.find((e) => e.id === value);
form.setFieldsValue({ flat_rate: emps?.flat_rate });
}}
/>
+
prev.employeeid !== cur.employeeid}>
{() => {
const employeeId = form.getFieldValue("employeeid");
- const emps =
- employeeAutoCompleteOptions && employeeAutoCompleteOptions.filter((e) => e.id === employeeId)[0];
+ const emps = employeeAutoCompleteOptions?.find((e) => e.id === employeeId);
return (
@@ -185,52 +198,46 @@ export function TimeTicketModalComponent({
{() => (
- <>
- ({
- validator(rule, value) {
- if (!bodyshop.tt_enforce_hours_for_tech_console) {
- return Promise.resolve();
- }
- if (!value || getFieldValue("cost_center") === null || !lineTicketData) return Promise.resolve();
+ ({
+ validator(rule, value) {
+ if (!bodyshop.tt_enforce_hours_for_tech_console) return Promise.resolve();
+ if (!value || getFieldValue("cost_center") === null || !lineTicketData) return Promise.resolve();
- //Check the cost center,
- const totals = CalculateAllocationsTotals(
- bodyshop,
- lineTicketData.joblines,
- lineTicketData.timetickets,
- lineTicketData.jobs_by_pk.lbr_adjustments
- );
+ const totals = CalculateAllocationsTotals(
+ bodyshop,
+ lineTicketData.joblines,
+ lineTicketData.timetickets,
+ lineTicketData.jobs_by_pk.lbr_adjustments
+ );
- const fieldTypeToCheck = bodyshopHasDmsKey(bodyshop) ? "mod_lbr_ty" : "cost_center";
+ const fieldTypeToCheck = bodyshopHasDmsKey(bodyshop) ? "mod_lbr_ty" : "cost_center";
- const costCenterDiff =
- Math.round(
- totals.find((total) => total[fieldTypeToCheck] === getFieldValue("cost_center"))?.difference *
- 10
- ) / 10;
+ const costCenterDiff =
+ Math.round(
+ (totals.find((total) => total[fieldTypeToCheck] === getFieldValue("cost_center"))?.difference ||
+ 0) * 10
+ ) / 10;
- if (value > costCenterDiff)
- return Promise.reject(t("timetickets.validation.hoursenteredmorethanavailable"));
- else {
- return Promise.resolve();
- }
+ if (value > costCenterDiff) {
+ return Promise.reject(t("timetickets.validation.hoursenteredmorethanavailable"));
}
- }),
- {
- required: form.getFieldValue("cost_center") !== "timetickets.labels.shift"
- //message: t("general.validation.required"),
+ return Promise.resolve();
}
- ]}
- >
-
-
- >
+ }),
+ {
+ required: form.getFieldValue("cost_center") !== "timetickets.labels.shift"
+ }
+ ]}
+ >
+
+
)}
+
- {
- <>
-
-
-
- ({
- validator(rule, value) {
- const clockon = getFieldValue("clockon");
- if (!value) return Promise.resolve();
- if (!clockon && value) return Promise.reject(t("timetickets.validation.clockoffwithoutclockon"));
- // TODO - Verify this exists
- if (value?.isSameOrAfter && !value.isSameOrAfter(clockon))
- return Promise.reject(t("timetickets.validation.clockoffmustbeafterclockon"));
-
- return Promise.resolve();
- }
+ <>
+
+
-
+
+
+ ({
+ validator(rule, value) {
+ const clockon = getFieldValue("clockon");
+ if (!value) return Promise.resolve();
+ if (!clockon && value) return Promise.reject(t("timetickets.validation.clockoffwithoutclockon"));
+ if (value?.isSameOrAfter && !value.isSameOrAfter(clockon)) {
+ return Promise.reject(t("timetickets.validation.clockoffmustbeafterclockon"));
+ }
+ return Promise.resolve();
}
- />
-
- >
- }
+ })
+ ]}
+ >
+
+
+ >
+
{() => (
@@ -323,15 +326,7 @@ export function TimeTicketModalComponent({
-
- {() => {
- const jobid = form.getFieldValue("jobid");
- if ((!called && jobid) || (jobid && lineTicketData?.jobs_by_pk?.id !== jobid && !loading)) {
- loadLineTicketData({ id: jobid });
- }
- return ;
- }}
-
+
);
}
@@ -341,17 +336,20 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
if (loading) return ;
if (!lineTicketData) return null;
if (!jobid) return null;
+
return (
+
+
{!hideTimeTickets && (
)}