Skip to content

Commit 8e492ef

Browse files
authored
Merge pull request Expensify#79214 from software-mansion-labs/feat/time-confirmation-hours-rate-edit
[Behind Time Tracking Beta] Allow editing hour count and rate in time expense confirmation page
2 parents a12d38c + f402ed6 commit 8e492ef

25 files changed

Lines changed: 319 additions & 27 deletions

src/ROUTES.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,16 @@ const ROUTES = {
13081308
label ? `${backTo || state ? '&' : '?'}label=${encodeURIComponent(label)}` : ''
13091309
}` as const,
13101310
},
1311+
MONEY_REQUEST_STEP_TIME_RATE: {
1312+
route: ':action/:iouType/rate/:transactionID/:reportID/:reportActionID?',
1313+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, reportActionID?: string) =>
1314+
`${action as string}/${iouType as string}/rate/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const,
1315+
},
1316+
MONEY_REQUEST_STEP_HOURS: {
1317+
route: ':action/:iouType/hours/:transactionID/:reportID/:reportActionID?',
1318+
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, reportActionID?: string) =>
1319+
`${action as string}/${iouType as string}/hours/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const,
1320+
},
13111321
DISTANCE_REQUEST_CREATE: {
13121322
route: ':action/:iouType/start/:transactionID/:reportID/distance-new/:backToReport?',
13131323
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backToReport?: string) =>

src/SCREENS.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ const SCREENS = {
332332
STEP_DISTANCE_GPS: 'Money_Request_Step_Distance_GPS',
333333
STEP_DISTANCE_ODOMETER: 'Money_Request_Step_Distance_Odometer',
334334
RECEIPT_PREVIEW: 'Money_Request_Receipt_preview',
335+
STEP_TIME_RATE: 'Money_Request_Step_Time_Rate',
336+
STEP_HOURS: 'Money_Request_Step_Hours',
335337
},
336338

337339
TRANSACTION_DUPLICATE: {

src/components/MoneyRequestConfirmationList.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {getTagLists, isTaxTrackingEnabled} from '@libs/PolicyUtils';
4343
import {isSelectedManagerMcTest} from '@libs/ReportUtils';
4444
import type {OptionData} from '@libs/ReportUtils';
4545
import {hasEnabledTags, hasMatchingTag} from '@libs/TagsOptionsListUtils';
46+
import {isValidTimeExpenseAmount} from '@libs/TimeTrackingUtils';
4647
import {
4748
areRequiredFieldsEmpty,
4849
calculateTaxAmount,
@@ -117,6 +118,12 @@ type MoneyRequestConfirmationListProps = {
117118
/** IOU isBillable */
118119
iouIsBillable?: boolean;
119120

121+
/** Time expense's hour count */
122+
iouTimeCount?: number;
123+
124+
/** Time expense's hourly rate */
125+
iouTimeRate?: number;
126+
120127
/** Callback to toggle the billable state */
121128
onToggleBillable?: (isOn: boolean) => void;
122129

@@ -254,6 +261,8 @@ function MoneyRequestConfirmationList({
254261
onToggleReimbursable,
255262
showRemoveExpenseConfirmModal,
256263
isTimeRequest = false,
264+
iouTimeCount,
265+
iouTimeRate,
257266
}: MoneyRequestConfirmationListProps) {
258267
const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true});
259268
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true});
@@ -348,7 +357,7 @@ function MoneyRequestConfirmationList({
348357
? !policy || shouldSelectPolicy || hasEnabledOptions(Object.values(policyCategories ?? {}))
349358
: (isPolicyExpenseChat || isTypeInvoice) && (!!iouCategory || hasEnabledOptions(Object.values(policyCategories ?? {})));
350359

351-
const shouldShowMerchant = (shouldShowSmartScanFields || isTypeSend) && !isDistanceRequest && !isPerDiemRequest;
360+
const shouldShowMerchant = (shouldShowSmartScanFields || isTypeSend) && !isDistanceRequest && !isPerDiemRequest && !isTimeRequest;
352361

353362
const policyTagLists = useMemo(() => getTagLists(policyTags), [policyTags]);
354363

@@ -963,6 +972,11 @@ function MoneyRequestConfirmationList({
963972
return;
964973
}
965974

975+
if (isTimeRequest && !isValidTimeExpenseAmount(iouAmount, iouCurrencyCode)) {
976+
setFormError('iou.timeTracking.amountTooLargeError');
977+
return;
978+
}
979+
966980
if (isPerDiemRequest) {
967981
if (!isValidPerDiemExpenseAmount(transaction.comment?.customUnit ?? {}, iouCurrencyCode)) {
968982
setFormError('iou.error.invalidQuantity');
@@ -1030,6 +1044,7 @@ function MoneyRequestConfirmationList({
10301044
policyCategories,
10311045
iouAttendees,
10321046
currentUserPersonalDetails,
1047+
isTimeRequest,
10331048
],
10341049
);
10351050

@@ -1179,11 +1194,14 @@ function MoneyRequestConfirmationList({
11791194
iouIsBillable={iouIsBillable}
11801195
iouMerchant={iouMerchant}
11811196
iouType={iouType}
1197+
iouTimeCount={iouTimeCount}
1198+
iouTimeRate={iouTimeRate}
11821199
isCategoryRequired={isCategoryRequired}
11831200
isDistanceRequest={isDistanceRequest}
11841201
isManualDistanceRequest={isManualDistanceRequest}
11851202
isOdometerDistanceRequest={isOdometerDistanceRequest}
11861203
isPerDiemRequest={isPerDiemRequest}
1204+
isTimeRequest={isTimeRequest}
11871205
isMerchantEmpty={isMerchantEmpty}
11881206
isMerchantRequired={isMerchantRequired}
11891207
isPolicyExpenseChat={isPolicyExpenseChat}
@@ -1271,5 +1289,7 @@ export default memo(
12711289
prevProps.reportActionID === nextProps.reportActionID &&
12721290
prevProps.action === nextProps.action &&
12731291
prevProps.shouldDisplayReceipt === nextProps.shouldDisplayReceipt &&
1274-
prevProps.isTimeRequest === nextProps.isTimeRequest,
1292+
prevProps.isTimeRequest === nextProps.isTimeRequest &&
1293+
prevProps.iouTimeCount === nextProps.iouTimeCount &&
1294+
prevProps.iouTimeRate === nextProps.iouTimeRate,
12751295
);

src/components/MoneyRequestConfirmationListFooter.tsx

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ type MoneyRequestConfirmationListFooterProps = {
102102
/** The merchant of the IOU */
103103
iouMerchant: string | undefined;
104104

105+
/** The hours count of the time request */
106+
iouTimeCount: number | undefined;
107+
108+
/** The hourly rate of the time request */
109+
iouTimeRate: number | undefined;
110+
105111
/** The type of the IOU */
106112
iouType: Exclude<IOUType, typeof CONST.IOU.TYPE.REQUEST | typeof CONST.IOU.TYPE.SEND>;
107113

@@ -120,6 +126,9 @@ type MoneyRequestConfirmationListFooterProps = {
120126
/** Flag indicating if it is a per diem request */
121127
isPerDiemRequest: boolean;
122128

129+
/** Flag indicating if it is a time request */
130+
isTimeRequest: boolean;
131+
123132
/** Flag indicating if the merchant is empty */
124133
isMerchantEmpty: boolean;
125134

@@ -231,11 +240,14 @@ function MoneyRequestConfirmationListFooter({
231240
iouIsBillable,
232241
iouMerchant,
233242
iouType,
243+
iouTimeCount,
244+
iouTimeRate,
234245
isCategoryRequired,
235246
isDistanceRequest,
236247
isManualDistanceRequest,
237248
isOdometerDistanceRequest = false,
238249
isPerDiemRequest,
250+
isTimeRequest,
239251
isMerchantEmpty,
240252
isMerchantRequired,
241253
isPolicyExpenseChat,
@@ -441,12 +453,12 @@ function MoneyRequestConfirmationListFooter({
441453
item: (
442454
<MenuItemWithTopDescription
443455
key={translate('iou.amount')}
444-
shouldShowRightIcon={!isReadOnly && !isDistanceRequest}
456+
shouldShowRightIcon={!isReadOnly && !isDistanceRequest && !isTimeRequest}
445457
title={formattedAmount}
446458
description={translate('iou.amount')}
447-
interactive={!isReadOnly}
459+
interactive={!isReadOnly && !isTimeRequest}
448460
onPress={() => {
449-
if (isDistanceRequest || !transactionID) {
461+
if (isDistanceRequest || isTimeRequest || !transactionID) {
450462
return;
451463
}
452464

@@ -591,6 +603,48 @@ function MoneyRequestConfirmationListFooter({
591603
),
592604
shouldShow: shouldShowMerchant,
593605
},
606+
{
607+
item: (
608+
<MenuItemWithTopDescription
609+
key={translate('iou.timeTracking.hours')}
610+
shouldShowRightIcon={!isReadOnly}
611+
title={`${iouTimeCount}`}
612+
description={translate('iou.timeTracking.hours')}
613+
style={styles.moneyRequestMenuItem}
614+
titleStyle={styles.flex1}
615+
onPress={() => {
616+
if (!transactionID) {
617+
return;
618+
}
619+
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, reportID, reportActionID));
620+
}}
621+
disabled={didConfirm}
622+
interactive={!isReadOnly}
623+
/>
624+
),
625+
shouldShow: isTimeRequest,
626+
},
627+
{
628+
item: (
629+
<MenuItemWithTopDescription
630+
key={`time_${translate('common.rate')}`}
631+
shouldShowRightIcon={!isReadOnly}
632+
title={translate('iou.timeTracking.ratePreview', convertToDisplayString(iouTimeRate, iouCurrencyCode))}
633+
description={translate('common.rate')}
634+
style={styles.moneyRequestMenuItem}
635+
titleStyle={styles.flex1}
636+
onPress={() => {
637+
if (!transactionID) {
638+
return;
639+
}
640+
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TIME_RATE.getRoute(action, iouType, transactionID, reportID, reportActionID));
641+
}}
642+
disabled={didConfirm}
643+
interactive={!isReadOnly}
644+
/>
645+
),
646+
shouldShow: isTimeRequest,
647+
},
594648
{
595649
item: (
596650
<MenuItemWithTopDescription
@@ -1114,5 +1168,8 @@ export default memo(
11141168
prevProps.shouldShowTax === nextProps.shouldShowTax &&
11151169
prevProps.transaction === nextProps.transaction &&
11161170
prevProps.transactionID === nextProps.transactionID &&
1117-
prevProps.unit === nextProps.unit,
1171+
prevProps.unit === nextProps.unit &&
1172+
prevProps.isTimeRequest === nextProps.isTimeRequest &&
1173+
prevProps.iouTimeCount === nextProps.iouTimeCount &&
1174+
prevProps.iouTimeRate === nextProps.iouTimeRate,
11181175
);

src/languages/de.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1472,7 +1472,13 @@ const translations: TranslationDeepObject<typeof en> = {
14721472
splitDateRange: ({startDate, endDate, count}: SplitDateRangeParams) => `${startDate} bis ${endDate} (${count} Tage)`,
14731473
splitByDate: 'Nach Datum aufteilen',
14741474
routedDueToDEW: ({to}: RoutedDueToDEWParams) => `bericht aufgrund eines benutzerdefinierten Genehmigungsworkflows an ${to} weitergeleitet`,
1475-
timeTracking: {hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? 'Stunde' : 'Stunden'} @ ${rate} / Stunde`, hrs: 'Std.'},
1475+
timeTracking: {
1476+
hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? 'Stunde' : 'Stunden'} @ ${rate} / Stunde`,
1477+
hrs: 'Std.',
1478+
hours: 'Stunden',
1479+
ratePreview: (rate: string) => `${rate} / Stunde`,
1480+
amountTooLargeError: 'Der Gesamtbetrag ist zu hoch. Verringere die Stunden oder reduziere den Satz.',
1481+
},
14761482
},
14771483
transactionMerge: {
14781484
listPage: {

src/languages/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,6 +1458,9 @@ const translations = {
14581458
timeTracking: {
14591459
hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? 'hour' : 'hours'} @ ${rate} / hour`,
14601460
hrs: 'hrs',
1461+
hours: 'Hours',
1462+
ratePreview: (rate: string) => `${rate} / hour`,
1463+
amountTooLargeError: 'The total amount is too large. Lower the hours or reduce the rate.',
14611464
},
14621465
},
14631466
transactionMerge: {

src/languages/es.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,9 @@ const translations: TranslationDeepObject<typeof en> = {
12031203
timeTracking: {
12041204
hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? 'hora' : 'horas'} a ${rate} / hora`,
12051205
hrs: 'h',
1206+
hours: 'Horas',
1207+
ratePreview: (rate: string) => `${rate} / hora`,
1208+
amountTooLargeError: 'El importe total es demasiado alto. Reduce las horas o disminuye la tasa.',
12061209
},
12071210
},
12081211
transactionMerge: {

src/languages/fr.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1475,7 +1475,13 @@ const translations: TranslationDeepObject<typeof en> = {
14751475
splitDateRange: ({startDate, endDate, count}: SplitDateRangeParams) => `Du ${startDate} au ${endDate} (${count} jours)`,
14761476
splitByDate: 'Scinder par date',
14771477
routedDueToDEW: ({to}: RoutedDueToDEWParams) => `rapport acheminé vers ${to} en raison d'un workflow d'approbation personnalisé`,
1478-
timeTracking: {hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? 'heure' : 'heures'} @ ${rate} / heure`, hrs: 'h'},
1478+
timeTracking: {
1479+
hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? 'heure' : 'heures'} @ ${rate} / heure`,
1480+
hrs: 'h',
1481+
hours: 'Heures',
1482+
ratePreview: (rate: string) => `${rate} / heure`,
1483+
amountTooLargeError: 'Le montant total est trop élevé. Réduisez le nombre d’heures ou diminuez le tarif.',
1484+
},
14791485
},
14801486
transactionMerge: {
14811487
listPage: {

src/languages/it.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1469,7 +1469,13 @@ const translations: TranslationDeepObject<typeof en> = {
14691469
splitDateRange: ({startDate, endDate, count}: SplitDateRangeParams) => `${startDate} a ${endDate} (${count} giorni)`,
14701470
splitByDate: 'Dividi per data',
14711471
routedDueToDEW: ({to}: RoutedDueToDEWParams) => `rapporto inoltrato a ${to} a causa del flusso di lavoro di approvazione personalizzato`,
1472-
timeTracking: {hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? 'ora' : 'ore'} @ ${rate} / ora`, hrs: 'ore'},
1472+
timeTracking: {
1473+
hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? 'ora' : 'ore'} @ ${rate} / ora`,
1474+
hrs: 'ore',
1475+
hours: 'Ore',
1476+
ratePreview: (rate: string) => `${rate} / ora`,
1477+
amountTooLargeError: 'L’importo totale è troppo elevato. Riduci le ore o diminuisci la tariffa.',
1478+
},
14731479
},
14741480
transactionMerge: {
14751481
listPage: {

src/languages/ja.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1467,7 +1467,13 @@ const translations: TranslationDeepObject<typeof en> = {
14671467
splitDateRange: ({startDate, endDate, count}: SplitDateRangeParams) => `${startDate} から ${endDate} まで(${count} 日間)`,
14681468
splitByDate: '日付で分割',
14691469
routedDueToDEW: ({to}: RoutedDueToDEWParams) => `カスタム承認ワークフローにより、${to} 宛にルーティングされたレポート`,
1470-
timeTracking: {hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? '時間' : '時間'} @ ${rate} / 時間`, hrs: '時間'},
1470+
timeTracking: {
1471+
hoursAt: (hours: number, rate: string) => `${hours} ${hours === 1 ? '時間' : '時間'} @ ${rate} / 時間`,
1472+
hrs: '時間',
1473+
hours: '時間',
1474+
ratePreview: (rate: string) => `${rate} / 時間`,
1475+
amountTooLargeError: '合計金額が大きすぎます。時間を減らすか、レートを下げてください。',
1476+
},
14711477
},
14721478
transactionMerge: {
14731479
listPage: {

0 commit comments

Comments
 (0)