-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Allow creating time expenses from global menu #79780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8ab23f4
34a3e84
f6d3ab9
49e4c39
5eb6205
52d9c44
704325d
eb00d06
f94caa3
97a75f2
f35e93e
a2579fa
3f9b62d
74a112f
c8ffb45
9638050
b18ae23
c4d5329
9a8f960
27b3ef2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| import React from 'react'; | ||
| import {View} from 'react-native'; | ||
| import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; | ||
| import SearchBar from '@components/SearchBar'; | ||
| import SelectionList from '@components/SelectionList'; | ||
| import type {ListItem} from '@components/SelectionList/ListItem/types'; | ||
| import UserListItem from '@components/SelectionList/ListItem/UserListItem'; | ||
| import Text from '@components/Text'; | ||
| import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; | ||
| import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; | ||
| import useLocalize from '@hooks/useLocalize'; | ||
| import useOnyx from '@hooks/useOnyx'; | ||
| import useSearchResults from '@hooks/useSearchResults'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {sortWorkspacesBySelected} from '@libs/PolicyUtils'; | ||
| import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; | ||
| import tokenizedSearch from '@libs/tokenizedSearch'; | ||
| import CONST from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import type SCREENS from '@src/SCREENS'; | ||
| import type {Policy} from '@src/types/onyx'; | ||
| import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound'; | ||
| import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; | ||
|
|
||
| type WorkspaceListItem = ListItem & { | ||
| value: string; | ||
| }; | ||
|
|
||
| type BaseRequestStepWorkspaceProps = WithFullTransactionOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.CREATE> & { | ||
| /** Function returning available workspaces for the list. */ | ||
| getPolicies: (allPolicies: OnyxCollection<Policy>, currentUserLogin: string | undefined) => Policy[]; | ||
|
|
||
| /** Function to run after selecting a workspace. */ | ||
| onSelectWorkspace: (policy: OnyxEntry<Policy>) => void; | ||
| }; | ||
|
|
||
| function BaseRequestStepWorkspace({transaction, getPolicies, onSelectWorkspace}: BaseRequestStepWorkspaceProps) { | ||
| const icons = useMemoizedLazyExpensifyIcons(['FallbackWorkspaceAvatar']); | ||
| const styles = useThemeStyles(); | ||
| const {translate, localeCompare} = useLocalize(); | ||
|
|
||
| const {login: currentUserLogin} = useCurrentUserPersonalDetails(); | ||
| const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); | ||
| const selectedWorkspace = transaction?.participants?.[0]; | ||
| const availableWorkspaces = getPolicies(allPolicies, currentUserLogin); | ||
| const workspaceOptions: WorkspaceListItem[] = availableWorkspaces | ||
| .sort((policy1, policy2) => | ||
| sortWorkspacesBySelected( | ||
| {policyID: policy1.id, name: policy1.name}, | ||
| {policyID: policy2.id, name: policy2.name}, | ||
| selectedWorkspace?.policyID ? [selectedWorkspace?.policyID] : [], | ||
| localeCompare, | ||
| ), | ||
| ) | ||
| .map((policy) => ({ | ||
| text: policy.name, | ||
| value: policy.id, | ||
| keyForList: policy.id, | ||
| icons: [ | ||
| { | ||
| id: policy.id, | ||
| source: policy?.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy.name), | ||
| fallbackIcon: icons.FallbackWorkspaceAvatar, | ||
| name: policy.name, | ||
| type: CONST.ICON_TYPE_WORKSPACE, | ||
| }, | ||
| ], | ||
| isSelected: selectedWorkspace?.policyID === policy.id, | ||
| })); | ||
|
|
||
| const filterWorkspace = (workspaceOption: WorkspaceListItem, searchInput: string) => { | ||
| const results = tokenizedSearch([workspaceOption], searchInput, (option) => [option.text ?? '']); | ||
| return results.length > 0; | ||
| }; | ||
| const sortWorkspaces = (data: WorkspaceListItem[]) => data.sort((a, b) => localeCompare(a.text ?? '', b?.text ?? '')); | ||
| const [inputValue, setInputValue, filteredWorkspaceOptions] = useSearchResults(workspaceOptions, filterWorkspace, sortWorkspaces); | ||
|
|
||
| const selectWorkspace = (item: WorkspaceListItem) => onSelectWorkspace(allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]); | ||
|
|
||
| return ( | ||
| <> | ||
| {workspaceOptions.length > CONST.SEARCH_ITEM_LIMIT ? ( | ||
| <SearchBar | ||
| label={translate('workspace.common.findWorkspace')} | ||
| inputValue={inputValue} | ||
| onChangeText={setInputValue} | ||
| shouldShowEmptyState={workspaceOptions.length > 0 && filteredWorkspaceOptions.length === 0} | ||
| /> | ||
| ) : ( | ||
| <View style={[styles.optionsListSectionHeader]}> | ||
| <Text style={[styles.ph5, styles.textLabelSupporting]}>{translate('iou.chooseWorkspace')}</Text> | ||
| </View> | ||
| )} | ||
| <SelectionList | ||
| key={selectedWorkspace?.policyID} | ||
| data={filteredWorkspaceOptions} | ||
| onSelectRow={selectWorkspace} | ||
| shouldSingleExecuteRowSelect | ||
| ListItem={UserListItem} | ||
| initiallyFocusedItemKey={selectedWorkspace?.policyID} | ||
| /> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default withFullTransactionOrNotFound(BaseRequestStepWorkspace); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,10 +10,12 @@ import useOnyx from '@hooks/useOnyx'; | |
| import useResponsiveLayout from '@hooks/useResponsiveLayout'; | ||
| import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import {setTransactionReport} from '@libs/actions/Transaction'; | ||
| import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities'; | ||
| import {shouldUseTransactionDraft} from '@libs/IOUUtils'; | ||
| import Navigation from '@libs/Navigation/Navigation'; | ||
| import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; | ||
| import {getPolicyExpenseChat} from '@libs/ReportUtils'; | ||
| import {computeTimeAmount, formatTimeMerchant} from '@libs/TimeTrackingUtils'; | ||
| import variables from '@styles/variables'; | ||
| import {setMoneyRequestAmount, setMoneyRequestMerchant, setMoneyRequestParticipantsFromReport, setMoneyRequestTimeCount, setMoneyRequestTimeRate} from '@userActions/IOU'; | ||
|
|
@@ -27,8 +29,12 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; | |
| import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; | ||
| import withWritableReportOrNotFound from './withWritableReportOrNotFound'; | ||
|
|
||
| type IOURequestStepHoursProps = WithWritableReportOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.CREATE | typeof SCREENS.MONEY_REQUEST.STEP_HOURS> & | ||
| WithFullTransactionOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.CREATE | typeof SCREENS.MONEY_REQUEST.STEP_HOURS>; | ||
| type IOURequestStepHoursProps = WithWritableReportOrNotFoundProps< | ||
| typeof SCREENS.MONEY_REQUEST.CREATE | typeof SCREENS.MONEY_REQUEST.STEP_HOURS | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT | ||
| > & | ||
| WithFullTransactionOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.CREATE | typeof SCREENS.MONEY_REQUEST.STEP_HOURS | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT> & { | ||
| explicitPolicyID?: string; | ||
| }; | ||
|
|
||
| function IOURequestStepHours({ | ||
| report, | ||
|
|
@@ -37,9 +43,11 @@ function IOURequestStepHours({ | |
| name: routeName, | ||
| }, | ||
| transaction, | ||
| explicitPolicyID, | ||
| }: IOURequestStepHoursProps) { | ||
| const isEditingConfirmation = routeName === SCREENS.MONEY_REQUEST.STEP_HOURS; | ||
| const policyID = report?.policyID; | ||
| const isEditingConfirmation = routeName === SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; | ||
| const isEmbeddedInStartPage = routeName === SCREENS.MONEY_REQUEST.CREATE; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check looks outdated, now
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's not, this is for when the component is used directly in the "time" tab in the create expense RHP. so when starting to create an expense in a workspace chat or from global create, but when there is just one workspace |
||
| const policyID = explicitPolicyID ?? report?.policyID; | ||
| const isTransactionDraft = shouldUseTransactionDraft(action); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we can't edit the time expense, does that mean isTransactionDraft will always be true?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, I think isTransactionDraft is always true in this step, but I thought I would use the util anyways to make sure it's correct. but I can remove it if you want |
||
| const [selectedTab] = useOnyx(`${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.IOU_REQUEST_TYPE}`, {canBeMissing: true}); | ||
| const {accountID} = useCurrentUserPersonalDetails(); | ||
|
|
@@ -96,18 +104,36 @@ function IOURequestStepHours({ | |
| navigateBack(); | ||
| return; | ||
| } | ||
|
|
||
| setMoneyRequestTimeRate(transactionID, rate, isTransactionDraft); | ||
| setMoneyRequestParticipantsFromReport(transactionID, report, accountID).then(() => | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we remove this .then?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's a very good question. I first implemented it using |
||
| Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)), | ||
| ); | ||
|
|
||
| if (isEmbeddedInStartPage) { | ||
| if (explicitPolicyID) { | ||
| const policyExpenseChat = getPolicyExpenseChat(accountID, policyID); | ||
| if (!policyExpenseChat) { | ||
| console.error(`Couldn't find policy expense chat for policyID: ${policyID}`); | ||
| return; | ||
| } | ||
|
|
||
| setTransactionReport(transactionID, {reportID: policyExpenseChat.reportID}, isTransactionDraft); | ||
| setMoneyRequestParticipantsFromReport(transactionID, policyExpenseChat, accountID); | ||
|
|
||
| return Navigation.setNavigationActionToMicrotaskQueue(() => | ||
| Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, policyExpenseChat.reportID)), | ||
| ); | ||
| } | ||
| setMoneyRequestParticipantsFromReport(transactionID, report, accountID); | ||
| } | ||
|
Comment on lines
+110
to
+126
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason we're setting report and participant here? I would expect this to happen when the user selects the workspace.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, we set it in creating an expense in workspace chat/report: isEmbeddedInStartPage: true, explicitPolicyID: undefined |
||
|
|
||
| Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); | ||
| }; | ||
|
|
||
| return ( | ||
| <StepScreenWrapper | ||
| headerTitle={translate('iou.time')} | ||
| headerTitle={translate(isEditingConfirmation ? 'iou.timeTracking.hours' : 'iou.createExpense')} | ||
grgia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| onBackButtonPress={navigateBack} | ||
| testID="IOURequestStepHours" | ||
| shouldShowWrapper={isEditingConfirmation} | ||
| shouldShowWrapper={!isEmbeddedInStartPage} | ||
| includeSafeAreaPaddingBottom | ||
| shouldShowNotFoundPage={shouldShowNotFoundPage} | ||
| > | ||
|
|
@@ -146,9 +172,4 @@ function IOURequestStepHours({ | |
| ); | ||
| } | ||
|
|
||
| // eslint-disable-next-line rulesdir/no-negated-variables | ||
| const IOURequestStepHoursWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepHours); | ||
| // eslint-disable-next-line rulesdir/no-negated-variables | ||
| const IOURequestStepHoursWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepHoursWithWritableReportOrNotFound); | ||
|
|
||
| export default IOURequestStepHoursWithFullTransactionOrNotFound; | ||
| export default withFullTransactionOrNotFound(withWritableReportOrNotFound(IOURequestStepHours)); | ||
Uh oh!
There was an error while loading. Please reload this page.