From 0ef7db08ed71aa66db57f1cf961fac9bfa9747ce Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 2 May 2024 02:45:51 +0500 Subject: [PATCH 1/8] feat: create workspace at the end --- src/ONYXKEYS.ts | 4 + src/components/CategoryPicker.tsx | 12 +- .../MoneyRequestConfirmationList.tsx | 22 ++- src/libs/OptionsListUtils.ts | 1 + src/libs/ReportUtils.ts | 50 ++++++- src/libs/actions/IOU.ts | 16 ++- src/libs/actions/Policy.ts | 131 +++++++++++++++++- .../request/step/IOURequestStepCategory.tsx | 26 +++- .../step/IOURequestStepConfirmation.tsx | 26 +++- .../step/withWritableReportOrNotFound.tsx | 6 + 10 files changed, 265 insertions(+), 29 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1a27d691e2ef..99bd61bda1da 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -319,6 +319,7 @@ const ONYXKEYS = { POLICY_DRAFTS: 'policyDrafts_', POLICY_JOIN_MEMBER: 'policyJoinMember_', POLICY_CATEGORIES: 'policyCategories_', + POLICY_CATEGORIES_DRAFT: 'policyCategoriesDraft_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', POLICY_RECENTLY_USED_TAGS: 'nvp_recentlyUsedTags_', @@ -332,6 +333,7 @@ const ONYXKEYS = { WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_', REPORT: 'report_', + REPORT_DRAFT: 'reportDraft_', // REPORT_METADATA is a perf optimization used to hold loading states (isLoadingInitialReportActions, isLoadingOlderReportActions, isLoadingNewerReportActions). // A lot of components are connected to the Report entity and do not care about the actions. Setting the loading state // directly on the report caused a lot of unnecessary re-renders @@ -535,6 +537,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories; + [ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT]: OnyxTypes.PolicyCategories; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED]: boolean; @@ -542,6 +545,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: OnyxTypes.InvitedEmailsToAccountIDs; [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MESSAGE_DRAFT]: string; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; + [ONYXKEYS.COLLECTION.REPORT_DRAFT]: OnyxTypes.Report; [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportActions; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: OnyxTypes.ReportActionsDrafts; diff --git a/src/components/CategoryPicker.tsx b/src/components/CategoryPicker.tsx index f26d7c25c7e2..9484cff26005 100644 --- a/src/components/CategoryPicker.tsx +++ b/src/components/CategoryPicker.tsx @@ -14,6 +14,7 @@ import type {ListItem} from './SelectionList/types'; type CategoryPickerOnyxProps = { policyCategories: OnyxEntry; + policyCategoriesDraft: OnyxEntry; policyRecentlyUsedCategories: OnyxEntry; }; @@ -25,7 +26,7 @@ type CategoryPickerProps = CategoryPickerOnyxProps & { onSubmit: (item: ListItem) => void; }; -function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}: CategoryPickerProps) { +function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, policyCategoriesDraft, onSubmit}: CategoryPickerProps) { const {translate} = useLocalize(); const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); @@ -45,7 +46,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC }, [selectedCategory]); const [sections, headerMessage, shouldShowTextInput] = useMemo(() => { - const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter((p) => !isEmptyObject(p)); + const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter?.((p) => !isEmptyObject(p)); const {categoryOptions} = OptionsListUtils.getFilteredOptions( [], [], @@ -56,7 +57,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC false, false, true, - policyCategories ?? {}, + policyCategories ?? policyCategoriesDraft ?? {}, validPolicyRecentlyUsedCategories, false, ); @@ -68,7 +69,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC const showInput = !isCategoriesCountBelowThreshold; return [categoryOptions, header, showInput]; - }, [policyRecentlyUsedCategories, debouncedSearchValue, selectedOptions, policyCategories]); + }, [policyRecentlyUsedCategories, debouncedSearchValue, selectedOptions, policyCategories, policyCategoriesDraft]); const selectedOptionKey = useMemo(() => (sections?.[0]?.data ?? []).filter((category) => category.searchText === selectedCategory)[0]?.keyForList, [sections, selectedCategory]); @@ -93,6 +94,9 @@ export default withOnyx({ policyCategories: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, }, + policyCategoriesDraft: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`, + }, policyRecentlyUsedCategories: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`, }, diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 17ee0d31d63c..ffc629005b1c 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -60,12 +60,18 @@ type MoneyRequestConfirmationListOnyxProps = { /** Collection of categories attached to a policy */ policyCategories: OnyxEntry; + /** Collection of draft categories attached to a policy */ + policyCategoriesDraft: OnyxEntry; + /** Collection of tags attached to a policy */ policyTags: OnyxEntry; /** The policy of the report */ policy: OnyxEntry; + /** The draft policy of the report */ + policyDraft: OnyxEntry; + /** The session of the logged in user */ session: OnyxEntry; @@ -193,10 +199,12 @@ function MoneyRequestConfirmationList({ iouType = CONST.IOU.TYPE.SUBMIT, isScanRequest = false, iouAmount, - policyCategories, + policyCategories: policyCategoriesReal, + policyCategoriesDraft, mileageRates, isDistanceRequest = false, - policy, + policy: policyReal, + policyDraft, isPolicyExpenseChat = false, iouCategory = '', shouldShowSmartScanFields = true, @@ -227,6 +235,8 @@ function MoneyRequestConfirmationList({ allPolicies, action = CONST.IOU.ACTION.CREATE, }: MoneyRequestConfirmationListProps) { + const policy = policyReal ?? policyDraft; + const policyCategories = policyCategoriesReal ?? policyCategoriesDraft; const theme = useTheme(); const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); @@ -468,8 +478,8 @@ function MoneyRequestConfirmationList({ ); } else { const formattedSelectedParticipants = selectedParticipants.map((participant) => ({ - ...participant, isDisabled: !participant.isPolicyExpenseChat && !participant.isSelfDM && ReportUtils.isOptimisticPersonalDetail(participant.accountID ?? -1), + ...participant, })); sections.push({ title: translate('common.to'), @@ -1105,6 +1115,9 @@ export default withOnyx `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, }, + policyCategoriesDraft: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`, + }, policyTags: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, @@ -1119,6 +1132,9 @@ export default withOnyx `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, + policyDraft: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`, + }, lastSelectedDistanceRates: { key: ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, }, diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 5bbbf19f3adb..d4e11ddb744c 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -777,6 +777,7 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { option.text = ReportUtils.getPolicyName(report); option.alternateText = Localize.translateLocal('workspace.common.workspace'); } + option.isDisabled = ReportUtils.isDraftReport(participant.reportID); option.selected = participant.selected; option.isSelected = participant.selected; return option; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 769832747c41..88c028a57716 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -520,6 +520,13 @@ Onyx.connect({ callback: (value) => (allReports = value), }); +let allReportsDraft: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_DRAFT, + waitForCollectionCallback: true, + callback: (value) => (allReportsDraft = value), +}); + let allPolicies: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, @@ -590,11 +597,23 @@ function getChatType(report: OnyxEntry | Participant | EmptyObject): Val * Get the report given a reportID */ function getReport(reportID: string | undefined): OnyxEntry { - if (!allReports) { + if (!allReports && !allReportsDraft) { return null; } - return allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const draftReport = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`]; + + return report ?? draftReport; +} + +/** + * Check if a report is a draft report + */ +function isDraftReport(reportID: string | undefined): boolean { + const draftReport = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`]; + + return !!draftReport; } /** @@ -904,8 +923,8 @@ function isGroupPolicy(policyType: string): boolean { /** * Whether the provided report belongs to a Free, Collect or Control policy */ -function isReportInGroupPolicy(report: OnyxEntry): boolean { - const policyType = getPolicyType(report, allPolicies); +function isReportInGroupPolicy(report: OnyxEntry, policy?: OnyxEntry): boolean { + const policyType = policy?.type ?? getPolicyType(report, allPolicies); return isGroupPolicy(policyType); } @@ -4624,7 +4643,7 @@ function buildOptimisticClosedReportAction(emailClosingReport: string, policyNam }; } -function buildOptimisticWorkspaceChats(policyID: string, policyName: string): OptimisticWorkspaceChats { +function buildOptimisticWorkspaceChats(policyID: string, policyName: string, expenseReportId?: string): OptimisticWorkspaceChats { const announceChatData = buildOptimisticChatReport( currentUserAccountID ? [currentUserAccountID] : [], CONST.REPORT.WORKSPACE_CHAT_ROOMS.ANNOUNCE, @@ -4663,7 +4682,23 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string): Op [adminsCreatedAction.reportActionID]: adminsCreatedAction, }; - const expenseChatData = buildOptimisticChatReport([currentUserAccountID ?? -1], '', CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, policyID, currentUserAccountID, true, policyName); + const expenseChatData = buildOptimisticChatReport( + [currentUserAccountID ?? -1], + '', + CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID, + currentUserAccountID, + true, + policyName, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + expenseReportId, + ); const expenseChatReportID = expenseChatData.reportID; const expenseReportCreatedAction = buildOptimisticCreatedReportAction(currentUserEmail ?? ''); const expenseReportActionData = { @@ -6396,7 +6431,7 @@ function createDraftTransactionAndNavigateToParticipantSelector(transactionID: s return; } - const {expenseChatReportID, policyID, policyName} = PolicyActions.createWorkspace(); + const {expenseChatReportID, policyID, policyName} = PolicyActions.createDraftWorkspace(); const isCategorizing = actionName === CONST.IOU.ACTION.CATEGORIZE; IOU.setMoneyRequestParticipants(transactionID, [ @@ -6710,6 +6745,7 @@ export { getInvoiceChatByParticipants, shouldShowMerchantColumn, isCurrentUserInvoiceReceiver, + isDraftReport, }; export type { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 133f08ac97c0..0c04fdbf3786 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1113,6 +1113,9 @@ function buildOnyxDataForTrackExpense( const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); const optimisticData: OnyxUpdate[] = []; + const successData: OnyxUpdate[] = []; + const failureData: OnyxUpdate[] = []; + let newQuickAction: ValueOf = CONST.QUICK_ACTIONS.TRACK_MANUAL; if (isScanRequest) { newQuickAction = CONST.QUICK_ACTIONS.TRACK_SCAN; @@ -1121,6 +1124,15 @@ function buildOnyxDataForTrackExpense( } const existingTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${existingTransactionThreadReportID}`] ?? null; + const isDraftReport = ReportUtils.isDraftReport(chatReport?.reportID); + + if (isDraftReport) { + const workspaceData = Policy.buildPolicyData(undefined, policy?.makeMeAdmin, policy?.name, policy?.id, chatReport?.reportID); + optimisticData.push(...workspaceData.optimisticData); + successData.push(...workspaceData.successData); + failureData.push(...workspaceData.failureData); + } + if (chatReport) { optimisticData.push( { @@ -1226,8 +1238,6 @@ function buildOnyxDataForTrackExpense( }); } - const successData: OnyxUpdate[] = []; - if (iouReport) { successData.push( { @@ -1310,8 +1320,6 @@ function buildOnyxDataForTrackExpense( }); } - const failureData: OnyxUpdate[] = []; - failureData.push({ onyxMethod: Onyx.METHOD.SET, key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 8ae0e2257705..dc888670073f 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2128,6 +2128,11 @@ function buildOptimisticPolicyCategories(policyID: string, categories: readonly key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, value: optimisticCategoryMap, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`, + value: null, + }, ], successData: [ { @@ -2149,14 +2154,15 @@ function buildOptimisticPolicyCategories(policyID: string, categories: readonly } /** - * Optimistically creates a new workspace and default workspace chats + * Generates onyx data for creating a new workspace * * @param [policyOwnerEmail] the email of the account to make the owner of the policy * @param [makeMeAdmin] leave the calling account as an admin on the policy * @param [policyName] custom policy name we will use for created workspace * @param [policyID] custom policy id we will use for created workspace + * @param [expenseReportId] the reportID of the expense report that is being used to create the workspace */ -function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID()): CreateWorkspaceParams { +function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticCustomUnits(); @@ -2174,7 +2180,7 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName expenseChatData, expenseReportActionData, expenseCreatedReportActionID, - } = ReportUtils.buildOptimisticWorkspaceChats(policyID, workspaceName); + } = ReportUtils.buildOptimisticWorkspaceChats(policyID, workspaceName, expenseReportId); const optimisticCategoriesData = buildOptimisticPolicyCategories(policyID, CONST.POLICY.DEFAULT_CATEGORIES); @@ -2223,6 +2229,11 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName ...announceChatData, }, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${announceChatReportID}`, + value: null, + }, { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, @@ -2263,6 +2274,11 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName key: `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`, value: null, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${expenseChatReportID}`, + value: null, + }, ]; const successData: OnyxUpdate[] = [ @@ -2397,11 +2413,118 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName customUnitRateID, }; + return {successData, optimisticData, failureData, params}; +} + +/** + * Optimistically creates a new workspace and default workspace chats + * + * @param [policyOwnerEmail] the email of the account to make the owner of the policy + * @param [makeMeAdmin] leave the calling account as an admin on the policy + * @param [policyName] custom policy name we will use for created workspace + * @param [policyID] custom policy id we will use for created workspace + */ +function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID()): CreateWorkspaceParams { + const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID); API.write(WRITE_COMMANDS.CREATE_WORKSPACE, params, {optimisticData, successData, failureData}); return params; } +/** + * Creates a draft workspace for various money request flows + * + * @param [policyOwnerEmail] the email of the account to make the owner of the policy + * @param [makeMeAdmin] leave the calling account as an admin on the policy + * @param [policyName] custom policy name we will use for created workspace + * @param [policyID] custom policy id we will use for created workspace + */ +function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID()): CreateWorkspaceParams { + const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); + + const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticCustomUnits(); + + const {expenseChatData, announceChatReportID, announceCreatedReportActionID, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, expenseCreatedReportActionID} = + ReportUtils.buildOptimisticWorkspaceChats(policyID, workspaceName); + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`, + value: { + id: policyID, + type: CONST.POLICY.TYPE.TEAM, + name: workspaceName, + role: CONST.POLICY.ROLE.ADMIN, + owner: sessionEmail, + ownerAccountID: sessionAccountID, + isPolicyExpenseChatEnabled: true, + outputCurrency, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + autoReporting: true, + approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL, + harvesting: { + enabled: true, + }, + customUnits, + areCategoriesEnabled: true, + areTagsEnabled: false, + areDistanceRatesEnabled: false, + areWorkflowsEnabled: false, + areReportFieldsEnabled: false, + areConnectionsEnabled: false, + employeeList: { + [sessionEmail]: { + role: CONST.POLICY.ROLE.ADMIN, + errors: {}, + }, + }, + chatReportIDAdmins: makeMeAdmin ? Number(adminsChatReportID) : undefined, + }, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${expenseChatReportID}`, + value: expenseChatData, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`, + value: CONST.POLICY.DEFAULT_CATEGORIES.reduce( + (acc, category) => ({ + ...acc, + [category]: { + name: category, + enabled: true, + errors: null, + }, + }), + {}, + ), + }, + ]; + + const params: CreateWorkspaceParams = { + policyID, + announceChatReportID, + adminsChatReportID, + expenseChatReportID, + ownerEmail: policyOwnerEmail, + makeMeAdmin, + policyName: workspaceName, + type: CONST.POLICY.TYPE.TEAM, + announceCreatedReportActionID, + adminsCreatedReportActionID, + expenseCreatedReportActionID, + customUnitID, + customUnitRateID, + }; + + Onyx.update(optimisticData); + + return params; +} + function openWorkspaceReimburseView(policyID: string) { if (!policyID) { Log.warn('openWorkspaceReimburseView invalid params', {policyID}); @@ -5130,6 +5253,8 @@ export { setPolicyDistanceRatesEnabled, deletePolicyDistanceRates, getPrimaryPolicy, + createDraftWorkspace, + buildPolicyData, }; export type {NewCustomUnit}; diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index 4b34a6a19600..f60b04e6ec98 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -32,9 +32,15 @@ type IOURequestStepCategoryOnyxProps = { /** The policy of the report */ policy: OnyxEntry; + /** The draft policy of the report */ + policyDraft: OnyxEntry; + /** Collection of categories attached to a policy */ policyCategories: OnyxEntry; + /** Collection of draft categories attached to a policy */ + policyCategoriesDraft: OnyxEntry; + /** Collection of tags attached to a policy */ policyTags: OnyxEntry; @@ -50,18 +56,24 @@ type IOURequestStepCategoryProps = IOURequestStepCategoryOnyxProps & WithFullTransactionOrNotFoundProps; function IOURequestStepCategory({ - report, + report: reportReal, + reportDraft, route: { params: {transactionID, backTo, action, iouType, reportActionID}, }, transaction, splitDraftTransaction, - policy, + policy: policyReal, + policyDraft, policyTags, - policyCategories, + policyCategories: policyCategoriesReal, + policyCategoriesDraft, reportActions, session, }: IOURequestStepCategoryProps) { + const report = reportReal ?? reportDraft; + const policy = policyReal ?? policyDraft; + const policyCategories = policyCategoriesReal ?? policyCategoriesDraft; const styles = useThemeStyles(); const {translate} = useLocalize(); const isEditing = action === CONST.IOU.ACTION.EDIT; @@ -73,7 +85,7 @@ function IOURequestStepCategory({ // The transactionCategory can be an empty string, so to maintain the logic we'd like to keep it in this shape until utils refactor // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const shouldShowCategory = ReportUtils.isReportInGroupPolicy(report) && (!!transactionCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); + const shouldShowCategory = ReportUtils.isReportInGroupPolicy(report, policy) && (!!transactionCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); @@ -159,9 +171,15 @@ const IOURequestStepCategoryWithOnyx = withOnyx `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, }, + policyDraft: { + key: ({reportDraft}) => `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${reportDraft ? reportDraft.policyID : '0'}`, + }, policyCategories: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, }, + policyCategoriesDraft: { + key: ({reportDraft}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${reportDraft ? reportDraft.policyID : '0'}`, + }, policyTags: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index e33e4d8fb763..e31876dc8cbe 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -39,9 +39,15 @@ type IOURequestStepConfirmationOnyxProps = { /** The policy of the report */ policy: OnyxEntry; + /** The draft policy of the report */ + policyDraft: OnyxEntry; + /** The category configuration of the report's policy */ policyCategories: OnyxEntry; + /** The draft category configuration of the report's policy */ + policyCategoriesDraft: OnyxEntry; + /** The tag configuration of the report's policy */ policyTags: OnyxEntry; }; @@ -51,10 +57,13 @@ type IOURequestStepConfirmationProps = IOURequestStepConfirmationOnyxProps & WithFullTransactionOrNotFoundProps; function IOURequestStepConfirmation({ - policy, + policy: policyReal, + policyDraft, policyTags, - policyCategories, - report, + policyCategories: policyCategoriesReal, + policyCategoriesDraft, + report: reportReal, + reportDraft, route: { params: {iouType, reportID, transactionID, action}, }, @@ -63,6 +72,10 @@ function IOURequestStepConfirmation({ const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + const report = reportReal ?? reportDraft; + const policy = policyReal ?? policyDraft; + const policyCategories = policyCategoriesReal ?? policyCategoriesDraft; + const styles = useThemeStyles(); const {translate} = useLocalize(); const {windowWidth} = useWindowDimensions(); @@ -129,7 +142,6 @@ function IOURequestStepConfirmation({ if (participant.isSender && iouType === CONST.IOU.TYPE.INVOICE) { return participant; } - return participantAccountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); }) ?? [], [transaction?.participants, personalDetails, iouType], @@ -584,9 +596,15 @@ const IOURequestStepConfirmationWithOnyx = withOnyx `${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, report)}`, }, + policyDraft: { + key: ({reportDraft, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`, + }, policyCategories: { key: ({report, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, report)}`, }, + policyCategoriesDraft: { + key: ({reportDraft, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`, + }, policyTags: { key: ({report, transaction}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, report)}`, }, diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index 4a020ee8d411..a8620a83db14 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -15,6 +15,9 @@ import type {Report} from '@src/types/onyx'; type WithWritableReportOrNotFoundOnyxProps = { /** The report corresponding to the reportID in the route params */ report: OnyxEntry; + + /** The draft report corresponding to the reportID in the route params */ + reportDraft: OnyxEntry; }; type MoneyRequestRouteName = @@ -70,6 +73,9 @@ export default function `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '0'}`, }, + reportDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT}${route.params.reportID ?? '0'}`, + }, })(forwardRef(WithWritableReportOrNotFound)); } From 68525d3f50f7776619b5406af57446252c4bba07 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 2 May 2024 03:38:58 +0500 Subject: [PATCH 2/8] feat: fix the order of workspace creation --- src/libs/actions/IOU.ts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0c04fdbf3786..e361e5e85394 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1124,15 +1124,6 @@ function buildOnyxDataForTrackExpense( } const existingTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${existingTransactionThreadReportID}`] ?? null; - const isDraftReport = ReportUtils.isDraftReport(chatReport?.reportID); - - if (isDraftReport) { - const workspaceData = Policy.buildPolicyData(undefined, policy?.makeMeAdmin, policy?.name, policy?.id, chatReport?.reportID); - optimisticData.push(...workspaceData.optimisticData); - successData.push(...workspaceData.successData); - failureData.push(...workspaceData.failureData); - } - if (chatReport) { optimisticData.push( { @@ -1976,6 +1967,10 @@ function getTrackExpenseInformation( linkedTrackedExpenseReportAction?: OnyxTypes.ReportAction, existingTransactionID?: string, ): TrackExpenseInformation | EmptyObject { + const optimisticData: OnyxUpdate[] = []; + const successData: OnyxUpdate[] = []; + const failureData: OnyxUpdate[] = []; + const isPolicyExpenseChat = participant.isPolicyExpenseChat; // STEP 1: Get existing chat report @@ -1992,6 +1987,16 @@ function getTrackExpenseInformation( return {}; } + // Check if the report is a draft + const isDraftReport = ReportUtils.isDraftReport(chatReport?.reportID); + + if (isDraftReport) { + const workspaceData = Policy.buildPolicyData(undefined, policy?.makeMeAdmin, policy?.name, policy?.id, chatReport?.reportID); + optimisticData.push(...workspaceData.optimisticData); + successData.push(...workspaceData.successData); + failureData.push(...workspaceData.failureData); + } + // STEP 2: If not in the self-DM flow, we need to use the expense report. // For this, first use the chatReport.iouReportID property. Build a new optimistic expense report if needed. const shouldUseMoneyReport = !!isPolicyExpenseChat; @@ -2099,7 +2104,7 @@ function getTrackExpenseInformation( } // STEP 5: Build Onyx Data - const [optimisticData, successData, failureData] = buildOnyxDataForTrackExpense( + const trackExpenseOnyxData = buildOnyxDataForTrackExpense( chatReport, iouReport, optimisticTransaction, @@ -2125,9 +2130,9 @@ function getTrackExpenseInformation( transactionThreadReportID: optimisticTransactionThread.reportID, createdReportActionIDForThread: optimisticCreatedActionForTransactionThread.reportActionID, onyxData: { - optimisticData, - successData, - failureData, + optimisticData: [...optimisticData, ...trackExpenseOnyxData[0]], + successData: [...successData, ...trackExpenseOnyxData[1]], + failureData: [...failureData, ...trackExpenseOnyxData[2]], }, }; } From eae88b0ba923c984059db88aecae9653004472ca Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 2 May 2024 04:00:46 +0500 Subject: [PATCH 3/8] typecheck fix --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 88c028a57716..424082b35dd6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -604,7 +604,7 @@ function getReport(reportID: string | undefined): OnyxEntry { const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const draftReport = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`]; - return report ?? draftReport; + return report ?? draftReport ?? null; } /** From 8748f512d2476161fbab74800c478d9067501a3d Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 2 May 2024 21:19:10 +0500 Subject: [PATCH 4/8] add expense report id --- src/libs/actions/IOU.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 823b758cec32..43ba76db716e 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3089,6 +3089,7 @@ function categorizeTrackedExpense( taxAmount = 0, billable?: boolean, receipt?: Receipt, + expenseReportID?: string, ) { const {optimisticData, successData, failureData} = onyxData; @@ -3116,6 +3117,7 @@ function categorizeTrackedExpense( transactionID, moneyRequestPreviewReportActionID, moneyRequestReportID, + policyExpenseChatReportID: expenseReportID, moneyRequestCreatedReportActionID, actionableWhisperReportActionID, modifiedExpenseReportActionID, @@ -3159,6 +3161,7 @@ function shareTrackedExpense( taxAmount = 0, billable?: boolean, receipt?: Receipt, + expenseReportID?: string, ) { const {optimisticData, successData, failureData} = onyxData; @@ -3190,6 +3193,7 @@ function shareTrackedExpense( actionableWhisperReportActionID, modifiedExpenseReportActionID, reportPreviewReportActionID, + policyExpenseChatReportID: expenseReportID, amount, currency, comment, @@ -3490,6 +3494,7 @@ function trackExpense( taxAmount, billable, receipt, + chatReport.isPolicyExpenseChat ? chatReport.reportID : undefined, ); break; } @@ -3520,6 +3525,7 @@ function trackExpense( taxAmount, billable, receipt, + chatReport.isPolicyExpenseChat ? chatReport.reportID : undefined, ); break; } From 1de9a297c0135355ecae0edc36afcaf5646763d6 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Fri, 3 May 2024 13:26:15 +0500 Subject: [PATCH 5/8] fix: add workspace params to categorize and share expenses --- src/libs/actions/IOU.ts | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 43ba76db716e..a74495008fd8 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -10,6 +10,7 @@ import type { ApproveMoneyRequestParams, CompleteSplitBillParams, CreateDistanceRequestParams, + CreateWorkspaceParams, DeleteMoneyRequestParams, DetachReceiptParams, EditMoneyRequestParams, @@ -82,6 +83,7 @@ type MoneyRequestInformation = { }; type TrackExpenseInformation = { + createdWorkspaceParams?: CreateWorkspaceParams; iouReport?: OnyxTypes.Report; chatReport: OnyxTypes.Report; transaction: OnyxTypes.Transaction; @@ -1990,8 +1992,11 @@ function getTrackExpenseInformation( // Check if the report is a draft const isDraftReport = ReportUtils.isDraftReport(chatReport?.reportID); + let createdWorkspaceParams: CreateWorkspaceParams | undefined; + if (isDraftReport) { const workspaceData = Policy.buildPolicyData(undefined, policy?.makeMeAdmin, policy?.name, policy?.id, chatReport?.reportID); + createdWorkspaceParams = workspaceData.params; optimisticData.push(...workspaceData.optimisticData); successData.push(...workspaceData.successData); failureData.push(...workspaceData.failureData); @@ -2120,6 +2125,7 @@ function getTrackExpenseInformation( ); return { + createdWorkspaceParams, chatReport, iouReport: iouReport ?? undefined, transaction: optimisticTransaction, @@ -3089,7 +3095,7 @@ function categorizeTrackedExpense( taxAmount = 0, billable?: boolean, receipt?: Receipt, - expenseReportID?: string, + createdWorkspaceParams?: CreateWorkspaceParams, ) { const {optimisticData, successData, failureData} = onyxData; @@ -3117,7 +3123,6 @@ function categorizeTrackedExpense( transactionID, moneyRequestPreviewReportActionID, moneyRequestReportID, - policyExpenseChatReportID: expenseReportID, moneyRequestCreatedReportActionID, actionableWhisperReportActionID, modifiedExpenseReportActionID, @@ -3133,6 +3138,12 @@ function categorizeTrackedExpense( billable, created, receipt, + policyExpenseChatReportID: createdWorkspaceParams?.expenseChatReportID, + policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, + announceChatReportID: createdWorkspaceParams?.announceChatReportID, + announceCreatedReportActionID: createdWorkspaceParams?.announceCreatedReportActionID, + adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, + adminsCreatedReportActionID: createdWorkspaceParams?.adminsCreatedReportActionID, }; API.write(WRITE_COMMANDS.CATEGORIZE_TRACKED_EXPENSE, parameters, {optimisticData, successData, failureData}); @@ -3161,7 +3172,7 @@ function shareTrackedExpense( taxAmount = 0, billable?: boolean, receipt?: Receipt, - expenseReportID?: string, + createdWorkspaceParams?: CreateWorkspaceParams, ) { const {optimisticData, successData, failureData} = onyxData; @@ -3193,7 +3204,6 @@ function shareTrackedExpense( actionableWhisperReportActionID, modifiedExpenseReportActionID, reportPreviewReportActionID, - policyExpenseChatReportID: expenseReportID, amount, currency, comment, @@ -3205,6 +3215,12 @@ function shareTrackedExpense( taxAmount, billable, receipt, + policyExpenseChatReportID: createdWorkspaceParams?.expenseChatReportID, + policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, + announceChatReportID: createdWorkspaceParams?.announceChatReportID, + announceCreatedReportActionID: createdWorkspaceParams?.announceCreatedReportActionID, + adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, + adminsCreatedReportActionID: createdWorkspaceParams?.adminsCreatedReportActionID, }; API.write(WRITE_COMMANDS.SHARE_TRACKED_EXPENSE, parameters, {optimisticData, successData, failureData}); @@ -3433,6 +3449,7 @@ function trackExpense( const currentCreated = DateUtils.enrichMoneyRequestTimestamp(created); const { + createdWorkspaceParams, iouReport, chatReport, transaction, @@ -3494,7 +3511,7 @@ function trackExpense( taxAmount, billable, receipt, - chatReport.isPolicyExpenseChat ? chatReport.reportID : undefined, + createdWorkspaceParams, ); break; } @@ -3525,7 +3542,7 @@ function trackExpense( taxAmount, billable, receipt, - chatReport.isPolicyExpenseChat ? chatReport.reportID : undefined, + createdWorkspaceParams, ); break; } From 471a6a51f47f4208d88a4259dc6f2aecd7803c1e Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Fri, 3 May 2024 16:57:36 +0500 Subject: [PATCH 6/8] fix: categories not showing the second time --- src/libs/ReportUtils.ts | 2 +- src/libs/actions/Policy.ts | 2 +- src/pages/iou/request/step/IOURequestStepCategory.tsx | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7ae807a1767c..c5d5827ceef0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6423,7 +6423,7 @@ function createDraftTransactionAndNavigateToParticipantSelector(transactionID: s } as Transaction); const filteredPolicies = Object.values(allPolicies ?? {}).filter( - (policy) => policy?.type !== CONST.POLICY.TYPE.PERSONAL && policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + (policy) => policy && policy.type !== CONST.POLICY.TYPE.PERSONAL && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, ); if (actionName === CONST.IOU.ACTION.SUBMIT || (allPolicies && filteredPolicies.length > 0)) { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index ab251cc468d3..9e0aff991088 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2520,7 +2520,7 @@ function createDraftWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policy value: expenseChatData, }, { - onyxMethod: Onyx.METHOD.MERGE, + onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`, value: CONST.POLICY.DEFAULT_CATEGORIES.reduce( (acc, category) => ({ diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index f60b04e6ec98..0bd546318186 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -99,6 +99,7 @@ function IOURequestStepCategory({ PolicyActions.openDraftWorkspaceRequest(report?.policyID ?? ''); }; + useNetwork({onReconnect: fetchData}); useEffect(() => { From e42520c2b14f9db86a06f17bea90970a986563a3 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 6 May 2024 20:52:54 +0500 Subject: [PATCH 7/8] call open draft workspace request only for non pending workspaces --- src/pages/iou/request/step/IOURequestStepConfirmation.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index e31876dc8cbe..f0585a38e768 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry, PendingAction} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -151,7 +151,7 @@ function IOURequestStepConfirmation({ useEffect(() => { const policyExpenseChat = participants?.find((participant) => participant.isPolicyExpenseChat); - if (policyExpenseChat?.policyID) { + if (policyExpenseChat?.policyID && policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { openDraftWorkspaceRequest(policyExpenseChat.policyID); } }, [isOffline, participants, transaction?.billable, policy, transactionID]); From c9b666c5730b40d0c45d0281e49d488cc9d334ef Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 6 May 2024 20:56:46 +0500 Subject: [PATCH 8/8] remove unused var --- src/pages/iou/request/step/IOURequestStepConfirmation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index f0585a38e768..08599496859f 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry, PendingAction} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons';