From e407d21d691643e23cf2667db684abe385cccd56 Mon Sep 17 00:00:00 2001 From: Filip Solecki Date: Thu, 22 Aug 2024 01:24:17 +0200 Subject: [PATCH 01/38] Add CSV import flow to Categories page --- assets/images/spreadsheet-computer.svg | 186 ++++++++++++ assets/images/table.svg | 3 + package-lock.json | 14 +- package.json | 3 +- src/CONST.ts | 27 ++ src/ONYXKEYS.ts | 4 + src/ROUTES.ts | 8 + src/SCREENS.ts | 2 + .../ButtonWithDropdownMenu/index.tsx | 7 +- .../ButtonWithDropdownMenu/types.ts | 5 +- src/components/FilePicker/index.native.tsx | 278 ++++++++++++++++++ src/components/FilePicker/index.tsx | 79 +++++ src/components/FilePicker/types.ts | 40 +++ src/components/Icon/Expensicons.ts | 2 + src/components/ImportColumn.tsx | 182 ++++++++++++ src/components/ImportSpreadsheet.tsx | 214 ++++++++++++++ src/components/ImportSpreadsheetColumns.tsx | 93 ++++++ src/components/OfflineWithFeedback.tsx | 14 +- src/languages/en.ts | 24 +- src/languages/es.ts | 24 +- .../parameters/ImportCategoriesSpreadsheet.ts | 10 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../ModalStackNavigators/index.tsx | 2 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 2 + src/libs/Navigation/linkingConfig/config.ts | 6 + src/libs/Navigation/types.ts | 6 + src/libs/actions/ImportSpreadsheet.ts | 27 ++ src/libs/actions/Policy/Category.ts | 65 ++++ src/pages/iou/ReceiptDropUI.tsx | 8 +- .../step/IOURequestStepScan/index.native.tsx | 4 +- .../request/step/IOURequestStepScan/index.tsx | 18 +- .../categories/ImportCategoriesPage.tsx | 21 ++ .../categories/ImportedCategoriesPage.tsx | 150 ++++++++++ .../categories/WorkspaceCategoriesPage.tsx | 35 ++- src/styles/index.ts | 27 +- src/styles/theme/themes/dark.ts | 2 +- src/styles/theme/themes/light.ts | 2 +- src/styles/theme/types.ts | 2 +- src/types/onyx/ImportedSpreadsheet.ts | 22 ++ src/types/onyx/index.ts | 2 + 41 files changed, 1585 insertions(+), 38 deletions(-) create mode 100644 assets/images/spreadsheet-computer.svg create mode 100644 assets/images/table.svg create mode 100644 src/components/FilePicker/index.native.tsx create mode 100644 src/components/FilePicker/index.tsx create mode 100644 src/components/FilePicker/types.ts create mode 100644 src/components/ImportColumn.tsx create mode 100644 src/components/ImportSpreadsheet.tsx create mode 100644 src/components/ImportSpreadsheetColumns.tsx create mode 100644 src/libs/API/parameters/ImportCategoriesSpreadsheet.ts create mode 100644 src/libs/actions/ImportSpreadsheet.ts create mode 100644 src/pages/workspace/categories/ImportCategoriesPage.tsx create mode 100644 src/pages/workspace/categories/ImportedCategoriesPage.tsx create mode 100644 src/types/onyx/ImportedSpreadsheet.ts diff --git a/assets/images/spreadsheet-computer.svg b/assets/images/spreadsheet-computer.svg new file mode 100644 index 000000000000..74cac455537a --- /dev/null +++ b/assets/images/spreadsheet-computer.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/table.svg b/assets/images/table.svg new file mode 100644 index 000000000000..a9cfe68f339e --- /dev/null +++ b/assets/images/table.svg @@ -0,0 +1,3 @@ + + + diff --git a/package-lock.json b/package-lock.json index 338308e8e564..a468f3c03e1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -132,7 +132,8 @@ "react-web-config": "^1.0.0", "react-webcam": "^7.1.1", "react-window": "^1.8.9", - "semver": "^7.5.2" + "semver": "^7.5.2", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "devDependencies": { "@actions/core": "1.10.0", @@ -44216,6 +44217,17 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/xlsx": { + "version": "0.20.3", + "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/xml2js": { "version": "0.6.0", "license": "MIT", diff --git a/package.json b/package.json index ce2ead203c24..d9ba4cbd82ef 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,8 @@ "react-web-config": "^1.0.0", "react-webcam": "^7.1.1", "react-window": "^1.8.9", - "semver": "^7.5.2" + "semver": "^7.5.2", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "devDependencies": { "@actions/core": "1.10.0", diff --git a/src/CONST.ts b/src/CONST.ts index 88a35f1c3e8a..bb49c921b92d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -120,6 +120,9 @@ const CONST = { // Allowed extensions for receipts ALLOWED_RECEIPT_EXTENSIONS: ['jpg', 'jpeg', 'gif', 'png', 'pdf', 'htm', 'html', 'text', 'rtf', 'doc', 'tif', 'tiff', 'msword', 'zip', 'xml', 'message'], + + // Allowed extensions for spreadsheets import + ALLOWED_SPREADSHEET_EXTENSIONS: ['xls', 'xlsx', 'csv', 'txt'], }, // This is limit set on servers, do not update without wider internal discussion @@ -5496,6 +5499,30 @@ const CONST = { REMOVE: 'remove', }, }, + + CSV_IMPORT_COLUMNS: { + EMAIL: 'email', + NAME: 'name', + GL_CODE: 'glCode', + SUBMIT_TO: 'submitTo', + APPROVE_TO: 'approveTo', + CUSTOM_FIELD_1: 'customField1', + CUSTOM_FIELD_2: 'customField2', + ROLE: 'role', + REPORT_THRESHHOLD: 'reportThreshold', + APPROVE_TO_ALTERNATE: 'approveToAlternate', + SUBRATE: 'subRate', + AMOUNT: 'amount', + CURRENCY: 'currency', + RATE_ID: 'rateID', + ENABLED: 'enabled', + IGNORE: 'ignore', + }, + + IMPORT_SPREADSHEET: { + ICON_WIDTH: 180, + ICON_HEIGHT: 160, + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b7b6cf53a176..dea8e81dc2cf 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -400,6 +400,9 @@ const ONYXKEYS = { /** Stores the information about currently edited advanced approval workflow */ APPROVAL_WORKFLOW: 'approvalWorkflow', + /** Stores information about recently uploaded spreadsheet file */ + IMPORTED_SPREADSHEET: 'importedSpreadsheet', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -891,6 +894,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_WORKSPACE_TOOLTIP]: OnyxTypes.WorkspaceTooltip; [ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[]; [ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx; + [ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index dd87e5a9996f..8c69c7f26528 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -742,6 +742,14 @@ const ROUTES = { route: 'settings/workspaces/:policyID/categories/settings', getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories/settings` as const, }, + WORKSPACE_CATEGORIES_IMPORT: { + route: 'settings/workspaces/:policyID/categories/import', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories/import` as const, + }, + WORKSPACE_CATEGORIES_IMPORTED: { + route: 'settings/workspaces/:policyID/categories/imported', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories/imported` as const, + }, WORKSPACE_CATEGORY_CREATE: { route: 'settings/workspaces/:policyID/categories/new', getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories/new` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index cc4360d7695d..e5fa9dc6d5ed 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -428,6 +428,8 @@ const SCREENS = { CATEGORY_GL_CODE: 'Category_GL_Code', CATEGORY_SETTINGS: 'Category_Settings', CATEGORIES_SETTINGS: 'Categories_Settings', + CATEGORIES_IMPORT: 'Categories_Import', + CATEGORIES_IMPORTED: 'Categories_Imported', MORE_FEATURES: 'Workspace_More_Features', MEMBER_DETAILS: 'Workspace_Member_Details', OWNER_CHANGE_CHECK: 'Workspace_Owner_Change_Check', diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index 74b38f515a06..68923339509a 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -39,11 +39,12 @@ function ButtonWithDropdownMenu({ enterKeyEventListenerPriority = 0, wrapperStyle, useKeyboardShortcuts = false, + defaultSelectedIndex = 0, }: ButtonWithDropdownMenuProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const [selectedItemIndex, setSelectedItemIndex] = useState(0); + const [selectedItemIndex, setSelectedItemIndex] = useState(defaultSelectedIndex); const [isMenuVisible, setIsMenuVisible] = useState(false); const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); const {windowWidth, windowHeight} = useWindowDimensions(); @@ -97,7 +98,7 @@ function ButtonWithDropdownMenu({ return ( {shouldAlwaysShowDropdownMenu || options.length > 1 ? ( - + <> )} - + ) : ( )} - + ) : (