diff --git a/src/components/Payroll/PayrollConfiguration/PayrollConfiguration.tsx b/src/components/Payroll/PayrollConfiguration/PayrollConfiguration.tsx index 9fca8279..8c29b3c3 100644 --- a/src/components/Payroll/PayrollConfiguration/PayrollConfiguration.tsx +++ b/src/components/Payroll/PayrollConfiguration/PayrollConfiguration.tsx @@ -1,5 +1,6 @@ -import { useEffect, useMemo, useState, type ReactNode } from 'react' -import { useEmployeesListSuspense } from '@gusto/embedded-api/react-query/employeesList' +import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react' +import { useEmployeesList } from '@gusto/embedded-api/react-query/employeesList' +import { keepPreviousData } from '@tanstack/react-query' import { usePayrollsGetSuspense } from '@gusto/embedded-api/react-query/payrollsGet' import { usePayrollsCalculateMutation } from '@gusto/embedded-api/react-query/payrollsCalculate' import type { Employee } from '@gusto/embedded-api/models/components/employee' @@ -9,6 +10,7 @@ import { useTranslation } from 'react-i18next' import { usePayrollsUpdateMutation } from '@gusto/embedded-api/react-query/payrollsUpdate' import type { PayrollEmployeeCompensationsType } from '@gusto/embedded-api/models/components/payrollemployeecompensationstype' import type { PayrollUpdateEmployeeCompensations } from '@gusto/embedded-api/models/components/payrollupdate' +import type { PayrollPrepared } from '@gusto/embedded-api/models/components/payrollprepared' import { usePreparedPayrollData } from '../usePreparedPayrollData' import { payrollSubmitHandler, type ApiPayrollBlocker } from '../PayrollBlocker/payrollHelpers' import { PayrollConfigurationPresentation } from './PayrollConfigurationPresentation' @@ -18,6 +20,7 @@ import { componentEvents } from '@/shared/constants' import { useComponentDictionary, useI18n } from '@/i18n' import { useBase } from '@/components/Base' import type { PaginationItemsPerPage } from '@/components/Common/PaginationControl/PaginationControlTypes' +import { Loading } from '@/components/Common' import { useDateFormatter } from '@/hooks/useDateFormatter' const isCalculating = (processingRequest?: PayrollProcessingRequest | null) => @@ -60,21 +63,48 @@ export const Root = ({ const [isPolling, setIsPolling] = useState(false) const [payrollBlockers, setPayrollBlockers] = useState([]) - const { data: employeeData, isFetching: isFetchingEmployeeData } = useEmployeesListSuspense({ - companyId, - payrollUuid: payrollId, // get back list of employees to specific payroll - per: itemsPerPage, - page: currentPage, - sortBy: 'name', // sort alphanumeric by employee last_names - }) + const { data: employeeData, isFetching: isFetchingEmployeeData } = useEmployeesList( + { + companyId, + payrollUuid: payrollId, + per: itemsPerPage, + page: currentPage, + sortBy: 'name', + }, + { placeholderData: keepPreviousData }, + ) + + const employeeDataRef = useRef(employeeData) + employeeDataRef.current = employeeData - // get list of employee uuids to filter into prepare endpoint to get back employee_compensation data const employeeUuids = useMemo(() => { - return employeeData.showEmployees?.map(e => e.uuid) || [] - }, [employeeData.showEmployees]) + return employeeData?.showEmployees?.map(e => e.uuid) || [] + }, [employeeData?.showEmployees]) + + const totalPages = Number(employeeData?.httpMeta.response.headers.get('x-total-pages') ?? 1) + const totalCount = Number(employeeData?.httpMeta.response.headers.get('x-total-count') ?? 0) + + const [displayedEmployees, setDisplayedEmployees] = useState([]) + const [isDataInSync, setIsDataInSync] = useState(false) + + const handlePayrollDataReady = useCallback((preparedPayroll: PayrollPrepared) => { + const currentEmployeeData = employeeDataRef.current + if (!currentEmployeeData?.showEmployees || !preparedPayroll.employeeCompensations) { + setIsDataInSync(false) + return + } + + const employeeUuids = currentEmployeeData.showEmployees.map(e => e.uuid) + const preparedUuids = new Set(preparedPayroll.employeeCompensations.map(c => c.employeeUuid)) + const inSync = employeeUuids.length > 0 && employeeUuids.every(uuid => preparedUuids.has(uuid)) - const totalPages = Number(employeeData.httpMeta.response.headers.get('x-total-pages') ?? 1) - const totalCount = Number(employeeData.httpMeta.response.headers.get('x-total-count') ?? 0) + if (inSync) { + setDisplayedEmployees(currentEmployeeData.showEmployees) + setIsDataInSync(true) + } else { + setIsDataInSync(false) + } + }, []) const handleItemsPerPageChange = (newCount: PaginationItemsPerPage) => { setItemsPerPage(newCount) @@ -92,19 +122,6 @@ export const Root = ({ setCurrentPage(totalPages) } - const pagination = { - currentPage, - handleFirstPage, - handlePreviousPage, - handleNextPage, - handleLastPage, - handleItemsPerPageChange, - totalPages, - totalCount, - isFetching: isFetchingEmployeeData, - itemsPerPage, - } - const { data: payrollData } = usePayrollsGetSuspense( { companyId, @@ -122,14 +139,31 @@ export const Root = ({ preparedPayroll, paySchedule, isLoading: isPrepareLoading, + isPaginating, handlePreparePayroll, } = usePreparedPayrollData({ companyId, payrollId, employeeUuids, - sortBy: 'last_name', // sort alphanumeric by employee last_names to match employees GET + sortBy: 'last_name', + onDataReady: handlePayrollDataReady, }) + const isPaginationFetching = isFetchingEmployeeData || isPaginating || !isDataInSync + + const pagination = { + currentPage, + handleFirstPage, + handlePreviousPage, + handleNextPage, + handleLastPage, + handleItemsPerPageChange, + totalPages, + totalCount, + isFetching: isPaginationFetching, + itemsPerPage, + } + const onCalculatePayroll = async () => { // Clear any existing blockers before attempting calculation setPayrollBlockers([]) @@ -241,6 +275,10 @@ export const Root = ({ } : undefined + if (!employeeData) { + return + } + return ( void } interface UsePreparedPayrollDataReturn { @@ -18,6 +19,8 @@ interface UsePreparedPayrollDataReturn { preparedPayroll: PayrollPrepared | undefined paySchedule: PayScheduleObject | undefined isLoading: boolean + isPaginating: boolean + hasInitialData: boolean } export const usePreparedPayrollData = ({ @@ -25,12 +28,16 @@ export const usePreparedPayrollData = ({ payrollId, employeeUuids, sortBy, + onDataReady, }: UsePreparedPayrollDataParams): UsePreparedPayrollDataReturn => { const { mutateAsync: preparePayroll, isPending: isPreparePayrollPending } = usePayrollsPrepareMutation() const [preparedPayroll, setPreparedPayroll] = useState() + const hasInitialDataRef = useRef(false) const { baseSubmitHandler } = useBase() + const employeeUuidsKey = useMemo(() => employeeUuids?.join(',') ?? '', [employeeUuids]) + const { data: payScheduleData, isLoading: isPayScheduleLoading } = usePaySchedulesGet( { companyId, @@ -54,19 +61,35 @@ export const usePreparedPayrollData = ({ }, }) setPreparedPayroll(result.payrollPrepared) + if (result.payrollPrepared) { + hasInitialDataRef.current = true + onDataReady?.(result.payrollPrepared) + } }) - }, [companyId, payrollId, preparePayroll, employeeUuids, baseSubmitHandler]) + }, [ + companyId, + payrollId, + preparePayroll, + employeeUuidsKey, + baseSubmitHandler, + sortBy, + onDataReady, + ]) useEffect(() => { void handlePreparePayroll() }, [handlePreparePayroll]) - const isLoading = isPreparePayrollPending || isPayScheduleLoading + const isInitialLoading = isPreparePayrollPending && !hasInitialDataRef.current + const isPaginating = isPreparePayrollPending && hasInitialDataRef.current + const isLoading = isInitialLoading || isPayScheduleLoading return { handlePreparePayroll, preparedPayroll, paySchedule: payScheduleData?.payScheduleObject, isLoading, + isPaginating, + hasInitialData: hasInitialDataRef.current, } }