1- import React , { useCallback , useEffect , useState } from 'react' ;
1+ import React , { useCallback , useEffect , useMemo , useState } from 'react' ;
22import { View } from 'react-native' ;
3+ import ActivityIndicator from '@components/ActivityIndicator' ;
34import Button from '@components/Button' ;
5+ import ConfirmModal from '@components/ConfirmModal' ;
46import ConnectToGustoFlow from '@components/ConnectToGustoFlow' ;
57import HeaderWithBackButton from '@components/HeaderWithBackButton' ;
68import MenuItem from '@components/MenuItem' ;
79import ScreenWrapper from '@components/ScreenWrapper' ;
810import ScrollView from '@components/ScrollView' ;
911import Section from '@components/Section' ;
1012import Text from '@components/Text' ;
13+ import ThreeDotsMenu from '@components/ThreeDotsMenu' ;
14+ import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types' ;
1115import { useMemoizedLazyExpensifyIcons , useMemoizedLazyIllustrations } from '@hooks/useLazyAsset' ;
1216import useLocalize from '@hooks/useLocalize' ;
1317import useNetwork from '@hooks/useNetwork' ;
18+ import useOnyx from '@hooks/useOnyx' ;
1419import usePermissions from '@hooks/usePermissions' ;
1520import usePolicy from '@hooks/usePolicy' ;
1621import useResponsiveLayout from '@hooks/useResponsiveLayout' ;
1722import useThemeStyles from '@hooks/useThemeStyles' ;
1823import useWorkspaceDocumentTitle from '@hooks/useWorkspaceDocumentTitle' ;
24+ import { isConnectionInProgress , removePolicyConnection , syncConnection } from '@libs/actions/connections' ;
1925import { openPolicyHRPage } from '@libs/actions/PolicyConnections' ;
2026import Navigation from '@libs/Navigation/Navigation' ;
2127import type { PlatformStackScreenProps } from '@libs/Navigation/PlatformStackNavigation/types' ;
2228import type { WorkspaceSplitNavigatorParamList } from '@libs/Navigation/types' ;
23- import { isGustoConnected } from '@libs/PolicyUtils' ;
29+ import { getIntegrationLastSuccessfulDate , isGustoConnected } from '@libs/PolicyUtils' ;
2430import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper' ;
2531import CONST from '@src/CONST' ;
32+ import ONYXKEYS from '@src/ONYXKEYS' ;
2633import type SCREENS from '@src/SCREENS' ;
2734
2835type WorkspaceHRPageProps = PlatformStackScreenProps < WorkspaceSplitNavigatorParamList , typeof SCREENS . WORKSPACE . HR > ;
@@ -32,28 +39,68 @@ function WorkspaceHRPage({
3239 params : { policyID} ,
3340 } ,
3441} : WorkspaceHRPageProps ) {
35- const { translate} = useLocalize ( ) ;
42+ const { translate, datetimeToRelative , getLocalDateFromDatetime } = useLocalize ( ) ;
3643 const { isBetaEnabled} = usePermissions ( ) ;
3744 const styles = useThemeStyles ( ) ;
3845 const { shouldUseNarrowLayout} = useResponsiveLayout ( ) ;
3946 const policy = usePolicy ( policyID ) ;
4047 const [ activeGustoFlowKey , setActiveGustoFlowKey ] = useState < number > ( ) ;
41- const icons = useMemoizedLazyExpensifyIcons ( [ 'GustoSquare' ] ) ;
48+ const [ isDisconnectModalOpen , setIsDisconnectModalOpen ] = useState ( false ) ;
49+ const [ connectionSyncProgress ] = useOnyx ( `${ ONYXKEYS . COLLECTION . POLICY_CONNECTION_SYNC_PROGRESS } ${ policyID } ` ) ;
50+ const icons = useMemoizedLazyExpensifyIcons ( [ 'GustoSquare' , 'Sync' , 'Trashcan' ] ) ;
4251 const illustrations = useMemoizedLazyIllustrations ( [ 'NewUser' ] ) ;
52+ const gustoConnection = policy ?. connections ?. gusto ;
4353 const isConnected = isGustoConnected ( policy ) ;
54+ const isGustoSyncInProgress = connectionSyncProgress ?. connectionName === CONST . POLICY . CONNECTIONS . NAME . GUSTO && isConnectionInProgress ( connectionSyncProgress , policy ) ;
55+ const successfulDate = getIntegrationLastSuccessfulDate (
56+ getLocalDateFromDatetime ,
57+ gustoConnection ,
58+ connectionSyncProgress ?. connectionName === CONST . POLICY . CONNECTIONS . NAME . GUSTO ? connectionSyncProgress : undefined ,
59+ ) ;
60+ const hasGustoSyncError = ! isGustoSyncInProgress && gustoConnection ?. lastSync ?. isSuccessful === false && ! ! gustoConnection ?. lastSync ?. errorDate ;
61+ const lastSyncErrorMessage = hasGustoSyncError ? ( gustoConnection ?. lastSync ?. errorMessage ?? translate ( 'workspace.hr.gusto.syncError' ) ) : undefined ;
62+ const connectionDescription = useMemo ( ( ) => {
63+ if ( isGustoSyncInProgress && connectionSyncProgress ?. stageInProgress ) {
64+ return translate ( 'workspace.hr.syncStageName' , { stage : connectionSyncProgress . stageInProgress } ) ;
65+ }
66+
67+ if ( successfulDate && ! lastSyncErrorMessage ) {
68+ return translate ( 'workspace.hr.gusto.lastSync' , datetimeToRelative ( successfulDate ) ) ;
69+ }
70+
71+ return undefined ;
72+ } , [ connectionSyncProgress ?. stageInProgress , datetimeToRelative , isGustoSyncInProgress , lastSyncErrorMessage , successfulDate , translate ] ) ;
4473
4574 useWorkspaceDocumentTitle ( undefined , 'workspace.common.hr' ) ;
4675
4776 const fetchPolicyHRPage = useCallback ( ( ) => {
4877 openPolicyHRPage ( policyID ) ;
4978 } , [ policyID ] ) ;
5079
51- useNetwork ( { onReconnect : fetchPolicyHRPage } ) ;
80+ const { isOffline } = useNetwork ( { onReconnect : fetchPolicyHRPage } ) ;
5281
5382 useEffect ( ( ) => {
5483 fetchPolicyHRPage ( ) ;
5584 } , [ fetchPolicyHRPage ] ) ;
5685
86+ const overflowMenu : ThreeDotsMenuProps [ 'menuItems' ] = useMemo (
87+ ( ) => [
88+ {
89+ icon : icons . Sync ,
90+ text : translate ( 'workspace.hr.gusto.syncNow' ) ,
91+ onSelected : ( ) => syncConnection ( policy , CONST . POLICY . CONNECTIONS . NAME . GUSTO ) ,
92+ disabled : isOffline ,
93+ } ,
94+ {
95+ icon : icons . Trashcan ,
96+ text : translate ( 'workspace.hr.gusto.disconnect' ) ,
97+ onSelected : ( ) => setIsDisconnectModalOpen ( true ) ,
98+ shouldCallAfterModalHide : true ,
99+ } ,
100+ ] ,
101+ [ icons . Sync , icons . Trashcan , isOffline , policy , translate ] ,
102+ ) ;
103+
57104 return (
58105 < AccessOrNotFoundWrapper
59106 accessVariants = { [ CONST . POLICY . ACCESS_VARIANTS . ADMIN , CONST . POLICY . ACCESS_VARIANTS . CONTROL ] }
@@ -92,22 +139,55 @@ function WorkspaceHRPage({
92139 title = { translate ( 'workspace.hr.gusto.title' ) }
93140 icon = { icons . GustoSquare }
94141 iconType = { CONST . ICON_TYPE_AVATAR }
95- wrapperStyle = { [ styles . ph0 , styles . pv2 , styles . mt4 ] }
142+ wrapperStyle = { [ styles . ph0 , styles . pv2 , styles . mt4 , ! ! lastSyncErrorMessage && styles . pb0 ] }
96143 interactive = { false }
97- shouldShowRightComponent = { ! isConnected }
144+ description = { connectionDescription }
145+ errorText = { lastSyncErrorMessage }
146+ errorTextStyle = { [ styles . mt5 ] }
147+ shouldShowRedDotIndicator
148+ shouldShowRightComponent
98149 rightComponent = {
99150 ! isConnected ? (
100151 < Button
101152 small
102153 text = { translate ( 'workspace.hr.gusto.connect' ) }
103154 onPress = { ( ) => setActiveGustoFlowKey ( Math . random ( ) ) }
104155 />
105- ) : undefined
156+ ) : isGustoSyncInProgress ? (
157+ < ActivityIndicator
158+ style = { [ styles . popoverMenuIcon ] }
159+ reasonAttributes = { { context : 'WorkspaceHRPage.gustoSync' } }
160+ />
161+ ) : (
162+ < ThreeDotsMenu
163+ shouldSelfPosition
164+ menuItems = { overflowMenu }
165+ anchorAlignment = { {
166+ horizontal : CONST . MODAL . ANCHOR_ORIGIN_HORIZONTAL . RIGHT ,
167+ vertical : CONST . MODAL . ANCHOR_ORIGIN_VERTICAL . TOP ,
168+ } }
169+ />
170+ )
106171 }
107172 />
108173 </ Section >
109174 </ View >
110175 </ ScrollView >
176+ < ConfirmModal
177+ title = { translate ( 'workspace.hr.gusto.disconnectTitle' ) }
178+ isVisible = { isDisconnectModalOpen }
179+ onConfirm = { ( ) => {
180+ if ( policy ) {
181+ removePolicyConnection ( policy , CONST . POLICY . CONNECTIONS . NAME . GUSTO ) ;
182+ }
183+ setIsDisconnectModalOpen ( false ) ;
184+ } }
185+ onCancel = { ( ) => setIsDisconnectModalOpen ( false ) }
186+ prompt = { translate ( 'workspace.hr.gusto.disconnectPrompt' ) }
187+ confirmText = { translate ( 'workspace.hr.gusto.disconnect' ) }
188+ cancelText = { translate ( 'common.cancel' ) }
189+ danger
190+ />
111191 </ ScreenWrapper >
112192 </ AccessOrNotFoundWrapper >
113193 ) ;
0 commit comments