Skip to content
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

[TS migration] Migrate WorkspaceReimburse Page #35399

Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4feb092
[TS Migration] Migrate WorkspaceCard to Typescript
ruben-rebelo Jan 23, 2024
b974629
[TS migration] Migrate WorkspaceReimburse page
ruben-rebelo Feb 1, 2024
f51dfad
Revert "[TS Migration] Migrate WorkspaceCard to Typescript"
ruben-rebelo Feb 1, 2024
3a7ad1e
[TS migration][WorkspaceReimburse] Missing imports
ruben-rebelo Feb 1, 2024
d11c98a
[TS migration][WorkspaceReimburse] Code improvements
ruben-rebelo Feb 2, 2024
794d552
Merge branch 'main' into ts-migration/workspacereimburse-page
ruben-rebelo Feb 2, 2024
e66017b
Merge branch 'main' into ts-migration/workspacereimburse-page
ruben-rebelo Feb 8, 2024
34135a2
[TS migration][WorkspaceReimburse] Code improvements
ruben-rebelo Feb 8, 2024
7cd655d
Merge branch 'main' into ts-migration/workspacereimburse-page
ruben-rebelo Feb 22, 2024
458be5e
[TS migration][WorkspaceReimburse] Ts issues fix
ruben-rebelo Feb 27, 2024
aafd0e5
[TS migration][WorkspaceReimburse] Lint
ruben-rebelo Feb 27, 2024
a04d571
Merge branch 'main' into ts-migration/workspacereimburse-page
ruben-rebelo Feb 28, 2024
c403cfa
Merge branch 'main' into ts-migration/workspacereimburse-page
ruben-rebelo Mar 12, 2024
9cda701
Merge branch 'main' into ts-migration/workspacereimburse-page
ruben-rebelo Mar 18, 2024
026d8e7
Merge branch 'main' into ts-migration/workspacereimburse-page
ruben-rebelo Mar 21, 2024
64cfc7e
[TS migration][WorkspaceReimburse] TS issue fix
ruben-rebelo Mar 21, 2024
1f240fd
Merge branch 'main' into ts-migration/workspacereimburse-page
ruben-rebelo Mar 22, 2024
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
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
6 changes: 3 additions & 3 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PersonalDetailsList, Policy, PolicyMembers, PolicyTagList, PolicyTags} from '@src/types/onyx';
import type {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 @@ -51,7 +51,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 @@ -67,7 +67,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 @@ -3303,3 +3303,5 @@ export {
enablePolicyWorkflows,
openPolicyDistanceRatesPage,
};

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 {CentralPaneNavigatorParamList} 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<CentralPaneNavigatorParamList, typeof SCREENS.WORKSPACE.REIMBURSE>;

function WorkspaceReimbursePage({route, policy}: WorkspaceReimbursePageProps) {
const {translate} = useLocalize();
ruben-rebelo marked this conversation as resolved.
Show resolved Hide resolved

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
Loading