Skip to content

Commit 984c986

Browse files
authored
Merge pull request Expensify#78350 from abzokhattab/workflow-approval-limit-followup
Bring approval overLimitForwardsTo configuration V2
2 parents f903a08 + ae3c936 commit 984c986

38 files changed

Lines changed: 1279 additions & 123 deletions

src/ROUTES.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,9 +1754,19 @@ const ROUTES = {
17541754
},
17551755
WORKSPACE_WORKFLOWS_APPROVALS_APPROVER: {
17561756
route: 'workspaces/:policyID/workflows/approvals/approver',
1757-
getRoute: (policyID: string, approverIndex: number, backTo?: string) =>
1758-
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
1759-
getUrlWithBackToParam(`workspaces/${policyID}/workflows/approvals/approver?approverIndex=${approverIndex}` as const, backTo),
1757+
getRoute: (policyID: string, approverIndex: number) => `workspaces/${policyID}/workflows/approvals/approver?approverIndex=${approverIndex}` as const,
1758+
},
1759+
WORKSPACE_WORKFLOWS_APPROVALS_APPROVER_CHANGE: {
1760+
route: 'workspaces/:policyID/workflows/approvals/approver-change',
1761+
getRoute: (policyID: string, approverIndex: number) => `workspaces/${policyID}/workflows/approvals/approver-change?approverIndex=${approverIndex}` as const,
1762+
},
1763+
WORKSPACE_WORKFLOWS_APPROVALS_APPROVAL_LIMIT: {
1764+
route: 'workspaces/:policyID/workflows/approvals/approval-limit',
1765+
getRoute: (policyID: string, approverIndex: number) => `workspaces/${policyID}/workflows/approvals/approval-limit?approverIndex=${approverIndex}` as const,
1766+
},
1767+
WORKSPACE_WORKFLOWS_APPROVALS_OVER_LIMIT_APPROVER: {
1768+
route: 'workspaces/:policyID/workflows/approvals/over-limit-approver',
1769+
getRoute: (policyID: string, approverIndex: number) => `workspaces/${policyID}/workflows/approvals/over-limit-approver?approverIndex=${approverIndex}` as const,
17601770
},
17611771
WORKSPACE_WORKFLOWS_PAYER: {
17621772
route: 'workspaces/:policyID/workflows/payer',

src/SCREENS.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,9 @@ const SCREENS = {
671671
WORKFLOWS_APPROVALS_EDIT: 'Workspace_Approvals_Edit',
672672
WORKFLOWS_APPROVALS_EXPENSES_FROM: 'Workspace_Workflows_Approvals_Expenses_From',
673673
WORKFLOWS_APPROVALS_APPROVER: 'Workspace_Workflows_Approvals_Approver',
674+
WORKFLOWS_APPROVALS_APPROVER_CHANGE: 'Workspace_Workflows_Approvals_Approver_Change',
675+
WORKFLOWS_APPROVALS_APPROVAL_LIMIT: 'Workspace_Workflows_Approvals_Approval_Limit',
676+
WORKFLOWS_APPROVALS_OVER_LIMIT_APPROVER: 'Workspace_Workflows_Approvals_Over_Limit_Approver',
674677
WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency',
675678
WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET: 'Workspace_Workflows_Auto_Reporting_Monthly_Offset',
676679
WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT: 'Workspace_Workflows_Connect_Existing_Bank_Account',

src/components/AmountForm.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
55
import {getCurrencyDecimals, getLocalizedCurrencySymbol} from '@libs/CurrencyUtils';
66
import CONST from '@src/CONST';
77
import NumberWithSymbolForm from './NumberWithSymbolForm';
8+
import type {NumberWithSymbolFormRef} from './NumberWithSymbolForm';
89
import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types';
910

1011
type AmountFormProps = {
@@ -44,8 +45,17 @@ type AmountFormProps = {
4445
/** Whether to hide the currency symbol */
4546
hideCurrencySymbol?: boolean;
4647

48+
/** Whether the input should be disabled */
49+
disabled?: boolean;
50+
4751
/** Reference to the outer element */
4852
ref?: ForwardedRef<BaseTextInputRef>;
53+
54+
/** Reference to the number form for imperative updates */
55+
numberFormRef?: ForwardedRef<NumberWithSymbolFormRef>;
56+
57+
/** Callback when the user presses the submit key (Enter) */
58+
onSubmitEditing?: () => void;
4959
} & Pick<BaseTextInputProps, 'autoFocus' | 'autoGrowExtraSpace' | 'autoGrowMarginSide'>;
5060

5161
/**
@@ -63,10 +73,13 @@ function AmountForm({
6373
label,
6474
decimals: decimalsProp,
6575
hideCurrencySymbol = false,
76+
disabled = false,
6677
autoFocus,
6778
autoGrowExtraSpace,
6879
autoGrowMarginSide,
80+
onSubmitEditing,
6981
ref,
82+
numberFormRef,
7083
}: AmountFormProps) {
7184
const {preferredLocale} = useLocalize();
7285
const styles = useThemeStyles();
@@ -89,6 +102,7 @@ function AmountForm({
89102
ref.current = newRef;
90103
}
91104
}}
105+
numberFormRef={numberFormRef}
92106
symbol={getLocalizedCurrencySymbol(preferredLocale, currency) ?? ''}
93107
symbolPosition={CONST.TEXT_INPUT_SYMBOL_POSITION.PREFIX}
94108
isSymbolPressable={isCurrencyPressable}
@@ -101,9 +115,11 @@ function AmountForm({
101115
autoFocus={autoFocus}
102116
autoGrowExtraSpace={autoGrowExtraSpace}
103117
autoGrowMarginSide={autoGrowMarginSide}
118+
onSubmitEditing={onSubmitEditing}
119+
disabled={disabled}
104120
/>
105121
);
106122
}
107123

108124
export default AmountForm;
109-
export type {AmountFormProps};
125+
export type {AmountFormProps, NumberWithSymbolFormRef};

src/components/ApprovalWorkflowSection.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import {Str} from 'expensify-common';
2-
import React, {useCallback, useMemo} from 'react';
2+
import React from 'react';
33
import {View} from 'react-native';
44
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
55
import useLocalize from '@hooks/useLocalize';
6+
import useOnyx from '@hooks/useOnyx';
67
import useResponsiveLayout from '@hooks/useResponsiveLayout';
78
import useTheme from '@hooks/useTheme';
89
import useThemeStyles from '@hooks/useThemeStyles';
910
import {sortAlphabetically} from '@libs/OptionsListUtils';
11+
import {getApprovalLimitDescription} from '@libs/WorkflowUtils';
12+
import CONST from '@src/CONST';
13+
import ONYXKEYS from '@src/ONYXKEYS';
14+
import {personalDetailsByEmailSelector} from '@src/selectors/PersonalDetails';
1015
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
1116
import Icon from './Icon';
1217
import MenuItem from './MenuItem';
@@ -19,30 +24,30 @@ type ApprovalWorkflowSectionProps = {
1924

2025
/** A function that is called when the section is pressed */
2126
onPress: () => void;
27+
28+
/** Currency used for formatting approval limits */
29+
currency?: string;
2230
};
2331

24-
function ApprovalWorkflowSection({approvalWorkflow, onPress}: ApprovalWorkflowSectionProps) {
32+
function ApprovalWorkflowSection({approvalWorkflow, onPress, currency = CONST.CURRENCY.USD}: ApprovalWorkflowSectionProps) {
2533
const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'Lightbulb', 'Users', 'UserCheck']);
2634
const styles = useThemeStyles();
2735
const theme = useTheme();
2836
const {translate, toLocaleOrdinal, localeCompare} = useLocalize();
2937
const {shouldUseNarrowLayout} = useResponsiveLayout();
38+
const [personalDetailsByEmail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {
39+
canBeMissing: true,
40+
selector: personalDetailsByEmailSelector,
41+
});
3042

31-
const approverTitle = useCallback(
32-
(index: number) =>
33-
approvalWorkflow.approvers.length > 1 ? `${toLocaleOrdinal(index + 1, true)} ${translate('workflowsPage.approver').toLowerCase()}` : `${translate('workflowsPage.approver')}`,
34-
[approvalWorkflow.approvers.length, toLocaleOrdinal, translate],
35-
);
36-
37-
const members = useMemo(() => {
38-
if (approvalWorkflow.isDefault) {
39-
return translate('workspace.common.everyone');
40-
}
43+
const approverTitle = (index: number) =>
44+
approvalWorkflow.approvers.length > 1 ? `${toLocaleOrdinal(index + 1, true)} ${translate('workflowsPage.approver').toLowerCase()}` : `${translate('workflowsPage.approver')}`;
4145

42-
return sortAlphabetically(approvalWorkflow.members, 'displayName', localeCompare)
43-
.map((m) => Str.removeSMSDomain(m.displayName))
44-
.join(', ');
45-
}, [approvalWorkflow.isDefault, approvalWorkflow.members, translate, localeCompare]);
46+
const members = approvalWorkflow.isDefault
47+
? translate('workspace.common.everyone')
48+
: sortAlphabetically(approvalWorkflow.members, 'displayName', localeCompare)
49+
.map((m) => Str.removeSMSDomain(m.displayName))
50+
.join(', ');
4651

4752
return (
4853
<PressableWithoutFeedback
@@ -100,6 +105,8 @@ function ApprovalWorkflowSection({approvalWorkflow, onPress}: ApprovalWorkflowSe
100105
iconFill={theme.icon}
101106
onPress={onPress}
102107
shouldRemoveBackground
108+
helperText={getApprovalLimitDescription({approver, currency, translate, personalDetailsByEmail})}
109+
helperTextStyle={styles.workflowApprovalLimitText}
103110
/>
104111
</View>
105112
))}

src/components/ApproverSelectionList.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ function ApproverSelectionList({
167167
addBottomSafeAreaPadding
168168
shouldUpdateFocusedIndex={shouldUpdateFocusedIndex}
169169
showScrollIndicator
170+
isRowMultilineSupported
170171
/>
171172
</FullPageNotFoundView>
172173
</ScreenWrapper>

src/components/Icon/chunks/expensify-icons.chunk.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ import Phone from '@assets/images/phone.svg';
169169
import Pin from '@assets/images/pin.svg';
170170
import Plane from '@assets/images/plane.svg';
171171
import Play from '@assets/images/play.svg';
172+
import PlusMinus from '@assets/images/plus-minus.svg';
172173
import Plus from '@assets/images/plus.svg';
173174
import Printer from '@assets/images/printer.svg';
174175
import Profile from '@assets/images/profile.svg';
@@ -371,6 +372,7 @@ const Expensicons = {
371372
Pin,
372373
Play,
373374
Plus,
375+
PlusMinus,
374376
Printer,
375377
Profile,
376378
QBOSquare,

src/components/NumberWithSymbolForm.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import CONST from '@src/CONST';
2626
import BigNumberPad from './BigNumberPad';
2727
import Button from './Button';
2828
import FormHelpMessage from './FormHelpMessage';
29-
import * as Expensicons from './Icon/Expensicons';
3029
import ScrollView from './ScrollView';
3130
import TextInput from './TextInput';
3231
import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused';
@@ -85,6 +84,9 @@ type NumberWithSymbolFormProps = {
8584

8685
/** Reference to the outer element */
8786
ref?: ForwardedRef<BaseTextInputRef>;
87+
88+
/** Callback when the user presses the submit key (Enter) */
89+
onSubmitEditing?: () => void;
8890
} & Omit<TextInputWithSymbolProps, 'formattedAmount' | 'onAmountChange' | 'placeholder' | 'onSelectionChange' | 'onKeyPress' | 'onMouseDown' | 'onMouseUp'>;
8991

9092
type NumberWithSymbolFormRef = {
@@ -146,9 +148,10 @@ function NumberWithSymbolForm({
146148
clearNegative,
147149
ref,
148150
disabled,
151+
onSubmitEditing,
149152
...props
150153
}: NumberWithSymbolFormProps) {
151-
const icons = useMemoizedLazyExpensifyIcons(['DownArrow']);
154+
const icons = useMemoizedLazyExpensifyIcons(['DownArrow', 'PlusMinus']);
152155
const styles = useThemeStyles();
153156
const {toLocaleDigit, numberFormat, translate} = useLocalize();
154157

@@ -388,6 +391,7 @@ function NumberWithSymbolForm({
388391
autoFocus={props.autoFocus}
389392
autoGrowExtraSpace={props.autoGrowExtraSpace}
390393
autoGrowMarginSide={props.autoGrowMarginSide}
394+
onSubmitEditing={onSubmitEditing}
391395
/>
392396
);
393397
}
@@ -510,7 +514,7 @@ function NumberWithSymbolForm({
510514
<Button
511515
shouldShowRightIcon
512516
small
513-
iconRight={Expensicons.PlusMinus}
517+
iconRight={icons.PlusMinus}
514518
onPress={toggleNegative}
515519
style={styles.minWidth18}
516520
isContentCentered

src/components/WorkspaceMembersSelectionList.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function WorkspaceMembersSelectionList({policyID, selectedApprover, setApprover}
5151
.map((employee): SelectionListApprover | null => {
5252
const email = employee.email;
5353

54-
if (!email) {
54+
if (!email || employee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
5555
return null;
5656
}
5757

@@ -110,6 +110,7 @@ function WorkspaceMembersSelectionList({policyID, selectedApprover, setApprover}
110110
disableMaintainingScrollPosition
111111
addBottomSafeAreaPadding
112112
showScrollIndicator
113+
isRowMultilineSupported
113114
/>
114115
);
115116
}

src/languages/de.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2299,7 +2299,25 @@ ${amount} für ${merchant} – ${date}`,
22992299
},
23002300
workflowsApproverPage: {
23012301
genericErrorMessage: 'Der Genehmiger konnte nicht geändert werden. Bitte versuche es erneut oder kontaktiere den Support.',
2302-
header: 'Zur Genehmigung an dieses Mitglied senden:',
2302+
title: 'Zur Genehmigung an dieses Mitglied senden:',
2303+
description: 'Diese Person wird die Ausgaben genehmigen.',
2304+
},
2305+
workflowsApprovalLimitPage: {
2306+
title: 'Genehmiger',
2307+
header: '(Optional) Möchten Sie ein Genehmigungslimit hinzufügen?',
2308+
description: ({approverName}: {approverName: string}) =>
2309+
approverName
2310+
? `Fügen Sie einen weiteren Genehmiger hinzu, wenn <strong>${approverName}</strong> Genehmiger ist und der Bericht den folgenden Betrag überschreitet:`
2311+
: 'Fügen Sie einen weiteren Genehmiger hinzu, wenn der Bericht den folgenden Betrag überschreitet:',
2312+
reportAmountLabel: 'Berichtsbetrag',
2313+
additionalApproverLabel: 'Zusätzlicher Genehmiger',
2314+
skip: 'Überspringen',
2315+
next: 'Weiter',
2316+
removeLimit: 'Limit entfernen',
2317+
enterAmountError: 'Bitte geben Sie einen gültigen Betrag ein',
2318+
enterApproverError: 'Ein Genehmiger ist erforderlich, wenn Sie ein Berichtslimit festlegen',
2319+
enterBothError: 'Geben Sie einen Berichtsbetrag und einen zusätzlichen Genehmiger ein',
2320+
forwardLimitDescription: ({approvalLimit, approverName}: {approvalLimit: string; approverName: string}) => `Berichte über ${approvalLimit} werden an ${approverName} weitergeleitet`,
23032321
},
23042322
workflowsPayerPage: {
23052323
title: 'Autorisierter Zahler',

src/languages/en.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2252,7 +2252,25 @@ const translations = {
22522252
},
22532253
workflowsApproverPage: {
22542254
genericErrorMessage: "The approver couldn't be changed. Please try again or contact support.",
2255-
header: 'Send to this member for approval:',
2255+
title: 'Set approver',
2256+
description: 'This person will approve the expenses.',
2257+
},
2258+
workflowsApprovalLimitPage: {
2259+
title: 'Approver',
2260+
header: '(Optional) Want to add an approval limit?',
2261+
description: ({approverName}: {approverName: string}) =>
2262+
approverName
2263+
? `Add another approver when <strong>${approverName}</strong> is approver and report exceeds the amount below:`
2264+
: 'Add another approver when a report exceeds the amount below:',
2265+
reportAmountLabel: 'Report amount',
2266+
additionalApproverLabel: 'Additional approver',
2267+
skip: 'Skip',
2268+
next: 'Next',
2269+
removeLimit: 'Remove limit',
2270+
enterAmountError: 'Please enter a valid amount',
2271+
enterApproverError: 'Approver is required when you set a report limit',
2272+
enterBothError: 'Enter a report amount and additional approver',
2273+
forwardLimitDescription: ({approvalLimit, approverName}: {approvalLimit: string; approverName: string}) => `Reports above ${approvalLimit} forward to ${approverName}`,
22562274
},
22572275
workflowsPayerPage: {
22582276
title: 'Authorized payer',

0 commit comments

Comments
 (0)