Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7153,6 +7153,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
syncError: (providerName: string) => `Verbindung zu ${providerName} nicht möglich`,
connectionDescription: (providerName: string) => `Verbinden Sie ${providerName}, um Mitarbeitergenehmigungen mit Ihrem Workspace zu synchronisieren.`,
approvalMode: 'Genehmigungsmodus',
providerApprovalMode: (providerName: string) => `${providerName}-Genehmigungsmodus`,
finalApprover: 'Endgültige:r Genehmiger:in',
providerFinalApprover: (providerName: string) => `${providerName} Endgenehmigende*r`,
notSet: 'Nicht festgelegt',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6455,6 +6455,7 @@ const translations = {
syncError: (providerName: string) => `Can't connect to ${providerName}`,
connectionDescription: (providerName: string) => `Connect ${providerName} to keep employee approvals in sync with your workspace.`,
approvalMode: 'Approval mode',
providerApprovalMode: (providerName: string) => `${providerName} approval mode`,
finalApprover: 'Final approver',
providerFinalApprover: (providerName: string) => `${providerName} final approver`,
notSet: 'Not set',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6268,6 +6268,7 @@ ${amount} para ${merchant} - ${date}`,
syncError: (providerName: string) => `No se puede conectar con ${providerName}`,
connectionDescription: (providerName: string) => `Conecta ${providerName} para mantener sincronizadas las aprobaciones de empleados con tu espacio de trabajo.`,
approvalMode: 'Modo de aprobación',
providerApprovalMode: (providerName: string) => `Modo de aprobación de ${providerName}`,
finalApprover: 'Aprobador final',
providerFinalApprover: (providerName: string) => `Aprobador final de ${providerName}`,
notSet: 'No configurado',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7176,6 +7176,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
syncError: (providerName: string) => `Impossible de se connecter à ${providerName}`,
connectionDescription: (providerName: string) => `Connectez ${providerName} pour synchroniser les approbations des employés avec votre espace de travail.`,
approvalMode: "Mode d'approbation",
providerApprovalMode: (providerName: string) => `Mode d'approbation ${providerName}`,
finalApprover: 'Approbateur final',
providerFinalApprover: (providerName: string) => `Approbateur final ${providerName}`,
notSet: 'Non défini',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7140,6 +7140,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
syncError: (providerName: string) => `Impossibile connettersi a ${providerName}`,
connectionDescription: (providerName: string) => `Collega ${providerName} per mantenere sincronizzate le approvazioni dei dipendenti con il tuo spazio di lavoro.`,
approvalMode: 'Modalità di approvazione',
providerApprovalMode: (providerName: string) => `Modalità di approvazione ${providerName}`,
finalApprover: 'Approvatore finale',
providerFinalApprover: (providerName: string) => `Approvatore finale ${providerName}`,
notSet: 'Non impostato',
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7059,6 +7059,7 @@ ${reportName}
syncError: (providerName: string) => `${providerName}に接続できません`,
connectionDescription: (providerName: string) => `${providerName}を接続して、従業員の承認をワークスペースと同期させましょう。`,
approvalMode: '承認モード',
providerApprovalMode: (providerName: string) => `${providerName} 承認モード`,
finalApprover: '最終承認者',
providerFinalApprover: (providerName: string) => `${providerName} 最終承認者`,
notSet: '未設定',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7115,6 +7115,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
syncError: (providerName: string) => `Kan geen verbinding maken met ${providerName}`,
connectionDescription: (providerName: string) => `Verbind ${providerName} om goedkeuringen van werknemers gesynchroniseerd te houden met je werkruimte.`,
approvalMode: 'Goedkeuringsmodus',
providerApprovalMode: (providerName: string) => `${providerName}-goedkeuringsmodus`,
finalApprover: 'Eindgoedkeurder',
providerFinalApprover: (providerName: string) => `Laatste ${providerName}-fiatteur`,
notSet: 'Niet ingesteld',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7110,6 +7110,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
syncError: (providerName: string) => `Nie można połączyć z ${providerName}`,
connectionDescription: (providerName: string) => `Połącz ${providerName}, aby synchronizować akceptacje pracowników z Twoim miejscem pracy.`,
approvalMode: 'Tryb zatwierdzania',
providerApprovalMode: (providerName: string) => `Tryb zatwierdzania ${providerName}`,
finalApprover: 'Ostateczny zatwierdzający',
providerFinalApprover: (providerName: string) => `Ostateczny zatwierdzający ${providerName}`,
notSet: 'Nie ustawiono',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7115,6 +7115,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
syncError: (providerName: string) => `Não é possível conectar ao ${providerName}`,
connectionDescription: (providerName: string) => `Conecte ${providerName} para manter as aprovações de funcionários sincronizadas com seu workspace.`,
approvalMode: 'Modo de aprovação',
providerApprovalMode: (providerName: string) => `Modo de aprovação do ${providerName}`,
finalApprover: 'Aprovador final',
providerFinalApprover: (providerName: string) => `Aprovador final de ${providerName}`,
notSet: 'Não definido',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6932,6 +6932,7 @@ ${reportName}
syncError: (providerName: string) => `无法连接到 ${providerName}`,
connectionDescription: (providerName: string) => `连接 ${providerName},以在您的工作区中同步员工审批。`,
approvalMode: '审批模式',
providerApprovalMode: (providerName: string) => `${providerName} 审批模式`,
finalApprover: '最终审批人',
providerFinalApprover: (providerName: string) => `${providerName} 最终审批人`,
notSet: '未设置',
Expand Down
160 changes: 160 additions & 0 deletions src/pages/workspace/hr/HRApprovalModePageBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import React, {useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import Button from '@components/Button';
import FixedFooter from '@components/FixedFooter';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {ModalActions} from '@components/Modal/Global/ModalContext';
import RenderHTML from '@components/RenderHTML';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import useConfirmModal from '@hooks/useConfirmModal';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import usePolicy from '@hooks/usePolicy';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type Beta from '@src/types/onyx/Beta';
import type Policy from '@src/types/onyx/Policy';
import type {PolicyConnectionSyncProgress} from '@src/types/onyx/Policy';

type ApprovalModeValue = ValueOf<typeof CONST.GUSTO.APPROVAL_MODE> | ValueOf<typeof CONST.MERGE_HR.APPROVAL_MODE>;

type HRApprovalModeProviderConfig<T extends ApprovalModeValue = ApprovalModeValue> = {
testID: string;
beta: Beta;
isConnected: (policy: OnyxEntry<Policy>) => boolean;
approvalModes: {BASIC: T; MANAGER: T; CUSTOM: T};
getCurrentApprovalMode: (policy: OnyxEntry<Policy>) => T | null;
getProviderName: (policy: OnyxEntry<Policy>) => string;
getHeaderTitle: (providerName: string) => string;
handleSave: (params: {policyID: string; draftApprovalMode: T; currentApprovalMode: T | null; connectionSyncProgress?: OnyxEntry<PolicyConnectionSyncProgress>}) => void;
};

type ApprovalModeListItem<T extends ApprovalModeValue = ApprovalModeValue> = ListItem & {
value: T;
};

type HRApprovalModePageBaseProps<T extends ApprovalModeValue = ApprovalModeValue> = {
policyID: string;
config: HRApprovalModeProviderConfig<T>;
};

function HRApprovalModePageBase<T extends ApprovalModeValue>({policyID, config}: HRApprovalModePageBaseProps<T>) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const {showConfirmModal} = useConfirmModal();
const {isBetaEnabled} = usePermissions();
const policy = usePolicy(policyID);
const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`);

const providerName = config.getProviderName(policy);
const currentApprovalMode = config.getCurrentApprovalMode(policy);
const [draftApprovalMode, setDraftApprovalMode] = useState<T | undefined>();
const selectedApprovalMode = draftApprovalMode ?? currentApprovalMode;
const isSaveDisabled = !draftApprovalMode || draftApprovalMode === currentApprovalMode;

const approvalModeOptions: Array<ApprovalModeListItem<T>> = [
{
text: translate('workspace.hr.approvalModes.basic.label'),
alternateText: translate('workspace.hr.approvalModes.basic.description'),
keyForList: config.approvalModes.BASIC,
value: config.approvalModes.BASIC,
isSelected: selectedApprovalMode === config.approvalModes.BASIC,
},
{
text: translate('workspace.hr.approvalModes.manager.label'),
alternateText: translate('workspace.hr.approvalModes.manager.description', providerName),
keyForList: config.approvalModes.MANAGER,
value: config.approvalModes.MANAGER,
isSelected: selectedApprovalMode === config.approvalModes.MANAGER,
},
{
text: translate('workspace.hr.approvalModes.custom.label'),
alternateText: translate('workspace.hr.approvalModes.custom.description'),
keyForList: config.approvalModes.CUSTOM,
value: config.approvalModes.CUSTOM,
isSelected: selectedApprovalMode === config.approvalModes.CUSTOM,
},
];
const selectedApprovalModeKey = approvalModeOptions.find((option) => option.isSelected)?.keyForList;

const saveApprovalMode = () => {
if (!draftApprovalMode) {
return;
}

config.handleSave({policyID, draftApprovalMode, currentApprovalMode, connectionSyncProgress});
Navigation.goBack();
};

const confirmSaveApprovalMode = () => {
showConfirmModal({
title: translate('workspace.hr.approvalModeWarningTitle'),
prompt: (
<View style={[styles.renderHTML, styles.flexRow]}>
<RenderHTML html={translate('workspace.hr.approvalModeWarningPrompt', providerName, CONST.CONFIGURE_APPROVAL_WORKFLOWS_HELP_URL)} />
</View>
),
confirmText: translate('workspace.hr.approvalModeWarningConfirm'),
cancelText: translate('common.cancel'),
}).then((result) => {
if (result?.action !== ModalActions.CONFIRM) {
return;
}
saveApprovalMode();
});
};

return (
<AccessOrNotFoundWrapper
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.CONTROL]}
policyID={policyID}
featureName={CONST.POLICY.MORE_FEATURES.IS_HR_ENABLED}
shouldBeBlocked={!isBetaEnabled(config.beta) || (!!policy && !config.isConnected(policy))}
>
<ScreenWrapper
enableEdgeToEdgeBottomSafeAreaPadding
shouldEnableMaxHeight
testID={config.testID}
>
<HeaderWithBackButton
title={config.getHeaderTitle(providerName)}
onBackButtonPress={() => Navigation.goBack()}
/>
<View style={styles.flex1}>
<Text style={[styles.textSupporting, styles.ph5, styles.mt3, styles.mb3]}>{translate('workspace.hr.approvalModeDescription', providerName)}</Text>
<SelectionList
data={approvalModeOptions}
ListItem={SingleSelectListItem}
onSelectRow={(option) => setDraftApprovalMode(option.value)}
shouldSingleExecuteRowSelect
initiallyFocusedItemKey={selectedApprovalModeKey}
alternateNumberOfSupportedLines={3}
showScrollIndicator={false}
/>
<FixedFooter style={styles.mtAuto}>
<Button
large
success
text={translate('common.save')}
onPress={confirmSaveApprovalMode}
isDisabled={isSaveDisabled}
/>
</FixedFooter>
</View>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
);
}

export default HRApprovalModePageBase;
export type {HRApprovalModeProviderConfig};
6 changes: 5 additions & 1 deletion src/pages/workspace/hr/HRProviderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ function HRProviderCard({card, policy, handleConnect}: HRProviderCardProps) {
fallbackIcon={fallbackIcon}
/>
{card.isConnected && !!approvalModeRoute && (
<OfflineWithFeedback pendingAction={card.config?.pendingFields?.approvalMode}>
<OfflineWithFeedback
pendingAction={card.config?.pendingFields?.approvalMode}
errors={card.config?.errorFields?.approvalMode}
onClose={() => clearHRConnectionErrorField(policy?.id, card.connectionName, 'approvalMode')}
>
<MenuItemWithTopDescription
description={translate('workspace.hr.approvalMode')}
title={card.approvalModeLabel}
Expand Down
Loading
Loading