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 && ( )}