import { useLazyQuery } from "@apollo/client/react"; import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react"; import { Card, Form, Input, InputNumber, Select, Space, Switch } from "antd"; import { useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries"; import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; import { default as DateTimePicker, default as FormDateTimePicker } from "../form-date-time-picker/form-date-time-picker.component"; import JobSearchSelect from "../job-search-select/job-search-select.component"; import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component"; import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility"; import { bodyshopHasDmsKey } from "../../utils/dmsUtils.js"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import TimeTicketList from "../time-ticket-list/time-ticket-list.component"; import JobEmployeeAssignmentsContainer from "./../job-employee-assignments/job-employee-assignments.container"; import { PayrollLaborAllocationsTable } from "../labor-allocations-table/labor-allocations-table.payroll.component.jsx"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, authLevel: selectAuthLevel }); const mapDispatchToProps = () => ({}); export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketModalComponent); export function TimeTicketModalComponent({ form, bodyshop, authLevel, employeeAutoCompleteOptions, disabled, employeeSelectDisabled, lineTicketRefreshKey, isOpen }) { const { t } = useTranslation(); const { treatments: { Enhanced_Payroll } } = useTreatmentsWithConfig({ attributes: {}, names: ["Enhanced_Payroll"], splitKey: bodyshop?.imexshopid }); const [loadLineTicketData, { loading, data: lineTicketData, refetch }] = useLazyQuery(GET_LINE_TICKET_BY_PK, { fetchPolicy: "network-only", nextFetchPolicy: "network-only" }); 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 === 0) return; loadLineTicketData({ variables: { id: watchedJobId } }); }, [lineTicketRefreshKey, isOpen, watchedJobId, loadLineTicketData]); const CostCenterSelect = ({ emps, value, ...props }) => ( ); return (
{() => ( )} { 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?.find((e) => e.id === employeeId); return ( ); }} {() => ( ({ validator(rule, value) { if (!bodyshop.tt_enforce_hours_for_tech_console) return Promise.resolve(); if (!value || getFieldValue("cost_center") === null || !lineTicketData) return Promise.resolve(); const totals = CalculateAllocationsTotals( bodyshop, lineTicketData.joblines, lineTicketData.timetickets, lineTicketData.jobs_by_pk.lbr_adjustments ); const fieldTypeToCheck = bodyshopHasDmsKey(bodyshop) ? "mod_lbr_ty" : "cost_center"; 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")); } return Promise.resolve(); } }), { required: form.getFieldValue("cost_center") !== "timetickets.labels.shift" } ]} > )} ({ async validator(rule, value) { if (value) { const prodHrs = getFieldValue("productivehrs"); if (prodHrs < 0 && value !== 0) { return Promise.reject(t("timetickets.labels.zeroactualnegativeprod")); } } 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(); } }) ]} > {() => ( )}
); } export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideTimeTickets = false, bodyshop, refetch }) { const { t } = useTranslation(); const { treatments: { Enhanced_Payroll } } = useTreatmentsWithConfig({ attributes: {}, names: ["Enhanced_Payroll"], splitKey: bodyshop?.imexshopid }); if (loading) return ; if (!lineTicketData) return null; if (!jobid) return null; return ( {Enhanced_Payroll.treatment === "on" ? ( ) : ( )} {!hideTimeTickets && ( )} ); }