Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hook
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import type {GustoSyncResult} from '@libs/API/GustoSyncResult';
import type {HrSyncResult} from '@libs/API/HrSyncResult';
import CONST from '@src/CONST';
import Button from './Button';
import FixedFooter from './FixedFooter';
Expand All @@ -16,46 +16,52 @@ import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import ScrollView from './ScrollView';
import Text from './Text';

type GustoSyncResultsModalProps = ModalProps & {
/** Sync result returned by the completed Gusto sync job */
result: GustoSyncResult;
type HRSyncResultsModalProps = ModalProps & {
/** Sync result returned by the completed HR sync job */
Comment thread
mhawryluk marked this conversation as resolved.
result: HrSyncResult;

/** Human-readable display name for the HR provider (e.g. "Gusto") */
providerDisplayName: string;
};

function GustoSyncResultsModal({result, closeModal}: GustoSyncResultsModalProps) {
function HRSyncResultsModal({result, providerDisplayName, closeModal}: HRSyncResultsModalProps) {
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
const icons = useMemoizedLazyExpensifyIcons(['DownArrow']);
const illustrations = useMemoizedLazyIllustrations(['SyncUsers']);
const [isSkippedSectionExpanded, setIsSkippedSectionExpanded] = useState(false);
const [isVisible, setIsVisible] = useState(true);

const addedCount = result.addedEmployeesCount ?? 0;
const removedCount = result.removedEmployeesCount ?? 0;
const skippedCount = result.skippedEmployees?.length ?? 0;
const closeResultsModal = () => closeModal();

const hideModal = () => setIsVisible(false);
Comment thread
mhawryluk marked this conversation as resolved.

const renderResultSummary = (label: string, count: number) => (
<View style={[styles.mb6]}>
<Text style={[styles.textSupporting, styles.mb1]}>{label}</Text>
<Text style={[styles.textNormalThemeText, styles.textStrong]}>{translate('workspace.hr.gusto.syncResults.employeeCount', {count})}</Text>
<Text style={[styles.textNormalThemeText, styles.textStrong]}>{translate('workspace.hr.syncResults.employeeCount', {count})}</Text>
</View>
);

return (
<Modal
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
isVisible
onClose={closeResultsModal}
isVisible={isVisible}
onClose={hideModal}
onModalHide={closeModal}
shouldHandleNavigationBack
enableEdgeToEdgeBottomSafeAreaPadding
>
<View
testID="GustoSyncResultsModal"
testID="HRSyncResultsModal"
style={[styles.flex1, styles.appBG]}
>
<HeaderWithBackButton
title={translate('workspace.hr.gusto.syncResults.title')}
onBackButtonPress={closeResultsModal}
title={translate('workspace.hr.syncResults.title', providerDisplayName)}
onBackButtonPress={hideModal}
/>
<ScrollView contentContainerStyle={[styles.flexGrow1, styles.ph5, styles.pb8]}>
<View style={[styles.alignItemsCenter, styles.mt4, styles.mb4, styles.pRelative]}>
Expand All @@ -65,19 +71,19 @@ function GustoSyncResultsModal({result, closeModal}: GustoSyncResultsModalProps)
height={68}
/>
</View>
<Text style={[styles.textHeadlineH1, styles.mb8]}>{translate('workspace.hr.gusto.syncResults.successTitle')}</Text>
{renderResultSummary(translate('workspace.hr.gusto.syncResults.added'), addedCount)}
{renderResultSummary(translate('workspace.hr.gusto.syncResults.removed'), removedCount)}
<Text style={[styles.textHeadlineH1, styles.mb8]}>{translate('workspace.hr.syncResults.successTitle', providerDisplayName)}</Text>
Comment thread
mhawryluk marked this conversation as resolved.
{renderResultSummary(translate('workspace.hr.syncResults.added'), addedCount)}
{renderResultSummary(translate('workspace.hr.syncResults.removed'), removedCount)}
<PressableWithoutFeedback
accessibilityLabel={translate('workspace.hr.gusto.syncResults.skipped')}
sentryLabel="GustoSyncResultsModal-SkippedEmployees"
accessibilityLabel={translate('workspace.hr.syncResults.skipped')}
sentryLabel="HRSyncResultsModal-SkippedEmployees"
role={CONST.ROLE.BUTTON}
onPress={() => setIsSkippedSectionExpanded((isExpanded) => !isExpanded)}
style={[styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter]}
>
<View>
<Text style={[styles.textSupporting, styles.mb1]}>{translate('workspace.hr.gusto.syncResults.skipped')}</Text>
<Text style={[styles.textNormalThemeText, styles.textStrong]}>{translate('workspace.hr.gusto.syncResults.employeeCount', {count: skippedCount})}</Text>
<Text style={[styles.textSupporting, styles.mb1]}>{translate('workspace.hr.syncResults.skipped')}</Text>
<Text style={[styles.textNormalThemeText, styles.textStrong]}>{translate('workspace.hr.syncResults.employeeCount', {count: skippedCount})}</Text>
</View>
<Icon
src={icons.DownArrow}
Expand All @@ -101,12 +107,12 @@ function GustoSyncResultsModal({result, closeModal}: GustoSyncResultsModalProps)
large
success
text={translate('common.buttonConfirm')}
onPress={closeResultsModal}
onPress={hideModal}
/>
</FixedFooter>
</View>
</Modal>
);
}

export default GustoSyncResultsModal;
export default HRSyncResultsModal;
45 changes: 0 additions & 45 deletions src/hooks/useGustoSyncResultsModal.ts

This file was deleted.

61 changes: 61 additions & 0 deletions src/hooks/useHRSyncResultsModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {useEffect} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import type {TupleToUnion} from 'type-fest';
import HRSyncResultsModal from '@components/HRSyncResultsModal';
import {useModal} from '@components/Modal/Global/ModalContext';
import {getConnectedHRProvider} from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PolicyConnectionSyncProgress} from '@src/types/onyx/Policy';
import useOnyx from './useOnyx';
import usePrevious from './usePrevious';

/**
* Watches an HR provider's sync progress and automatically opens the `HRSyncResultsModal`
* when the sync transitions to the `JOB_DONE` stage with a result payload.
*/
function useHRSyncResultsModal(policyID: string, connectionSyncProgress: OnyxEntry<PolicyConnectionSyncProgress>, isFocused: boolean) {
Comment thread
mhawryluk marked this conversation as resolved.
const modal = useModal();
const previousSyncProgress = usePrevious(connectionSyncProgress);

const connectionName = connectionSyncProgress?.connectionName;
const [providerDisplayName] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {
Comment thread
mhawryluk marked this conversation as resolved.
Outdated
selector: (policy) => {
const hrProvider = getConnectedHRProvider(policy);
return hrProvider?.displayName ?? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName as keyof typeof CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY] ?? connectionName ?? '';
},
});

useEffect(() => {
const syncResult = connectionSyncProgress?.result;
const isHRSyncDoneWithResult =
CONST.POLICY.CONNECTIONS.HR_CONNECTION_NAMES.includes(connectionName as TupleToUnion<typeof CONST.POLICY.CONNECTIONS.HR_CONNECTION_NAMES>) &&
connectionSyncProgress?.stageInProgress === CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE &&
!!syncResult;
const didTransitionToJobDone = previousSyncProgress?.connectionName === connectionName && previousSyncProgress?.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE;
const didHRSyncComplete = isFocused && isHRSyncDoneWithResult && didTransitionToJobDone;

if (!didHRSyncComplete || !syncResult || !connectionName) {
return;
}

modal.showModal({
component: HRSyncResultsModal,
props: {result: syncResult, providerDisplayName: providerDisplayName ?? ''},
id: `${connectionName}-sync-results-${policyID}`,
});
}, [
connectionName,
connectionSyncProgress?.result,
connectionSyncProgress?.stageInProgress,
connectionSyncProgress?.timestamp,
isFocused,
providerDisplayName,
policyID,
previousSyncProgress?.connectionName,
previousSyncProgress?.stageInProgress,
modal,
]);
}

export default useHRSyncResultsModal;
22 changes: 11 additions & 11 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7189,19 +7189,19 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
}
}
},
syncResults: {
title: (provider: string) => `${provider}-Synchronisierung abgeschlossen`,
successTitle: (provider: string) => `Ihre ${provider}-Verbindung wurde erfolgreich synchronisiert!`,
added: 'Hinzugefügt',
removed: 'Entfernt',
skipped: 'Übersprungen',
employeeCount: () => ({
one: '1 Mitarbeiter',
other: (count: number) => `${count} Mitarbeitende`,
}),
},
gusto: {
title: 'Gusto',
syncResults: {
title: 'Gusto-Synchronisierungsergebnisse',
successTitle: 'Ihre Gusto-Verbindung wurde erfolgreich synchronisiert!',
added: 'Hinzugefügt',
removed: 'Entfernt',
skipped: 'Übersprungen',
employeeCount: () => ({
one: '1 Mitarbeiter',
other: (count: number) => `${count} Mitarbeitende`,
}),
},
},
zenefits: {
title: 'TriNet',
Expand Down
22 changes: 11 additions & 11 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6493,19 +6493,19 @@ const translations = {
}
}
},
syncResults: {
title: (provider: string) => `${provider} sync complete`,
successTitle: (provider: string) => `Successfully synced your ${provider} connection!`,
added: 'Added',
removed: 'Removed',
skipped: 'Skipped',
employeeCount: () => ({
one: '1 employee',
other: (count: number) => `${count} employees`,
}),
},
gusto: {
title: 'Gusto',
syncResults: {
title: 'Gusto sync results',
successTitle: 'Successfully synced your Gusto connection!',
added: 'Added',
removed: 'Removed',
skipped: 'Skipped',
employeeCount: () => ({
one: '1 employee',
other: (count: number) => `${count} employees`,
}),
},
},
zenefits: {
title: 'TriNet',
Expand Down
22 changes: 11 additions & 11 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6306,19 +6306,19 @@ ${amount} para ${merchant} - ${date}`,
}
}
},
syncResults: {
title: (provider: string) => `Sincronización de ${provider} completada`,
successTitle: (provider: string) => `¡Se sincronizó correctamente tu conexión de ${provider}!`,
added: 'Añadido',
removed: 'Eliminado',
skipped: 'Omitido',
employeeCount: () => ({
one: '1 empleado',
other: (count: number) => `${count} empleados`,
}),
},
gusto: {
title: 'Gusto',
syncResults: {
title: 'Resultados de la sincronización de Gusto',
successTitle: '¡Se sincronizó correctamente tu conexión con Gusto!',
added: 'Añadido',
removed: 'Eliminado',
skipped: 'Omitido',
employeeCount: () => ({
one: '1 empleado',
other: (count: number) => `${count} empleados`,
}),
},
},
zenefits: {
title: 'TriNet',
Expand Down
22 changes: 11 additions & 11 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7218,19 +7218,19 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
}
}
},
syncResults: {
title: (provider: string) => `Synchronisation ${provider} terminée`,
Comment thread
mhawryluk marked this conversation as resolved.
successTitle: (provider: string) => `Connexion ${provider} synchronisée avec succès !`,
added: 'Ajouté',
removed: 'Supprimé',
skipped: 'Ignoré',
employeeCount: () => ({
one: '1 employé',
other: (count: number) => `${count} employés`,
}),
},
gusto: {
title: 'Gusto',
syncResults: {
title: 'Résultats de la synchronisation Gusto',
successTitle: 'Connexion Gusto synchronisée avec succès !',
added: 'Ajouté',
removed: 'Supprimé',
skipped: 'Ignoré',
employeeCount: () => ({
one: '1 employé',
other: (count: number) => `${count} employés`,
}),
},
},
zenefits: {
title: 'TriNet',
Expand Down
22 changes: 11 additions & 11 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7176,19 +7176,19 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
}
}
},
syncResults: {
title: (provider: string) => `Sincronizzazione ${provider} completata`,
successTitle: (provider: string) => `Connessione ${provider} sincronizzata correttamente!`,
added: 'Aggiunto',
removed: 'Rimosso',
skipped: 'Saltato',
employeeCount: () => ({
one: '1 dipendente',
other: (count: number) => `${count} dipendenti`,
}),
},
gusto: {
title: 'Gusto',
syncResults: {
title: 'Risultati sincronizzazione Gusto',
successTitle: 'Connessione a Gusto sincronizzata con successo!',
added: 'Aggiunto',
removed: 'Rimosso',
skipped: 'Saltato',
employeeCount: () => ({
one: '1 dipendente',
other: (count: number) => `${count} dipendenti`,
}),
},
},
zenefits: {
title: 'TriNet',
Expand Down
Loading
Loading