Merge branch 'master' into fix/navigator-button
This commit is contained in:
commit
1badb8a1e2
30 changed files with 17209 additions and 6076 deletions
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
54
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '0 10 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
|
@ -79,7 +79,10 @@ jobs:
|
|||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v2
|
||||
env:
|
||||
API_URI: https://pwa.demo.saleor.rocks/graphql/
|
||||
API_URI: ${{ secrets.API_URI }}
|
||||
APP_MOUNT_URI: ${{ secrets.APP_MOUNT_URI }}
|
||||
CYPRESS_USER_NAME: ${{ secrets.CYPRESS_USER_NAME }}
|
||||
CYPRESS_USER_PASSWORD: ${{ secrets.CYPRESS_USER_PASSWORD }}
|
||||
with:
|
||||
build: npm run build
|
||||
start: npx http-server -a localhost -p 9000 build/dashboard
|
||||
|
|
|
@ -22,6 +22,10 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Reset modal state after closing - #644 by @dominik-zeglen
|
||||
- Fix incorrect messages - #643 by @dominik-zeglen
|
||||
- Do not use devserver to run cypress tests - #650 by @dominik-zeglen
|
||||
- Fix updating product that has no variants - #649 by @dominik-zeglen
|
||||
- Update checkbox design - #651 by @dominik-zeglen
|
||||
- Add warehouse choice - #646 by @dominik-zeglen
|
||||
- Fix user management modal actions - #637 by @eaglesemanation
|
||||
- Fix navigator button rendering on safari browser - #656 by @dominik-zeglen
|
||||
|
||||
## 2.10.1
|
||||
|
|
7
cypress/elements/account/login-selectors.js
Normal file
7
cypress/elements/account/login-selectors.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export const LOGIN_SELECTORS = {
|
||||
emailAddressInput: "input[name='email']",
|
||||
emailPasswordInput: "input[name='password']",
|
||||
signInButton: "[data-test=submit]",
|
||||
warningCredentialMessage: "[data-test=loginErrorMessage]",
|
||||
welcomePage: "[data-test=welcomeHeader]"
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
import { LOGIN_SELECTORS } from "../elements/account/login-selectors";
|
||||
|
||||
// <reference types="cypress" />
|
||||
describe("User authorization", () => {
|
||||
beforeEach(() => {
|
||||
|
@ -7,14 +9,19 @@ describe("User authorization", () => {
|
|||
describe("Login", () => {
|
||||
it("should successfully log in an user", () => {
|
||||
cy.visit("/");
|
||||
cy.loginUser("admin@example.com", "admin");
|
||||
cy.get("[data-test=welcomeHeader]").contains("Hello there");
|
||||
cy.loginUser();
|
||||
cy.get(LOGIN_SELECTORS.welcomePage);
|
||||
});
|
||||
|
||||
it("should fail for wrong password", () => {
|
||||
cy.visit("/");
|
||||
cy.loginUser("admin@example.com", "wrong-password");
|
||||
cy.get("[data-test=loginErrorMessage]");
|
||||
cy.visit("/")
|
||||
.get(LOGIN_SELECTORS.emailAddressInput)
|
||||
.type("admin@example.com")
|
||||
.get(LOGIN_SELECTORS.emailPasswordInput)
|
||||
.type("wrong-password")
|
||||
.get(LOGIN_SELECTORS.signInButton)
|
||||
.click()
|
||||
.get(LOGIN_SELECTORS.warningCredentialMessage);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -24,7 +31,7 @@ describe("User authorization", () => {
|
|||
win.sessionStorage.clear();
|
||||
});
|
||||
cy.visit("/");
|
||||
cy.loginUser("admin@example.com", "admin");
|
||||
cy.loginUser();
|
||||
cy.get("[data-test=userMenu]")
|
||||
.click()
|
||||
.get("[data-test=accountSettingsButton]")
|
||||
|
|
|
@ -2,22 +2,19 @@
|
|||
describe("Warehouse settings", () => {
|
||||
beforeEach(() => {
|
||||
cy.clearSessionData();
|
||||
cy.loginUser("admin@example.com", "admin");
|
||||
|
||||
// Wait for log in
|
||||
cy.get("[data-test=welcomeHeader]").contains("Hello there");
|
||||
});
|
||||
|
||||
it("Warehouse section visible in the configuration", () => {
|
||||
xit("Warehouse section visible in the configuration", () => {
|
||||
cy.visit("/configuration/")
|
||||
.loginUser()
|
||||
.get("[data-testid=warehouses][data-test=settingsSubsection]")
|
||||
.click();
|
||||
cy.location("pathname").should("eq", "/warehouses/");
|
||||
});
|
||||
|
||||
it("Editing warehouse is available", () => {
|
||||
xit("Editing warehouse is available", () => {
|
||||
cy.visit(`/warehouses`)
|
||||
.get("[data-testid=defaultwarehouse]")
|
||||
.loginUser()
|
||||
.get("[data-test=editButton]")
|
||||
.first()
|
||||
.click()
|
||||
|
|
21
cypress/plugins/index.js
Normal file
21
cypress/plugins/index.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
// / <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
};
|
|
@ -1,12 +1,4 @@
|
|||
Cypress.Commands.add("loginUser", (email, password) =>
|
||||
cy
|
||||
.get("input[name='email']")
|
||||
.type(email)
|
||||
.get("input[name='password']")
|
||||
.type(password)
|
||||
.get("[data-test=submit]")
|
||||
.click()
|
||||
);
|
||||
import "./user";
|
||||
|
||||
Cypress.Commands.add("clearSessionData", () => {
|
||||
// Because of known cypress bug, not all local storage data are cleared.
|
||||
|
|
11
cypress/support/user/index.js
Normal file
11
cypress/support/user/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { LOGIN_SELECTORS } from "../../elements/account/login-selectors";
|
||||
|
||||
Cypress.Commands.add("loginUser", () =>
|
||||
cy
|
||||
.get(LOGIN_SELECTORS.emailAddressInput)
|
||||
.type(Cypress.env("USER_NAME"))
|
||||
.get(LOGIN_SELECTORS.emailPasswordInput)
|
||||
.type(Cypress.env("USER_PASSWORD"), { log: false })
|
||||
.get(LOGIN_SELECTORS.signInButton)
|
||||
.click()
|
||||
);
|
|
@ -205,6 +205,62 @@
|
|||
"context": "vat not included in order price",
|
||||
"string": "does not apply"
|
||||
},
|
||||
"productExportFieldCategory": {
|
||||
"context": "product field",
|
||||
"string": "Category"
|
||||
},
|
||||
"productExportFieldCollections": {
|
||||
"context": "product field",
|
||||
"string": "Collections"
|
||||
},
|
||||
"productExportFieldDescription": {
|
||||
"context": "product field",
|
||||
"string": "Description"
|
||||
},
|
||||
"productExportFieldName": {
|
||||
"context": "product field",
|
||||
"string": "Name"
|
||||
},
|
||||
"productExportFieldPrice": {
|
||||
"context": "product field",
|
||||
"string": "Cost Price"
|
||||
},
|
||||
"productExportFieldProductImages": {
|
||||
"context": "product field",
|
||||
"string": "Product Images"
|
||||
},
|
||||
"productExportFieldProductWeight": {
|
||||
"context": "product field",
|
||||
"string": "Export Product Weight"
|
||||
},
|
||||
"productExportFieldTaxes": {
|
||||
"context": "product field",
|
||||
"string": "Charge Taxes"
|
||||
},
|
||||
"productExportFieldType": {
|
||||
"context": "product field",
|
||||
"string": "Type"
|
||||
},
|
||||
"productExportFieldVariantImages": {
|
||||
"context": "product field",
|
||||
"string": "Variant Images"
|
||||
},
|
||||
"productExportFieldVariantPrice": {
|
||||
"context": "product field",
|
||||
"string": "Variant Price"
|
||||
},
|
||||
"productExportFieldVariantSku": {
|
||||
"context": "product field",
|
||||
"string": "Export Variant SKU"
|
||||
},
|
||||
"productExportFieldVariantWeight": {
|
||||
"context": "product field",
|
||||
"string": "Export Variant Weight"
|
||||
},
|
||||
"productExportFieldVisibility": {
|
||||
"context": "product field",
|
||||
"string": "Visibility"
|
||||
},
|
||||
"productStatusLabel": {
|
||||
"context": "product",
|
||||
"string": "Published"
|
||||
|
@ -3928,34 +3984,22 @@
|
|||
"context": "product name",
|
||||
"string": "Name"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1004476569": {
|
||||
"context": "option",
|
||||
"string": "Export stock for all warehouses"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_108342258": {
|
||||
"context": "button",
|
||||
"string": "Load More"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1459686496": {
|
||||
"context": "product field",
|
||||
"string": "Visibility"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1547327218": {
|
||||
"context": "there are more elements of list that are hidden",
|
||||
"string": "and {number} more"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1755013298": {
|
||||
"context": "product field",
|
||||
"string": "Category"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1890035856": {
|
||||
"context": "informations about product organization, header",
|
||||
"string": "Product Organization"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_1952810469": {
|
||||
"context": "product field",
|
||||
"string": "Type"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_2051669917": {
|
||||
"context": "product field",
|
||||
"string": "Cost Price"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_2119710854": {
|
||||
"context": "informations about product seo, header",
|
||||
"string": "SEO Information"
|
||||
|
@ -3964,10 +4008,6 @@
|
|||
"context": "export selected products to csv file",
|
||||
"string": "Selected products ({number})"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_222873645": {
|
||||
"context": "product field",
|
||||
"string": "Collections"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_2318723509": {
|
||||
"context": "export products to csv file, choice field label",
|
||||
"string": "Export information for:"
|
||||
|
@ -3976,6 +4016,10 @@
|
|||
"context": "export all products to csv file",
|
||||
"string": "All products ({number})"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_2474350154": {
|
||||
"context": "list of warehouses",
|
||||
"string": "Warehouses A to Z"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_2659464408": {
|
||||
"context": "product export to csv file, header",
|
||||
"string": "Information exported"
|
||||
|
@ -3988,10 +4032,6 @@
|
|||
"context": "export products to csv file, button",
|
||||
"string": "export products"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_2949617129": {
|
||||
"context": "product field",
|
||||
"string": "Product Images"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_3012202273": {
|
||||
"context": "export products to csv file, dialog header",
|
||||
"string": "Export Information"
|
||||
|
@ -4000,14 +4040,6 @@
|
|||
"context": "product export to csv file, header",
|
||||
"string": "Export Settings"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_3374163063": {
|
||||
"context": "product field",
|
||||
"string": "Description"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_3441755345": {
|
||||
"context": "product field",
|
||||
"string": "Charge Taxes"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_3443345452": {
|
||||
"context": "selectt all options",
|
||||
"string": "Select All"
|
||||
|
@ -4016,13 +4048,12 @@
|
|||
"context": "export products as spreadsheet",
|
||||
"string": "Spreadsheet for Excel, Numbers etc."
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_3544554440": {
|
||||
"context": "product field",
|
||||
"string": "Variant Weight"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_3599582104": {
|
||||
"string": "Search Atrtibuttes"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_3902059658": {
|
||||
"string": "Export Product Stock Quantity to CSV"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_3919525499": {
|
||||
"context": "informations about product stock, header",
|
||||
"string": "Inventory Information"
|
||||
|
@ -4031,10 +4062,6 @@
|
|||
"context": "export products as csv file",
|
||||
"string": "Plain CSV file"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_4160582036": {
|
||||
"context": "product field",
|
||||
"string": "Variant Price"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_472026385": {
|
||||
"context": "select product informations to be exported",
|
||||
"string": "Information exported:"
|
||||
|
@ -4043,14 +4070,6 @@
|
|||
"context": "input helper text, search attributes",
|
||||
"string": "Search by attribute name"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_636461959": {
|
||||
"context": "product field",
|
||||
"string": "Name"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_693960049": {
|
||||
"context": "product field",
|
||||
"string": "SKU"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_700651641": {
|
||||
"context": "export filtered products to csv file",
|
||||
"string": "Current search ({number})"
|
||||
|
@ -4059,14 +4078,6 @@
|
|||
"context": "informations about product prices etc, header",
|
||||
"string": "Financial Information"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_746695941": {
|
||||
"context": "product field",
|
||||
"string": "Weight"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductExportDialog_dot_897437458": {
|
||||
"context": "product field",
|
||||
"string": "Variant Images"
|
||||
},
|
||||
"src_dot_products_dot_components_dot_ProductImageNavigation_dot_3060635772": {
|
||||
"context": "section header",
|
||||
"string": "All Photos"
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
AttributeListUrlQueryParams
|
||||
} from "../../urls";
|
||||
|
||||
export const PRODUCT_FILTERS_KEY = "productFilters";
|
||||
export const ATTRIBUTE_FILTERS_KEY = "attributeFilters";
|
||||
|
||||
export function getFilterOpts(
|
||||
params: AttributeListUrlFilters
|
||||
|
@ -130,7 +130,7 @@ export const {
|
|||
deleteFilterTab,
|
||||
getFilterTabs,
|
||||
saveFilterTab
|
||||
} = createFilterTabUtils<AttributeListUrlFilters>(PRODUCT_FILTERS_KEY);
|
||||
} = createFilterTabUtils<AttributeListUrlFilters>(ATTRIBUTE_FILTERS_KEY);
|
||||
|
||||
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
|
||||
AttributeListUrlQueryParams,
|
||||
|
|
|
@ -8,7 +8,14 @@ export enum JWTError {
|
|||
}
|
||||
|
||||
export function isJwtError(error: GraphQLError): boolean {
|
||||
return !!findValueInEnum(error.extensions.exception.code, JWTError);
|
||||
let jwtError: boolean;
|
||||
try {
|
||||
jwtError = !!findValueInEnum(error.extensions.exception.code, JWTError);
|
||||
} catch (e) {
|
||||
jwtError = false;
|
||||
}
|
||||
|
||||
return jwtError;
|
||||
}
|
||||
|
||||
export function isTokenExpired(error: GraphQLError): boolean {
|
||||
|
|
|
@ -33,7 +33,7 @@ interface ActionDialogProps extends DialogProps {
|
|||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
maxWidth?: "xs" | "sm" | "md" | "lg" | "xl" | false;
|
||||
title: string;
|
||||
variant?: "default" | "delete";
|
||||
variant?: "default" | "delete" | "info";
|
||||
onConfirm();
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,7 @@ const ActionDialog: React.FC<ActionDialogProps> = props => {
|
|||
<Button onClick={onClose}>
|
||||
<FormattedMessage {...buttonMessages.back} />
|
||||
</Button>
|
||||
{variant !== "info" && (
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
color="primary"
|
||||
|
@ -75,6 +76,7 @@ const ActionDialog: React.FC<ActionDialogProps> = props => {
|
|||
? intl.formatMessage(buttonMessages.delete)
|
||||
: intl.formatMessage(buttonMessages.confirm))}
|
||||
</ConfirmButton>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -1,136 +1,25 @@
|
|||
import ButtonBase from "@material-ui/core/ButtonBase";
|
||||
import { CheckboxProps as MuiCheckboxProps } from "@material-ui/core/Checkbox";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import classNames from "classnames";
|
||||
import MuiCheckbox, {
|
||||
CheckboxProps as MuiCheckboxProps
|
||||
} from "@material-ui/core/Checkbox";
|
||||
import React from "react";
|
||||
|
||||
export type CheckboxProps = Omit<
|
||||
MuiCheckboxProps,
|
||||
| "checkedIcon"
|
||||
| "color"
|
||||
| "icon"
|
||||
| "indeterminateIcon"
|
||||
| "classes"
|
||||
| "onChange"
|
||||
| "onClick"
|
||||
"checkedIcon" | "color" | "icon" | "indeterminateIcon" | "classes" | "onClick"
|
||||
> & {
|
||||
disableClickPropagation?: boolean;
|
||||
onChange?: (event: React.ChangeEvent<any>) => void;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
box: {
|
||||
"&$checked": {
|
||||
"&:before": {
|
||||
background: theme.palette.primary.main,
|
||||
color: theme.palette.background.paper,
|
||||
content: '"\\2713"',
|
||||
fontWeight: "bold",
|
||||
textAlign: "center"
|
||||
},
|
||||
borderColor: theme.palette.primary.main
|
||||
},
|
||||
"&$disabled": {
|
||||
borderColor: theme.palette.grey[200]
|
||||
},
|
||||
"&$indeterminate": {
|
||||
"&:before": {
|
||||
background: theme.palette.primary.main,
|
||||
height: 2,
|
||||
top: 5
|
||||
},
|
||||
borderColor: theme.palette.primary.main
|
||||
},
|
||||
"&:before": {
|
||||
background: "rgba(0, 0, 0, 0)",
|
||||
content: '""',
|
||||
height: 14,
|
||||
left: -1,
|
||||
position: "absolute",
|
||||
top: -1,
|
||||
transition: theme.transitions.duration.short + "ms",
|
||||
width: 14
|
||||
},
|
||||
|
||||
WebkitAppearance: "none",
|
||||
border: `1px solid ${theme.palette.action.active}`,
|
||||
boxSizing: "border-box",
|
||||
cursor: "pointer",
|
||||
height: 14,
|
||||
outline: "0",
|
||||
position: "relative",
|
||||
userSelect: "none",
|
||||
width: 14
|
||||
},
|
||||
checked: {},
|
||||
disabled: {},
|
||||
indeterminate: {},
|
||||
root: {
|
||||
"&:hover": {
|
||||
background: fade(theme.palette.primary.main, 0.1)
|
||||
},
|
||||
alignSelf: "start",
|
||||
borderRadius: "100%",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
height: 30,
|
||||
justifyContent: "center",
|
||||
margin: "5px 9px",
|
||||
width: 30
|
||||
}
|
||||
}),
|
||||
{ name: "Checkbox" }
|
||||
);
|
||||
const Checkbox: React.FC<CheckboxProps> = props => {
|
||||
const {
|
||||
checked,
|
||||
className,
|
||||
|
||||
disabled,
|
||||
disableClickPropagation,
|
||||
indeterminate,
|
||||
onChange,
|
||||
value,
|
||||
name,
|
||||
...rest
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
const handleClick = React.useCallback(
|
||||
disableClickPropagation
|
||||
? event => {
|
||||
event.stopPropagation();
|
||||
inputRef.current.click();
|
||||
}
|
||||
: () => inputRef.current.click(),
|
||||
[]
|
||||
);
|
||||
const { disableClickPropagation, ...rest } = props;
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
<MuiCheckbox
|
||||
{...rest}
|
||||
centerRipple
|
||||
className={classNames(classes.root, className)}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<input
|
||||
className={classNames(classes.box, {
|
||||
[classes.checked]: checked,
|
||||
[classes.disabled]: disabled,
|
||||
[classes.indeterminate]: indeterminate
|
||||
})}
|
||||
disabled={disabled}
|
||||
type="checkbox"
|
||||
name={name}
|
||||
value={checked !== undefined && checked.toString()}
|
||||
ref={inputRef}
|
||||
onChange={onChange}
|
||||
onClick={
|
||||
disableClickPropagation ? event => event.stopPropagation() : undefined
|
||||
}
|
||||
/>
|
||||
</ButtonBase>
|
||||
);
|
||||
};
|
||||
Checkbox.displayName = "Checkbox";
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import React from "react";
|
||||
|
||||
import Checkbox from "./Checkbox";
|
||||
|
||||
interface ControlledCheckboxProps {
|
||||
className?: string;
|
||||
name: string;
|
||||
|
@ -27,7 +26,6 @@ export const ControlledCheckbox: React.FC<ControlledCheckboxProps> = ({
|
|||
checked={checked}
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
disableClickPropagation
|
||||
onChange={() => onChange({ target: { name, value: !checked } })}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -28,23 +28,6 @@ const useStyles = makeStyles(
|
|||
cell: {
|
||||
padding: 0
|
||||
},
|
||||
checkboxPartialSelect: {
|
||||
"& input": {
|
||||
"&:before": {
|
||||
background: [theme.palette.background.paper, "!important"] as any,
|
||||
border: `solid 1px ${theme.palette.primary.main}`,
|
||||
content: "''"
|
||||
},
|
||||
background: theme.palette.background.paper
|
||||
},
|
||||
"&:after": {
|
||||
background: theme.palette.primary.main,
|
||||
content: "''",
|
||||
height: 2,
|
||||
position: "absolute",
|
||||
width: 6
|
||||
}
|
||||
},
|
||||
checkboxSelected: {
|
||||
backgroundColor: fade(theme.palette.primary.main, 0.05)
|
||||
},
|
||||
|
@ -113,10 +96,7 @@ const TableHead: React.FC<TableHeadProps> = props => {
|
|||
})}
|
||||
>
|
||||
<Checkbox
|
||||
className={classNames({
|
||||
[classes.checkboxPartialSelect]:
|
||||
items && items.length > selected && selected > 0
|
||||
})}
|
||||
indeterminate={items && items.length > selected && selected > 0}
|
||||
checked={selected === 0 ? false : true}
|
||||
disabled={disabled}
|
||||
onChange={() => toggleAll(items, selected)}
|
||||
|
|
18
src/icons/Checkbox.tsx
Normal file
18
src/icons/Checkbox.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
|
||||
import React from "react";
|
||||
|
||||
const Checkbox = createSvgIcon(
|
||||
<>
|
||||
<rect
|
||||
x="5"
|
||||
y="5"
|
||||
width="14"
|
||||
height="14"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
/>
|
||||
</>,
|
||||
"Checkbox"
|
||||
);
|
||||
|
||||
export default Checkbox;
|
17
src/icons/CheckboxChecked.tsx
Normal file
17
src/icons/CheckboxChecked.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
|
||||
import React from "react";
|
||||
|
||||
const CheckboxChecked = createSvgIcon(
|
||||
<>
|
||||
<rect x="5" y="5" width="14" height="14" fill="currentColor" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M 16.7527 9.33783 L 10.86618 15.7595 L 8 12.32006 L 8.76822 11.67988 L 10.90204 14.24046 L 16.0155 8.66211 L 16.7527 9.33783 Z"
|
||||
fill="white"
|
||||
/>
|
||||
</>,
|
||||
"CheckboxChecked"
|
||||
);
|
||||
|
||||
export default CheckboxChecked;
|
19
src/icons/CheckboxIndeterminate.tsx
Normal file
19
src/icons/CheckboxIndeterminate.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import createSvgIcon from "@material-ui/icons/utils/createSvgIcon";
|
||||
import React from "react";
|
||||
|
||||
const CheckboxIndeterminate = createSvgIcon(
|
||||
<>
|
||||
<rect
|
||||
x="5"
|
||||
y="5"
|
||||
width="14"
|
||||
height="14"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
/>
|
||||
<rect x="8" y="11" width="8" height="2" fill="currentColor" />
|
||||
</>,
|
||||
"CheckboxIndeterminate"
|
||||
);
|
||||
|
||||
export default CheckboxIndeterminate;
|
|
@ -306,10 +306,10 @@ export function createHref(url: string) {
|
|||
interface AnyEvent {
|
||||
stopPropagation: () => void;
|
||||
}
|
||||
export function stopPropagation(cb: () => void) {
|
||||
export function stopPropagation(cb: (event?: AnyEvent) => void) {
|
||||
return (event: AnyEvent) => {
|
||||
event.stopPropagation();
|
||||
cb();
|
||||
cb(event);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ const MembersErrorDialog: React.FC<MembersErrorDialogProps> = ({
|
|||
defaultMessage: "Unassign users",
|
||||
description: "dialog title"
|
||||
})}
|
||||
variant="default"
|
||||
variant="info"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
ExportErrorCode,
|
||||
ExportProductsInput
|
||||
} from "@saleor/types/globalTypes";
|
||||
import { warehouseList } from "@saleor/warehouses/fixtures";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
|
@ -30,7 +31,8 @@ const props: ProductExportDialogProps = {
|
|||
all: 100,
|
||||
filter: 32
|
||||
},
|
||||
selectedProducts: 18
|
||||
selectedProducts: 18,
|
||||
warehouses: warehouseList
|
||||
};
|
||||
|
||||
storiesOf("Views / Products / Export / Export settings", module)
|
||||
|
|
|
@ -25,11 +25,13 @@ import {
|
|||
import getExportErrorMessage from "@saleor/utils/errors/export";
|
||||
import { toggle } from "@saleor/utils/lists";
|
||||
import { mapNodeToChoice } from "@saleor/utils/maps";
|
||||
import { WarehouseList_warehouses_edges_node } from "@saleor/warehouses/types/WarehouseList";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ProductExportDialogInfo, {
|
||||
attributeNamePrefix
|
||||
attributeNamePrefix,
|
||||
warehouseNamePrefix
|
||||
} from "./ProductExportDialogInfo";
|
||||
import ProductExportDialogSettings, {
|
||||
ProductQuantity
|
||||
|
@ -64,7 +66,8 @@ function useSteps(): Array<Step<ProductExportStep>> {
|
|||
const initialForm: ExportProductsInput = {
|
||||
exportInfo: {
|
||||
attributes: [],
|
||||
fields: []
|
||||
fields: [],
|
||||
warehouses: []
|
||||
},
|
||||
fileType: FileTypesEnum.CSV,
|
||||
scope: ExportScope.ALL
|
||||
|
@ -78,6 +81,7 @@ export interface ProductExportDialogProps extends DialogProps, FetchMoreProps {
|
|||
errors: ExportErrorFragment[];
|
||||
productQuantity: ProductQuantity;
|
||||
selectedProducts: number;
|
||||
warehouses: WarehouseList_warehouses_edges_node[];
|
||||
onFetch: (query: string) => void;
|
||||
onSubmit: (data: ExportProductsInput) => void;
|
||||
}
|
||||
|
@ -91,6 +95,7 @@ const ProductExportDialog: React.FC<ProductExportDialogProps> = ({
|
|||
onSubmit,
|
||||
open,
|
||||
selectedProducts,
|
||||
warehouses,
|
||||
...fetchMoreProps
|
||||
}) => {
|
||||
const [step, { next, prev, set: setStep }] = useWizard(
|
||||
|
@ -114,6 +119,7 @@ const ProductExportDialog: React.FC<ProductExportDialogProps> = ({
|
|||
});
|
||||
|
||||
const attributeChoices = mapNodeToChoice(attributes);
|
||||
const warehouseChoices = mapNodeToChoice(warehouses);
|
||||
|
||||
const handleAttributeSelect: FormChange = event => {
|
||||
const id = event.target.name.substr(attributeNamePrefix.length);
|
||||
|
@ -135,6 +141,35 @@ const ProductExportDialog: React.FC<ProductExportDialogProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const handleWarehouseSelect: FormChange = event =>
|
||||
change({
|
||||
target: {
|
||||
name: "exportInfo",
|
||||
value: {
|
||||
...data.exportInfo,
|
||||
warehouses: toggle(
|
||||
event.target.name.substr(warehouseNamePrefix.length),
|
||||
data.exportInfo.warehouses,
|
||||
(a, b) => a === b
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const handleToggleAllWarehouses: FormChange = () =>
|
||||
change({
|
||||
target: {
|
||||
name: "exportInfo",
|
||||
value: {
|
||||
...data.exportInfo,
|
||||
warehouses:
|
||||
data.exportInfo.warehouses.length === warehouses.length
|
||||
? []
|
||||
: warehouses.map(warehouse => warehouse.id)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} open={open} maxWidth="sm" fullWidth>
|
||||
<>
|
||||
|
@ -156,7 +191,10 @@ const ProductExportDialog: React.FC<ProductExportDialogProps> = ({
|
|||
data={data}
|
||||
selectedAttributes={selectedAttributes}
|
||||
onAttrtibuteSelect={handleAttributeSelect}
|
||||
onWarehouseSelect={handleWarehouseSelect}
|
||||
onChange={change}
|
||||
warehouses={warehouseChoices}
|
||||
onSelectAllWarehouses={handleToggleAllWarehouses}
|
||||
{...fetchMoreProps}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Accordion, { AccordionProps } from "@saleor/components/Accordion";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Chip from "@saleor/components/Chip";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
|
||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { FetchMoreProps } from "@saleor/types";
|
||||
|
@ -22,9 +22,18 @@ import React from "react";
|
|||
import { useIntl } from "react-intl";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import useProductExportFieldMessages from "./messages";
|
||||
|
||||
export const attributeNamePrefix = "attribute-";
|
||||
export const warehouseNamePrefix = "warehouse-";
|
||||
const maxChips = 4;
|
||||
|
||||
const inventoryFields = [
|
||||
ProductFieldEnum.PRODUCT_WEIGHT,
|
||||
ProductFieldEnum.VARIANT_SKU,
|
||||
ProductFieldEnum.VARIANT_WEIGHT
|
||||
];
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
accordion: {
|
||||
|
@ -45,12 +54,23 @@ const useStyles = makeStyles(
|
|||
marginBottom: theme.spacing(3),
|
||||
marginTop: theme.spacing(3)
|
||||
},
|
||||
hrWarehouses: {
|
||||
marginBottom: theme.spacing(3),
|
||||
marginTop: theme.spacing(1)
|
||||
},
|
||||
label: {
|
||||
"&&": {
|
||||
overflow: "visible"
|
||||
},
|
||||
"&:first-of-type": {
|
||||
paddingTop: 0
|
||||
},
|
||||
"&:not(:last-of-type)": {
|
||||
borderBottom: `1px solid ${theme.palette.divider}`
|
||||
},
|
||||
justifyContent: "space-between",
|
||||
margin: theme.spacing(0),
|
||||
padding: theme.spacing(1, 0),
|
||||
width: "100%"
|
||||
},
|
||||
loadMoreContainer: {
|
||||
|
@ -62,8 +82,14 @@ const useStyles = makeStyles(
|
|||
display: "inline-block",
|
||||
marginBottom: theme.spacing()
|
||||
},
|
||||
optionLabel: {
|
||||
marginLeft: 0
|
||||
},
|
||||
quickPeekContainer: {
|
||||
marginBottom: theme.spacing(-1)
|
||||
},
|
||||
warehousesLabel: {
|
||||
marginBottom: theme.spacing(2)
|
||||
}
|
||||
}),
|
||||
{
|
||||
|
@ -80,12 +106,14 @@ const Option: React.FC<{
|
|||
|
||||
return (
|
||||
<FormControlLabel
|
||||
classes={{
|
||||
label: classes.optionLabel
|
||||
}}
|
||||
color="primary"
|
||||
control={
|
||||
<Checkbox
|
||||
className={classes.checkbox}
|
||||
checked={checked}
|
||||
color="primary"
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
@ -104,66 +132,7 @@ const FieldAccordion: React.FC<AccordionProps & {
|
|||
onToggleAll: (field: ProductFieldEnum[], setTo: boolean) => void;
|
||||
}> = ({ data, fields, onChange, onToggleAll, ...props }) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
||||
const fieldNames: Record<ProductFieldEnum, string> = {
|
||||
[ProductFieldEnum.CATEGORY]: intl.formatMessage({
|
||||
defaultMessage: "Category",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.CHARGE_TAXES]: intl.formatMessage({
|
||||
defaultMessage: "Charge Taxes",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.COLLECTIONS]: intl.formatMessage({
|
||||
defaultMessage: "Collections",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.COST_PRICE]: intl.formatMessage({
|
||||
defaultMessage: "Cost Price",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.DESCRIPTION]: intl.formatMessage({
|
||||
defaultMessage: "Description",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.NAME]: intl.formatMessage({
|
||||
defaultMessage: "Name",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.PRODUCT_IMAGES]: intl.formatMessage({
|
||||
defaultMessage: "Product Images",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.PRODUCT_TYPE]: intl.formatMessage({
|
||||
defaultMessage: "Type",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.PRODUCT_WEIGHT]: intl.formatMessage({
|
||||
defaultMessage: "Weight",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.VARIANT_IMAGES]: intl.formatMessage({
|
||||
defaultMessage: "Variant Images",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.VARIANT_PRICE]: intl.formatMessage({
|
||||
defaultMessage: "Variant Price",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.VARIANT_SKU]: intl.formatMessage({
|
||||
defaultMessage: "SKU",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.VARIANT_WEIGHT]: intl.formatMessage({
|
||||
defaultMessage: "Variant Weight",
|
||||
description: "product field"
|
||||
}),
|
||||
[ProductFieldEnum.VISIBLE]: intl.formatMessage({
|
||||
defaultMessage: "Visibility",
|
||||
description: "product field"
|
||||
})
|
||||
};
|
||||
const getFieldLabel = useProductExportFieldMessages();
|
||||
|
||||
const selectedAll = fields.every(field =>
|
||||
data.exportInfo.fields.includes(field)
|
||||
|
@ -181,7 +150,7 @@ const FieldAccordion: React.FC<AccordionProps & {
|
|||
{selectedFields.slice(0, maxChips).map(field => (
|
||||
<Chip
|
||||
className={classes.chip}
|
||||
label={fieldNames[field]}
|
||||
label={getFieldLabel(field)}
|
||||
onClose={() =>
|
||||
onChange({
|
||||
target: {
|
||||
|
@ -226,7 +195,7 @@ const FieldAccordion: React.FC<AccordionProps & {
|
|||
onChange={onChange}
|
||||
key={field}
|
||||
>
|
||||
{fieldNames[field]}
|
||||
{getFieldLabel(field)}
|
||||
</Option>
|
||||
))}
|
||||
</Accordion>
|
||||
|
@ -235,11 +204,14 @@ const FieldAccordion: React.FC<AccordionProps & {
|
|||
|
||||
export interface ProductExportDialogInfoProps extends FetchMoreProps {
|
||||
attributes: MultiAutocompleteChoiceType[];
|
||||
warehouses: MultiAutocompleteChoiceType[];
|
||||
data: ExportProductsInput;
|
||||
selectedAttributes: MultiAutocompleteChoiceType[];
|
||||
onAttrtibuteSelect: (event: ChangeEvent) => void;
|
||||
onChange: (event: ChangeEvent) => void;
|
||||
onAttrtibuteSelect: FormChange;
|
||||
onWarehouseSelect: FormChange;
|
||||
onChange: FormChange;
|
||||
onFetch: (query: string) => void;
|
||||
onSelectAllWarehouses: FormChange;
|
||||
}
|
||||
|
||||
const ProductExportDialogInfo: React.FC<ProductExportDialogInfoProps> = ({
|
||||
|
@ -248,14 +220,18 @@ const ProductExportDialogInfo: React.FC<ProductExportDialogInfoProps> = ({
|
|||
hasMore,
|
||||
selectedAttributes,
|
||||
loading,
|
||||
warehouses,
|
||||
onAttrtibuteSelect,
|
||||
onWarehouseSelect,
|
||||
onChange,
|
||||
onFetch,
|
||||
onFetchMore
|
||||
onFetchMore,
|
||||
onSelectAllWarehouses
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||
const getFieldLabel = useProductExportFieldMessages();
|
||||
|
||||
const handleFieldChange = (event: ChangeEvent) =>
|
||||
onChange({
|
||||
|
@ -290,6 +266,12 @@ const ProductExportDialogInfo: React.FC<ProductExportDialogInfoProps> = ({
|
|||
}
|
||||
});
|
||||
|
||||
const selectedInventoryFields = data.exportInfo.fields.filter(field =>
|
||||
inventoryFields.includes(field)
|
||||
);
|
||||
const selectedAllInventoryFields =
|
||||
selectedInventoryFields.length === inventoryFields.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography className={classes.dialogLabel}>
|
||||
|
@ -410,22 +392,134 @@ const ProductExportDialogInfo: React.FC<ProductExportDialogInfoProps> = ({
|
|||
onToggleAll={handleToggleAllFields}
|
||||
data-test="financial"
|
||||
/>
|
||||
<FieldAccordion
|
||||
<Accordion
|
||||
className={classes.accordion}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Inventory Information",
|
||||
description: "informations about product stock, header"
|
||||
})}
|
||||
data={data}
|
||||
fields={[
|
||||
ProductFieldEnum.PRODUCT_WEIGHT,
|
||||
ProductFieldEnum.VARIANT_SKU,
|
||||
ProductFieldEnum.VARIANT_WEIGHT
|
||||
]}
|
||||
onChange={handleFieldChange}
|
||||
onToggleAll={handleToggleAllFields}
|
||||
data-test="inventory"
|
||||
quickPeek={
|
||||
(data.exportInfo.warehouses.length > 0 ||
|
||||
selectedInventoryFields.length > 0) && (
|
||||
<div className={classes.quickPeekContainer}>
|
||||
{selectedInventoryFields.slice(0, maxChips).map(field => (
|
||||
<Chip
|
||||
className={classes.chip}
|
||||
label={getFieldLabel(field)}
|
||||
onClose={() =>
|
||||
onChange({
|
||||
target: {
|
||||
name: field,
|
||||
value: false
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{data.exportInfo.warehouses
|
||||
.slice(0, maxChips - selectedInventoryFields.length)
|
||||
.map(warehouseId => (
|
||||
<Chip
|
||||
className={classes.chip}
|
||||
label={
|
||||
warehouses.find(
|
||||
warehouse => warehouse.value === warehouseId
|
||||
).label
|
||||
}
|
||||
onClose={() =>
|
||||
onWarehouseSelect({
|
||||
target: {
|
||||
name: warehouseNamePrefix + warehouseId,
|
||||
value: undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{data.exportInfo.warehouses.length +
|
||||
selectedInventoryFields.length >
|
||||
maxChips && (
|
||||
<Typography className={classes.moreLabel} variant="caption">
|
||||
<FormattedMessage
|
||||
defaultMessage="and {number} more"
|
||||
description="there are more elements of list that are hidden"
|
||||
values={{
|
||||
number:
|
||||
data.exportInfo.warehouses.length +
|
||||
selectedInventoryFields.length -
|
||||
maxChips
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
data-test="inventory"
|
||||
>
|
||||
<div>
|
||||
<Option
|
||||
checked={selectedAllInventoryFields}
|
||||
name="all"
|
||||
onChange={() =>
|
||||
handleToggleAllFields(
|
||||
inventoryFields,
|
||||
!selectedAllInventoryFields
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Select All"
|
||||
description="selectt all options"
|
||||
/>
|
||||
</Option>
|
||||
{inventoryFields.map(field => (
|
||||
<Option
|
||||
checked={data.exportInfo.fields.includes(field)}
|
||||
name={field}
|
||||
onChange={handleFieldChange}
|
||||
key={field}
|
||||
>
|
||||
{getFieldLabel(field)}
|
||||
</Option>
|
||||
))}
|
||||
</div>
|
||||
<Hr className={classes.hrWarehouses} />
|
||||
<Typography>
|
||||
<FormattedMessage defaultMessage="Export Product Stock Quantity to CSV" />
|
||||
</Typography>
|
||||
<div>
|
||||
<Option
|
||||
checked={warehouses.every(warehouse =>
|
||||
data.exportInfo.warehouses.includes(warehouse.value)
|
||||
)}
|
||||
name="all-warehouses"
|
||||
onChange={onSelectAllWarehouses}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Export stock for all warehouses"
|
||||
description="option"
|
||||
/>
|
||||
</Option>
|
||||
</div>
|
||||
<Hr className={classes.hrWarehouses} />
|
||||
<Typography className={classes.warehousesLabel} variant="subtitle1">
|
||||
<FormattedMessage
|
||||
defaultMessage="Warehouses A to Z"
|
||||
description="list of warehouses"
|
||||
/>
|
||||
</Typography>
|
||||
{warehouses.map(warehouse => (
|
||||
<Option
|
||||
checked={data.exportInfo.warehouses.includes(warehouse.value)}
|
||||
name={warehouseNamePrefix + warehouse.value}
|
||||
onChange={onWarehouseSelect}
|
||||
key={warehouse.value}
|
||||
>
|
||||
{warehouse.label}
|
||||
</Option>
|
||||
))}
|
||||
</Accordion>
|
||||
<FieldAccordion
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "SEO Information",
|
||||
|
|
83
src/products/components/ProductExportDialog/messages.ts
Normal file
83
src/products/components/ProductExportDialog/messages.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { ProductFieldEnum } from "@saleor/types/globalTypes";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
function useProductExportFieldMessages() {
|
||||
const intl = useIntl();
|
||||
|
||||
const messages = {
|
||||
[ProductFieldEnum.CATEGORY]: intl.formatMessage({
|
||||
defaultMessage: "Category",
|
||||
description: "product field",
|
||||
id: "productExportFieldCategory"
|
||||
}),
|
||||
[ProductFieldEnum.CHARGE_TAXES]: intl.formatMessage({
|
||||
defaultMessage: "Charge Taxes",
|
||||
description: "product field",
|
||||
id: "productExportFieldTaxes"
|
||||
}),
|
||||
[ProductFieldEnum.COLLECTIONS]: intl.formatMessage({
|
||||
defaultMessage: "Collections",
|
||||
description: "product field",
|
||||
id: "productExportFieldCollections"
|
||||
}),
|
||||
[ProductFieldEnum.COST_PRICE]: intl.formatMessage({
|
||||
defaultMessage: "Cost Price",
|
||||
description: "product field",
|
||||
id: "productExportFieldPrice"
|
||||
}),
|
||||
[ProductFieldEnum.DESCRIPTION]: intl.formatMessage({
|
||||
defaultMessage: "Description",
|
||||
description: "product field",
|
||||
id: "productExportFieldDescription"
|
||||
}),
|
||||
[ProductFieldEnum.NAME]: intl.formatMessage({
|
||||
defaultMessage: "Name",
|
||||
description: "product field",
|
||||
id: "productExportFieldName"
|
||||
}),
|
||||
[ProductFieldEnum.PRODUCT_IMAGES]: intl.formatMessage({
|
||||
defaultMessage: "Product Images",
|
||||
description: "product field",
|
||||
id: "productExportFieldProductImages"
|
||||
}),
|
||||
[ProductFieldEnum.PRODUCT_TYPE]: intl.formatMessage({
|
||||
defaultMessage: "Type",
|
||||
description: "product field",
|
||||
id: "productExportFieldType"
|
||||
}),
|
||||
[ProductFieldEnum.PRODUCT_WEIGHT]: intl.formatMessage({
|
||||
defaultMessage: "Export Product Weight",
|
||||
description: "product field",
|
||||
id: "productExportFieldProductWeight"
|
||||
}),
|
||||
[ProductFieldEnum.VARIANT_IMAGES]: intl.formatMessage({
|
||||
defaultMessage: "Variant Images",
|
||||
description: "product field",
|
||||
id: "productExportFieldVariantImages"
|
||||
}),
|
||||
[ProductFieldEnum.VARIANT_PRICE]: intl.formatMessage({
|
||||
defaultMessage: "Variant Price",
|
||||
description: "product field",
|
||||
id: "productExportFieldVariantPrice"
|
||||
}),
|
||||
[ProductFieldEnum.VARIANT_SKU]: intl.formatMessage({
|
||||
defaultMessage: "Export Variant SKU",
|
||||
description: "product field",
|
||||
id: "productExportFieldVariantSku"
|
||||
}),
|
||||
[ProductFieldEnum.VARIANT_WEIGHT]: intl.formatMessage({
|
||||
defaultMessage: "Export Variant Weight",
|
||||
description: "product field",
|
||||
id: "productExportFieldVariantWeight"
|
||||
}),
|
||||
[ProductFieldEnum.VISIBLE]: intl.formatMessage({
|
||||
defaultMessage: "Visibility",
|
||||
description: "product field",
|
||||
id: "productExportFieldVisibility"
|
||||
})
|
||||
};
|
||||
|
||||
return (field: ProductFieldEnum) => messages[field];
|
||||
}
|
||||
|
||||
export default useProductExportFieldMessages;
|
|
@ -167,8 +167,17 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
const hasVariants = maybe(() => product.productType.hasVariants, false);
|
||||
|
||||
const handleSubmit = (data: ProductUpdatePageFormData) => {
|
||||
if (product.productType.hasVariants) {
|
||||
onSubmit({
|
||||
...data,
|
||||
addStocks: [],
|
||||
attributes,
|
||||
removeStocks: [],
|
||||
updateStocks: []
|
||||
});
|
||||
} else {
|
||||
const dataStocks = stocks.map(stock => stock.id);
|
||||
const variantStocks = product.variants[0].stocks.map(
|
||||
const variantStocks = product.variants[0]?.stocks.map(
|
||||
stock => stock.warehouse.id
|
||||
);
|
||||
const stockDiff = diff(variantStocks, dataStocks);
|
||||
|
@ -184,6 +193,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -39,6 +39,7 @@ import { ListViews } from "@saleor/types";
|
|||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
||||
import { getSortUrlVariables } from "@saleor/utils/sort";
|
||||
import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -124,6 +125,11 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
first: 10
|
||||
}
|
||||
});
|
||||
const warehouses = useWarehouseList({
|
||||
variables: {
|
||||
first: 100
|
||||
}
|
||||
});
|
||||
|
||||
React.useEffect(
|
||||
() =>
|
||||
|
@ -551,6 +557,11 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
filter: data?.products.totalCount
|
||||
}}
|
||||
selectedProducts={listElements.length}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(
|
||||
edge => edge.node
|
||||
) || []
|
||||
}
|
||||
onClose={closeModal}
|
||||
onSubmit={data =>
|
||||
exportProducts({
|
||||
|
|
|
@ -35,7 +35,7 @@ const useStyles = makeStyles(
|
|||
width: 120
|
||||
},
|
||||
avatarDefault: {
|
||||
"& p": {
|
||||
"& div": {
|
||||
color: "#fff",
|
||||
fontSize: 35,
|
||||
fontWeight: "bold",
|
||||
|
|
File diff suppressed because it is too large
Load diff
12
src/theme.ts
12
src/theme.ts
|
@ -1,10 +1,15 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import { createMuiTheme, Theme } from "@material-ui/core/styles";
|
||||
import { darken, fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { createElement } from "react";
|
||||
|
||||
import { IThemeColors } from "./components/Theme/themes";
|
||||
import CheckboxIcon from "./icons/Checkbox";
|
||||
import CheckboxCheckedIcon from "./icons/CheckboxChecked";
|
||||
import CheckboxIndeterminateIcon from "./icons/CheckboxIndeterminate";
|
||||
|
||||
const createShadow = (pv, pb, ps, uv, ub, us, av, ab, as) =>
|
||||
[
|
||||
|
@ -562,3 +567,10 @@ Card.defaultProps = {
|
|||
Typography.defaultProps = {
|
||||
component: "div"
|
||||
};
|
||||
|
||||
Checkbox.defaultProps = {
|
||||
checkedIcon: createElement(CheckboxCheckedIcon),
|
||||
color: "primary",
|
||||
icon: createElement(CheckboxIcon),
|
||||
indeterminateIcon: createElement(CheckboxIndeterminateIcon)
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue