Skip to content

Commit

Permalink
Merge pull request #35399 from ruben-rebelo/ts-migration/workspacerei…
Browse files Browse the repository at this point in the history
…mburse-page

[TS migration] Migrate WorkspaceReimburse Page
  • Loading branch information
tylerkaraszewski authored Mar 22, 2024
2 parents 2ff0ed2 + 1f240fd commit 107b378
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 157 deletions.
4 changes: 2 additions & 2 deletions src/components/Picker/BasePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ function BasePicker<TPickerValue>(
*/
const onValueChange = (inputValue: TPickerValue, index: number) => {
if (inputID) {
onInputChange(inputValue);
onInputChange?.(inputValue);
return;
}

onInputChange(inputValue, index);
onInputChange?.(inputValue, index);
};

const enableHighlight = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Picker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type BasePickerProps<TPickerValue> = {
shouldShowOnlyTextWhenDisabled?: boolean;

/** A callback method that is called when the value changes and it receives the selected value as an argument */
onInputChange: (value: TPickerValue, index?: number) => void;
onInputChange?: (value: TPickerValue, index?: number) => void;

/** Size of a picker component */
size?: PickerSize;
Expand Down
3 changes: 2 additions & 1 deletion src/libs/DistanceRequestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ function getDistanceMerchant(

const formattedDistance = getDistanceForDisplay(hasRoute, distanceInMeters, unit, rate, translate);
const singularDistanceUnit = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer');
const ratePerUnit = PolicyUtils.getUnitRateValue({rate}, toLocaleDigit);
const ratePerUnit = PolicyUtils.getUnitRateValue(toLocaleDigit, {rate});

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const currencySymbol = CurrencyUtils.getCurrencySymbol(currency) || `${currency} `;

Expand Down
7 changes: 3 additions & 4 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PersonalDetailsList, Policy, PolicyCategories, PolicyMembers, PolicyTagList, PolicyTags, TaxRate} from '@src/types/onyx';
import type {PolicyFeatureName} from '@src/types/onyx/Policy';
import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import Navigation from './Navigation/Navigation';

type MemberEmailsToAccountIDs = Record<string, number>;
type UnitRate = {rate: number};

/**
* Filter out the active policies, which will exclude policies with pending deletion
Expand Down Expand Up @@ -66,7 +65,7 @@ function hasCustomUnitsError(policy: OnyxEntry<Policy>): boolean {
return Object.keys(policy?.customUnits?.errors ?? {}).length > 0;
}

function getNumericValue(value: number, toLocaleDigit: (arg: string) => string): number | string {
function getNumericValue(value: number | string, toLocaleDigit: (arg: string) => string): number | string {
const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.'));
if (Number.isNaN(numValue)) {
return NaN;
Expand All @@ -82,7 +81,7 @@ function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => stri
return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length);
}

function getUnitRateValue(customUnitRate: UnitRate, toLocaleDigit: (arg: string) => string) {
function getUnitRateValue(toLocaleDigit: (arg: string) => string, customUnitRate?: Rate) {
return getRateDisplayValue((customUnitRate?.rate ?? 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET, toLocaleDigit);
}

Expand Down
2 changes: 2 additions & 0 deletions src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4623,3 +4623,5 @@ export {
setPolicyDistanceRatesEnabled,
deletePolicyDistanceRates,
};

export type {NewCustomUnit};
158 changes: 158 additions & 0 deletions src/pages/workspace/reimburse/WorkspaceRateAndUnitPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useEffect} from 'react';
import {Keyboard, View} from 'react-native';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormOnyxValues} from '@components/Form/types';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import Picker from '@components/Picker';
import TextInput from '@components/TextInput';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import getPermittedDecimalSeparator from '@libs/getPermittedDecimalSeparator';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import * as NumberUtils from '@libs/NumberUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
import * as BankAccounts from '@userActions/BankAccounts';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Unit} from '@src/types/onyx/Policy';

type WorkspaceRateAndUnitPageProps = WithPolicyProps & StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.RATE_AND_UNIT>;

type ValidationError = {rate?: TranslationPaths | undefined};

function WorkspaceRateAndUnitPage({policy, route}: WorkspaceRateAndUnitPageProps) {
const {translate, toLocaleDigit} = useLocalize();
const styles = useThemeStyles();

useEffect(() => {
if ((policy?.customUnits ?? []).length !== 0) {
return;
}

BankAccounts.setReimbursementAccountLoading(true);
Policy.openWorkspaceReimburseView(policy?.id ?? '');
}, [policy?.customUnits, policy?.id]);

const unitItems = [
{label: translate('common.kilometers'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS},
{label: translate('common.miles'), value: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES},
];

const saveUnitAndRate = (unit: Unit, rate: string) => {
const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((customUnit) => customUnit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
if (!distanceCustomUnit) {
return;
}
const currentCustomUnitRate = Object.values(distanceCustomUnit?.rates ?? {}).find((r) => r.name === CONST.CUSTOM_UNITS.DEFAULT_RATE);
const unitID = distanceCustomUnit.customUnitID ?? '';
const unitName = distanceCustomUnit.name ?? '';
const rateNumValue = PolicyUtils.getNumericValue(rate, toLocaleDigit);

const newCustomUnit: Policy.NewCustomUnit = {
customUnitID: unitID,
name: unitName,
attributes: {unit},
rates: {
...currentCustomUnitRate,
rate: Number(rateNumValue) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET,
},
};

Policy.updateWorkspaceCustomUnitAndRate(policy?.id ?? '', distanceCustomUnit, newCustomUnit, policy?.lastModified);
};

const submit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM>) => {
saveUnitAndRate(values.unit as Unit, values.rate);
Keyboard.dismiss();
Navigation.goBack(ROUTES.WORKSPACE_REIMBURSE.getRoute(policy?.id ?? ''));
};

const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM>): ValidationError => {
const errors: ValidationError = {};
const decimalSeparator = toLocaleDigit('.');
const outputCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD;
// Allow one more decimal place for accuracy
const rateValueRegex = RegExp(String.raw`^-?\d{0,8}([${getPermittedDecimalSeparator(decimalSeparator)}]\d{1,${CurrencyUtils.getCurrencyDecimals(outputCurrency) + 1}})?$`, 'i');
if (!rateValueRegex.test(values.rate) || values.rate === '') {
errors.rate = 'workspace.reimburse.invalidRateError';
} else if (NumberUtils.parseFloatAnyLocale(values.rate) <= 0) {
errors.rate = 'workspace.reimburse.lowRateError';
}
return errors;
};

const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE);

return (
<WorkspacePageWithSections
headerText={translate('workspace.reimburse.trackDistance')}
route={route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE}
shouldSkipVBBACall
backButtonRoute={ROUTES.WORKSPACE_REIMBURSE.getRoute(policy?.id ?? '')}
shouldShowLoading={false}
>
{() => (
<FormProvider
formID={ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM}
submitButtonText={translate('common.save')}
style={[styles.mh5, styles.flexGrow1]}
scrollContextEnabled
validate={validate}
onSubmit={submit}
enabledWhenOffline
>
<OfflineWithFeedback
errors={{
...(distanceCustomUnit?.errors ?? {}),
...(distanceCustomRate?.errors ?? {}),
}}
pendingAction={distanceCustomUnit?.pendingAction ?? distanceCustomRate?.pendingAction}
onClose={() => Policy.clearCustomUnitErrors(policy?.id ?? '', distanceCustomUnit?.customUnitID ?? '', distanceCustomRate?.customUnitRateID ?? '')}
>
<InputWrapper
InputComponent={TextInput}
role={CONST.ROLE.PRESENTATION}
inputID="rate"
containerStyles={styles.mt4}
defaultValue={PolicyUtils.getUnitRateValue(toLocaleDigit, distanceCustomRate)}
label={translate('workspace.reimburse.trackDistanceRate')}
aria-label={translate('workspace.reimburse.trackDistanceRate')}
placeholder={policy?.outputCurrency ?? CONST.CURRENCY.USD}
autoCompleteType="off"
autoCorrect={false}
inputMode={CONST.INPUT_MODE.DECIMAL}
maxLength={12}
/>

<View style={styles.mt4}>
<InputWrapper
InputComponent={Picker}
inputID="unit"
label={translate('workspace.reimburse.trackDistanceUnit')}
items={unitItems}
defaultValue={distanceCustomUnit?.attributes.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}
/>
</View>
</OfflineWithFeedback>
</FormProvider>
)}
</WorkspacePageWithSections>
);
}

WorkspaceRateAndUnitPage.displayName = 'WorkspaceRateAndUnitPage';

export default withPolicy(WorkspaceRateAndUnitPage);
43 changes: 0 additions & 43 deletions src/pages/workspace/reimburse/WorkspaceReimbursePage.js

This file was deleted.

33 changes: 33 additions & 0 deletions src/pages/workspace/reimburse/WorkspaceReimbursePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import useLocalize from '@hooks/useLocalize';
import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicy from '@pages/workspace/withPolicy';
import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
import CONST from '@src/CONST';
import type SCREENS from '@src/SCREENS';
import WorkspaceReimburseView from './WorkspaceReimburseView';

type WorkspaceReimbursePageProps = WithPolicyProps & StackScreenProps<WorkspacesCentralPaneNavigatorParamList, typeof SCREENS.WORKSPACE.REIMBURSE>;

function WorkspaceReimbursePage({route, policy}: WorkspaceReimbursePageProps) {
const {translate} = useLocalize();

return (
<WorkspacePageWithSections
shouldUseScrollView
headerText={translate('workspace.common.reimburse')}
route={route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_REIMBURSE}
shouldSkipVBBACall
shouldShowLoading={false}
>
{() => <WorkspaceReimburseView policy={policy} />}
</WorkspacePageWithSections>
);
}

WorkspaceReimbursePage.displayName = 'WorkspaceReimbursePage';

export default withPolicy(WorkspaceReimbursePage);
Loading

0 comments on commit 107b378

Please sign in to comment.