Skip to content
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

Add CSV import flow to Categories page #47827

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e407d21
Add CSV import flow to Categories page
filip-solecki Aug 21, 2024
e8a6e49
Fix filePicker for native apps
filip-solecki Aug 22, 2024
e68b88c
Add acceptable types to fix mWeb file selecting
filip-solecki Aug 22, 2024
2773a18
Fix scrolling and import button position
filip-solecki Aug 22, 2024
d5714f7
Fix footer styles
filip-solecki Aug 22, 2024
da416c7
Fix duplicate column condition
filip-solecki Aug 22, 2024
4da476d
CR fixes
filip-solecki Aug 23, 2024
41d1906
Remove unnecessary succesData update
filip-solecki Aug 23, 2024
0b50987
UI fixes
filip-solecki Aug 23, 2024
4c7bc05
Add GL Code field for control policies
filip-solecki Aug 23, 2024
def44cd
Merge branch 'main' into filip-solecki/import-categories-csv
filip-solecki Aug 23, 2024
6184709
Remove unnecessary categories update on failure
filip-solecki Aug 23, 2024
9ae48f6
Rename Onyx update method
filip-solecki Aug 23, 2024
861c7b6
Prepare translations for other imports
filip-solecki Aug 23, 2024
f69f393
Revert additional translations
filip-solecki Aug 23, 2024
f5904e2
Create utils file
filip-solecki Aug 26, 2024
3d82cc4
rename hide modal function
filip-solecki Aug 26, 2024
d12397a
Make sure GL Code is a string
filip-solecki Aug 26, 2024
2755ef9
Make GL Code optional
filip-solecki Aug 26, 2024
88533d0
Apply approved translations
filip-solecki Aug 26, 2024
a1dcb10
Merge branch 'main' into filip-solecki/import-categories-csv
filip-solecki Aug 27, 2024
8bfe96f
Fix package lock
filip-solecki Aug 27, 2024
41a9430
Use large button for import
filip-solecki Aug 27, 2024
d2eb345
Add types comments for ImportSpreadsheetColumns file
filip-solecki Aug 27, 2024
6a3971f
Add types comments to ImportSpreadsheet file
filip-solecki Aug 27, 2024
0970694
Merge branch 'main' into filip-solecki/import-categories-csv
filip-solecki Aug 27, 2024
cd5d6c5
Remove scrollview margin
filip-solecki Aug 27, 2024
3524d45
Remove footer margin
filip-solecki Aug 27, 2024
a157217
Remove button margin
filip-solecki Aug 27, 2024
b6e0090
Add margin from scrollbar to view
filip-solecki Aug 27, 2024
09223e9
Merge main
filip-solecki Aug 27, 2024
0f7b3f8
Merge branch 'main' into filip-solecki/import-categories-csv
filip-solecki Aug 27, 2024
2d9c4e9
Add small button styling
filip-solecki Aug 27, 2024
ef6f76a
Use vendoring for xlsx library
filip-solecki Aug 27, 2024
a637a8a
Fix containsHeader logic and styling
filip-solecki Aug 29, 2024
564b002
Merge branch 'main' into filip-solecki/import-categories-csv
filip-solecki Aug 29, 2024
31a969b
Change errors logic and fix text ellipsis
filip-solecki Aug 29, 2024
383fc30
Slice header if containsHeader is true
filip-solecki Aug 30, 2024
e734d9e
Fix validation
filip-solecki Aug 30, 2024
f88212c
Merge branch 'main' into filip-solecki/import-categories-csv
filip-solecki Aug 30, 2024
f595652
Clear containsHeader logic
filip-solecki Aug 30, 2024
33dff0d
Remove brackets from Learn More Link
filip-solecki Sep 2, 2024
c68b384
Fix selected bg and add docs
filip-solecki Sep 3, 2024
77bbb95
Update src/libs/importSpreadsheetUtils.ts
filip-solecki Sep 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions assets/images/spreadsheet-computer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/images/table.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 85 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "^0.18.5"
},
"devDependencies": {
"@actions/core": "1.10.0",
Expand Down
27 changes: 27 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ const CONST = {
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
API_TRANSACTION_CATEGORY_MAX_LENGTH: 255,

Expand Down Expand Up @@ -5510,6 +5513,30 @@ const CONST = {
REMOVE: 'remove',
},
},

CSV_IMPORT_COLUMNS: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was the idea in the doc to split into constants for each file, should we update to match that?

Copy link
Contributor Author

@filip-solecki filip-solecki Aug 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the idea, but it is not needed IMO and in the function findColumnName we don't know it e.g. field GL Code is from categories or tags, so what should be set there as an attribute? (there is no difference, because they are the same) Example below:

    case 'glcode':
    case 'gl':
       attribute = CONST.CSV_IMPORT_COLUMNS.CATEGORIES.GL_CODE;
       break;

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;
Expand Down
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_',
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
8 changes: 6 additions & 2 deletions src/components/ButtonWithDropdownMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ function ButtonWithDropdownMenu<IValueType>({
enterKeyEventListenerPriority = 0,
wrapperStyle,
useKeyboardShortcuts = false,
defaultSelectedIndex = 0,
shouldShowSelectedItemCheck = false,
}: ButtonWithDropdownMenuProps<IValueType>) {
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<AnchorPosition | null>(null);
const {windowWidth, windowHeight} = useWindowDimensions();
Expand Down Expand Up @@ -93,11 +95,12 @@ function ButtonWithDropdownMenu<IValueType>({
isActive: useKeyboardShortcuts,
},
);
const splitButtonWrapperStyle = isSplitButton ? [styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter] : {};

return (
<View style={wrapperStyle}>
{shouldAlwaysShowDropdownMenu || options.length > 1 ? (
<View style={[styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, style]}>
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
<View style={[splitButtonWrapperStyle, style]}>
<Button
success={success}
pressOnEnter={pressOnEnter}
Expand Down Expand Up @@ -170,6 +173,7 @@ function ButtonWithDropdownMenu<IValueType>({
onModalShow={onOptionsMenuShow}
onItemSelected={() => setIsMenuVisible(false)}
anchorPosition={popoverAnchorPosition}
shouldShowSelectedItemCheck={shouldShowSelectedItemCheck}
anchorRef={nullCheckRef(dropdownAnchor)}
withoutOverlay
anchorAlignment={anchorAlignment}
Expand Down
8 changes: 7 additions & 1 deletion src/components/ButtonWithDropdownMenu/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type ButtonWithDropdownMenuProps<TValueType> = {
isLoading?: boolean;

/** The size of button size */
buttonSize: ValueOf<typeof CONST.DROPDOWN_BUTTON_SIZE>;
buttonSize?: ValueOf<typeof CONST.DROPDOWN_BUTTON_SIZE>;

/** Should the confirmation button be disabled? */
isDisabled?: boolean;
Expand Down Expand Up @@ -93,6 +93,12 @@ type ButtonWithDropdownMenuProps<TValueType> = {

/** Whether to use keyboard shortcuts for confirmation or not */
useKeyboardShortcuts?: boolean;

/** Decides which index in menuItems should be selected */
defaultSelectedIndex?: number;

/** Whether selected items should be marked as selected */
shouldShowSelectedItemCheck?: boolean;
};

export type {
Expand Down
Loading
Loading