diff --git a/src/components/ImportSpreadsheet.tsx b/src/components/ImportSpreadsheet.tsx index fd75bfc2d3b5..8aed242987fd 100644 --- a/src/components/ImportSpreadsheet.tsx +++ b/src/components/ImportSpreadsheet.tsx @@ -167,7 +167,7 @@ function ImportSpreedsheet({backTo, goTo}: ImportSpreedsheetProps) { Navigation.navigate(backTo)} /> diff --git a/src/languages/en.ts b/src/languages/en.ts index 63a7d36385e1..38784f14a77d 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -389,7 +389,6 @@ export default { ignore: 'Ignore', enabled: 'Enabled', import: 'Import', - importSpreadsheet: 'Import spreadsheet', offlinePrompt: "You can't take this action right now.", outstanding: 'Outstanding', chats: 'Chats', @@ -687,6 +686,8 @@ export default { importFailedDescription: 'Please ensure all fields are filled out correctly and try again. If the problem persists, please reach out to Concierge.', invalidFileMessage: 'The file you uploaded is either empty or contains invalid data. Please ensure that the file is correctly formatted and contains the necessary information before uploading it again.', + importSpreadsheet: 'Import spreadsheet', + downloadCSV: 'Download CSV', }, receipt: { upload: 'Upload receipt', diff --git a/src/languages/es.ts b/src/languages/es.ts index 19384f1310d3..a56204de6fe9 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -373,7 +373,6 @@ export default { filterLogs: 'Registros de filtrado', network: 'La red', reportID: 'ID del informe', - importSpreadsheet: 'Importar hoja de cálculo', chooseFile: 'Elegir archivo', dropTitle: 'Suéltalo', dropMessage: 'Suelta tu archivo aquí', @@ -680,6 +679,8 @@ export default { importSuccessfullTitle: 'Importar categorías', invalidFileMessage: 'El archivo que ha cargado está vacío o contiene datos no válidos. Asegúrese de que el archivo tiene el formato correcto y contiene la información necesaria antes de volver a cargarlo.', + importSpreadsheet: 'Importar hoja de cálculo', + downloadCSV: 'Descargar CSV', }, receipt: { upload: 'Subir recibo', diff --git a/src/libs/API/parameters/ExportCategoriesSpreadsheet.ts b/src/libs/API/parameters/ExportCategoriesSpreadsheet.ts new file mode 100644 index 000000000000..e62cbe684cf6 --- /dev/null +++ b/src/libs/API/parameters/ExportCategoriesSpreadsheet.ts @@ -0,0 +1,6 @@ +type ExportCategoriesSpreadsheetParams = { + /** ID of the policy */ + policyID: string; +}; + +export default ExportCategoriesSpreadsheetParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index cb18aae9b6a4..c6cc50df3004 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -308,6 +308,7 @@ export type {default as SetPolicyCategoryReceiptsRequiredParams} from './SetPoli export type {default as RemovePolicyCategoryReceiptsRequiredParams} from './RemovePolicyCategoryReceiptsRequiredParams'; export type {default as UpdateQuickbooksOnlineAutoCreateVendorParams} from './UpdateQuickbooksOnlineAutoCreateVendorParams'; export type {default as ImportCategoriesSpreadsheetParams} from './ImportCategoriesSpreadsheet'; +export type {default as ExportCategoriesSpreadsheetParams} from './ExportCategoriesSpreadsheet'; export type {default as UpdateXeroGenericTypeParams} from './UpdateXeroGenericTypeParams'; export type {default as UpdateCardSettlementFrequencyParams} from './UpdateCardSettlementFrequencyParams'; export type {default as UpdateCardSettlementAccountParams} from './UpdateCardSettlementAccountParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 12fddbf9e1e9..a59bffca1ddf 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -142,7 +142,8 @@ const WRITE_COMMANDS = { SET_WORKSPACE_CATEGORIES_ENABLED: 'SetWorkspaceCategoriesEnabled', SET_POLICY_TAGS_ENABLED: 'SetPolicyTagsEnabled', CREATE_WORKSPACE_CATEGORIES: 'CreateWorkspaceCategories', - IMPORT_CATEGORIES_SREADSHEET: 'ImportCategoriesSpreadsheet', + IMPORT_CATEGORIES_SPREADSHEET: 'ImportCategoriesSpreadsheet', + EXPORT_CATEGORIES_CSV: 'ExportCategoriesCSV', RENAME_WORKSPACE_CATEGORY: 'RenameWorkspaceCategory', CREATE_POLICY_TAG: 'CreatePolicyTag', RENAME_POLICY_TAG: 'RenamePolicyTag', @@ -501,7 +502,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.CREATE_WORKSPACE_FROM_IOU_PAYMENT]: Parameters.CreateWorkspaceFromIOUPaymentParams; [WRITE_COMMANDS.SET_WORKSPACE_CATEGORIES_ENABLED]: Parameters.SetWorkspaceCategoriesEnabledParams; [WRITE_COMMANDS.CREATE_WORKSPACE_CATEGORIES]: Parameters.CreateWorkspaceCategoriesParams; - [WRITE_COMMANDS.IMPORT_CATEGORIES_SREADSHEET]: Parameters.ImportCategoriesSpreadsheetParams; + [WRITE_COMMANDS.IMPORT_CATEGORIES_SPREADSHEET]: Parameters.ImportCategoriesSpreadsheetParams; + [WRITE_COMMANDS.EXPORT_CATEGORIES_CSV]: Parameters.ExportCategoriesSpreadsheetParams; [WRITE_COMMANDS.RENAME_WORKSPACE_CATEGORY]: Parameters.RenameWorkspaceCategoriesParams; [WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams; [WRITE_COMMANDS.DELETE_WORKSPACE_CATEGORIES]: Parameters.DeleteWorkspaceCategoriesParams; diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 16d28481f06f..501b7cbbe1e5 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -17,11 +17,14 @@ import type { UpdatePolicyCategoryGLCodeParams, } from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import * as ApiUtils from '@libs/ApiUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; +import fileDownload from '@libs/fileDownload'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import {translateLocal} from '@libs/Localize'; import Log from '@libs/Log'; +import enhanceParameters from '@libs/Network/enhanceParameters'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import {navigateWhenEnableFeature, removePendingFieldsFromCustomUnit} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -512,7 +515,7 @@ function importPolicyCategories(policyID: string, categories: PolicyCategory[]) categories: JSON.stringify([...categories.map((category) => ({name: category.name, enabled: category.enabled, 'GL Code': String(category['GL Code'])}))]), }; - API.write(WRITE_COMMANDS.IMPORT_CATEGORIES_SREADSHEET, parameters, onyxData); + API.write(WRITE_COMMANDS.IMPORT_CATEGORIES_SPREADSHEET, parameters, onyxData); } function renamePolicyCategory(policyID: string, policyCategory: {oldName: string; newName: string}) { @@ -998,6 +1001,19 @@ function setPolicyDistanceRatesDefaultCategory(policyID: string, currentCustomUn API.write(WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY, params, {optimisticData, successData, failureData}); } +function downloadCategoriesCSV(policyID: string) { + const finalParameters = enhanceParameters(WRITE_COMMANDS.EXPORT_CATEGORIES_CSV, { + policyID, + }); + + const formData = new FormData(); + Object.entries(finalParameters).forEach(([key, value]) => { + formData.append(key, String(value)); + }); + + fileDownload(ApiUtils.getCommandURL({command: WRITE_COMMANDS.EXPORT_CATEGORIES_CSV}), 'Categories.csv', '', false, formData, CONST.NETWORK.METHOD.POST); +} + function setWorkspaceCategoryDescriptionHint(policyID: string, categoryName: string, commentHint: string) { const originalCommentHint = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]?.[categoryName]?.commentHint; @@ -1311,4 +1327,5 @@ export { setPolicyCategoryApprover, setPolicyCategoryTax, importPolicyCategories, + downloadCategoriesCSV, }; diff --git a/src/libs/fileDownload/index.android.ts b/src/libs/fileDownload/index.android.ts index 83255231d26b..a1e81e47994d 100644 --- a/src/libs/fileDownload/index.android.ts +++ b/src/libs/fileDownload/index.android.ts @@ -111,8 +111,7 @@ const postDownloadFile = (url: string, fileName?: string, formData?: FormData, o }) .then((fileData) => { const finalFileName = FileUtils.appendTimeToFileName(fileName ?? 'Expensify'); - const downloadPath = `${RNFS.DownloadDirectoryPath}/Expensify/${finalFileName}`; - + const downloadPath = `${RNFS.DownloadDirectoryPath}/${finalFileName}`; return RNFS.writeFile(downloadPath, fileData, 'utf8').then(() => downloadPath); }) .then((downloadPath) => diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index a720b3f0e568..2de1d33e2765 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -36,6 +36,7 @@ import Navigation from '@libs/Navigation/Navigation'; 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 CONST from '@src/CONST'; @@ -300,15 +301,26 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const menuItems = [ { icon: Expensicons.Table, - text: translate('common.importSpreadsheet'), + text: translate('spreadsheet.importSpreadsheet'), onSelected: () => { if (isOffline) { - setIsOfflineModalVisible(true); + Modal.close(() => setIsOfflineModalVisible(true)); return; } Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES_IMPORT.getRoute(policyId)); }, }, + { + icon: Expensicons.Download, + text: translate('spreadsheet.downloadCSV'), + onSelected: () => { + if (isOffline) { + Modal.close(() => setIsOfflineModalVisible(true)); + return; + } + Category.downloadCategoriesCSV(policyId); + }, + }, ]; return menuItems;