11import { useContractorsListSuspense } from '@gusto/embedded-api/react-query/contractorsList'
22import { useContractorPaymentGroupsCreateMutation } from '@gusto/embedded-api/react-query/contractorPaymentGroupsCreate'
33import type { ContractorPayments } from '@gusto/embedded-api/models/operations/postv1companiescompanyidcontractorpaymentgroups'
4+ import { useContractorPaymentGroupsPreviewMutation } from '@gusto/embedded-api/react-query/contractorPaymentGroupsPreview'
45import { useMemo , useState } from 'react'
56import { RFCDate } from '@gusto/embedded-api/types/rfcdate'
67import { FormProvider , useForm } from 'react-hook-form'
78import { zodResolver } from '@hookform/resolvers/zod'
9+ import { useTranslation } from 'react-i18next'
10+ import type { ContractorPaymentGroupPreview } from '@gusto/embedded-api/models/components/contractorpaymentgrouppreview'
11+ import type { InternalAlert } from '../types'
812import { CreatePaymentPresentation } from './CreatePaymentPresentation'
913import {
1014 EditContractorPaymentPresentation ,
1115 EditContractorPaymentFormSchema ,
1216 type EditContractorPaymentFormValues ,
1317} from './EditContractorPaymentPresentation'
18+ import { PreviewPresentation } from './PreviewPresentation'
1419import { useComponentDictionary } from '@/i18n'
15- import { BaseComponent , type BaseComponentInterface } from '@/components/Base'
20+ import { BaseComponent , useBase , type BaseComponentInterface } from '@/components/Base'
1621import { componentEvents , ContractorOnboardingStatus } from '@/shared/constants'
1722import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
23+ import { firstLastName } from '@/helpers/formattedStrings'
1824
1925interface CreatePaymentProps extends BaseComponentInterface < 'Contractor.Payments.CreatePayment' > {
2026 companyId : string
@@ -30,21 +36,60 @@ export function CreatePayment(props: CreatePaymentProps) {
3036
3137export const Root = ( { companyId, dictionary, onEvent } : CreatePaymentProps ) => {
3238 useComponentDictionary ( 'Contractor.Payments.CreatePayment' , dictionary )
39+ const { t } = useTranslation ( 'Contractor.Payments.CreatePayment' )
3340 const { Modal } = useComponentContext ( )
3441 const [ isModalOpen , setIsModalOpen ] = useState ( false )
3542 const [ paymentDate , setPaymentDate ] = useState < string > (
3643 new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] || '' ,
3744 )
45+ const { baseSubmitHandler } = useBase ( )
46+ const [ alerts , setAlerts ] = useState < Record < string , InternalAlert > > ( { } )
47+ const [ previewData , setPreviewData ] = useState < ContractorPaymentGroupPreview | null > ( null )
48+ /**{
49+ uuid: null,
50+ companyUuid: '948d671f-0301-4f84-9cf1-a9ef0911d195',
51+ checkDate: '2025-12-24',
52+ debitDate: '2025-12-22',
53+ status: 'Unfunded',
54+ creationToken: 'f3f2706a-3ee1-4334-b30f-8f1d72a9967f',
55+ partnerOwnedDisbursement: null,
56+ submissionBlockers: [],
57+ creditBlockers: [],
58+ contractorPayments: [
59+ {
60+ uuid: null,
61+ contractorUuid: '69ff2cd6-3bd8-48e3-acfe-cfe45b3d5d88',
62+ bonus: '0.0',
63+ hours: '12.0',
64+ hourlyRate: '18.0',
65+ mayCancel: true,
66+ paymentMethod: 'Direct Deposit',
67+ reimbursement: '0.0',
68+ status: 'Unfunded',
69+ wage: '0.0',
70+ wageType: 'Hourly',
71+ wageTotal: '216.0',
72+ },
73+ ],
74+ totals: {
75+ amount: '216.00',
76+ debitAmount: '216.00',
77+ wageAmount: '216.00',
78+ reimbursementAmount: '0.00',
79+ checkAmount: '0.00',
80+ },
81+ } */
3882
3983 const { mutateAsync : createContractorPaymentGroup } = useContractorPaymentGroupsCreateMutation ( )
84+ const { mutateAsync : previewContractorPaymentGroup } = useContractorPaymentGroupsPreviewMutation ( )
4085
4186 const { data : contractorList } = useContractorsListSuspense ( { companyUuid : companyId } )
4287 const contractors = ( contractorList . contractorList || [ ] ) . filter (
4388 contractor =>
4489 contractor . isActive &&
4590 contractor . onboardingStatus === ContractorOnboardingStatus . ONBOARDING_COMPLETED ,
4691 )
47- const initialContractorPayments : ContractorPayments [ ] = useMemo (
92+ const initialContractorPayments : ( ContractorPayments & { isTouched : boolean } ) [ ] = useMemo (
4893 ( ) =>
4994 contractors . map ( contractor => ( {
5095 contractorUuid : contractor . uuid ,
@@ -53,11 +98,12 @@ export const Root = ({ companyId, dictionary, onEvent }: CreatePaymentProps) =>
5398 hours : 0 ,
5499 bonus : 0 ,
55100 reimbursement : 0 ,
101+ isTouched : false ,
56102 } ) ) ,
57103 [ contractors ] ,
58104 )
59105 const [ virtualContractorPayments , setVirtualContractorPayments ] =
60- useState < ContractorPayments [ ] > ( initialContractorPayments )
106+ useState < ( ContractorPayments & { isTouched : boolean } ) [ ] > ( initialContractorPayments )
61107 //TODO: fix totals - they are not correct
62108 const totals = useMemo (
63109 ( ) =>
@@ -94,18 +140,26 @@ export const Root = ({ companyId, dictionary, onEvent }: CreatePaymentProps) =>
94140 } ,
95141 } )
96142
97- const onSaveAndContinue = async ( ) => {
98- const response = await createContractorPaymentGroup ( {
99- request : {
100- companyId,
101- requestBody : {
102- checkDate : new RFCDate ( paymentDate ) ,
103- contractorPayments : virtualContractorPayments ,
104- creationToken : crypto . randomUUID ( ) ,
143+ const onCreatePaymentGroup = async ( ) => {
144+ await baseSubmitHandler ( null , async ( ) => {
145+ const contractorPayments = virtualContractorPayments . filter ( payment => payment . isTouched )
146+ if ( contractorPayments . length === 0 || ! previewData ?. creationToken ) {
147+ return
148+ }
149+ const creationToken = previewData . creationToken
150+ //TODO: add empty set error alert
151+ const response = await createContractorPaymentGroup ( {
152+ request : {
153+ companyId,
154+ requestBody : {
155+ checkDate : new RFCDate ( paymentDate ) ,
156+ contractorPayments : contractorPayments ,
157+ creationToken,
158+ } ,
105159 } ,
106- } ,
160+ } )
161+ onEvent ( componentEvents . CONTRACTOR_PAYMENT_CREATED , response . contractorPaymentGroup )
107162 } )
108- onEvent ( componentEvents . CONTRACTOR_PAYMENT_REVIEW , response )
109163 }
110164 const onEditContractor = ( contractorUuid : string ) => {
111165 const contractor = contractors . find ( contractor => contractor . uuid === contractorUuid )
@@ -125,6 +179,7 @@ export const Root = ({ companyId, dictionary, onEvent }: CreatePaymentProps) =>
125179 } ,
126180 { keepDirty : false , keepValues : false } ,
127181 )
182+ setAlerts ( { } )
128183 setIsModalOpen ( true )
129184 onEvent ( componentEvents . CONTRACTOR_PAYMENT_EDIT )
130185 }
@@ -140,27 +195,88 @@ export const Root = ({ companyId, dictionary, onEvent }: CreatePaymentProps) =>
140195 bonus : data . bonus ,
141196 reimbursement : data . reimbursement ,
142197 paymentMethod : data . paymentMethod ,
198+ isTouched : true ,
143199 }
144200 : payment ,
145201 ) ,
146202 )
203+ const contractor = contractors . find ( contractor => contractor . uuid === data . contractorUuid )
204+ const displayName =
205+ contractor ?. type === 'Individual'
206+ ? firstLastName ( { first_name : contractor . firstName , last_name : contractor . lastName } )
207+ : contractor ?. businessName
208+ setAlerts ( prevAlerts => ( {
209+ ...prevAlerts ,
210+ [ data . contractorUuid ] : {
211+ type : 'success' ,
212+ title : t ( 'alerts.contractorPaymentUpdated' , {
213+ contractorName : displayName ,
214+ } ) ,
215+ onDismiss : ( ) => {
216+ setAlerts ( prevAlerts => {
217+ const { [ data . contractorUuid ] : _ , ...rest } = prevAlerts
218+ return rest
219+ } )
220+ } ,
221+ } ,
222+ } ) )
147223 setIsModalOpen ( false )
148224 onEvent ( componentEvents . CONTRACTOR_PAYMENT_UPDATE , data )
149225 }
150226
151- //TODO: submit should not attemt to push virtualContractorPayments to API if they have not been changed
227+ //TODO: historical payment - check date should be in the past
228+ const onContinueToPreview = async ( ) => {
229+ await baseSubmitHandler ( null , async ( ) => {
230+ const contractorPayments = virtualContractorPayments . filter ( payment => payment . isTouched )
231+ if ( contractorPayments . length === 0 ) {
232+ return
233+ }
234+ //TODO: clear alerts
235+ //TODO: handle errors
236+ const response = await previewContractorPaymentGroup ( {
237+ request : {
238+ companyId,
239+ requestBody : {
240+ contractorPayments : contractorPayments . map ( payment => {
241+ const { isTouched, ...rest } = payment
242+ return rest
243+ } ) ,
244+ checkDate : new RFCDate ( paymentDate ) ,
245+ } ,
246+ } ,
247+ } )
248+ setAlerts ( { } )
249+ setPreviewData ( response . contractorPaymentGroupPreview || null )
250+ onEvent ( componentEvents . CONTRACTOR_PAYMENT_PREVIEW , response . contractorPaymentGroupPreview )
251+ } )
252+ }
253+ const onBackToEdit = ( ) => {
254+ setPreviewData ( null )
255+ onEvent ( componentEvents . CONTRACTOR_PAYMENT_BACK_TO_EDIT )
256+ }
152257
153258 return (
154259 < >
155- < CreatePaymentPresentation
156- contractors = { contractors }
157- contractorPayments = { virtualContractorPayments }
158- paymentDate = { paymentDate }
159- onPaymentDateChange = { setPaymentDate }
160- onSaveAndContinue = { onSaveAndContinue }
161- onEditContractor = { onEditContractor }
162- totals = { totals }
163- />
260+ { previewData && (
261+ < PreviewPresentation
262+ contractorPaymentGroup = { previewData }
263+ contractors = { contractors }
264+ onBackToEdit = { onBackToEdit }
265+ onSubmit = { onCreatePaymentGroup }
266+ />
267+ ) }
268+ { ! previewData && (
269+ < CreatePaymentPresentation
270+ contractors = { contractors }
271+ contractorPayments = { virtualContractorPayments }
272+ paymentDate = { paymentDate }
273+ onPaymentDateChange = { setPaymentDate }
274+ onSaveAndContinue = { onContinueToPreview }
275+ onEditContractor = { onEditContractor }
276+ totals = { totals }
277+ alerts = { alerts }
278+ />
279+ ) }
164280 { /* TODO: see if moving actions to modal footer is possible */ }
165281 < Modal
166282 isOpen = { isModalOpen }
0 commit comments