From e345f61900e5b0bfd954a632c48f6ff701697742 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 3 Jan 2025 11:16:47 +0700 Subject: [PATCH 01/72] fix: display message when user detach a receipt --- .../API/parameters/DetachReceiptParams.ts | 1 + src/libs/ReportUtils.ts | 38 ++++++++++++++++ src/libs/actions/IOU.ts | 45 ++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/libs/API/parameters/DetachReceiptParams.ts b/src/libs/API/parameters/DetachReceiptParams.ts index a19b525a0ef0..b0aeed075d51 100644 --- a/src/libs/API/parameters/DetachReceiptParams.ts +++ b/src/libs/API/parameters/DetachReceiptParams.ts @@ -1,5 +1,6 @@ type DetachReceiptParams = { transactionID: string; + reportActionID: string; }; export default DetachReceiptParams; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 027b11c516f1..0dca040f5301 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5341,6 +5341,43 @@ function buildOptimisticModifiedExpenseReportAction( }; } +/** + * Builds an optimistic DETACH_RECEIPT report action with a randomly generated reportActionID. + */ +function buildOptimisticDetachReceipt(reportID: string, transactionID: string, merchant: string = CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT) { + return { + actionName: CONST.REPORT.ACTIONS.TYPE.MANAGER_DETACH_RECEIPT, + actorAccountID: currentUserAccountID, + automatic: false, + avatar: getCurrentUserAvatar(), + created: DateUtils.getDBTime(), + isAttachmentOnly: false, + originalMessage: { + transactionID, + merchant: `${merchant}`, + }, + message: [ + { + type: 'COMMENT', + html: `detached a receipt from expense '${merchant}'`, + text: `detached a receipt from expense '${merchant}'`, + whisperedTo: [], + }, + ], + person: [ + { + style: 'strong', + text: currentUserPersonalDetails?.displayName ?? String(currentUserAccountID), + type: 'TEXT', + }, + ], + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + reportActionID: NumberUtils.rand64(), + reportID, + shouldShow: true, + }; +} + /** * Builds an optimistic modified expense action for a tracked expense move with a randomly generated reportActionID. * @param transactionThreadID - The reportID of the transaction thread @@ -8610,6 +8647,7 @@ export { buildOptimisticAnnounceChat, buildOptimisticWorkspaceChats, buildOptimisticCardAssignedReportAction, + buildOptimisticDetachReceipt, buildParticipantsFromAccountIDs, buildTransactionThread, canAccessReport, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 2832b0e610c3..b8ae61c7425b 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -8045,8 +8045,51 @@ function detachReceipt(transactionID: string) { }, }, ]; + const expenseReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`] ?? null; + const updatedReportAction = ReportUtils.buildOptimisticDetachReceipt(expenseReport?.reportID ?? '-1', transactionID, transaction?.merchant); - const parameters: DetachReceiptParams = {transactionID}; + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${updatedReportAction?.reportID}`, + value: { + [updatedReportAction.reportActionID]: updatedReportAction as OnyxTypes.ReportAction, + }, + }); + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${updatedReportAction?.reportID}`, + value: { + lastVisibleActionCreated: updatedReportAction.created, + lastReadTime: updatedReportAction.created, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${updatedReportAction?.reportID}`, + value: { + lastVisibleActionCreated: expenseReport?.lastVisibleActionCreated, + lastReadTime: expenseReport?.lastReadTime, + }, + }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`, + value: { + [updatedReportAction.reportActionID]: {pendingAction: null}, + }, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`, + value: { + [updatedReportAction.reportActionID]: { + ...(updatedReportAction as OnyxTypes.ReportAction), + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage'), + }, + }, + }); + + const parameters: DetachReceiptParams = {transactionID, reportActionID: updatedReportAction.reportActionID}; API.write(WRITE_COMMANDS.DETACH_RECEIPT, parameters, {optimisticData, successData, failureData}); } From 48bba1f3c01f099afc14a1140837e227e454d2ed Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 8 Jan 2025 14:05:09 +0100 Subject: [PATCH 02/72] Prevent payment for open invoice reports in canIOUBePaid function --- src/libs/actions/IOU.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index ee49b1c8e803..d816c99e5961 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7358,6 +7358,9 @@ function canIOUBePaid( } if (ReportUtils.isInvoiceReport(iouReport)) { + if (ReportUtils.isOpenInvoiceReport(iouReport)) { + return false; + } if (iouSettled) { return false; } From 71eceaa48adf88bf7da6071f83a2ace82da5e65d Mon Sep 17 00:00:00 2001 From: abzokhattab Date: Tue, 14 Jan 2025 04:04:53 +0100 Subject: [PATCH 03/72] Add toggle for enable/disable instead of label --- .../categories/WorkspaceCategoriesPage.tsx | 22 ++++-- .../distanceRates/PolicyDistanceRatesPage.tsx | 42 ++++++++++- .../ReportFieldsListValuesPage.tsx | 26 +++++-- .../workspace/tags/WorkspaceTagsPage.tsx | 69 ++++++++++++++----- .../workspace/tags/WorkspaceViewTagsPage.tsx | 22 ++++-- .../workspace/taxes/WorkspaceTaxesPage.tsx | 44 ++++++++---- 6 files changed, 179 insertions(+), 46 deletions(-) diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 7b85ddcb02c0..c16e642e4684 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -14,12 +14,12 @@ import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import LottieAnimations from '@components/LottieAnimations'; import ScreenWrapper from '@components/ScreenWrapper'; -import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel'; import TableListItem from '@components/SelectionList/TableListItem'; import type {ListItem} from '@components/SelectionList/types'; import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; +import Switch from '@components/Switch'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useAutoTurnSelectionModeOffWhenHasNoActiveOption from '@hooks/useAutoTurnSelectionModeOffWhenHasNoActiveOption'; @@ -42,8 +42,8 @@ import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import * as Modal from '@userActions/Modal'; -import {deleteWorkspaceCategories, setWorkspaceCategoryEnabled} from '@userActions/Policy/Category'; import * as Category from '@userActions/Policy/Category'; +import {deleteWorkspaceCategories, setWorkspaceCategoryEnabled} from '@userActions/Policy/Category'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -105,6 +105,13 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { setSelectedCategories({}); }, [isFocused]); + const updateWorkspaceRequiresCategory = useCallback( + (value: boolean, categoryName: string) => { + Category.setWorkspaceCategoryEnabled(policyId, {[categoryName]: {name: categoryName, enabled: value}}); + }, + [policyId], + ); + const categoryList = useMemo(() => { const categories = lodashSortBy(Object.values(policyCategories ?? {}), 'name', localeCompare) as PolicyCategory[]; return categories.reduce((acc, value) => { @@ -121,12 +128,19 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { isDisabled, pendingAction: value.pendingAction, errors: value.errors ?? undefined, - rightElement: , + rightElement: ( + updateWorkspaceRequiresCategory(newValue, value.name)} + /> + ), }); return acc; }, []); - }, [policyCategories, isOffline, selectedCategories, canSelectMultiple, translate]); + }, [policyCategories, isOffline, selectedCategories, canSelectMultiple, translate, updateWorkspaceRequiresCategory]); useAutoTurnSelectionModeOffWhenHasNoActiveOption(categoryList); diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 19878036030b..fa7d4fae85c3 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -8,11 +8,11 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; -import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel'; import TableListItem from '@components/SelectionList/TableListItem'; import type {ListItem} from '@components/SelectionList/types'; import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; +import Switch from '@components/Switch'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -98,6 +98,35 @@ function PolicyDistanceRatesPage({ setSelectedDistanceRates([]); }, [isFocused]); + const updateDistanceRateEnabled = useCallback( + (value: boolean, rateID: string) => { + if (!customUnit) { + return; + } + const rate = customUnit?.rates?.[rateID]; + // Rates can be disabled or deleted as long as in the remaining rates there is always at least one enabled rate and there are no pending delete action + const canDisableOrDeleteRate = Object.values(customUnit?.rates ?? {}).some( + (distanceRate: Rate) => distanceRate?.enabled && rateID !== distanceRate?.customUnitRateID && distanceRate?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + ); + + if (!rate?.enabled || canDisableOrDeleteRate) { + setSelectedDistanceRates((prevSelectedRates) => + prevSelectedRates.map((selectedRate) => { + if (selectedRate.customUnitRateID === rateID) { + return {...selectedRate, enabled: value}; + } + return selectedRate; + }), + ); + + DistanceRate.setPolicyDistanceRatesEnabled(policyID, customUnit, [{...rate, enabled: value}]); + } else { + setIsWarningModalVisible(true); + } + }, + [customUnit, policyID], + ); + const distanceRatesList = useMemo( () => Object.values(customUnitRates) @@ -119,9 +148,16 @@ function PolicyDistanceRatesPage({ value.pendingFields?.taxClaimablePercentage ?? (policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD ? policy?.pendingAction : undefined), errors: value.errors ?? undefined, - rightElement: , + rightElement: ( + updateDistanceRateEnabled(newValue, value.customUnitRateID)} + disabled={value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE} + /> + ), })), - [customUnit?.attributes?.unit, customUnitRates, selectedDistanceRates, translate, policy?.pendingAction, canSelectMultiple], + [customUnitRates, translate, customUnit, selectedDistanceRates, canSelectMultiple, policy?.pendingAction, updateDistanceRateEnabled], ); const addRate = () => { diff --git a/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx b/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx index 7203c37ca704..908888fc887e 100644 --- a/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx +++ b/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -10,12 +10,12 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; -import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel'; import TableListItem from '@components/SelectionList/TableListItem'; import type {ListItem} from '@components/SelectionList/types'; import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; +import Switch from '@components/Switch'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -90,6 +90,18 @@ function ReportFieldsListValuesPage({ return [reportFieldValues, reportFieldDisabledValues]; }, [formDraft?.disabledListValues, formDraft?.listValues, policy?.fieldList, reportFieldID]); + const updateReportFieldListValueEnabled = useCallback( + (value: boolean, valueIndex: number) => { + if (reportFieldID) { + ReportField.updateReportFieldListValueEnabled(policyID, reportFieldID, [Number(valueIndex)], value); + return; + } + + ReportField.setReportFieldsListValueEnabled([valueIndex], value); + }, + [policyID, reportFieldID], + ); + const listValuesSections = useMemo(() => { const data = listValues .map((value, index) => ({ @@ -99,11 +111,17 @@ function ReportFieldsListValuesPage({ keyForList: value, isSelected: selectedValues[value] && canSelectMultiple, enabled: !disabledListValues.at(index) ?? true, - rightElement: , + rightElement: ( + updateReportFieldListValueEnabled(newValue, index)} + /> + ), })) .sort((a, b) => localeCompare(a.value, b.value)); return [{data, isDisabled: false}]; - }, [canSelectMultiple, disabledListValues, listValues, selectedValues, translate]); + }, [canSelectMultiple, disabledListValues, listValues, selectedValues, translate, updateReportFieldListValueEnabled]); const shouldShowEmptyState = Object.values(listValues ?? {}).length <= 0; const selectedValuesArray = Object.keys(selectedValues).filter((key) => selectedValues[key]); diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index cd3510cfffb5..fb94af11481c 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -15,11 +15,11 @@ import * as Illustrations from '@components/Icon/Illustrations'; import LottieAnimations from '@components/LottieAnimations'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import ScreenWrapper from '@components/ScreenWrapper'; -import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel'; import TableListItem from '@components/SelectionList/TableListItem'; import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; +import Switch from '@components/Switch'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useEnvironment from '@hooks/useEnvironment'; @@ -103,23 +103,49 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { : undefined; }; + const updateWorkspaceTagEnabled = useCallback( + (value: boolean, tagName: string) => { + Tag.setWorkspaceTagEnabled(policyID, {[tagName]: {name: tagName, enabled: value}}, 0); + }, + [policyID], + ); + + const updateWorkspaceRequiresTag = useCallback( + (value: boolean, orderWeight: number) => { + Tag.setPolicyTagsRequired(policyID, value, orderWeight); + }, + [policyID], + ); const tagList = useMemo(() => { if (isMultiLevelTags) { - return policyTagLists.map((policyTagList) => ({ - value: policyTagList.name, - orderWeight: policyTagList.orderWeight, - text: PolicyUtils.getCleanedTagName(policyTagList.name), - keyForList: String(policyTagList.orderWeight), - isSelected: selectedTags[policyTagList.name] && canSelectMultiple, - pendingAction: getPendingAction(policyTagList), - enabled: true, - required: policyTagList.required, - rightElement: ( - tag.enabled) ? translate('common.required') : undefined} - /> - ), - })); + return policyTagLists.map((policyTagList) => { + const areTagsEnabled = !!Object.values(policyTagList?.tags ?? {}).some((tag) => tag.enabled); + const isSwitchDisabled = !policyTagList.required && !areTagsEnabled; + const isSwitchEnabled = policyTagList.required && areTagsEnabled; + + if (policyTagList.required && !areTagsEnabled) { + updateWorkspaceRequiresTag(false, policyTagList.orderWeight); + } + + return { + value: policyTagList.name, + orderWeight: policyTagList.orderWeight, + text: PolicyUtils.getCleanedTagName(policyTagList.name), + keyForList: String(policyTagList.orderWeight), + isSelected: selectedTags[policyTagList.name] && canSelectMultiple, + pendingAction: getPendingAction(policyTagList), + enabled: true, + required: policyTagList.required, + rightElement: ( + updateWorkspaceRequiresTag(newValue, policyTagList.orderWeight)} + disabled={isSwitchDisabled} + /> + ), + }; + }); } const sortedTags = lodashSortBy(Object.values(policyTagLists.at(0)?.tags ?? {}), 'name', localeCompare) as PolicyTag[]; return sortedTags.map((tag) => ({ @@ -131,9 +157,16 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { errors: tag.errors ?? undefined, enabled: tag.enabled, isDisabled: tag.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - rightElement: , + rightElement: ( + updateWorkspaceTagEnabled(newValue, tag.name)} + /> + ), })); - }, [isMultiLevelTags, policyTagLists, selectedTags, canSelectMultiple, translate]); + }, [isMultiLevelTags, policyTagLists, selectedTags, canSelectMultiple, translate, updateWorkspaceRequiresTag, updateWorkspaceTagEnabled]); const tagListKeyedByName = useMemo( () => diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index 173c89c41551..a69dccc2f606 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -10,10 +10,10 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; -import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel'; import TableListItem from '@components/SelectionList/TableListItem'; import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; +import Switch from '@components/Switch'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -52,7 +52,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { const dropdownButtonRef = useRef(null); const [isDeleteTagsConfirmModalVisible, setIsDeleteTagsConfirmModalVisible] = useState(false); const isFocused = useIsFocused(); - const policyID = route.params.policyID ?? '-1'; + const policyID = route.params.policyID; const backTo = route.params.backTo; const policy = usePolicy(policyID); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`); @@ -80,6 +80,13 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { }; }, [isFocused]); + const updateWorkspaceTagEnabled = useCallback( + (value: boolean, tagName: string) => { + Tag.setWorkspaceTagEnabled(policyID, {[tagName]: {name: tagName, enabled: value}}, route.params.orderWeight); + }, + [policyID, route.params.orderWeight], + ); + const tagList = useMemo( () => Object.values(currentPolicyTag?.tags ?? {}) @@ -93,9 +100,16 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { errors: tag.errors ?? undefined, enabled: tag.enabled, isDisabled: tag.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - rightElement: , + rightElement: ( + updateWorkspaceTagEnabled(newValue, tag.name)} + /> + ), })), - [currentPolicyTag, selectedTags, canSelectMultiple, translate], + [currentPolicyTag?.tags, selectedTags, canSelectMultiple, translate, updateWorkspaceTagEnabled], ); const hasDependentTags = useMemo(() => PolicyUtils.hasDependentTags(policy, policyTags), [policy, policyTags]); diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index e064c04878a1..deb304808707 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -10,11 +10,11 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; -import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel'; import TableListItem from '@components/SelectionList/TableListItem'; import type {ListItem} from '@components/SelectionList/types'; import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; +import Switch from '@components/Switch'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useEnvironment from '@hooks/useEnvironment'; @@ -111,24 +111,42 @@ function WorkspaceTaxesPage({ [defaultExternalID, foreignTaxDefault, translate], ); + const updateWorkspaceTaxEnabled = useCallback( + (value: boolean, taxID: string) => { + setPolicyTaxesEnabled(policyID, [taxID], value); + }, + [policyID], + ); + const taxesList = useMemo(() => { if (!policy) { return []; } return Object.entries(policy.taxRates?.taxes ?? {}) - .map(([key, value]) => ({ - text: value.name, - alternateText: textForDefault(key, value), - keyForList: key, - isSelected: !!selectedTaxesIDs.includes(key) && canSelectMultiple, - isDisabledCheckbox: !PolicyUtils.canEditTaxRate(policy, key), - isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - pendingAction: value.pendingAction ?? (Object.keys(value.pendingFields ?? {}).length > 0 ? CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE : null), - errors: value.errors ?? ErrorUtils.getLatestErrorFieldForAnyField(value), - rightElement: , - })) + .map(([key, value]) => { + const canEditTaxRate = policy && PolicyUtils.canEditTaxRate(policy, key); + + return { + text: value.name, + alternateText: textForDefault(key, value), + keyForList: key, + isSelected: !!selectedTaxesIDs.includes(key) && canSelectMultiple, + isDisabledCheckbox: !PolicyUtils.canEditTaxRate(policy, key), + isDisabled: value.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + pendingAction: value.pendingAction ?? (Object.keys(value.pendingFields ?? {}).length > 0 ? CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE : null), + errors: value.errors ?? ErrorUtils.getLatestErrorFieldForAnyField(value), + rightElement: ( + updateWorkspaceTaxEnabled(newValue, key)} + /> + ), + }; + }) .sort((a, b) => (a.text ?? a.keyForList ?? '').localeCompare(b.text ?? b.keyForList ?? '')); - }, [policy, textForDefault, selectedTaxesIDs, canSelectMultiple, translate]); + }, [policy, textForDefault, selectedTaxesIDs, canSelectMultiple, translate, updateWorkspaceTaxEnabled]); const isLoading = !isOffline && taxesList === undefined; From c6ddd5f9c5ed15f728c64104e4939e2d3cb80c1c Mon Sep 17 00:00:00 2001 From: abzokhattab Date: Tue, 14 Jan 2025 04:27:42 +0100 Subject: [PATCH 04/72] Fixing eslint errs --- .../categories/WorkspaceCategoriesPage.tsx | 33 +++++++------ .../distanceRates/PolicyDistanceRatesPage.tsx | 30 +++++++----- .../ReportFieldsListValuesPage.tsx | 35 ++++++++------ .../workspace/tags/WorkspaceTagsPage.tsx | 35 ++++++++------ .../workspace/tags/WorkspaceViewTagsPage.tsx | 46 +++++++++++-------- .../workspace/taxes/WorkspaceTaxesPage.tsx | 20 ++++---- 6 files changed, 112 insertions(+), 87 deletions(-) diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index c16e642e4684..066a867ae3d3 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -34,16 +34,15 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import {isConnectionInProgress} from '@libs/actions/connections'; import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {close} from '@libs/actions/Modal'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import localeCompare from '@libs/LocaleCompare'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; -import * as PolicyUtils from '@libs/PolicyUtils'; +import {getCurrentConnectionName, hasAccountingConnections, shouldShowSyncError} from '@libs/PolicyUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; -import * as Modal from '@userActions/Modal'; -import * as Category from '@userActions/Policy/Category'; -import {deleteWorkspaceCategories, setWorkspaceCategoryEnabled} from '@userActions/Policy/Category'; +import {clearCategoryErrors, deleteWorkspaceCategories, downloadCategoriesCSV, openPolicyCategoriesPage, setWorkspaceCategoryEnabled} from '@userActions/Policy/Category'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -79,15 +78,15 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`); const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`); const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy); - const hasSyncError = PolicyUtils.shouldShowSyncError(policy, isSyncInProgress); + const hasSyncError = shouldShowSyncError(policy, isSyncInProgress); const isConnectedToAccounting = Object.keys(policy?.connections ?? {}).length > 0; - const currentConnectionName = PolicyUtils.getCurrentConnectionName(policy); + const currentConnectionName = getCurrentConnectionName(policy); const isQuickSettingsFlow = !!backTo; const canSelectMultiple = isSmallScreenWidth ? selectionMode?.isEnabled : true; const fetchCategories = useCallback(() => { - Category.openPolicyCategoriesPage(policyId); + openPolicyCategoriesPage(policyId); }, [policyId]); const {isOffline} = useNetwork({onReconnect: fetchCategories}); @@ -107,7 +106,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const updateWorkspaceRequiresCategory = useCallback( (value: boolean, categoryName: string) => { - Category.setWorkspaceCategoryEnabled(policyId, {[categoryName]: {name: categoryName, enabled: value}}); + setWorkspaceCategoryEnabled(policyId, {[categoryName]: {name: categoryName, enabled: value}}); }, [policyId], ); @@ -187,7 +186,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { }; const dismissError = (item: PolicyOption) => { - Category.clearCategoryErrors(policyId, item.keyForList); + clearCategoryErrors(policyId, item.keyForList); }; const selectedCategoriesArray = Object.keys(selectedCategories).filter((key) => selectedCategories[key]); @@ -275,7 +274,7 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { return ( - {!PolicyUtils.hasAccountingConnections(policy) && ( + {!hasAccountingConnections(policy) && (