-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Expense rules phase 2 #79659
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Expense rules phase 2 #79659
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
1c6f6fd
Expense rules phase 2
situchan 11c3849
Merge branch 'main' of https://github.com/situchan/Expensify into fea…
situchan 913bcbe
tax support, hash key for duplicated merchantToMatch
situchan defe53a
edit rule suport
situchan fe1a35c
Merge branch 'main' of https://github.com/situchan/Expensify into fea…
situchan d44315b
edit support fot category, tag, tax rate
situchan e93c87c
Merge branch 'main' of https://github.com/situchan/Expensify into fea…
situchan 4c49b61
minor fix
situchan c0fc0f0
add translations
situchan 26166d9
address feedback
situchan fc816ae
address feedback
situchan 56d2c6e
simplify form error logic
situchan a6388a0
fix exhaustive-deps lint
situchan 3fa0316
hide Expense Rules section on Account settings
situchan b30e6e4
auto save when select option
situchan 693d23b
fix typo
situchan 30b2b7f
Merge branch 'main' of https://github.com/situchan/Expensify into fea…
situchan 6beea65
address feedback
situchan 677eb6d
Merge branch 'main' of https://github.com/situchan/Expensify into fea…
situchan 2a47ac9
show not found page when rule changed
situchan a4b1f89
handle duplicated rules
situchan 041375c
Merge branch 'main' of https://github.com/situchan/Expensify into fea…
situchan 235b28e
add unit tests
situchan eb7f1d6
update section title style
situchan 9f35ddd
fix test
situchan b4ed717
remove "Then apply these updates:"
situchan 3c79aaa
fix not found page briefly shows
situchan a0eb317
Merge branch 'main' of https://github.com/situchan/Expensify into fea…
situchan 7215c1a
Revert "remove "Then apply these updates:""
situchan 521d175
update copies
situchan 7e4efdb
update translations
situchan f3b0dda
minor update
situchan 5a905f4
Merge branch 'main' of https://github.com/situchan/Expensify into fea…
situchan 4decaf6
remove isLoading
situchan cf826a9
handle encoding of special characters
situchan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import React from 'react'; | ||
| import {View} from 'react-native'; | ||
| import type {ValueOf} from 'type-fest'; | ||
| import HeaderWithBackButton from '@components/HeaderWithBackButton'; | ||
| import ScreenWrapper from '@components/ScreenWrapper'; | ||
| import SelectionList from '@components/SelectionList'; | ||
| import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem'; | ||
| import type {ListItem} from '@components/SelectionList/ListItem/types'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import useOnyx from '@hooks/useOnyx'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {updateDraftRule} from '@libs/actions/User'; | ||
| import Navigation from '@libs/Navigation/Navigation'; | ||
| import CONST from '@src/CONST'; | ||
| import type {TranslationPaths} from '@src/languages/types'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import ROUTES from '@src/ROUTES'; | ||
| import type {InputID} from '@src/types/form/ExpenseRuleForm'; | ||
| import RuleNotFoundPageWrapper from './RuleNotFoundPageWrapper'; | ||
|
|
||
| type BooleanFilterItem = ListItem & { | ||
| value: ValueOf<typeof CONST.SEARCH.BOOLEAN>; | ||
| }; | ||
|
|
||
| type RuleBooleanBasePageProps = { | ||
| /** The key from boolean-based InputID */ | ||
| fieldID: InputID; | ||
|
|
||
| /** The translation key for the page title */ | ||
| titleKey: TranslationPaths; | ||
|
|
||
| /** The rule identifier */ | ||
| hash?: string; | ||
| }; | ||
|
|
||
| const booleanValues = Object.values(CONST.SEARCH.BOOLEAN); | ||
|
|
||
| function RuleBooleanBasePage({fieldID, titleKey, hash}: RuleBooleanBasePageProps) { | ||
| const {translate} = useLocalize(); | ||
| const [form] = useOnyx(ONYXKEYS.FORMS.EXPENSE_RULE_FORM, {canBeMissing: true}); | ||
| const styles = useThemeStyles(); | ||
|
|
||
| const selectedItem = | ||
| booleanValues.find((value) => { | ||
| if (!form?.[fieldID]) { | ||
| return false; | ||
| } | ||
| const booleanValue = form[fieldID] === 'true' ? CONST.SEARCH.BOOLEAN.YES : CONST.SEARCH.BOOLEAN.NO; | ||
| return booleanValue === value; | ||
| }) ?? null; | ||
|
|
||
| const items = booleanValues.map((value) => ({ | ||
| value, | ||
| keyForList: value, | ||
| text: translate(`common.${value}`), | ||
| isSelected: selectedItem === value, | ||
| })); | ||
|
|
||
| const goBack = () => { | ||
| Navigation.goBack(hash ? ROUTES.SETTINGS_RULES_EDIT.getRoute(hash) : ROUTES.SETTINGS_RULES_ADD.getRoute()); | ||
| }; | ||
|
|
||
| const onSelectItem = (selectedValue: BooleanFilterItem) => { | ||
| const newValue = selectedValue.isSelected ? null : selectedValue.value; | ||
| let value = ''; | ||
| if (newValue === CONST.SEARCH.BOOLEAN.YES) { | ||
| value = 'true'; | ||
| } else if (newValue === CONST.SEARCH.BOOLEAN.NO) { | ||
| value = 'false'; | ||
| } | ||
| updateDraftRule({[fieldID]: value}); | ||
| goBack(); | ||
| }; | ||
|
|
||
| return ( | ||
| <RuleNotFoundPageWrapper hash={hash}> | ||
| <ScreenWrapper | ||
| testID="RuleBooleanBasePage" | ||
| shouldShowOfflineIndicatorInWideScreen | ||
| offlineIndicatorStyle={styles.mtAuto} | ||
| includeSafeAreaPaddingBottom | ||
| shouldEnableMaxHeight | ||
| > | ||
| <HeaderWithBackButton | ||
| title={translate(titleKey)} | ||
| onBackButtonPress={goBack} | ||
| /> | ||
| <View style={[styles.flex1]}> | ||
| <SelectionList | ||
| shouldSingleExecuteRowSelect | ||
| data={items} | ||
| ListItem={SingleSelectListItem} | ||
| onSelectRow={onSelectItem} | ||
| /> | ||
| </View> | ||
| </ScreenWrapper> | ||
| </RuleNotFoundPageWrapper> | ||
| ); | ||
| } | ||
|
|
||
| export default RuleBooleanBasePage; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* eslint-disable rulesdir/no-negated-variables */ | ||
| import React from 'react'; | ||
| import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; | ||
| import useOnyx from '@hooks/useOnyx'; | ||
| import {getKeyForRule} from '@libs/ExpenseRuleUtils'; | ||
| import Navigation from '@navigation/Navigation'; | ||
| import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import ROUTES from '@src/ROUTES'; | ||
| import type {ExpenseRule} from '@src/types/onyx'; | ||
| import getEmptyArray from '@src/types/utils/getEmptyArray'; | ||
| import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; | ||
|
|
||
| type RuleNotFoundPageWrapperProps = { | ||
| children: React.ReactNode; | ||
| hash?: string; | ||
| shouldPreventShow?: boolean; | ||
| }; | ||
|
|
||
| function RuleNotFoundPageWrapper({children, hash, shouldPreventShow}: RuleNotFoundPageWrapperProps) { | ||
| const [expenseRules = getEmptyArray<ExpenseRule>(), rulesMetadata] = useOnyx(ONYXKEYS.NVP_EXPENSE_RULES, {canBeMissing: true}); | ||
| const doesRuleExist = !!hash && expenseRules.some((rule) => getKeyForRule(rule) === hash); | ||
|
|
||
| const shouldShowFullScreenLoadingIndicator = isLoadingOnyxValue(rulesMetadata); | ||
| const shouldShowNotFoundPage = !!hash && !doesRuleExist; | ||
|
|
||
| if (shouldShowFullScreenLoadingIndicator) { | ||
| return <FullscreenLoadingIndicator />; | ||
| } | ||
|
|
||
| if (!shouldPreventShow && shouldShowNotFoundPage) { | ||
| return ( | ||
| <NotFoundPage | ||
| onBackButtonPress={() => { | ||
| Navigation.goBack(ROUTES.SETTINGS_RULES); | ||
| }} | ||
| shouldShowBackButton | ||
| shouldShowOfflineIndicator={false} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| return children; | ||
| } | ||
|
|
||
| export default RuleNotFoundPageWrapper; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import React from 'react'; | ||
| import type {FormOnyxValues} from '@components/Form/types'; | ||
| import HeaderWithBackButton from '@components/HeaderWithBackButton'; | ||
| import ScreenWrapper from '@components/ScreenWrapper'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {updateDraftRule} from '@libs/actions/User'; | ||
| import Navigation from '@libs/Navigation/Navigation'; | ||
| import CONST from '@src/CONST'; | ||
| import type {TranslationPaths} from '@src/languages/types'; | ||
| import type ONYXKEYS from '@src/ONYXKEYS'; | ||
| import ROUTES from '@src/ROUTES'; | ||
| import type {InputID} from '@src/types/form/ExpenseRuleForm'; | ||
| import RuleNotFoundPageWrapper from './RuleNotFoundPageWrapper'; | ||
| import TextBase from './TextBase'; | ||
|
|
||
| // Text-based field IDs that accept string input | ||
| type RuleTextBaseProps = { | ||
| /** The key from text-based InputID */ | ||
| fieldID: InputID; | ||
|
|
||
| /** The translation key for the page title and input label if labelKey is missing */ | ||
| titleKey: TranslationPaths; | ||
|
|
||
| /** The translation key for the input label */ | ||
| labelKey?: TranslationPaths; | ||
|
|
||
| /** Test ID for the screen wrapper */ | ||
| testID: string; | ||
|
|
||
| /** The translation key for the hint text to display below the TextInput */ | ||
| hintKey?: TranslationPaths; | ||
|
|
||
| /** Whether this field is required */ | ||
| isRequired?: boolean; | ||
|
|
||
| /** The character limit for the input */ | ||
| characterLimit?: number; | ||
|
|
||
| /** The rule identifier */ | ||
| hash?: string; | ||
| }; | ||
|
|
||
| function RuleTextBase({fieldID, hintKey, isRequired, titleKey, labelKey, testID, hash, characterLimit = CONST.MERCHANT_NAME_MAX_BYTES}: RuleTextBaseProps) { | ||
|
situchan marked this conversation as resolved.
|
||
| const {translate} = useLocalize(); | ||
| const styles = useThemeStyles(); | ||
|
|
||
| const goBack = () => { | ||
| Navigation.goBack(hash ? ROUTES.SETTINGS_RULES_EDIT.getRoute(hash) : ROUTES.SETTINGS_RULES_ADD.getRoute()); | ||
| }; | ||
|
|
||
| const onSave = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.EXPENSE_RULE_FORM>) => { | ||
| updateDraftRule(values); | ||
| goBack(); | ||
| }; | ||
|
|
||
| return ( | ||
| <RuleNotFoundPageWrapper hash={hash}> | ||
| <ScreenWrapper | ||
| testID={testID} | ||
| shouldShowOfflineIndicatorInWideScreen | ||
| offlineIndicatorStyle={styles.mtAuto} | ||
| includeSafeAreaPaddingBottom | ||
| shouldEnableMaxHeight | ||
| > | ||
| <HeaderWithBackButton | ||
| title={translate(titleKey)} | ||
| onBackButtonPress={goBack} | ||
| /> | ||
| <TextBase | ||
| fieldID={fieldID} | ||
| hint={hintKey ? translate(hintKey) : undefined} | ||
| isRequired={isRequired} | ||
| label={translate(labelKey ?? titleKey)} | ||
| onSubmit={onSave} | ||
| title={translate(titleKey)} | ||
| characterLimit={characterLimit} | ||
| /> | ||
| </ScreenWrapper> | ||
| </RuleNotFoundPageWrapper> | ||
| ); | ||
| } | ||
|
|
||
| export default RuleTextBase; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.