From acd08281a9fdaa445f8cd2122d56e3bb42f3beb0 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Fri, 23 Jan 2026 12:59:46 +0100 Subject: [PATCH 01/17] Create Time Tracking settings page --- src/CONST/index.ts | 1 + src/ONYXKEYS.ts | 3 + src/ROUTES.ts | 15 ++++ src/SCREENS.ts | 2 + .../FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts | 1 + src/languages/en.ts | 6 ++ ...datePolicyTimeTrackingDefaultRateParams.ts | 6 ++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../ModalStackNavigators/index.tsx | 1 + .../Navigators/WorkspaceSplitNavigator.tsx | 1 + .../RELATIONS/WORKSPACE_TO_RHP.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 6 ++ src/libs/Navigation/types.ts | 6 ++ src/libs/PolicyUtils.ts | 3 + src/libs/actions/Policy/Policy.ts | 24 +++++ src/pages/workspace/WorkspaceInitialPage.tsx | 17 ++++ .../WorkspaceTimeTrackingPage.tsx | 87 +++++++++++++++++++ .../WorkspaceTimeTrackingRatePage.tsx | 80 +++++++++++++++++ .../form/WorkspaceTimeTrackingRateForm.ts | 18 ++++ src/types/form/index.ts | 1 + src/types/onyx/Policy.ts | 2 +- 22 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 src/libs/API/parameters/UpdatePolicyTimeTrackingDefaultRateParams.ts create mode 100644 src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx create mode 100644 src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx create mode 100644 src/types/form/WorkspaceTimeTrackingRateForm.ts diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 2b6e2f223baec..ecfeb55f4bbe4 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3159,6 +3159,7 @@ const CONST = { IS_ATTENDEE_TRACKING_ENABLED: 'isAttendeeTrackingEnabled', IS_TRAVEL_ENABLED: 'isTravelEnabled', REQUIRE_COMPANY_CARDS_ENABLED: 'requireCompanyCardsEnabled', + IS_TIME_TRACKING_ENABLED: 'isTimeTrackingEnabled', }, DEFAULT_CATEGORIES: { ADVERTISING: 'Advertising', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 3205cb49523a3..a069461119a46 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -913,6 +913,8 @@ const ONYXKEYS = { WORKSPACE_INVOICES_COMPANY_NAME_FORM_DRAFT: 'workspaceInvoicesCompanyNameFormDraft', WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM: 'workspaceInvoicesCompanyWebsiteForm', WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM_DRAFT: 'workspaceInvoicesCompanyWebsiteFormDraft', + WORKSPACE_TIME_TRACKING_RATE_FORM: 'workspaceTimeTrackingRateForm', + WORKSPACE_TIME_TRACKING_RATE_FORM_DRAFT: 'workspaceTimeTrackingRateFormDraft', NEW_CHAT_NAME_FORM: 'newChatNameForm', NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft', SUBSCRIPTION_SIZE_FORM: 'subscriptionSizeForm', @@ -1066,6 +1068,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; [ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_NAME_FORM]: FormTypes.WorkspaceInvoicesCompanyNameForm; [ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM]: FormTypes.WorkspaceInvoicesCompanyWebsiteForm; + [ONYXKEYS.FORMS.WORKSPACE_TIME_TRACKING_RATE_FORM]: FormTypes.WorkspaceTimeTrackingRateForm; [ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm; [ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm; [ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 50d6749e940fd..c38cf9a3b7df6 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2613,6 +2613,21 @@ const ROUTES = { route: 'workspaces/:policyID/per-diem/edit/currency/:rateID/:subRateID', getRoute: (policyID: string, rateID: string, subRateID: string) => `workspaces/${policyID}/per-diem/edit/currency/${rateID}/${subRateID}` as const, }, + + WORKSPACE_TIME_TRACKING: { + route: 'workspaces/:policyID/time-tracking', + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID is used to build the WORKSPACE_TIME_TRACKING route'); + } + return `workspaces/${policyID}/time-tracking` as const; + }, + }, + WORKSPACE_TIME_TRACKING_RATE: { + route: 'workspaces/:policyID/time-tracking/rate', + getRoute: (policyID: string) => `workspaces/${policyID}/time-tracking/rate` as const, + }, + REPORTS_DEFAULT_TITLE: { route: 'workspaces/:policyID/reports/name', getRoute: (policyID: string) => `workspaces/${policyID}/reports/name` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 739c5ac47279c..f7a91db4b0cf2 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -762,6 +762,8 @@ const SCREENS = { PER_DIEM_EDIT_SUBRATE: 'Per_Diem_Edit_Subrate', PER_DIEM_EDIT_AMOUNT: 'Per_Diem_Edit_Amount', PER_DIEM_EDIT_CURRENCY: 'Per_Diem_Edit_Currency', + TIME_TRACKING: 'Time_Tracking', + TIME_TRACKING_RATE: 'Time_Tracking_Rate', }, EDIT_REQUEST: { diff --git a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts index 16ce71ef17e8b..17e649a0402c0 100644 --- a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts +++ b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts @@ -37,6 +37,7 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [ SCREENS.WORKSPACE.RULES, SCREENS.WORKSPACE.PER_DIEM, SCREENS.WORKSPACE.RECEIPT_PARTNERS, + SCREENS.WORKSPACE.TIME_TRACKING, ]; export default WIDE_LAYOUT_INACTIVE_SCREENS; diff --git a/src/languages/en.ts b/src/languages/en.ts index 4d0b71df0d2ba..23d27ceae91e5 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5137,6 +5137,12 @@ const translations = { title: 'Rules', subtitle: 'Require receipts, flag high spend, and more.', }, + timeTracking: { + title: 'Time', + subtitle: 'Set an hourly billable rate for employees to get paid for their time.', + defaultHourlyRate: 'Default hourly rate', + defaultHourlyRateSubtitle: 'Set an hourly billable rate for time tracking.', + }, }, reports: { reportsCustomTitleExamples: 'Examples:', diff --git a/src/libs/API/parameters/UpdatePolicyTimeTrackingDefaultRateParams.ts b/src/libs/API/parameters/UpdatePolicyTimeTrackingDefaultRateParams.ts new file mode 100644 index 0000000000000..1059dfbb777dd --- /dev/null +++ b/src/libs/API/parameters/UpdatePolicyTimeTrackingDefaultRateParams.ts @@ -0,0 +1,6 @@ +type UpdatePolicyTimeTrackingDefaultRateParams = { + policyID: string; + rate: number; +}; + +export default UpdatePolicyTimeTrackingDefaultRateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index d5e1b7e63f185..9d4b736a96bd3 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -462,3 +462,4 @@ export type {default as ToggleConsolidatedDomainBillingParams} from './ToggleCon export type {default as RemoveDomainAdminParams} from './RemoveDomainAdminParams'; export type {default as DeleteDomainParams} from './DeleteDomainParams'; export type {default as GetDuplicateTransactionDetailsParams} from './GetDuplicateTransactionDetailsParams'; +export type {default as UpdatePolicyTimeTrackingDefaultRateParams} from './UpdatePolicyTimeTrackingDefaultRateParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 68781b4ae8a56..c6784f9d537f9 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -252,6 +252,7 @@ const WRITE_COMMANDS = { TOGGLE_POLICY_PER_DIEM: 'TogglePolicyPerDiem', ENABLE_POLICY_COMPANY_CARDS: 'EnablePolicyCompanyCards', ENABLE_POLICY_INVOICING: 'EnablePolicyInvoicing', + UPDATE_POLICY_TIME_TRACKING_DEFAULT_RATE: 'UpdatePolicyTimeTrackingDefaultRate', SET_POLICY_RULES_ENABLED: 'SetPolicyRulesEnabled', SET_POLICY_EXPENSE_MAX_AMOUNT_NO_RECEIPT: 'SetPolicyExpenseMaxAmountNoReceipt', SET_POLICY_EXPENSE_MAX_AMOUNT: 'SetPolicyExpenseMaxAmount', @@ -880,6 +881,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_DISTANCE_TAX_CLAIMABLE_VALUE]: Parameters.UpdatePolicyDistanceRateValueParams; [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_ENABLED]: Parameters.SetPolicyDistanceRatesEnabledParams; [WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams; + [WRITE_COMMANDS.UPDATE_POLICY_TIME_TRACKING_DEFAULT_RATE]: Parameters.UpdatePolicyTimeTrackingDefaultRateParams; [WRITE_COMMANDS.DISMISS_TRACK_EXPENSE_ACTIONABLE_WHISPER]: Parameters.DismissTrackExpenseActionableWhisperParams; [WRITE_COMMANDS.UPDATE_BILLING_CARD_CURRENCY]: Parameters.UpdateBillingCurrencyParams; [WRITE_COMMANDS.CONVERT_TRACKED_EXPENSE_TO_REQUEST]: Parameters.ConvertTrackedExpenseToRequestParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index b37624a215748..b0aa28100cd9d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -829,6 +829,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/receiptPartners/InviteReceiptPartnerPolicyPage').default, [SCREENS.WORKSPACE.RECEIPT_PARTNERS_INVITE_EDIT]: () => require('../../../../pages/workspace/receiptPartners/EditInviteReceiptPartnerPolicyPage').default, [SCREENS.WORKSPACE.RECEIPT_PARTNERS_CHANGE_BILLING_ACCOUNT]: () => require('../../../../pages/workspace/receiptPartners/ChangeReceiptBillingAccountPage').default, + [SCREENS.WORKSPACE.TIME_TRACKING_RATE]: () => require('../../../../pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage').default, [SCREENS.DOMAIN.VERIFY]: () => require('../../../../pages/domain/SamlVerifyDomainPage').default, [SCREENS.DOMAIN.VERIFIED]: () => require('../../../../pages/domain/SamlDomainVerifiedPage').default, [SCREENS.DOMAIN.ADMIN_DETAILS]: () => require('../../../../pages/domain/Admins/DomainAdminDetailsPage').default, diff --git a/src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx index 99a11121f25b4..2345747303a14 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx @@ -34,6 +34,7 @@ const CENTRAL_PANE_WORKSPACE_SCREENS = { [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default, [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../pages/workspace/travel/PolicyTravelPage').default, [SCREENS.WORKSPACE.RULES]: () => require('../../../../pages/workspace/rules/PolicyRulesPage').default, + [SCREENS.WORKSPACE.TIME_TRACKING]: () => require('../../../../pages/workspace/timeTracking/WorkspaceTimeTrackingPage').default, } satisfies Screens; const Split = createSplitNavigator(); diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index 0beb9e8ba14f1..2c797685261af 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -298,6 +298,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.PER_DIEM_EDIT_CURRENCY]: { path: ROUTES.WORKSPACE_PER_DIEM_EDIT_CURRENCY.route, }, + [SCREENS.WORKSPACE.TIME_TRACKING_RATE]: { + path: ROUTES.WORKSPACE_TIME_TRACKING_RATE.route, + }, [SCREENS.DOMAIN.VERIFY]: { path: ROUTES.DOMAIN_VERIFY.route, }, @@ -2055,6 +2058,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.RULES]: { path: ROUTES.WORKSPACE_RULES.route, }, + [SCREENS.WORKSPACE.TIME_TRACKING]: { + path: ROUTES.WORKSPACE_TIME_TRACKING.route, + }, }, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 107758857fcf2..f03b5e3d4e6c8 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1421,6 +1421,9 @@ type SettingsNavigatorParamList = { rateID: string; subRateID: string; }; + [SCREENS.WORKSPACE.TIME_TRACKING_RATE]: { + policyID: string; + }; [SCREENS.DOMAIN.VERIFY]: { domainAccountID: number; }; @@ -2579,6 +2582,9 @@ type WorkspaceSplitNavigatorParamList = { [SCREENS.WORKSPACE.RULES]: { policyID: string; }; + [SCREENS.WORKSPACE.TIME_TRACKING]: { + policyID: string; + }; }; type DomainSplitNavigatorParamList = { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 8ab0e8107bea7..37b8f112fb914 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -863,6 +863,9 @@ function isPolicyFeatureEnabled(policy: OnyxEntry, featureName: PolicyFe if (featureName === CONST.POLICY.MORE_FEATURES.ARE_RECEIPT_PARTNERS_ENABLED) { return policy?.receiptPartners?.enabled ?? false; } + if (featureName === CONST.POLICY.MORE_FEATURES.IS_TIME_TRACKING_ENABLED) { + return isTimeTrackingEnabled(policy); + } return !!policy?.[featureName]; } diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index de476dfe0bc2e..cc9f4d9fa0a55 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -4648,6 +4648,29 @@ function enablePolicyInvoicing(policyID: string, enabled: boolean) { } } +/** + * Update policy's time tracking default hourly rate. + */ +function updatePolicyTimeTrackingDefaultRate(policyID: string, rate: number) { + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + units: { + time: { + rate, + }, + }, + }, + }, + ], + }; + + API.write(WRITE_COMMANDS.UPDATE_POLICY_TIME_TRACKING_DEFAULT_RATE, {policyID, rate}, onyxData); +} + function openPolicyMoreFeaturesPage(policyID: string) { const params: OpenPolicyMoreFeaturesPageParams = {policyID}; @@ -6671,4 +6694,5 @@ export { inviteWorkspaceEmployeesToUber, setWorkspaceConfirmationCurrency, setPolicyRequireCompanyCardsEnabled, + updatePolicyTimeTrackingDefaultRate, }; diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 255a8644516ea..f48505c000780 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -38,6 +38,7 @@ import { isPendingDeletePolicy, isPolicyAdmin, isPolicyFeatureEnabled, + isTimeTrackingEnabled, shouldShowEmployeeListError, shouldShowSyncError, shouldShowTaxRateError, @@ -107,6 +108,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac 'Users', 'Workflows', 'LuggageWithLines', + 'Clock', ] as const); const policy = policyDraft?.id ? policyDraft : policyProp; @@ -129,6 +131,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac const wasRendered = useRef(false); const prevPendingFields = usePrevious(policy?.pendingFields); const shouldDisplayLHB = !shouldUseNarrowLayout; + const isPolicyTimeTrackingEnabled = isTimeTrackingEnabled(policy); const policyFeatureStates = useMemo( () => ({ [CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED]: policy?.areDistanceRatesEnabled, @@ -145,6 +148,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac [CONST.POLICY.MORE_FEATURES.ARE_PER_DIEM_RATES_ENABLED]: policy?.arePerDiemRatesEnabled, [CONST.POLICY.MORE_FEATURES.ARE_RECEIPT_PARTNERS_ENABLED]: isUberForBusinessEnabled && (policy?.receiptPartners?.enabled ?? false), [CONST.POLICY.MORE_FEATURES.IS_TRAVEL_ENABLED]: policy?.isTravelEnabled, + [CONST.POLICY.MORE_FEATURES.IS_TIME_TRACKING_ENABLED]: isPolicyTimeTrackingEnabled, }), [ policy?.areDistanceRatesEnabled, @@ -163,6 +167,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac policy?.receiptPartners?.enabled, isUberForBusinessEnabled, policy?.isTravelEnabled, + isPolicyTimeTrackingEnabled, ], ) as PolicyFeatureStates; @@ -338,6 +343,16 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac }); } + if (isBetaEnabled(CONST.BETAS.TIME_TRACKING) && featureStates?.[CONST.POLICY.MORE_FEATURES.IS_TIME_TRACKING_ENABLED]) { + protectedMenuItems.push({ + translationKey: 'iou.time', + icon: expensifyIcons.Clock, + action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING.getRoute(policyID)))), + screenName: SCREENS.WORKSPACE.TIME_TRACKING, + highlighted: highlightedFeature === CONST.POLICY.MORE_FEATURES.IS_TIME_TRACKING_ENABLED, + }); + } + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_INVOICES_ENABLED]) { const currencyCode = policy?.outputCurrency ?? CONST.CURRENCY.USD; protectedMenuItems.push({ @@ -394,6 +409,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac expensifyIcons.CreditCard, expensifyIcons.CalendarSolid, expensifyIcons.InvoiceGeneric, + expensifyIcons.Clock, singleExecution, waitForNavigate, featureStates, @@ -407,6 +423,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac shouldShowEnterCredentialsError, hasPolicyCategoryError, hasCompanyCardFeedError, + isBetaEnabled, ]); // We only update feature states if they aren't pending. diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx new file mode 100644 index 0000000000000..94a7dd8241ab1 --- /dev/null +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import Section from '@components/Section'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {convertAmountToDisplayString} from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type WorkspaceTimeTrackingPageProps = PlatformStackScreenProps; + +function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const horizontalPadding = shouldUseNarrowLayout ? styles.ph5 : styles.ph8; + + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + + return ( +
+ Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING_RATE.getRoute(policyID))} + style={horizontalPadding} + /> +
+ ); +} + +function WorkspaceTimeTrackingPage({route}: WorkspaceTimeTrackingPageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const illustrations = useMemoizedLazyIllustrations(['Clock']); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + return ( + + + {(_, policyID) => + !!policyID && ( + + + + ) + } + + + ); +} + +export default WorkspaceTimeTrackingPage; diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx new file mode 100644 index 0000000000000..cc4dccea82fb9 --- /dev/null +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import AmountForm from '@components/AmountForm'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {updatePolicyTimeTrackingDefaultRate} from '@libs/actions/Policy/Policy'; +import {convertToFrontendAmountAsString} from '@libs/CurrencyUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; +import {getFieldRequiredErrors} from '@libs/ValidationUtils'; +import Navigation from '@navigation/Navigation'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspaceTimeTrackingRateForm'; + +type WorkspaceTimeTrackingRatePageProps = PlatformStackScreenProps; + +function WorkspaceTimeTrackingRatePage({route}: WorkspaceTimeTrackingRatePageProps) { + const {policyID} = route.params; + + const {translate} = useLocalize(); + const {inputCallbackRef} = useAutoFocusInput(); + const styles = useThemeStyles(); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + + const validate = (values: FormOnyxValues): FormInputErrors => + getFieldRequiredErrors(values, [INPUT_IDS.RATE]); + + if (!policy) { + return ; + } + + return ( + + + + { + updatePolicyTimeTrackingDefaultRate(policyID, Number.parseFloat(values[INPUT_IDS.RATE])); + Navigation.goBack(); + }} + style={[styles.flex1, styles.mh5]} + enabledWhenOffline + validate={validate} + shouldHideFixErrorsAlert + addBottomSafeAreaPadding + > + + + + + ); +} + +export default WorkspaceTimeTrackingRatePage; diff --git a/src/types/form/WorkspaceTimeTrackingRateForm.ts b/src/types/form/WorkspaceTimeTrackingRateForm.ts new file mode 100644 index 0000000000000..27798a2185b12 --- /dev/null +++ b/src/types/form/WorkspaceTimeTrackingRateForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + RATE: 'rate', +} as const; + +type InputID = ValueOf; + +type WorkspaceTimeTrackingRateForm = Form< + InputID, + { + [INPUT_IDS.RATE]: string; + } +>; + +export type {WorkspaceTimeTrackingRateForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index ac687cf145dda..a15567ad200b6 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -103,3 +103,4 @@ export type {CreateDomainForm} from './CreateDomainForm'; export type {SplitExpenseEditDateForm} from './SplitExpenseEditDateForm'; export type {ResetDomainForm} from './ResetDomainForm'; export type {ExpenseRuleForm} from './ExpenseRuleForm'; +export type {WorkspaceTimeTrackingRateForm} from './WorkspaceTimeTrackingRateForm'; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index f005dc7d034d6..e1b8f6a400e98 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -2020,7 +2020,7 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether the policy requires purchases to be on a company card */ requireCompanyCardsEnabled?: boolean; } & Partial, - 'addWorkspaceRoom' | keyof ACHAccount | keyof Attributes + 'addWorkspaceRoom' | keyof ACHAccount | keyof Attributes | 'isTimeTrackingEnabled' >; /** Stages of policy connection sync */ From 561e200ea9cb2340c0b27975702cb0c205051620 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Fri, 23 Jan 2026 13:17:49 +0100 Subject: [PATCH 02/17] Fix import --- src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx index 94a7dd8241ab1..8f727cf033d2b 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx @@ -1,10 +1,10 @@ import React from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import Section from '@components/Section'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {convertAmountToDisplayString} from '@libs/CurrencyUtils'; From 60eeb907b814954cc05bc38f1dd4c70644528356 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Fri, 23 Jan 2026 17:34:02 +0100 Subject: [PATCH 03/17] Fix new workspace page components --- ...WorkspaceTimeTrackingHourlyRateSection.tsx | 45 +++++++++++++++++++ .../WorkspaceTimeTrackingPage.tsx | 42 +---------------- .../WorkspaceTimeTrackingRatePage.tsx | 4 +- src/pages/workspace/withPolicy.tsx | 3 +- 4 files changed, 51 insertions(+), 43 deletions(-) create mode 100644 src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx new file mode 100644 index 0000000000000..cfd94708bd079 --- /dev/null +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import Section from '@components/Section'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {convertAmountToDisplayString} from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; + +function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const horizontalPadding = shouldUseNarrowLayout ? styles.ph5 : styles.ph8; + + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); + + return ( +
+ Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING_RATE.getRoute(policyID))} + style={horizontalPadding} + /> +
+ ); +} + +export default WorkspaceTimeTrackingHourlyRateSection; diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx index 8f727cf033d2b..bae673c36f9d8 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx @@ -1,56 +1,18 @@ import React from 'react'; import {View} from 'react-native'; -import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import Section from '@components/Section'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import {convertAmountToDisplayString} from '@libs/CurrencyUtils'; -import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import WorkspaceTimeTrackingHourlyRateSection from './WorkspaceTimeTrackingHourlyRateSection'; -type WorkspaceTimeTrackingPageProps = PlatformStackScreenProps; - -function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) { - const {translate} = useLocalize(); - const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const horizontalPadding = shouldUseNarrowLayout ? styles.ph5 : styles.ph8; - - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); - - return ( -
- Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING_RATE.getRoute(policyID))} - style={horizontalPadding} - /> -
- ); -} +type WorkspaceTimeTrackingPageProps = PlatformStackScreenProps; function WorkspaceTimeTrackingPage({route}: WorkspaceTimeTrackingPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx index cc4dccea82fb9..b47bf9c0fe53f 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx @@ -31,7 +31,7 @@ function WorkspaceTimeTrackingRatePage({route}: WorkspaceTimeTrackingRatePagePro const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const styles = useThemeStyles(); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); const validate = (values: FormOnyxValues): FormInputErrors => getFieldRequiredErrors(values, [INPUT_IDS.RATE]); @@ -42,7 +42,7 @@ function WorkspaceTimeTrackingRatePage({route}: WorkspaceTimeTrackingRatePagePro return ( diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index fb5929e1c2966..b847b6b40f001 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -53,7 +53,8 @@ type PolicyRouteName = | typeof SCREENS.WORKSPACE.RULES | typeof SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW | typeof SCREENS.WORKSPACE.COMPANY_CARDS_BROKEN_CARD_FEED_CONNECTION - | typeof SCREENS.WORKSPACE.ACCOUNTING.CLAIM_OFFER; + | typeof SCREENS.WORKSPACE.ACCOUNTING.CLAIM_OFFER + | typeof SCREENS.WORKSPACE.TIME_TRACKING; type PolicyRoute = PlatformStackRouteProp; From 12538a9101f0a9d1e1b08bd82a4db59f2c2e727c Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Mon, 26 Jan 2026 18:37:58 +0100 Subject: [PATCH 04/17] Add navigation from more features to the new time page + refactor styling in rate section --- src/pages/workspace/WorkspaceMoreFeaturesPage.tsx | 5 ++++- .../WorkspaceTimeTrackingHourlyRateSection.tsx | 14 +++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 86a765983f1ef..d285e0a6fb354 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -271,7 +271,10 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro enablePolicyTimeTracking(policyID, isEnabled); }, onPress: () => { - // TODO: Navigate to the Time Tracking settings page when implemented. + if (!policyID) { + return; + } + Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING.getRoute(policyID)); }, }); } diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx index cfd94708bd079..896ffc7e9684b 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx @@ -3,7 +3,6 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {convertAmountToDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -14,20 +13,17 @@ import ROUTES from '@src/ROUTES'; function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const horizontalPadding = shouldUseNarrowLayout ? styles.ph5 : styles.ph8; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); return (
Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING_RATE.getRoute(policyID))} - style={horizontalPadding} + style={styles.sectionMenuItemTopDescription} />
); From 67a042881d973b56d42ccd9196a3006e811c3fd1 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 27 Jan 2026 12:46:28 +0100 Subject: [PATCH 05/17] Block unauthorized access to WorkspaceTimeTrackingPage --- .../workspace/timeTracking/WorkspaceTimeTrackingPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx index bae673c36f9d8..602ff46b9f356 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {View} from 'react-native'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; @@ -19,12 +20,14 @@ function WorkspaceTimeTrackingPage({route}: WorkspaceTimeTrackingPageProps) { const styles = useThemeStyles(); const illustrations = useMemoizedLazyIllustrations(['Clock']); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isBetaEnabled} = usePermissions(); return ( Date: Tue, 27 Jan 2026 13:09:54 +0100 Subject: [PATCH 06/17] Replace goBack with dismissModal in WorkspaceTimeTrackingRatePage --- .../workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx index b47bf9c0fe53f..8f7f247b3ffe9 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx @@ -53,7 +53,7 @@ function WorkspaceTimeTrackingRatePage({route}: WorkspaceTimeTrackingRatePagePro submitButtonText={translate('common.save')} onSubmit={(values) => { updatePolicyTimeTrackingDefaultRate(policyID, Number.parseFloat(values[INPUT_IDS.RATE])); - Navigation.goBack(); + Navigation.dismissModal(); }} style={[styles.flex1, styles.mh5]} enabledWhenOffline From cc601705c6c6589efd01e406a0601783898b4d88 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 27 Jan 2026 14:37:20 +0100 Subject: [PATCH 07/17] Add translations --- src/languages/de.ts | 7 ++++++- src/languages/es.ts | 2 ++ src/languages/fr.ts | 7 ++++++- src/languages/it.ts | 7 ++++++- src/languages/ja.ts | 7 ++++++- src/languages/nl.ts | 7 ++++++- src/languages/pl.ts | 7 ++++++- src/languages/pt-BR.ts | 7 ++++++- src/languages/zh-hans.ts | 7 ++++++- 9 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 9dbe2a90e6014..0735fc470b208 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -5239,7 +5239,12 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU title: 'Regeln', subtitle: 'Belege verlangen, hohe Ausgaben kennzeichnen und mehr.', }, - timeTracking: {title: 'Zeit', subtitle: 'Legen Sie einen abrechenbaren Stundensatz fest, damit Mitarbeitende für ihre Zeit bezahlt werden.'}, + timeTracking: { + title: 'Zeit', + subtitle: 'Legen Sie einen abrechenbaren Stundensatz fest, damit Mitarbeitende für ihre Zeit bezahlt werden.', + defaultHourlyRate: 'Standardstundensatz', + defaultHourlyRateSubtitle: 'Legen Sie einen abrechenbaren Stundensatz für die Zeiterfassung fest.', + }, }, reports: { reportsCustomTitleExamples: 'Beispiele:', diff --git a/src/languages/es.ts b/src/languages/es.ts index c8961f508b267..6f6f0350c727a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4899,6 +4899,8 @@ ${amount} para ${merchant} - ${date}`, timeTracking: { title: 'Tiempo', subtitle: 'Establece una tarifa facturable por hora para que los empleados reciban pago por su tiempo.', + defaultHourlyRate: 'Tasa horaria predeterminada', + defaultHourlyRateSubtitle: 'Establece una tasa facturable por hora para el seguimiento del tiempo.', }, }, reports: { diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 8e5ea4c8f31b2..defa7a5b0fc21 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -5247,7 +5247,12 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. title: 'Règles', subtitle: 'Exigez des reçus, signalez les dépenses élevées, et plus encore.', }, - timeTracking: {title: 'Heure', subtitle: 'Définissez un taux horaire facturable pour que les employés soient rémunérés pour leur temps.'}, + timeTracking: { + title: 'Heure', + subtitle: 'Définissez un taux horaire facturable pour que les employés soient rémunérés pour leur temps.', + defaultHourlyRate: 'Taux horaire par défaut', + defaultHourlyRateSubtitle: 'Définissez un taux horaire facturable pour le suivi du temps.', + }, }, reports: { reportsCustomTitleExamples: 'Exemples :', diff --git a/src/languages/it.ts b/src/languages/it.ts index ee09a7f6314eb..5786e5d4dbf0c 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -5226,7 +5226,12 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. title: 'Regole', subtitle: 'Richiedi ricevute, segnala spese elevate e altro ancora.', }, - timeTracking: {title: 'Ora', subtitle: 'Imposta una tariffa oraria fatturabile per consentire ai dipendenti di essere pagati per il loro tempo.'}, + timeTracking: { + title: 'Ora', + subtitle: 'Imposta una tariffa oraria fatturabile per consentire ai dipendenti di essere pagati per il loro tempo.', + defaultHourlyRate: 'Tariffa oraria predefinita', + defaultHourlyRateSubtitle: 'Imposta una tariffa oraria fatturabile per il monitoraggio del tempo.', + }, }, reports: { reportsCustomTitleExamples: 'Esempi:', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index a4aecc65c5324..2e1769aec2c91 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -5192,7 +5192,12 @@ _より詳しい手順については、[ヘルプサイトをご覧ください title: 'ルール', subtitle: 'レシートの必須化や高額支出のフラグ付けなどを設定できます。', }, - timeTracking: {title: '時間', subtitle: '従業員が作業時間に対して支払いを受けられるよう、時間単位の請求レートを設定します。'}, + timeTracking: { + title: '時間', + subtitle: '従業員が作業時間に対して支払いを受けられるよう、時間単位の請求レートを設定します。', + defaultHourlyRate: 'デフォルトの時間単価', + defaultHourlyRateSubtitle: 'タイムトラッキング用の時間単価請求レートを設定します。', + }, }, reports: { reportsCustomTitleExamples: '例:', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 5b0db59e1cded..90168e064d13c 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -5215,7 +5215,12 @@ _Voor gedetailleerdere instructies, [bezoek onze helpsite](${CONST.NETSUITE_IMPO title: 'Regels', subtitle: 'Vereis bonnetjes, markeer hoge uitgaven en meer.', }, - timeTracking: {title: 'Tijd', subtitle: 'Stel een uurtarief in waarmee medewerkers worden betaald voor hun tijd.'}, + timeTracking: { + title: 'Tijd', + subtitle: 'Stel een uurtarief in waarmee medewerkers worden betaald voor hun tijd.', + defaultHourlyRate: 'Standaard uurtarief', + defaultHourlyRateSubtitle: 'Stel een factureerbaar uurtarief in voor tijdregistratie.', + }, }, reports: { reportsCustomTitleExamples: 'Voorbeelden:', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index dea21683527cd..f8e390b8d3537 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -5208,7 +5208,12 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy title: 'Zasady', subtitle: 'Wymagaj paragonów, oznaczaj wysokie wydatki i nie tylko.', }, - timeTracking: {title: 'Czas', subtitle: 'Ustaw godzinową stawkę rozliczeniową, aby pracownicy byli wynagradzani za swój czas.'}, + timeTracking: { + title: 'Czas', + subtitle: 'Ustaw godzinową stawkę rozliczeniową, aby pracownicy byli wynagradzani za swój czas.', + defaultHourlyRate: 'Domyślna stawka godzinowa', + defaultHourlyRateSubtitle: 'Ustaw godzinową stawkę rozliczeniową do śledzenia czasu.', + }, }, reports: { reportsCustomTitleExamples: 'Przykłady:', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index a4c33c4793ef1..fd197d90bbc86 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -5208,7 +5208,12 @@ _Para instruções mais detalhadas, [visite nosso site de ajuda](${CONST.NETSUIT title: 'Regras', subtitle: 'Exigir recibos, sinalizar gastos elevados e muito mais.', }, - timeTracking: {title: 'Hora', subtitle: 'Defina uma taxa horária faturável para que os funcionários sejam pagos pelo tempo trabalhado.'}, + timeTracking: { + title: 'Hora', + subtitle: 'Defina uma taxa horária faturável para que os funcionários sejam pagos pelo tempo trabalhado.', + defaultHourlyRate: 'Taxa horária padrão', + defaultHourlyRateSubtitle: 'Defina uma taxa horária faturável para o controle de tempo.', + }, }, reports: { reportsCustomTitleExamples: 'Exemplos:', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index cefb15fbbc51c..fb252882260dc 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5102,7 +5102,12 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM title: '规则', subtitle: '要求收据、标记高额支出等。', }, - timeTracking: {title: '时间', subtitle: '为员工设置按小时计费的费率,以便根据他们的工作时间获得报酬。'}, + timeTracking: { + title: '时间', + subtitle: '为员工设置按小时计费的费率,以便根据他们的工作时间获得报酬。', + defaultHourlyRate: '默认时薪', + defaultHourlyRateSubtitle: '为时间跟踪设置按小时计费费率。', + }, }, reports: { reportsCustomTitleExamples: '示例:', From c45e2c63dfbd25d36dec2494ff5a8895a80c11f1 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 27 Jan 2026 14:54:48 +0100 Subject: [PATCH 08/17] Fix spanish translations --- src/languages/es.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 6f6f0350c727a..14d0ca2fe66ac 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4899,8 +4899,8 @@ ${amount} para ${merchant} - ${date}`, timeTracking: { title: 'Tiempo', subtitle: 'Establece una tarifa facturable por hora para que los empleados reciban pago por su tiempo.', - defaultHourlyRate: 'Tasa horaria predeterminada', - defaultHourlyRateSubtitle: 'Establece una tasa facturable por hora para el seguimiento del tiempo.', + defaultHourlyRate: 'Tarifa por hora predeterminada', + defaultHourlyRateSubtitle: 'Establecer una tarifa por hora facturable para el seguimiento de tiempo.', }, }, reports: { From afd0dc17c2d8e3a92d355e9353f0b326491607a4 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 27 Jan 2026 16:26:39 +0100 Subject: [PATCH 09/17] Fix command's name, add pending field --- .../SetPolicyTimeTrackingDefaultRateParams.ts | 8 +++++++ ...datePolicyTimeTrackingDefaultRateParams.ts | 6 ----- src/libs/API/parameters/index.ts | 2 +- src/libs/API/types.ts | 4 ++-- src/libs/actions/Policy/Policy.ts | 21 +++++++++++++--- ...WorkspaceTimeTrackingHourlyRateSection.tsx | 24 ++++++++++--------- .../WorkspaceTimeTrackingRatePage.tsx | 24 +++++++++---------- src/types/onyx/Policy.ts | 2 +- 8 files changed, 54 insertions(+), 37 deletions(-) create mode 100644 src/libs/API/parameters/SetPolicyTimeTrackingDefaultRateParams.ts delete mode 100644 src/libs/API/parameters/UpdatePolicyTimeTrackingDefaultRateParams.ts diff --git a/src/libs/API/parameters/SetPolicyTimeTrackingDefaultRateParams.ts b/src/libs/API/parameters/SetPolicyTimeTrackingDefaultRateParams.ts new file mode 100644 index 0000000000000..9703fb203e932 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyTimeTrackingDefaultRateParams.ts @@ -0,0 +1,8 @@ +type SetPolicyTimeTrackingDefaultRateParams = { + policyID: string; + + /** Default hourly rate in policy's currency units (not subunits). */ + defaultRate: number; +}; + +export default SetPolicyTimeTrackingDefaultRateParams; diff --git a/src/libs/API/parameters/UpdatePolicyTimeTrackingDefaultRateParams.ts b/src/libs/API/parameters/UpdatePolicyTimeTrackingDefaultRateParams.ts deleted file mode 100644 index 1059dfbb777dd..0000000000000 --- a/src/libs/API/parameters/UpdatePolicyTimeTrackingDefaultRateParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -type UpdatePolicyTimeTrackingDefaultRateParams = { - policyID: string; - rate: number; -}; - -export default UpdatePolicyTimeTrackingDefaultRateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index ef43cb2888668..2804a41ac6459 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -470,4 +470,4 @@ export type {default as GetDuplicateTransactionDetailsParams} from './GetDuplica export type {default as RegisterAuthenticationKeyParams} from './RegisterAuthenticationKeyParams'; export type {default as TroubleshootMultifactorAuthenticationParams} from './TroubleshootMultifactorAuthenticationParams'; export type {default as RequestAuthenticationChallengeParams} from './RequestAuthenticationChallengeParams'; -export type {default as UpdatePolicyTimeTrackingDefaultRateParams} from './UpdatePolicyTimeTrackingDefaultRateParams'; +export type {default as SetPolicyTimeTrackingDefaultRateParams} from './SetPolicyTimeTrackingDefaultRateParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index a4045ad3fe50e..9439e8597a900 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -253,7 +253,7 @@ const WRITE_COMMANDS = { ENABLE_POLICY_COMPANY_CARDS: 'EnablePolicyCompanyCards', ENABLE_POLICY_INVOICING: 'EnablePolicyInvoicing', ENABLE_POLICY_TIME_TRACKING: 'EnablePolicyTimeTracking', - UPDATE_POLICY_TIME_TRACKING_DEFAULT_RATE: 'UpdatePolicyTimeTrackingDefaultRate', + SET_POLICY_TIME_TRACKING_DEFAULT_RATE: 'SetPolicyTimeTrackingDefaultRate', SET_POLICY_RULES_ENABLED: 'SetPolicyRulesEnabled', SET_POLICY_EXPENSE_MAX_AMOUNT_NO_RECEIPT: 'SetPolicyExpenseMaxAmountNoReceipt', SET_POLICY_EXPENSE_MAX_AMOUNT_NO_ITEMIZED_RECEIPT: 'SetPolicyExpenseMaxAmountNoItemizedReceipt', @@ -890,7 +890,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_DISTANCE_TAX_CLAIMABLE_VALUE]: Parameters.UpdatePolicyDistanceRateValueParams; [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_ENABLED]: Parameters.SetPolicyDistanceRatesEnabledParams; [WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams; - [WRITE_COMMANDS.UPDATE_POLICY_TIME_TRACKING_DEFAULT_RATE]: Parameters.UpdatePolicyTimeTrackingDefaultRateParams; + [WRITE_COMMANDS.SET_POLICY_TIME_TRACKING_DEFAULT_RATE]: Parameters.SetPolicyTimeTrackingDefaultRateParams; [WRITE_COMMANDS.DISMISS_TRACK_EXPENSE_ACTIONABLE_WHISPER]: Parameters.DismissTrackExpenseActionableWhisperParams; [WRITE_COMMANDS.UPDATE_BILLING_CARD_CURRENCY]: Parameters.UpdateBillingCurrencyParams; [WRITE_COMMANDS.CONVERT_TRACKED_EXPENSE_TO_REQUEST]: Parameters.ConvertTrackedExpenseToRequestParams; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 5943cf191f287..29f09636f8a9c 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -4709,8 +4709,9 @@ function enablePolicyTimeTracking(policyID: string, enabled: boolean) { /** * Update policy's time tracking default hourly rate. + * The rate should be passed in currency units, not subunits. */ -function updatePolicyTimeTrackingDefaultRate(policyID: string, rate: number) { +function setPolicyTimeTrackingDefaultRate(policyID: string, rate: number) { const onyxData: OnyxData = { optimisticData: [ { @@ -4722,12 +4723,26 @@ function updatePolicyTimeTrackingDefaultRate(policyID: string, rate: number) { rate, }, }, + pendingFields: { + timeTrackingDefaultRate: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ], + finallyData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: { + timeTrackingDefaultRate: null, + }, }, }, ], }; - API.write(WRITE_COMMANDS.UPDATE_POLICY_TIME_TRACKING_DEFAULT_RATE, {policyID, rate}, onyxData); + API.write(WRITE_COMMANDS.SET_POLICY_TIME_TRACKING_DEFAULT_RATE, {policyID, defaultRate: rate}, onyxData); } function openPolicyMoreFeaturesPage(policyID: string) { @@ -6813,5 +6828,5 @@ export { inviteWorkspaceEmployeesToUber, setWorkspaceConfirmationCurrency, setPolicyRequireCompanyCardsEnabled, - updatePolicyTimeTrackingDefaultRate, + setPolicyTimeTrackingDefaultRate, }; diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx index 896ffc7e9684b..bbe27dc945f67 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx @@ -1,5 +1,6 @@ import React from 'react'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -13,7 +14,6 @@ import ROUTES from '@src/ROUTES'; function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); return ( @@ -21,19 +21,21 @@ function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) title={translate('workspace.moreFeatures.timeTracking.defaultHourlyRate')} subtitle={translate('workspace.moreFeatures.timeTracking.defaultHourlyRateSubtitle')} titleStyles={styles.accountSettingsSectionTitle} - childrenStyles={[styles.gap6, styles.pt6]} + childrenStyles={styles.pt6} subtitleMuted isCentralPane > - Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING_RATE.getRoute(policyID))} - style={styles.sectionMenuItemTopDescription} - /> + + Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING_RATE.getRoute(policyID))} + style={styles.sectionMenuItemTopDescription} + /> + ); } diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx index 8f7f247b3ffe9..2cc1839b8e7ac 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx @@ -2,7 +2,6 @@ import React from 'react'; import AmountForm from '@components/AmountForm'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -10,7 +9,7 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; -import {updatePolicyTimeTrackingDefaultRate} from '@libs/actions/Policy/Policy'; +import {setPolicyTimeTrackingDefaultRate} from '@libs/actions/Policy/Policy'; import {convertToFrontendAmountAsString} from '@libs/CurrencyUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; @@ -25,16 +24,16 @@ import INPUT_IDS from '@src/types/form/WorkspaceTimeTrackingRateForm'; type WorkspaceTimeTrackingRatePageProps = PlatformStackScreenProps; -function WorkspaceTimeTrackingRatePage({route}: WorkspaceTimeTrackingRatePageProps) { - const {policyID} = route.params; - +function WorkspaceTimeTrackingRatePage({ + route: { + params: {policyID}, + }, +}: WorkspaceTimeTrackingRatePageProps) { const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const styles = useThemeStyles(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); - - const validate = (values: FormOnyxValues): FormInputErrors => - getFieldRequiredErrors(values, [INPUT_IDS.RATE]); + const currency = policy?.outputCurrency ?? CONST.CURRENCY.USD; if (!policy) { return ; @@ -52,21 +51,20 @@ function WorkspaceTimeTrackingRatePage({route}: WorkspaceTimeTrackingRatePagePro formID={ONYXKEYS.FORMS.WORKSPACE_TIME_TRACKING_RATE_FORM} submitButtonText={translate('common.save')} onSubmit={(values) => { - updatePolicyTimeTrackingDefaultRate(policyID, Number.parseFloat(values[INPUT_IDS.RATE])); + setPolicyTimeTrackingDefaultRate(policyID, Number.parseFloat(values[INPUT_IDS.RATE])); Navigation.dismissModal(); }} style={[styles.flex1, styles.mh5]} enabledWhenOffline - validate={validate} - shouldHideFixErrorsAlert + validate={(values) => getFieldRequiredErrors(values, [INPUT_IDS.RATE])} addBottomSafeAreaPadding > , - 'addWorkspaceRoom' | keyof ACHAccount | keyof Attributes | 'isTimeTrackingEnabled' + 'addWorkspaceRoom' | keyof ACHAccount | keyof Attributes | 'isTimeTrackingEnabled' | 'timeTrackingDefaultRate' >; /** Stages of policy connection sync */ From 3a9262283a18652afdec622c064ebf6f7d820839 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 27 Jan 2026 17:50:36 +0100 Subject: [PATCH 10/17] Remove addBottomSafeAreaPadding in WorkspaceTimeTrackingRatePage form --- .../workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx index 2cc1839b8e7ac..261ea5223ed1c 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx @@ -57,7 +57,6 @@ function WorkspaceTimeTrackingRatePage({ style={[styles.flex1, styles.mh5]} enabledWhenOffline validate={(values) => getFieldRequiredErrors(values, [INPUT_IDS.RATE])} - addBottomSafeAreaPadding > Date: Wed, 28 Jan 2026 15:44:21 +0100 Subject: [PATCH 11/17] Rename RATE to DEFAULT_RATE in time tracking components --- src/ONYXKEYS.ts | 6 +++--- src/ROUTES.ts | 2 +- src/SCREENS.ts | 2 +- .../AppNavigator/ModalStackNavigators/index.tsx | 2 +- .../linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 4 ++-- src/libs/Navigation/types.ts | 2 +- ...sx => WorkspaceTimeTrackingDefaultRatePage.tsx} | 14 +++++++------- ...=> WorkspaceTimeTrackingDefaultRateSection.tsx} | 6 +++--- .../timeTracking/WorkspaceTimeTrackingPage.tsx | 4 ++-- ....ts => WorkspaceTimeTrackingDefaultRateForm.ts} | 4 ++-- src/types/form/index.ts | 2 +- 12 files changed, 25 insertions(+), 25 deletions(-) rename src/pages/workspace/timeTracking/{WorkspaceTimeTrackingRatePage.tsx => WorkspaceTimeTrackingDefaultRatePage.tsx} (87%) rename src/pages/workspace/timeTracking/{WorkspaceTimeTrackingHourlyRateSection.tsx => WorkspaceTimeTrackingDefaultRateSection.tsx} (90%) rename src/types/form/{WorkspaceTimeTrackingRateForm.ts => WorkspaceTimeTrackingDefaultRateForm.ts} (71%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 5775a45739f41..b40e2be8214bf 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -910,8 +910,8 @@ const ONYXKEYS = { WORKSPACE_INVOICES_COMPANY_NAME_FORM_DRAFT: 'workspaceInvoicesCompanyNameFormDraft', WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM: 'workspaceInvoicesCompanyWebsiteForm', WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM_DRAFT: 'workspaceInvoicesCompanyWebsiteFormDraft', - WORKSPACE_TIME_TRACKING_RATE_FORM: 'workspaceTimeTrackingRateForm', - WORKSPACE_TIME_TRACKING_RATE_FORM_DRAFT: 'workspaceTimeTrackingRateFormDraft', + WORKSPACE_TIME_TRACKING_DEFAULT_RATE_FORM: 'workspaceTimeTrackingDefaultRateForm', + WORKSPACE_TIME_TRACKING_DEFAULT_RATE_FORM_DRAFT: 'workspaceTimeTrackingDefaultRateFormDraft', NEW_CHAT_NAME_FORM: 'newChatNameForm', NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft', SUBSCRIPTION_SIZE_FORM: 'subscriptionSizeForm', @@ -1069,7 +1069,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; [ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_NAME_FORM]: FormTypes.WorkspaceInvoicesCompanyNameForm; [ONYXKEYS.FORMS.WORKSPACE_INVOICES_COMPANY_WEBSITE_FORM]: FormTypes.WorkspaceInvoicesCompanyWebsiteForm; - [ONYXKEYS.FORMS.WORKSPACE_TIME_TRACKING_RATE_FORM]: FormTypes.WorkspaceTimeTrackingRateForm; + [ONYXKEYS.FORMS.WORKSPACE_TIME_TRACKING_DEFAULT_RATE_FORM]: FormTypes.WorkspaceTimeTrackingDefaultRateForm; [ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm; [ONYXKEYS.FORMS.SUBSCRIPTION_SIZE_FORM]: FormTypes.SubscriptionSizeForm; [ONYXKEYS.FORMS.ISSUE_NEW_EXPENSIFY_CARD_FORM]: FormTypes.IssueNewExpensifyCardForm; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 5213aff918c35..e12cf38821566 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2632,7 +2632,7 @@ const ROUTES = { return `workspaces/${policyID}/time-tracking` as const; }, }, - WORKSPACE_TIME_TRACKING_RATE: { + WORKSPACE_TIME_TRACKING_DEFAULT_RATE: { route: 'workspaces/:policyID/time-tracking/rate', getRoute: (policyID: string) => `workspaces/${policyID}/time-tracking/rate` as const, }, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 57a78a10240a0..768d7aeee52f4 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -768,7 +768,7 @@ const SCREENS = { PER_DIEM_EDIT_AMOUNT: 'Per_Diem_Edit_Amount', PER_DIEM_EDIT_CURRENCY: 'Per_Diem_Edit_Currency', TIME_TRACKING: 'Time_Tracking', - TIME_TRACKING_RATE: 'Time_Tracking_Rate', + TIME_TRACKING_DEFAULT_RATE: 'Time_Tracking_Default_Rate', }, EDIT_REQUEST: { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 41ee5728be908..978c6ca12dfff 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -833,7 +833,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/receiptPartners/InviteReceiptPartnerPolicyPage').default, [SCREENS.WORKSPACE.RECEIPT_PARTNERS_INVITE_EDIT]: () => require('../../../../pages/workspace/receiptPartners/EditInviteReceiptPartnerPolicyPage').default, [SCREENS.WORKSPACE.RECEIPT_PARTNERS_CHANGE_BILLING_ACCOUNT]: () => require('../../../../pages/workspace/receiptPartners/ChangeReceiptBillingAccountPage').default, - [SCREENS.WORKSPACE.TIME_TRACKING_RATE]: () => require('../../../../pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage').default, + [SCREENS.WORKSPACE.TIME_TRACKING_DEFAULT_RATE]: () => require('../../../../pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage').default, [SCREENS.DOMAIN.VERIFY]: () => require('../../../../pages/domain/SamlVerifyDomainPage').default, [SCREENS.DOMAIN.VERIFIED]: () => require('../../../../pages/domain/SamlDomainVerifiedPage').default, [SCREENS.DOMAIN.ADMIN_DETAILS]: () => require('../../../../pages/domain/Admins/DomainAdminDetailsPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index 7dda058782d10..d1ee5214f8035 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -300,7 +300,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.PER_DIEM_EDIT_CURRENCY]: { path: ROUTES.WORKSPACE_PER_DIEM_EDIT_CURRENCY.route, }, - [SCREENS.WORKSPACE.TIME_TRACKING_RATE]: { - path: ROUTES.WORKSPACE_TIME_TRACKING_RATE.route, + [SCREENS.WORKSPACE.TIME_TRACKING_DEFAULT_RATE]: { + path: ROUTES.WORKSPACE_TIME_TRACKING_DEFAULT_RATE.route, }, [SCREENS.DOMAIN.VERIFY]: { path: ROUTES.DOMAIN_VERIFY.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 04f25174fc10b..61a1345ffa4ab 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1428,7 +1428,7 @@ type SettingsNavigatorParamList = { rateID: string; subRateID: string; }; - [SCREENS.WORKSPACE.TIME_TRACKING_RATE]: { + [SCREENS.WORKSPACE.TIME_TRACKING_DEFAULT_RATE]: { policyID: string; }; [SCREENS.DOMAIN.VERIFY]: { diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx similarity index 87% rename from src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx rename to src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx index 261ea5223ed1c..9c64896f894d1 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingRatePage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx @@ -20,15 +20,15 @@ import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import INPUT_IDS from '@src/types/form/WorkspaceTimeTrackingRateForm'; +import INPUT_IDS from '@src/types/form/WorkspaceTimeTrackingDefaultRateForm'; -type WorkspaceTimeTrackingRatePageProps = PlatformStackScreenProps; +type WorkspaceTimeTrackingDefaultRatePageProps = PlatformStackScreenProps; -function WorkspaceTimeTrackingRatePage({ +function WorkspaceTimeTrackingDefaultRatePage({ route: { params: {policyID}, }, -}: WorkspaceTimeTrackingRatePageProps) { +}: WorkspaceTimeTrackingDefaultRatePageProps) { const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const styles = useThemeStyles(); @@ -45,10 +45,10 @@ function WorkspaceTimeTrackingRatePage({ policyID={policyID} featureName={CONST.POLICY.MORE_FEATURES.IS_TIME_TRACKING_ENABLED} > - + { setPolicyTimeTrackingDefaultRate(policyID, Number.parseFloat(values[INPUT_IDS.RATE])); @@ -74,4 +74,4 @@ function WorkspaceTimeTrackingRatePage({ ); } -export default WorkspaceTimeTrackingRatePage; +export default WorkspaceTimeTrackingDefaultRatePage; diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx similarity index 90% rename from src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx rename to src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx index bbe27dc945f67..d408990c1c236 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingHourlyRateSection.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx @@ -11,7 +11,7 @@ import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) { +function WorkspaceTimeTrackingDefaultRateSection({policyID}: {policyID: string}) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); @@ -32,7 +32,7 @@ function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) shouldShowRightIcon title={policy ? convertAmountToDisplayString(getDefaultTimeTrackingRate(policy), policy?.outputCurrency) : ''} description={translate('workspace.moreFeatures.timeTracking.defaultHourlyRate')} - onPress={() => Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING_RATE.getRoute(policyID))} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_TIME_TRACKING_DEFAULT_RATE.getRoute(policyID))} style={styles.sectionMenuItemTopDescription} /> @@ -40,4 +40,4 @@ function WorkspaceTimeTrackingHourlyRateSection({policyID}: {policyID: string}) ); } -export default WorkspaceTimeTrackingHourlyRateSection; +export default WorkspaceTimeTrackingDefaultRateSection; diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx index 602ff46b9f356..79884262b71c8 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingPage.tsx @@ -11,7 +11,7 @@ import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; -import WorkspaceTimeTrackingHourlyRateSection from './WorkspaceTimeTrackingHourlyRateSection'; +import WorkspaceTimeTrackingDefaultRateSection from './WorkspaceTimeTrackingDefaultRateSection'; type WorkspaceTimeTrackingPageProps = PlatformStackScreenProps; @@ -40,7 +40,7 @@ function WorkspaceTimeTrackingPage({route}: WorkspaceTimeTrackingPageProps) { {(_, policyID) => !!policyID && ( - + ) } diff --git a/src/types/form/WorkspaceTimeTrackingRateForm.ts b/src/types/form/WorkspaceTimeTrackingDefaultRateForm.ts similarity index 71% rename from src/types/form/WorkspaceTimeTrackingRateForm.ts rename to src/types/form/WorkspaceTimeTrackingDefaultRateForm.ts index 27798a2185b12..ecb2da919422f 100644 --- a/src/types/form/WorkspaceTimeTrackingRateForm.ts +++ b/src/types/form/WorkspaceTimeTrackingDefaultRateForm.ts @@ -7,12 +7,12 @@ const INPUT_IDS = { type InputID = ValueOf; -type WorkspaceTimeTrackingRateForm = Form< +type WorkspaceTimeTrackingDefaultRateForm = Form< InputID, { [INPUT_IDS.RATE]: string; } >; -export type {WorkspaceTimeTrackingRateForm}; +export type {WorkspaceTimeTrackingDefaultRateForm}; export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index 76a4dd7a7e34b..5b32e87f0ca8a 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -105,4 +105,4 @@ export type {SplitExpenseEditDateForm} from './SplitExpenseEditDateForm'; export type {ResetDomainForm} from './ResetDomainForm'; export type {ExpenseRuleForm} from './ExpenseRuleForm'; export type {AddDomainMemberForm} from './AddDomainMemberForm'; -export type {WorkspaceTimeTrackingRateForm} from './WorkspaceTimeTrackingRateForm'; +export type {WorkspaceTimeTrackingDefaultRateForm} from './WorkspaceTimeTrackingDefaultRateForm'; From 6d953c7147dcc4b4a4cdf9d1d2b335e2f81bf2b5 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Wed, 28 Jan 2026 17:58:57 +0100 Subject: [PATCH 12/17] Implement review suggestions --- src/libs/PolicyUtils.ts | 4 ++-- src/pages/iou/request/step/IOURequestStepHours.tsx | 2 +- .../iou/request/step/IOURequestStepTimeWorkspace.tsx | 2 +- .../WorkspaceTimeTrackingDefaultRatePage.tsx | 3 ++- .../WorkspaceTimeTrackingDefaultRateSection.tsx | 6 +++++- .../timeTracking/WorkspaceTimeTrackingPage.tsx | 11 ++++------- src/selectors/Policy.ts | 11 ++++++++++- 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index daf5eb34c6dc9..7d62777dfb7b4 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1731,8 +1731,8 @@ function isTimeTrackingEnabled(policy: OnyxEntry): boolean { return !!policy?.units?.time?.enabled; } -function getDefaultTimeTrackingRate(policy: Policy): number | undefined { - return policy.units?.time?.rate ? convertToBackendAmount(policy.units?.time?.rate) : undefined; +function getDefaultTimeTrackingRate(policy: Partial>): number | undefined { + return policy?.units?.time?.rate ? convertToBackendAmount(policy.units.time.rate) : undefined; } export { diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index df9a527a9f2b3..d88be50cf8460 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -53,7 +53,7 @@ function IOURequestStepHours({ const {accountID} = useCurrentUserPersonalDetails(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); const currency = policy?.outputCurrency ?? CONST.CURRENCY.USD; - const defaultPolicyRate = policy ? getDefaultTimeTrackingRate(policy) : undefined; + const defaultPolicyRate = getDefaultTimeTrackingRate(policy); const rate = transaction?.comment?.units?.rate ?? defaultPolicyRate; const {translate} = useLocalize(); diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx index 4d726c798ab72..825fab3a1e6cf 100644 --- a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -37,7 +37,7 @@ function IOURequestStepTimeWorkspace({route, navigation}: IOURequestStepTimeWork setTransactionReport(transactionID, {reportID: policyExpenseChat.reportID}, isTransactionDraft); setMoneyRequestParticipantsFromReport(transactionID, policyExpenseChat, accountID); - const defaultRate = policy ? getDefaultTimeTrackingRate(policy) : undefined; + const defaultRate = getDefaultTimeTrackingRate(policy); if (defaultRate) { setMoneyRequestTimeRate(transactionID, defaultRate, isTransactionDraft); } diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx index 9c64896f894d1..4b095c8533788 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx @@ -20,6 +20,7 @@ import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; +import {policyTimeTrackingSelector} from '@src/selectors/Policy'; import INPUT_IDS from '@src/types/form/WorkspaceTimeTrackingDefaultRateForm'; type WorkspaceTimeTrackingDefaultRatePageProps = PlatformStackScreenProps; @@ -32,7 +33,7 @@ function WorkspaceTimeTrackingDefaultRatePage({ const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const styles = useThemeStyles(); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true, selector: policyTimeTrackingSelector}); const currency = policy?.outputCurrency ?? CONST.CURRENCY.USD; if (!policy) { diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx index d408990c1c236..ca6f91c93a54d 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx @@ -10,11 +10,15 @@ import Navigation from '@libs/Navigation/Navigation'; import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {policyTimeTrackingSelector} from '@src/selectors/Policy'; function WorkspaceTimeTrackingDefaultRateSection({policyID}: {policyID: string}) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + canBeMissing: true, + selector: policyTimeTrackingSelector, + }); return (
; function WorkspaceTimeTrackingPage({route}: WorkspaceTimeTrackingPageProps) { + const {policyID} = route.params; const {translate} = useLocalize(); const styles = useThemeStyles(); const illustrations = useMemoizedLazyIllustrations(['Clock']); @@ -37,13 +38,9 @@ function WorkspaceTimeTrackingPage({route}: WorkspaceTimeTrackingPageProps) { icon={illustrations.Clock} addBottomSafeAreaPadding > - {(_, policyID) => - !!policyID && ( - - - - ) - } + + + ); diff --git a/src/selectors/Policy.ts b/src/selectors/Policy.ts index 3361cd89bee04..a0230b8666326 100644 --- a/src/selectors/Policy.ts +++ b/src/selectors/Policy.ts @@ -37,4 +37,13 @@ const createAllPolicyReportFieldsSelector = (policies: OnyxCollection, l return Object.fromEntries(nonFormulaReportFields); }; -export {activePolicySelector, createPoliciesSelector, createAllPolicyReportFieldsSelector, ownerPoliciesSelector, activeAdminPoliciesSelector}; +const policyTimeTrackingSelector = (policy: OnyxEntry) => + policy && { + outputCurrency: policy.outputCurrency, + pendingFields: { + timeTrackingDefaultRate: policy.pendingFields?.timeTrackingDefaultRate, + }, + units: policy.units, + }; + +export {activePolicySelector, createPoliciesSelector, createAllPolicyReportFieldsSelector, ownerPoliciesSelector, activeAdminPoliciesSelector, policyTimeTrackingSelector}; From fa13cb9a8823e54a447762b1b68c9b47ec85c423 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Wed, 28 Jan 2026 18:15:45 +0100 Subject: [PATCH 13/17] Change copy for Time Tracking feature subtitle --- src/languages/de.ts | 3 +-- src/languages/en.ts | 3 +-- src/languages/es.ts | 3 +-- src/languages/fr.ts | 3 +-- src/languages/it.ts | 3 +-- src/languages/ja.ts | 3 +-- src/languages/nl.ts | 3 +-- src/languages/pl.ts | 3 +-- src/languages/pt-BR.ts | 3 +-- src/languages/zh-hans.ts | 3 +-- .../timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx | 2 +- 11 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 0735fc470b208..0830f49bfe1bd 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -5241,9 +5241,8 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU }, timeTracking: { title: 'Zeit', - subtitle: 'Legen Sie einen abrechenbaren Stundensatz fest, damit Mitarbeitende für ihre Zeit bezahlt werden.', + subtitle: 'Legen Sie einen abrechenbaren Stundensatz für die Zeiterfassung fest.', defaultHourlyRate: 'Standardstundensatz', - defaultHourlyRateSubtitle: 'Legen Sie einen abrechenbaren Stundensatz für die Zeiterfassung fest.', }, }, reports: { diff --git a/src/languages/en.ts b/src/languages/en.ts index 0fa6bdd3e45b8..0d3d101af8a59 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5147,9 +5147,8 @@ const translations = { }, timeTracking: { title: 'Time', - subtitle: 'Set an hourly billable rate for employees to get paid for their time.', + subtitle: 'Set a billable hourly rate for time tracking.', defaultHourlyRate: 'Default hourly rate', - defaultHourlyRateSubtitle: 'Set an hourly billable rate for time tracking.', }, }, reports: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 14d0ca2fe66ac..897fc25c28dba 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4898,9 +4898,8 @@ ${amount} para ${merchant} - ${date}`, }, timeTracking: { title: 'Tiempo', - subtitle: 'Establece una tarifa facturable por hora para que los empleados reciban pago por su tiempo.', + subtitle: 'Establecer una tarifa por hora facturable para el seguimiento de tiempo.', defaultHourlyRate: 'Tarifa por hora predeterminada', - defaultHourlyRateSubtitle: 'Establecer una tarifa por hora facturable para el seguimiento de tiempo.', }, }, reports: { diff --git a/src/languages/fr.ts b/src/languages/fr.ts index defa7a5b0fc21..7ba9c92ae527d 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -5249,9 +5249,8 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. }, timeTracking: { title: 'Heure', - subtitle: 'Définissez un taux horaire facturable pour que les employés soient rémunérés pour leur temps.', + subtitle: 'Définissez un taux horaire facturable pour le suivi du temps.', defaultHourlyRate: 'Taux horaire par défaut', - defaultHourlyRateSubtitle: 'Définissez un taux horaire facturable pour le suivi du temps.', }, }, reports: { diff --git a/src/languages/it.ts b/src/languages/it.ts index 5786e5d4dbf0c..cbea6ce17450f 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -5228,9 +5228,8 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. }, timeTracking: { title: 'Ora', - subtitle: 'Imposta una tariffa oraria fatturabile per consentire ai dipendenti di essere pagati per il loro tempo.', + subtitle: 'Imposta una tariffa oraria fatturabile per il monitoraggio del tempo.', defaultHourlyRate: 'Tariffa oraria predefinita', - defaultHourlyRateSubtitle: 'Imposta una tariffa oraria fatturabile per il monitoraggio del tempo.', }, }, reports: { diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 2e1769aec2c91..8fbed6a65ed2f 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -5194,9 +5194,8 @@ _より詳しい手順については、[ヘルプサイトをご覧ください }, timeTracking: { title: '時間', - subtitle: '従業員が作業時間に対して支払いを受けられるよう、時間単位の請求レートを設定します。', + subtitle: 'タイムトラッキング用の時間単価請求レートを設定します。', defaultHourlyRate: 'デフォルトの時間単価', - defaultHourlyRateSubtitle: 'タイムトラッキング用の時間単価請求レートを設定します。', }, }, reports: { diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 90168e064d13c..74d918e2ec7bc 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -5217,9 +5217,8 @@ _Voor gedetailleerdere instructies, [bezoek onze helpsite](${CONST.NETSUITE_IMPO }, timeTracking: { title: 'Tijd', - subtitle: 'Stel een uurtarief in waarmee medewerkers worden betaald voor hun tijd.', + subtitle: 'Stel een factureerbaar uurtarief in voor tijdregistratie.', defaultHourlyRate: 'Standaard uurtarief', - defaultHourlyRateSubtitle: 'Stel een factureerbaar uurtarief in voor tijdregistratie.', }, }, reports: { diff --git a/src/languages/pl.ts b/src/languages/pl.ts index f8e390b8d3537..2adeb6318fcb3 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -5210,9 +5210,8 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy }, timeTracking: { title: 'Czas', - subtitle: 'Ustaw godzinową stawkę rozliczeniową, aby pracownicy byli wynagradzani za swój czas.', + subtitle: 'Ustaw godzinową stawkę rozliczeniową do śledzenia czasu.', defaultHourlyRate: 'Domyślna stawka godzinowa', - defaultHourlyRateSubtitle: 'Ustaw godzinową stawkę rozliczeniową do śledzenia czasu.', }, }, reports: { diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index fd197d90bbc86..17ed7b16e40b3 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -5210,9 +5210,8 @@ _Para instruções mais detalhadas, [visite nosso site de ajuda](${CONST.NETSUIT }, timeTracking: { title: 'Hora', - subtitle: 'Defina uma taxa horária faturável para que os funcionários sejam pagos pelo tempo trabalhado.', + subtitle: 'Defina uma taxa horária faturável para o controle de tempo.', defaultHourlyRate: 'Taxa horária padrão', - defaultHourlyRateSubtitle: 'Defina uma taxa horária faturável para o controle de tempo.', }, }, reports: { diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index fb252882260dc..f22e11b41145b 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5104,9 +5104,8 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM }, timeTracking: { title: '时间', - subtitle: '为员工设置按小时计费的费率,以便根据他们的工作时间获得报酬。', + subtitle: '为时间跟踪设置按小时计费费率。', defaultHourlyRate: '默认时薪', - defaultHourlyRateSubtitle: '为时间跟踪设置按小时计费费率。', }, }, reports: { diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx index ca6f91c93a54d..f13a95616cc66 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRateSection.tsx @@ -23,7 +23,7 @@ function WorkspaceTimeTrackingDefaultRateSection({policyID}: {policyID: string}) return (
Date: Wed, 28 Jan 2026 18:28:02 +0100 Subject: [PATCH 14/17] Fix after merge --- babel.config.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/babel.config.js b/babel.config.js index f262c5454a7cd..9d6ecba10d938 100644 --- a/babel.config.js +++ b/babel.config.js @@ -65,7 +65,7 @@ const webpack = { }; const metro = { - presets: [require('@react-native/babel-preset')], + presets: [[require('@react-native/babel-preset'), {disableImportExportTransform: true}]], plugins: [ ['babel-plugin-react-compiler', ReactCompilerConfig], // must run first! @@ -177,5 +177,13 @@ module.exports = (api) => { const runningIn = api.caller((args = {}) => args.name); console.debug(' - running in: ', runningIn); + const isJest = runningIn === 'babel-jest'; + if (isJest) { + return { + ...metro, + presets: [[require('@react-native/babel-preset'), {disableImportExportTransform: false}]], + }; + } + return ['metro', 'babel-jest'].includes(runningIn) ? metro : webpack; -}; +}; \ No newline at end of file From 1b1b5aed655a2bbc415c4c5f35ff34f67d7514f5 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Wed, 28 Jan 2026 18:32:00 +0100 Subject: [PATCH 15/17] Fix after merge 2 --- babel.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/babel.config.js b/babel.config.js index 9d6ecba10d938..374ab6122fda3 100644 --- a/babel.config.js +++ b/babel.config.js @@ -186,4 +186,4 @@ module.exports = (api) => { } return ['metro', 'babel-jest'].includes(runningIn) ? metro : webpack; -}; \ No newline at end of file +}; From 892ba384ce00f7e66fb46b5bcb53db93a0faeb23 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Thu, 29 Jan 2026 10:09:39 +0100 Subject: [PATCH 16/17] Check for loading onyx value in WorkspaceTimeTrackingDefaultRatePage --- .../timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx index 4b095c8533788..17b97aa35bab3 100644 --- a/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx +++ b/src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx @@ -22,6 +22,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import {policyTimeTrackingSelector} from '@src/selectors/Policy'; import INPUT_IDS from '@src/types/form/WorkspaceTimeTrackingDefaultRateForm'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; type WorkspaceTimeTrackingDefaultRatePageProps = PlatformStackScreenProps; @@ -33,10 +34,10 @@ function WorkspaceTimeTrackingDefaultRatePage({ const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const styles = useThemeStyles(); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true, selector: policyTimeTrackingSelector}); + const [policy, policyFetchStatus] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true, selector: policyTimeTrackingSelector}); const currency = policy?.outputCurrency ?? CONST.CURRENCY.USD; - if (!policy) { + if (!policy || isLoadingOnyxValue(policyFetchStatus)) { return ; } From 3c02538267aa62aae6d5eba7032c93a30620d12e Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Thu, 29 Jan 2026 13:14:25 +0100 Subject: [PATCH 17/17] Fix offline indicator jumping on dismiss keyboard on mweb safari --- src/pages/workspace/WorkspacePageWithSections.tsx | 6 +++++- .../workspace/timeTracking/WorkspaceTimeTrackingPage.tsx | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 75a28c0f3f779..77585ce1e9248 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -92,6 +92,9 @@ type WorkspacePageWithSectionsProps = WithPolicyAndFullscreenLoadingProps & /** Content to be added as modal */ modals?: ReactNode; + + /** Whether to use the maxHeight (true) or use the 100% of the height (false) */ + shouldEnableMaxHeight?: boolean; }; function fetchData(policyID: string | undefined, skipVBBACal?: boolean) { @@ -118,6 +121,7 @@ function WorkspacePageWithSections({ shouldShowLoading = true, shouldShowOfflineIndicatorInWideScreen = false, shouldShowNonAdmin = false, + shouldEnableMaxHeight = true, headerContent, testID, shouldShowNotFoundPage = false, @@ -200,7 +204,7 @@ function WorkspacePageWithSections({