Skip to content

Commit 7ac8e54

Browse files
authored
Merge pull request Expensify#90526 from ShridharGoel/gusto1.14
[NoQA] Show Gusto sync results
2 parents 8a6331d + cc33ab1 commit 7ac8e54

17 files changed

Lines changed: 285 additions & 10 deletions

File tree

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import React, {useState} from 'react';
2+
import {View} from 'react-native';
3+
import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
4+
import useLocalize from '@hooks/useLocalize';
5+
import useTheme from '@hooks/useTheme';
6+
import useThemeStyles from '@hooks/useThemeStyles';
7+
import type {GustoSyncResult} from '@libs/API/GustoSyncResult';
8+
import CONST from '@src/CONST';
9+
import Button from './Button';
10+
import FixedFooter from './FixedFooter';
11+
import HeaderWithBackButton from './HeaderWithBackButton';
12+
import Icon from './Icon';
13+
import Modal from './Modal';
14+
import type {ModalProps} from './Modal/Global/ModalContext';
15+
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
16+
import ScrollView from './ScrollView';
17+
import Text from './Text';
18+
19+
type GustoSyncResultsModalProps = ModalProps & {
20+
/** Sync result returned by the completed Gusto sync job */
21+
result: GustoSyncResult;
22+
};
23+
24+
function GustoSyncResultsModal({result, closeModal}: GustoSyncResultsModalProps) {
25+
const {translate} = useLocalize();
26+
const theme = useTheme();
27+
const styles = useThemeStyles();
28+
const icons = useMemoizedLazyExpensifyIcons(['DownArrow']);
29+
const illustrations = useMemoizedLazyIllustrations(['SyncUsers']);
30+
const [isSkippedSectionExpanded, setIsSkippedSectionExpanded] = useState(false);
31+
32+
const addedCount = result.addedEmployeesCount ?? 0;
33+
const removedCount = result.removedEmployeesCount ?? 0;
34+
const skippedCount = result.skippedEmployees?.length ?? 0;
35+
const closeResultsModal = () => closeModal();
36+
37+
const renderResultSummary = (label: string, count: number) => (
38+
<View style={[styles.mb6]}>
39+
<Text style={[styles.textSupporting, styles.mb1]}>{label}</Text>
40+
<Text style={[styles.textNormalThemeText, styles.textStrong]}>{translate('workspace.hr.gusto.syncResults.employeeCount', {count})}</Text>
41+
</View>
42+
);
43+
44+
return (
45+
<Modal
46+
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
47+
isVisible
48+
onClose={closeResultsModal}
49+
shouldHandleNavigationBack
50+
enableEdgeToEdgeBottomSafeAreaPadding
51+
>
52+
<View
53+
testID="GustoSyncResultsModal"
54+
style={[styles.flex1, styles.appBG]}
55+
>
56+
<HeaderWithBackButton
57+
title={translate('workspace.hr.gusto.syncResults.title')}
58+
onBackButtonPress={closeResultsModal}
59+
/>
60+
<ScrollView contentContainerStyle={[styles.flexGrow1, styles.ph5, styles.pb8]}>
61+
<View style={[styles.alignItemsCenter, styles.mt4, styles.mb4, styles.pRelative]}>
62+
<Icon
63+
src={illustrations.SyncUsers}
64+
width={68}
65+
height={68}
66+
/>
67+
</View>
68+
<Text style={[styles.textHeadlineH1, styles.mb8]}>{translate('workspace.hr.gusto.syncResults.successTitle')}</Text>
69+
{renderResultSummary(translate('workspace.hr.gusto.syncResults.added'), addedCount)}
70+
{renderResultSummary(translate('workspace.hr.gusto.syncResults.removed'), removedCount)}
71+
<PressableWithoutFeedback
72+
accessibilityLabel={translate('workspace.hr.gusto.syncResults.skipped')}
73+
sentryLabel="GustoSyncResultsModal-SkippedEmployees"
74+
role={CONST.ROLE.BUTTON}
75+
onPress={() => setIsSkippedSectionExpanded((isExpanded) => !isExpanded)}
76+
style={[styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter]}
77+
>
78+
<View>
79+
<Text style={[styles.textSupporting, styles.mb1]}>{translate('workspace.hr.gusto.syncResults.skipped')}</Text>
80+
<Text style={[styles.textNormalThemeText, styles.textStrong]}>{translate('workspace.hr.gusto.syncResults.employeeCount', {count: skippedCount})}</Text>
81+
</View>
82+
<Icon
83+
src={icons.DownArrow}
84+
fill={theme.icon}
85+
additionalStyles={isSkippedSectionExpanded ? {transform: [{rotate: '180deg'}]} : undefined}
86+
/>
87+
</PressableWithoutFeedback>
88+
{isSkippedSectionExpanded &&
89+
result.skippedEmployees?.map((employee) => (
90+
<View
91+
key={employee.id}
92+
style={[styles.mt4]}
93+
>
94+
<Text style={[styles.textNormalThemeText, styles.textStrong]}>{employee.name}</Text>
95+
<Text style={[styles.textSupporting]}>{employee.reason}</Text>
96+
</View>
97+
))}
98+
</ScrollView>
99+
<FixedFooter addBottomSafeAreaPadding>
100+
<Button
101+
large
102+
success
103+
text={translate('common.buttonConfirm')}
104+
onPress={closeResultsModal}
105+
/>
106+
</FixedFooter>
107+
</View>
108+
</Modal>
109+
);
110+
}
111+
112+
export default GustoSyncResultsModal;

src/components/Icon/chunks/illustrations.chunk.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ import Stopwatch from '@assets/images/simple-illustrations/simple-illustration__
186186
import SubscriptionAnnual from '@assets/images/simple-illustrations/simple-illustration__subscription-annual.svg';
187187
import SubscriptionPPU from '@assets/images/simple-illustrations/simple-illustration__subscription-ppu.svg';
188188
import Sunglasses from '@assets/images/simple-illustrations/simple-illustration__sunglasses.svg';
189+
import SyncUsers from '@assets/images/simple-illustrations/simple-illustration__syncusers.svg';
189190
import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg';
190191
import Target from '@assets/images/simple-illustrations/simple-illustration__target.svg';
191192
import TeachersUnite from '@assets/images/simple-illustrations/simple-illustration__teachers-unite.svg';
@@ -391,6 +392,7 @@ const Illustrations = {
391392
LockOpen,
392393
Mailbox,
393394
NewUser,
395+
SyncUsers,
394396
ShieldYellow,
395397
Clock,
396398
Members,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {useEffect} from 'react';
2+
import type {OnyxEntry} from 'react-native-onyx';
3+
import GustoSyncResultsModal from '@components/GustoSyncResultsModal';
4+
import {useModal} from '@components/Modal/Global/ModalContext';
5+
import CONST from '@src/CONST';
6+
import type {PolicyConnectionSyncProgress} from '@src/types/onyx/Policy';
7+
import usePrevious from './usePrevious';
8+
9+
function useGustoSyncResultsModal(policyID: string, connectionSyncProgress: OnyxEntry<PolicyConnectionSyncProgress>, isFocused: boolean) {
10+
const modal = useModal();
11+
const previousSyncProgress = usePrevious(connectionSyncProgress);
12+
13+
useEffect(() => {
14+
const syncResult = connectionSyncProgress?.result;
15+
const isGustoSyncDoneWithResult =
16+
connectionSyncProgress?.connectionName === CONST.POLICY.CONNECTIONS.NAME.GUSTO &&
17+
connectionSyncProgress?.stageInProgress === CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE &&
18+
!!syncResult;
19+
const didTransitionToJobDone =
20+
previousSyncProgress?.connectionName === CONST.POLICY.CONNECTIONS.NAME.GUSTO && previousSyncProgress?.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE;
21+
const didGustoSyncComplete = isFocused && isGustoSyncDoneWithResult && didTransitionToJobDone;
22+
23+
if (!didGustoSyncComplete || !syncResult) {
24+
return;
25+
}
26+
27+
modal.showModal({
28+
component: GustoSyncResultsModal,
29+
props: {result: syncResult},
30+
id: `gusto-sync-results-${policyID}`,
31+
});
32+
}, [
33+
connectionSyncProgress?.connectionName,
34+
connectionSyncProgress?.result,
35+
connectionSyncProgress?.stageInProgress,
36+
connectionSyncProgress?.timestamp,
37+
isFocused,
38+
policyID,
39+
previousSyncProgress?.connectionName,
40+
previousSyncProgress?.stageInProgress,
41+
modal,
42+
]);
43+
}
44+
45+
export default useGustoSyncResultsModal;

src/languages/de.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7108,6 +7108,17 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
71087108
syncError: 'Verbindung zu Gusto nicht möglich',
71097109
disconnectTitle: 'Gusto trennen',
71107110
disconnectPrompt: 'Bist du sicher, dass du Gusto trennen möchtest?',
7111+
syncResults: {
7112+
title: 'Gusto-Synchronisierungsergebnisse',
7113+
successTitle: 'Ihre Gusto-Verbindung wurde erfolgreich synchronisiert!',
7114+
added: 'Hinzugefügt',
7115+
removed: 'Entfernt',
7116+
skipped: 'Übersprungen',
7117+
employeeCount: () => ({
7118+
one: '1 Mitarbeiter',
7119+
other: (count: number) => `${count} Mitarbeitende`,
7120+
}),
7121+
},
71117122
},
71127123
zenefits: {
71137124
title: 'TriNet',

src/languages/en.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6469,6 +6469,17 @@ const translations = {
64696469
description: 'I’ll manually setup approval workflows in Expensify.',
64706470
},
64716471
},
6472+
syncResults: {
6473+
title: 'Gusto sync results',
6474+
successTitle: 'Successfully synced your Gusto connection!',
6475+
added: 'Added',
6476+
removed: 'Removed',
6477+
skipped: 'Skipped',
6478+
employeeCount: () => ({
6479+
one: '1 employee',
6480+
other: (count: number) => `${count} employees`,
6481+
}),
6482+
},
64726483
},
64736484
zenefits: {
64746485
title: 'TriNet',

src/languages/es.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6282,6 +6282,17 @@ ${amount} para ${merchant} - ${date}`,
62826282
description: 'Configuraré manualmente los flujos de aprobación en Expensify.',
62836283
},
62846284
},
6285+
syncResults: {
6286+
title: 'Resultados de la sincronización de Gusto',
6287+
successTitle: '¡Se sincronizó correctamente tu conexión con Gusto!',
6288+
added: 'Añadido',
6289+
removed: 'Eliminado',
6290+
skipped: 'Omitido',
6291+
employeeCount: () => ({
6292+
one: '1 empleado',
6293+
other: (count: number) => `${count} empleados`,
6294+
}),
6295+
},
62856296
},
62866297
zenefits: {
62876298
title: 'TriNet',

src/languages/fr.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7130,6 +7130,17 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
71307130
syncError: 'Impossible de se connecter à Gusto',
71317131
disconnectTitle: 'Déconnecter Gusto',
71327132
disconnectPrompt: 'Voulez-vous vraiment déconnecter Gusto ?',
7133+
syncResults: {
7134+
title: 'Résultats de la synchronisation Gusto',
7135+
successTitle: 'Connexion Gusto synchronisée avec succès !',
7136+
added: 'Ajouté',
7137+
removed: 'Supprimé',
7138+
skipped: 'Ignoré',
7139+
employeeCount: () => ({
7140+
one: '1 employé',
7141+
other: (count: number) => `${count} employés`,
7142+
}),
7143+
},
71337144
},
71347145
zenefits: {
71357146
title: 'TriNet',

src/languages/it.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7094,6 +7094,17 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
70947094
syncError: 'Impossibile connettersi a Gusto',
70957095
disconnectTitle: 'Disconnetti Gusto',
70967096
disconnectPrompt: 'Sei sicuro di voler disconnettere Gusto?',
7097+
syncResults: {
7098+
title: 'Risultati sincronizzazione Gusto',
7099+
successTitle: 'Connessione a Gusto sincronizzata con successo!',
7100+
added: 'Aggiunto',
7101+
removed: 'Rimosso',
7102+
skipped: 'Saltato',
7103+
employeeCount: () => ({
7104+
one: '1 dipendente',
7105+
other: (count: number) => `${count} dipendenti`,
7106+
}),
7107+
},
70977108
},
70987109
zenefits: {
70997110
title: 'TriNet',

src/languages/ja.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7015,6 +7015,17 @@ ${reportName}
70157015
syncError: 'Gusto に接続できません',
70167016
disconnectTitle: 'Gusto の接続を解除',
70177017
disconnectPrompt: 'Gusto との接続を本当に解除しますか?',
7018+
syncResults: {
7019+
title: 'Gusto 同期結果',
7020+
successTitle: 'Gusto との連携が正常に同期されました!',
7021+
added: '追加済み',
7022+
removed: '削除済み',
7023+
skipped: 'スキップ済み',
7024+
employeeCount: () => ({
7025+
one: '1 従業員',
7026+
other: (count: number) => `${count} 従業員`,
7027+
}),
7028+
},
70187029
},
70197030
zenefits: {
70207031
title: 'TriNet',

0 commit comments

Comments
 (0)