diff --git a/src/products/components/ProductExportDialog/ProductExportDialog.stories.tsx b/src/products/components/ProductExportDialog/ProductExportDialog.stories.tsx index 734452efd..f81aead66 100644 --- a/src/products/components/ProductExportDialog/ProductExportDialog.stories.tsx +++ b/src/products/components/ProductExportDialog/ProductExportDialog.stories.tsx @@ -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) diff --git a/src/products/components/ProductExportDialog/ProductExportDialog.tsx b/src/products/components/ProductExportDialog/ProductExportDialog.tsx index e5d5c043d..5f9581eeb 100644 --- a/src/products/components/ProductExportDialog/ProductExportDialog.tsx +++ b/src/products/components/ProductExportDialog/ProductExportDialog.tsx @@ -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> { 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 = ({ onSubmit, open, selectedProducts, + warehouses, ...fetchMoreProps }) => { const [step, { next, prev, set: setStep }] = useWizard( @@ -114,6 +119,7 @@ const ProductExportDialog: React.FC = ({ }); 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 = ({ ); }; + 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 ( <> @@ -156,7 +191,10 @@ const ProductExportDialog: React.FC = ({ data={data} selectedAttributes={selectedAttributes} onAttrtibuteSelect={handleAttributeSelect} + onWarehouseSelect={handleWarehouseSelect} onChange={change} + warehouses={warehouseChoices} + onSelectAllWarehouses={handleToggleAllWarehouses} {...fetchMoreProps} /> )} diff --git a/src/products/components/ProductExportDialog/ProductExportDialogInfo.tsx b/src/products/components/ProductExportDialog/ProductExportDialogInfo.tsx index 02654101f..c1cae4bb8 100644 --- a/src/products/components/ProductExportDialog/ProductExportDialogInfo.tsx +++ b/src/products/components/ProductExportDialog/ProductExportDialogInfo.tsx @@ -9,7 +9,7 @@ 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,6 +106,9 @@ const Option: React.FC<{ return ( void; }> = ({ data, fields, onChange, onToggleAll, ...props }) => { const classes = useStyles({}); - const intl = useIntl(); - - const fieldNames: Record = { - [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) @@ -180,7 +150,7 @@ const FieldAccordion: React.FC ( onChange({ target: { @@ -225,7 +195,7 @@ const FieldAccordion: React.FC - {fieldNames[field]} + {getFieldLabel(field)} ))} @@ -234,11 +204,14 @@ const FieldAccordion: React.FC void; - onChange: (event: ChangeEvent) => void; + onAttrtibuteSelect: FormChange; + onWarehouseSelect: FormChange; + onChange: FormChange; onFetch: (query: string) => void; + onSelectAllWarehouses: FormChange; } const ProductExportDialogInfo: React.FC = ({ @@ -247,14 +220,18 @@ const ProductExportDialogInfo: React.FC = ({ 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({ @@ -289,6 +266,12 @@ const ProductExportDialogInfo: React.FC = ({ } }); + const selectedInventoryFields = data.exportInfo.fields.filter(field => + inventoryFields.includes(field) + ); + const selectedAllInventoryFields = + selectedInventoryFields.length === inventoryFields.length; + return ( <> @@ -409,22 +392,134 @@ const ProductExportDialogInfo: React.FC = ({ onToggleAll={handleToggleAllFields} data-test="financial" /> - 0 || + selectedInventoryFields.length > 0) && ( +
+ {selectedInventoryFields.slice(0, maxChips).map(field => ( + + onChange({ + target: { + name: field, + value: false + } + }) + } + /> + ))} + {data.exportInfo.warehouses + .slice(0, maxChips - selectedInventoryFields.length) + .map(warehouseId => ( + warehouse.value === warehouseId + ).label + } + onClose={() => + onWarehouseSelect({ + target: { + name: warehouseNamePrefix + warehouseId, + value: undefined + } + }) + } + /> + ))} + {data.exportInfo.warehouses.length + + selectedInventoryFields.length > + maxChips && ( + + + + )} +
+ ) + } data-test="inventory" - /> + > +
+ + {inventoryFields.map(field => ( + + ))} +
+
+ + + +
+ +
+
+ + + + {warehouses.map(warehouse => ( + + ))} + intl.formatMessage(messages[field]); +} + +export default useProductExportFieldMessages; diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 8e9089ed3..0f55ed6c0 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -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 = ({ params }) => { first: 10 } }); + const warehouses = useWarehouseList({ + variables: { + first: 100 + } + }); React.useEffect( () => @@ -551,6 +557,11 @@ export const ProductList: React.FC = ({ params }) => { filter: data?.products.totalCount }} selectedProducts={listElements.length} + warehouses={ + warehouses.data?.warehouses.edges.map( + edge => edge.node + ) || [] + } onClose={closeModal} onSubmit={data => exportProducts({