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

Migrating workspace reimburse view #22172

Closed
Closed
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
247 changes: 125 additions & 122 deletions src/pages/workspace/reimburse/WorkspaceReimburseView.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {useState, useEffect, useCallback} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
Expand All @@ -19,6 +19,7 @@ import * as Policy from '../../../libs/actions/Policy';
import CONST from '../../../CONST';
import ROUTES from '../../../ROUTES';
import ONYXKEYS from '../../../ONYXKEYS';
import usePrevious from '../../../hooks/usePrevious';
import * as ReimbursementAccountProps from '../../ReimbursementAccount/reimbursementAccountPropTypes';
import {withNetwork} from '../../../components/OnyxProvider';
import networkPropTypes from '../../../components/networkPropTypes';
Expand Down Expand Up @@ -64,140 +65,142 @@ const defaultProps = {
reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps,
};

class WorkspaceReimburseView extends React.Component {
constructor(props) {
super(props);
this.state = {
currentRatePerUnit: this.getCurrentRatePerUnitLabel(),
};
}

componentDidMount() {
this.fetchData();
}

componentDidUpdate(prevProps) {
if (prevProps.policy.customUnits !== this.props.policy.customUnits || prevProps.preferredLocale !== this.props.preferredLocale) {
this.setState({currentRatePerUnit: this.getCurrentRatePerUnitLabel()});
}

const reconnecting = prevProps.network.isOffline && !this.props.network.isOffline;
if (!reconnecting) {
return;
}
function WorkspaceReimburseView(props) {
const [currentRatePerUnit, setCurrentRatePerUnit] = useState('');
const prevIsOffline = usePrevious(props.network.isOffline);
const viewAllReceiptsUrl = `expenses?policyIDList=${props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`;
const distanceCustomUnit = _.find(lodashGet(props.policy, 'customUnits', {}), (unit) => unit.name === 'Distance');
const distanceCustomRate = _.find(lodashGet(props.distanceCustomUnit, 'rates', {}), (rate) => rate.name === 'Default Rate');

const getNumericValue = useCallback(
(value) => {
const propsToLocaleDigit = props.toLocaleDigit;
const numValue = parseFloat(value.toString().replace(propsToLocaleDigit('.'), '.'));
if (Number.isNaN(numValue)) {
return NaN;
}
return numValue.toFixed(3);
},
[props.toLocaleDigit],
);

const getRateDisplayValue = useCallback(
(value) => {
const propsToLocaleDigit = props.toLocaleDigit;
const numValue = getNumericValue(value);
if (Number.isNaN(numValue)) {
return '';
}
return numValue.toString().replace('.', propsToLocaleDigit('.')).substring(0, value.length);
},
[getNumericValue, props.toLocaleDigit],
);

this.fetchData();
}
const getRateLabel = useCallback((customUnitRate) => getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET), [getRateDisplayValue]);

getCurrentRatePerUnitLabel() {
const distanceCustomUnit = _.find(lodashGet(this.props, 'policy.customUnits', {}), (unit) => unit.name === 'Distance');
const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === 'Default Rate');
const currentUnit = this.getUnitLabel(lodashGet(distanceCustomUnit, 'attributes.unit', 'mi'));
const currentRate = this.getRateLabel(customUnitRate);
const perWord = this.props.translate('common.per');
const getUnitLabel = useCallback(
(value) => {
const propsTranslate = props.translate;
return propsTranslate(`common.${value}`);
},
[props.translate],
);

const getCurrentRatePerUnitLabel = useCallback(() => {
const propsTranslate = props.translate;
const customUnitRate = _.find(distanceCustomUnit && distanceCustomUnit.rates, (rate) => rate && rate.name === 'Default Rate');
const currentUnit = getUnitLabel((distanceCustomUnit && distanceCustomUnit.attributes && distanceCustomUnit.attributes.unit) || 'mi');
const currentRate = getRateLabel(customUnitRate);
const perWord = propsTranslate('common.per');
return `${currentRate} ${perWord} ${currentUnit}`;
}

getRateLabel(customUnitRate) {
return this.getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET);
}

getUnitLabel(value) {
return this.props.translate(`common.${value}`);
}

getRateDisplayValue(value) {
const numValue = this.getNumericValue(value);
if (Number.isNaN(numValue)) {
return '';
}
return numValue.toString().replace('.', this.props.toLocaleDigit('.')).substring(0, value.length);
}

getNumericValue(value) {
const numValue = parseFloat(value.toString().replace(this.props.toLocaleDigit('.'), '.'));
if (Number.isNaN(numValue)) {
return NaN;
}
return numValue.toFixed(3);
}
}, [props.translate, distanceCustomUnit, getUnitLabel, getRateLabel]);

fetchData() {
const fetchData = useCallback(() => {
// Instead of setting the reimbursement account loading within the optimistic data of the API command, use a separate action so that the Onyx value is updated right away.
// openWorkspaceReimburseView uses API.read which will not make the request until all WRITE requests in the sequential queue have finished responding, so there would be a delay in
// updating Onyx with the optimistic data.
const propsPolicyId = props.policy.id;
BankAccounts.setReimbursementAccountLoading(true);
Policy.openWorkspaceReimburseView(this.props.policy.id);
}

render() {
const viewAllReceiptsUrl = `expenses?policyIDList=${this.props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`;
const distanceCustomUnit = _.find(lodashGet(this.props, 'policy.customUnits', {}), (unit) => unit.name === 'Distance');
const distanceCustomRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === 'Default Rate');
return (
<>
<Section
title={this.props.translate('workspace.reimburse.captureReceipts')}
icon={Illustrations.MoneyReceipts}
menuItems={[
{
title: this.props.translate('workspace.reimburse.viewAllReceipts'),
onPress: () => Link.openOldDotLink(viewAllReceiptsUrl),
icon: Expensicons.Receipt,
shouldShowRightIcon: true,
iconRight: Expensicons.NewWindow,
wrapperStyle: [styles.cardMenuItem],
link: () => Link.buildOldDotURL(viewAllReceiptsUrl),
},
]}
>
<View style={[styles.mv3, styles.flexRow, styles.flexWrap]}>
<Text numberOfLines={100}>
{this.props.translate('workspace.reimburse.captureNoVBACopyBeforeEmail')}
<CopyTextToClipboard
text="receipts@expensify.com"
textStyles={[styles.textBlue]}
/>
<Text>{this.props.translate('workspace.reimburse.captureNoVBACopyAfterEmail')}</Text>
</Text>
</View>
</Section>

<Section
title={this.props.translate('workspace.reimburse.trackDistance')}
icon={Illustrations.TrackShoe}
>
<View style={[styles.mv3]}>
<Text>{this.props.translate('workspace.reimburse.trackDistanceCopy')}</Text>
</View>
<OfflineWithFeedback
pendingAction={lodashGet(distanceCustomUnit, 'pendingAction') || lodashGet(distanceCustomRate, 'pendingAction')}
shouldShowErrorMessages={false}
>
<MenuItemWithTopDescription
title={this.state.currentRatePerUnit}
description={this.props.translate('workspace.reimburse.trackDistanceRate')}
shouldShowRightIcon
onPress={() => Navigation.navigate(ROUTES.getWorkspaceRateAndUnitRoute(this.props.policy.id))}
wrapperStyle={[styles.mhn5, styles.wAuto]}
brickRoadIndicator={(lodashGet(distanceCustomUnit, 'errors') || lodashGet(distanceCustomRate, 'errors')) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR}
Policy.openWorkspaceReimburseView(propsPolicyId);
}, [props.policy.id]);

useEffect(() => {
fetchData();
}, [fetchData]);

useEffect(() => {
setCurrentRatePerUnit(getCurrentRatePerUnitLabel());
const isReconnecting = prevIsOffline && !props.network.isOffline;
if (!isReconnecting) {
return;
}
fetchData();
}, [props.policy.customUnits, props.preferredLocale, props.network.isOffline, prevIsOffline, getCurrentRatePerUnitLabel, fetchData]);

return (
<>
<Section
title={props.translate('workspace.reimburse.captureReceipts')}
icon={Illustrations.MoneyReceipts}
menuItems={[
{
title: props.translate('workspace.reimburse.viewAllReceipts'),
onPress: () => Link.openOldDotLink(viewAllReceiptsUrl),
icon: Expensicons.Receipt,
shouldShowRightIcon: true,
iconRight: Expensicons.NewWindow,
wrapperStyle: [styles.cardMenuItem],
link: () => Link.buildOldDotURL(viewAllReceiptsUrl),
},
]}
>
<View style={[styles.mv3, styles.flexRow, styles.flexWrap]}>
<Text numberOfLines={100}>
{props.translate('workspace.reimburse.captureNoVBACopyBeforeEmail')}
<CopyTextToClipboard
text="receipts@expensify.com"
textStyles={[styles.textBlue]}
/>
</OfflineWithFeedback>
</Section>

<WorkspaceReimburseSection
policy={this.props.policy}
reimbursementAccount={this.props.reimbursementAccount}
network={this.props.network}
translate={this.props.translate}
/>
</>
);
}
<Text>{props.translate('workspace.reimburse.captureNoVBACopyAfterEmail')}</Text>
</Text>
</View>
</Section>

<Section
title={props.translate('workspace.reimburse.trackDistance')}
icon={Illustrations.TrackShoe}
>
<View style={[styles.mv3]}>
<Text>{props.translate('workspace.reimburse.trackDistanceCopy')}</Text>
</View>
<OfflineWithFeedback
pendingAction={lodashGet(distanceCustomUnit, 'pendingAction') || lodashGet(distanceCustomRate, 'pendingAction')}
shouldShowErrorMessages={false}
>
<MenuItemWithTopDescription
title={currentRatePerUnit}
description={props.translate('workspace.reimburse.trackDistanceRate')}
shouldShowRightIcon
onPress={() => Navigation.navigate(ROUTES.getWorkspaceRateAndUnitRoute(props.policy.id))}
wrapperStyle={[styles.mhn5, styles.wAuto]}
brickRoadIndicator={(lodashGet(distanceCustomUnit, 'errors') || lodashGet(distanceCustomRate, 'errors')) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR}
/>
</OfflineWithFeedback>
</Section>

<WorkspaceReimburseSection
policy={props.policy}
reimbursementAccount={props.reimbursementAccount}
network={props.network}
translate={props.translate}
/>
</>
);
}

WorkspaceReimburseView.defaultProps = defaultProps;
WorkspaceReimburseView.propTypes = propTypes;
WorkspaceReimburseView.displayName = 'WorkspaceReimburseView';

export default compose(
withLocalize,
Expand Down
Loading