Add assign warehouse section in channel page (#2127)

* Add assign warehouse section in channel page

* Update data-test-ids on channel page

* Update channel page form details

* Update shipping zones and warehouses cards in chaannel page

* Assigning warehouses by channel in product and variant pages (#2135)

* Assigning warehouses by channel in product and variant pages

* Disable warehouse assignment when no channel on variant page

* Update products stocks section messages
This commit is contained in:
Dawid 2022-07-11 11:43:08 +02:00 committed by GitHub
parent eb58e2c8ed
commit f1ffb5093f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 4263 additions and 1560 deletions

View file

@ -9,7 +9,10 @@ export const ADD_CHANNEL_FORM_SELECTORS = {
slugValidationMessage: "[data-test-id='slug-text-input-helper-text']", slugValidationMessage: "[data-test-id='slug-text-input-helper-text']",
currencyAutocompleteDropdown: currencyAutocompleteDropdown:
"[data-test-id='single-autocomplete-select-option'][data-test-type='custom']", "[data-test-id='single-autocomplete-select-option'][data-test-type='custom']",
addShippingZoneButton: '[data-test-id="add-shipping-zone-button"]', addShippingZoneButton: '[data-test-id="shipping-add-button"]',
addWarehouseButton: '[data-test-id="warehouse-add-button"]',
shippingAutocompleteSelect: "[data-test-id='shipping-auto-complete-select']", shippingAutocompleteSelect: "[data-test-id='shipping-auto-complete-select']",
countryAutocompleteInput: '[data-test-id="country-select-input"]' warehouseAutocompleteSelect:
"[data-test-id='warehouse-auto-complete-select']",
countryAutocompleteInput: '[data-test-id="country-select-input"]',
}; };

File diff suppressed because it is too large Load diff

View file

@ -27,10 +27,6 @@
"context": "order shipping method name", "context": "order shipping method name",
"string": "Shipping" "string": "Shipping"
}, },
"+G9l7u": {
"context": "all selected zones card message",
"string": "All available shipping zones have been selected"
},
"+HuipK": { "+HuipK": {
"context": "variant sku", "context": "variant sku",
"string": "SKU {sku}" "string": "SKU {sku}"
@ -170,6 +166,10 @@
"context": "dialog header", "context": "dialog header",
"string": "Add Tracking Code" "string": "Add Tracking Code"
}, },
"/C//FB": {
"context": "header, allocated product quantity",
"string": "Allocated"
},
"/JENWS": { "/JENWS": {
"string": "Reduced Tax Rates" "string": "Reduced Tax Rates"
}, },
@ -224,6 +224,10 @@
"/glQgs": { "/glQgs": {
"string": "No channels found" "string": "No channels found"
}, },
"/iijFq": {
"context": "input label",
"string": "Global threshold"
},
"/kWzY1": { "/kWzY1": {
"string": "Are you sure you want to delete this address from users address book?" "string": "Are you sure you want to delete this address from users address book?"
}, },
@ -601,9 +605,6 @@
"context": "payment status", "context": "payment status",
"string": "Fully paid" "string": "Fully paid"
}, },
"2qJc9y": {
"string": "CANCEL END DATE"
},
"2r4cTE": { "2r4cTE": {
"context": "button", "context": "button",
"string": "Enable Dark Mode" "string": "Enable Dark Mode"
@ -1126,9 +1127,6 @@
"context": "attribute value deleted", "context": "attribute value deleted",
"string": "Value deleted" "string": "Value deleted"
}, },
"7Ii5ZQ": {
"string": "SETUP END DATE"
},
"7JAAul": { "7JAAul": {
"context": "product field", "context": "product field",
"string": "Export Product Weight" "string": "Export Product Weight"
@ -1206,10 +1204,6 @@
"context": "info text", "context": "info text",
"string": "This rate will apply to all orders" "string": "This rate will apply to all orders"
}, },
"7wkGxW": {
"context": "app has been installed",
"string": "{unitsLeft} units left"
},
"7yKZvp": { "7yKZvp": {
"context": "section header", "context": "section header",
"string": "User Status" "string": "User Status"
@ -1226,10 +1220,6 @@
"context": "navigator placeholder", "context": "navigator placeholder",
"string": "Order Number" "string": "Order Number"
}, },
"8CbACQ": {
"context": "add shipping zone title",
"string": "Add Shipping Zones"
},
"8EGagh": { "8EGagh": {
"context": "search box label", "context": "search box label",
"string": "Filter Countries" "string": "Filter Countries"
@ -1331,6 +1321,10 @@
"context": "option", "context": "option",
"string": "Create single variant" "string": "Create single variant"
}, },
"9IWg/f": {
"context": "button",
"string": "SETUP END DATE"
},
"9OtpHt": { "9OtpHt": {
"string": "Order line deleted" "string": "Order line deleted"
}, },
@ -1462,10 +1456,6 @@
"context": "order refund subtitle", "context": "order refund subtitle",
"string": "Refunded items can't be fulfilled" "string": "Refunded items can't be fulfilled"
}, },
"ANRRpG": {
"context": "card title",
"string": "Shipping Zones"
},
"AOI4LW": { "AOI4LW": {
"context": "navigator placeholder", "context": "navigator placeholder",
"string": "Search in Catalog" "string": "Search in Catalog"
@ -1710,10 +1700,6 @@
"context": "dialog content", "context": "dialog content",
"string": "{counter,plural,one{Are you sure you want to delete this shipping zone?} other{Are you sure you want to delete {displayQuantity} shipping zones?}}" "string": "{counter,plural,one{Are you sure you want to delete this shipping zone?} other{Are you sure you want to delete {displayQuantity} shipping zones?}}"
}, },
"CEavJt": {
"context": "section header",
"string": "Unlimited"
},
"CG+awx": { "CG+awx": {
"context": "dialog content", "context": "dialog content",
"string": "Which address would you like to use as shipping address for selected customer:" "string": "Which address would you like to use as shipping address for selected customer:"
@ -2033,6 +2019,10 @@
"ErNH3D": { "ErNH3D": {
"string": "Define where this attribute should be used in Saleor system" "string": "Define where this attribute should be used in Saleor system"
}, },
"ErvPaM": {
"context": "header",
"string": "Warehouse Name"
},
"EsZH44": { "EsZH44": {
"context": "VariantDetailsChannelsAvailabilityCard item subtitle hidden", "context": "VariantDetailsChannelsAvailabilityCard item subtitle hidden",
"string": "Hidden" "string": "Hidden"
@ -2041,6 +2031,10 @@
"context": "header", "context": "header",
"string": "{pluginName} Details" "string": "{pluginName} Details"
}, },
"EuOXmr": {
"context": "add items title",
"string": "Add {itemsName}"
},
"Ev6SEF": { "Ev6SEF": {
"string": "New Password" "string": "New Password"
}, },
@ -2428,6 +2422,10 @@
"context": "number of variants", "context": "number of variants",
"string": "Variants ({quantity})" "string": "Variants ({quantity})"
}, },
"HYC6cH": {
"context": "input description",
"string": "Threshold that cannot be exceeded even if per channel thresholds are still available"
},
"HYHLsB": { "HYHLsB": {
"context": "product field", "context": "product field",
"string": "Export Variant ID" "string": "Export Variant ID"
@ -2585,6 +2583,10 @@
"context": "header", "context": "header",
"string": "Edit Media" "string": "Edit Media"
}, },
"ImTelT": {
"context": "card subtitle",
"string": "Select warehouses that will be used in this channel. You can assign warehouses to multiple channels."
},
"IoCMjg": { "IoCMjg": {
"context": "no collections", "context": "no collections",
"string": "No collections found" "string": "No collections found"
@ -2748,6 +2750,10 @@
"context": "section header", "context": "section header",
"string": "Organize Product" "string": "Organize Product"
}, },
"JkO0jp": {
"context": "input description",
"string": "{unitsLeft} units left"
},
"JnzDrI": { "JnzDrI": {
"context": "discount type", "context": "discount type",
"string": "Fixed Amount" "string": "Fixed Amount"
@ -2779,10 +2785,6 @@
"context": "select product informations to be exported", "context": "select product informations to be exported",
"string": "Information exported:" "string": "Information exported:"
}, },
"JyQEHU": {
"context": "tabel column header",
"string": "Channels"
},
"JyQoES": { "JyQoES": {
"context": "delete attribute value", "context": "delete attribute value",
"string": "Are you sure you want to delete \"{name}\" value?" "string": "Are you sure you want to delete \"{name}\" value?"
@ -2847,10 +2849,6 @@
"context": "product available for purchase date", "context": "product available for purchase date",
"string": "will become available on {date}" "string": "will become available on {date}"
}, },
"KTAg0f": {
"context": "tabel column header",
"string": "Warehouse Name"
},
"KXkdMH": { "KXkdMH": {
"context": "order discount removed title", "context": "order discount removed title",
"string": "Order discount was removed by" "string": "Order discount was removed by"
@ -3202,9 +3200,6 @@
"context": "gift card bulk create success dialog content", "context": "gift card bulk create success dialog content",
"string": "We have issued all of your requested gift cards. You can download the list of new gift cards using the button below." "string": "We have issued all of your requested gift cards. You can download the list of new gift cards using the button below."
}, },
"NcY4ph": {
"string": "Threshold that cannot be exceeded even if per channel thresholds are still available"
},
"Nfh9QM": { "Nfh9QM": {
"context": "checkbox gift cards label description", "context": "checkbox gift cards label description",
"string": "when activated non-shippable gift cards will be automatically set as fulfilled and sent to customer" "string": "when activated non-shippable gift cards will be automatically set as fulfilled and sent to customer"
@ -3699,9 +3694,6 @@
"context": "use attribute in filtering", "context": "use attribute in filtering",
"string": "Use in Filtering" "string": "Use in Filtering"
}, },
"RJ5QxE": {
"string": "Global threshold"
},
"RLBLPQ": { "RLBLPQ": {
"context": "no warehouses info", "context": "no warehouses info",
"string": "There are no warehouses set up for your store. To add stock quantity to the product please <a>configure a warehouse</a>" "string": "There are no warehouses set up for your store. To add stock quantity to the product please <a>configure a warehouse</a>"
@ -3833,6 +3825,10 @@
"SKFr04": { "SKFr04": {
"string": "Attribute not found." "string": "Attribute not found."
}, },
"SM+yG0": {
"context": "input label",
"string": "SKU (Stock Keeping Unit)"
},
"SMakqb": { "SMakqb": {
"context": "customer contact section, header", "context": "customer contact section, header",
"string": "Contact Information" "string": "Contact Information"
@ -4208,6 +4204,10 @@
"context": "dialog header", "context": "dialog header",
"string": "Edit Shipping Method" "string": "Edit Shipping Method"
}, },
"V1MytH": {
"context": "shipping zones section name",
"string": "Shipping Zones"
},
"V1mqpZ": { "V1mqpZ": {
"context": "button", "context": "button",
"string": "Create Permission Group" "string": "Create Permission Group"
@ -4466,6 +4466,10 @@
"context": "gift card bulk create success dialog title", "context": "gift card bulk create success dialog title",
"string": "Bulk Issue Gift Cards" "string": "Bulk Issue Gift Cards"
}, },
"Wyl25+": {
"context": "product inventory, checkbox description",
"string": "Active inventory tracking will automatically calculate changes of stock"
},
"WzA5Ll": { "WzA5Ll": {
"string": "Cannot remove user from last group" "string": "Cannot remove user from last group"
}, },
@ -5395,9 +5399,6 @@
"context": "button", "context": "button",
"string": "Choose file" "string": "Choose file"
}, },
"ekXood": {
"string": "Unlimited"
},
"erC44f": { "erC44f": {
"context": "filters error messages dependencies missing", "context": "filters error messages dependencies missing",
"string": "Filter requires other filters: {dependencies}" "string": "Filter requires other filters: {dependencies}"
@ -5550,10 +5551,6 @@
"g/BrOt": { "g/BrOt": {
"string": "Url has invalid format" "string": "Url has invalid format"
}, },
"g/FRtd": {
"context": "table column header, allocated product quantity",
"string": "Allocated"
},
"g1WQlC": { "g1WQlC": {
"context": "page title", "context": "page title",
"string": "Summary" "string": "Summary"
@ -5612,10 +5609,6 @@
"context": "plugin filters error messages channels", "context": "plugin filters error messages channels",
"string": "No channels selected" "string": "No channels selected"
}, },
"ge/xFX": {
"context": "table column header",
"string": "Quantity"
},
"ghGLbJ": { "ghGLbJ": {
"context": "OrderPayment click&collect shipping method", "context": "OrderPayment click&collect shipping method",
"string": "click&collect" "string": "click&collect"
@ -5643,10 +5636,6 @@
"context": "page header", "context": "page header",
"string": "Create Page" "string": "Create Page"
}, },
"gtKcPf": {
"context": "title",
"string": "{zonesCount} / {totalCount} shipping zones"
},
"gvOzOl": { "gvOzOl": {
"string": "Page Title" "string": "Page Title"
}, },
@ -5890,9 +5879,6 @@
"context": "attribute values", "context": "attribute values",
"string": "Values" "string": "Values"
}, },
"jABdx1": {
"string": "Active inventory tracking will automatically calculate changes of stock"
},
"jBu2yj": { "jBu2yj": {
"context": "acre-inch unit", "context": "acre-inch unit",
"string": "acre-inch" "string": "acre-inch"
@ -7174,10 +7160,18 @@
"context": "Header row stock label", "context": "Header row stock label",
"string": "Stock" "string": "Stock"
}, },
"taS/08": {
"context": "variant stocks section subtitle",
"string": "Assign this variant to a channel in the product channel manager to define warehouses allocation"
},
"taX/V3": { "taX/V3": {
"context": "order total amount", "context": "order total amount",
"string": "Total" "string": "Total"
}, },
"tlGXkh": {
"context": "input description",
"string": "Unlimited"
},
"toDL5R": { "toDL5R": {
"context": "order status", "context": "order status",
"string": "Draft" "string": "Draft"
@ -7247,6 +7241,10 @@
"context": "app has been removed", "context": "app has been removed",
"string": "App successfully removed" "string": "App successfully removed"
}, },
"uKlrEk": {
"context": "all selected items message",
"string": "All available {itemsName} have been selected"
},
"uMpv1v": { "uMpv1v": {
"string": "Fulfillment successfully cancelled" "string": "Fulfillment successfully cancelled"
}, },
@ -7364,6 +7362,10 @@
"v3WWK+": { "v3WWK+": {
"string": "Status is invalid" "string": "Status is invalid"
}, },
"v9ILn/": {
"context": "button",
"string": "CANCEL END DATE"
},
"vC8vyb": { "vC8vyb": {
"context": "enabled status option label", "context": "enabled status option label",
"string": "Enabled" "string": "Enabled"
@ -7628,9 +7630,6 @@
"context": "dialog header", "context": "dialog header",
"string": "Assign Variant" "string": "Assign Variant"
}, },
"xB7BTp": {
"string": "SKU (Stock Keeping Unit)"
},
"xHj9Qe": { "xHj9Qe": {
"context": "gift card settings header", "context": "gift card settings header",
"string": "Gift Cards Settings" "string": "Gift Cards Settings"

View file

@ -493,7 +493,7 @@ type App implements Node & ObjectWithMetadata {
"""Description of the data privacy defined for this app.""" """Description of the data privacy defined for this app."""
dataPrivacy: String @deprecated(reason: "This field will be removed in Saleor 4.0. Use `dataPrivacyUrl` instead.") dataPrivacy: String @deprecated(reason: "This field will be removed in Saleor 4.0. Use `dataPrivacyUrl` instead.")
"""Url to details about the privacy policy on the app owner page.""" """URL to details about the privacy policy on the app owner page."""
dataPrivacyUrl: String dataPrivacyUrl: String
"""Homepage of the app.""" """Homepage of the app."""
@ -502,14 +502,14 @@ type App implements Node & ObjectWithMetadata {
"""Support page for the app.""" """Support page for the app."""
supportUrl: String supportUrl: String
"""Url to iframe with the configuration for the app.""" """URL to iframe with the configuration for the app."""
configurationUrl: String @deprecated(reason: "This field will be removed in Saleor 4.0. Use `appUrl` instead.") configurationUrl: String @deprecated(reason: "This field will be removed in Saleor 4.0. Use `appUrl` instead.")
"""Url to iframe with the app.""" """URL to iframe with the app."""
appUrl: String appUrl: String
""" """
Url to manifest used during app's installation. URL to manifest used during app's installation.
Added in Saleor 3.5. Added in Saleor 3.5.
""" """
@ -1711,6 +1711,29 @@ input AttributeValueCreateInput {
name: String! name: String!
} }
type AttributeValueCreated implements Event {
"""Time of the event."""
issuedAt: DateTime
"""Saleor version that triggered the event."""
version: String
"""The user or application that triggered the event."""
issuingPrincipal: IssuingPrincipal
"""The application receiving the webhook."""
recipient: App
"""
The attribute value the event relates to.
Added in Saleor 3.5.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
attributeValue: AttributeValue
}
""" """
Deletes a value of an attribute. Deletes a value of an attribute.
@ -1724,6 +1747,29 @@ type AttributeValueDelete {
attributeValue: AttributeValue attributeValue: AttributeValue
} }
type AttributeValueDeleted implements Event {
"""Time of the event."""
issuedAt: DateTime
"""Saleor version that triggered the event."""
version: String
"""The user or application that triggered the event."""
issuingPrincipal: IssuingPrincipal
"""The application receiving the webhook."""
recipient: App
"""
The attribute value the event relates to.
Added in Saleor 3.5.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
attributeValue: AttributeValue
}
input AttributeValueFilterInput { input AttributeValueFilterInput {
search: String search: String
ids: [ID!] ids: [ID!]
@ -1871,6 +1917,29 @@ input AttributeValueUpdateInput {
name: String name: String
} }
type AttributeValueUpdated implements Event {
"""Time of the event."""
issuedAt: DateTime
"""Saleor version that triggered the event."""
version: String
"""The user or application that triggered the event."""
issuingPrincipal: IssuingPrincipal
"""The application receiving the webhook."""
recipient: App
"""
The attribute value the event relates to.
Added in Saleor 3.5.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
attributeValue: AttributeValue
}
input BulkAttributeValueInput { input BulkAttributeValueInput {
"""ID of the selected attribute.""" """ID of the selected attribute."""
id: ID id: ID
@ -2373,6 +2442,15 @@ type Channel implements Node {
Added in Saleor 3.1. Added in Saleor 3.1.
""" """
defaultCountry: CountryDisplay! defaultCountry: CountryDisplay!
"""
List of warehouses assigned to this channel.
Added in Saleor 3.5.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
warehouses: [Warehouse!]!
} }
""" """
@ -2402,6 +2480,18 @@ input ChannelCreateInput {
"""isActive flag.""" """isActive flag."""
isActive: Boolean isActive: Boolean
"""List of shipping zones to assign to the channel."""
addShippingZones: [ID!]
"""
List of warehouses to assign to the channel.
Added in Saleor 3.5.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
addWarehouses: [ID!]
"""Name of the channel.""" """Name of the channel."""
name: String! name: String!
@ -2415,11 +2505,10 @@ input ChannelCreateInput {
Default country for the channel. Default country can be used in checkout to determine the stock quantities or calculate taxes when the country was not explicitly provided. Default country for the channel. Default country can be used in checkout to determine the stock quantities or calculate taxes when the country was not explicitly provided.
Added in Saleor 3.1. Added in Saleor 3.1.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
""" """
defaultCountry: CountryCode! defaultCountry: CountryCode!
"""List of shipping zones to assign to the channel."""
addShippingZones: [ID!]
} }
type ChannelCreated implements Event { type ChannelCreated implements Event {
@ -2510,6 +2599,9 @@ type ChannelError {
"""List of shipping zone IDs which causes the error.""" """List of shipping zone IDs which causes the error."""
shippingZones: [ID!] shippingZones: [ID!]
"""List of warehouses IDs which causes the error."""
warehouses: [ID!]
} }
"""An enumeration.""" """An enumeration."""
@ -2563,6 +2655,18 @@ input ChannelUpdateInput {
"""isActive flag.""" """isActive flag."""
isActive: Boolean isActive: Boolean
"""List of shipping zones to assign to the channel."""
addShippingZones: [ID!]
"""
List of warehouses to assign to the channel.
Added in Saleor 3.5.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
addWarehouses: [ID!]
"""Name of the channel.""" """Name of the channel."""
name: String name: String
@ -2576,11 +2680,17 @@ input ChannelUpdateInput {
""" """
defaultCountry: CountryCode defaultCountry: CountryCode
"""List of shipping zones to assign to the channel."""
addShippingZones: [ID!]
"""List of shipping zones to unassign from the channel.""" """List of shipping zones to unassign from the channel."""
removeShippingZones: [ID!] removeShippingZones: [ID!]
"""
List of warehouses to unassign from the channel.
Added in Saleor 3.5.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
removeWarehouses: [ID!]
} }
type ChannelUpdated implements Event { type ChannelUpdated implements Event {
@ -2972,8 +3082,54 @@ type CheckoutLanguageCodeUpdate {
} }
"""Represents an item in the checkout.""" """Represents an item in the checkout."""
type CheckoutLine implements Node { type CheckoutLine implements Node & ObjectWithMetadata {
id: ID! id: ID!
"""List of private metadata items. Requires staff permissions to access."""
privateMetadata: [MetadataItem!]!
"""
A single key from private metadata. Requires staff permissions to access.
Tip: Use GraphQL aliases to fetch multiple keys.
Added in Saleor 3.3.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
privateMetafield(key: String!): String
"""
Private metadata. Requires staff permissions to access. Use `keys` to control which fields you want to include. The default is to include everything.
Added in Saleor 3.3.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
privateMetafields(keys: [String!]): Metadata
"""List of public metadata items. Can be accessed without permissions."""
metadata: [MetadataItem!]!
"""
A single key from public metadata.
Tip: Use GraphQL aliases to fetch multiple keys.
Added in Saleor 3.3.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
metafield(key: String!): String
"""
Public metadata. Use `keys` to control which fields you want to include. The default is to include everything.
Added in Saleor 3.3.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
metafields(keys: [String!]): Metadata
variant: ProductVariant! variant: ProductVariant!
quantity: Int! quantity: Int!
@ -7520,7 +7676,7 @@ type Manifest {
permissions: [Permission!] permissions: [Permission!]
appUrl: String appUrl: String
"""Url to iframe with the configuration for the app.""" """URL to iframe with the configuration for the app."""
configurationUrl: String @deprecated(reason: "This field will be removed in Saleor 4.0. Use `appUrl` instead.") configurationUrl: String @deprecated(reason: "This field will be removed in Saleor 4.0. Use `appUrl` instead.")
tokenTargetUrl: String tokenTargetUrl: String
@ -12818,8 +12974,54 @@ type OrderFullyPaid implements Event {
} }
"""Represents order line of particular order.""" """Represents order line of particular order."""
type OrderLine implements Node { type OrderLine implements Node & ObjectWithMetadata {
id: ID! id: ID!
"""List of private metadata items. Requires staff permissions to access."""
privateMetadata: [MetadataItem!]!
"""
A single key from private metadata. Requires staff permissions to access.
Tip: Use GraphQL aliases to fetch multiple keys.
Added in Saleor 3.3.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
privateMetafield(key: String!): String
"""
Private metadata. Requires staff permissions to access. Use `keys` to control which fields you want to include. The default is to include everything.
Added in Saleor 3.3.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
privateMetafields(keys: [String!]): Metadata
"""List of public metadata items. Can be accessed without permissions."""
metadata: [MetadataItem!]!
"""
A single key from public metadata.
Tip: Use GraphQL aliases to fetch multiple keys.
Added in Saleor 3.3.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
metafield(key: String!): String
"""
Public metadata. Use `keys` to control which fields you want to include. The default is to include everything.
Added in Saleor 3.3.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
metafields(keys: [String!]): Metadata
productName: String! productName: String!
variantName: String! variantName: String!
productSku: String productSku: String
@ -21946,6 +22148,7 @@ input WarehouseFilterInput {
search: String search: String
ids: [ID!] ids: [ID!]
isPrivate: Boolean isPrivate: Boolean
channels: [ID!]
} }
""" """
@ -22247,6 +22450,15 @@ enum WebhookEventTypeAsyncEnum {
"""An attribute is deleted.""" """An attribute is deleted."""
ATTRIBUTE_DELETED ATTRIBUTE_DELETED
"""A new attribute value is created."""
ATTRIBUTE_VALUE_CREATED
"""An attribute value is updated."""
ATTRIBUTE_VALUE_UPDATED
"""An attribute value is deleted."""
ATTRIBUTE_VALUE_DELETED
"""A new category created.""" """A new category created."""
CATEGORY_CREATED CATEGORY_CREATED
@ -22341,6 +22553,9 @@ enum WebhookEventTypeAsyncEnum {
"""A customer account is updated.""" """A customer account is updated."""
CUSTOMER_UPDATED CUSTOMER_UPDATED
"""A customer account is deleted."""
CUSTOMER_DELETED
"""A new collection is created.""" """A new collection is created."""
COLLECTION_CREATED COLLECTION_CREATED
@ -22488,6 +22703,15 @@ enum WebhookEventTypeEnum {
"""An attribute is deleted.""" """An attribute is deleted."""
ATTRIBUTE_DELETED ATTRIBUTE_DELETED
"""A new attribute value is created."""
ATTRIBUTE_VALUE_CREATED
"""An attribute value is updated."""
ATTRIBUTE_VALUE_UPDATED
"""An attribute value is deleted."""
ATTRIBUTE_VALUE_DELETED
"""A new category created.""" """A new category created."""
CATEGORY_CREATED CATEGORY_CREATED
@ -22582,6 +22806,9 @@ enum WebhookEventTypeEnum {
"""A customer account is updated.""" """A customer account is updated."""
CUSTOMER_UPDATED CUSTOMER_UPDATED
"""A customer account is deleted."""
CUSTOMER_DELETED
"""A new collection is created.""" """A new collection is created."""
COLLECTION_CREATED COLLECTION_CREATED
@ -22730,6 +22957,9 @@ enum WebhookSampleEventTypeEnum {
ATTRIBUTE_CREATED ATTRIBUTE_CREATED
ATTRIBUTE_UPDATED ATTRIBUTE_UPDATED
ATTRIBUTE_DELETED ATTRIBUTE_DELETED
ATTRIBUTE_VALUE_CREATED
ATTRIBUTE_VALUE_UPDATED
ATTRIBUTE_VALUE_DELETED
CATEGORY_CREATED CATEGORY_CREATED
CATEGORY_UPDATED CATEGORY_UPDATED
CATEGORY_DELETED CATEGORY_DELETED
@ -22764,6 +22994,7 @@ enum WebhookSampleEventTypeEnum {
INVOICE_SENT INVOICE_SENT
CUSTOMER_CREATED CUSTOMER_CREATED
CUSTOMER_UPDATED CUSTOMER_UPDATED
CUSTOMER_DELETED
COLLECTION_CREATED COLLECTION_CREATED
COLLECTION_UPDATED COLLECTION_UPDATED
COLLECTION_DELETED COLLECTION_DELETED

View file

@ -0,0 +1,60 @@
import { Accordion, Divider, Typography } from "@material-ui/core";
import React from "react";
import { defineMessages, useIntl } from "react-intl";
import AssignmentListFooter from "./AssignmentListFooter";
import AssignmentListHeader from "./AssignmentListHeader";
import Item from "./Item";
import { useExpanderStyles, useStyles } from "./styles";
import { AssignmentListProps } from "./types";
const messages = defineMessages({
allSelectedMessage: {
id: "uKlrEk",
defaultMessage: "All available {itemsName} have been selected",
description: "all selected items message",
},
});
const AssignmentList: React.FC<AssignmentListProps> = props => {
const {
items,
itemsName,
fetchMoreItems: { totalCount },
removeItem,
} = props;
const intl = useIntl();
const classes = useStyles();
const expanderClasses = useExpanderStyles();
const hasMoreItemsToBeSelected = totalCount !== items.length;
return (
<Accordion classes={expanderClasses}>
<AssignmentListHeader
assignCount={items.length}
totalCount={totalCount}
itemsName={itemsName}
/>
<Divider />
{items.map(item => (
<Item key={item.id} item={item} onDelete={removeItem} />
))}
{hasMoreItemsToBeSelected ? (
<AssignmentListFooter {...props} />
) : (
<Typography
color="textSecondary"
variant="subtitle1"
className={classes.infoMessage}
>
{intl.formatMessage(messages.allSelectedMessage, {
itemsName: itemsName.toLowerCase(),
})}
</Typography>
)}
</Accordion>
);
};
export default AssignmentList;

View file

@ -1,82 +1,87 @@
import { ClickAwayListener } from "@material-ui/core"; import { ClickAwayListener } from "@material-ui/core";
import { ChannelShippingZones } from "@saleor/channels/pages/ChannelDetailsPage/types";
import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField"; import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField";
import CardAddItemsFooter from "@saleor/products/components/ProductStocks/CardAddItemsFooter"; import CardAddItemsFooter from "@saleor/products/components/ProductStocks/CardAddItemsFooter";
import { mapNodeToChoice } from "@saleor/utils/maps"; import { mapNodeToChoice } from "@saleor/utils/maps";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { defineMessages } from "react-intl"; import { defineMessages, useIntl } from "react-intl";
import useStyles from "./styles"; import { useStyles } from "./styles";
import { ShippingZonesProps } from "./types"; import { AssignItem, AssignmentListProps } from "./types";
const messages = defineMessages({ const messages = defineMessages({
addZoneTitle: { addItemTitle: {
id: "8CbACQ", id: "EuOXmr",
defaultMessage: "Add Shipping Zones", defaultMessage: "Add {itemsName}",
description: "add shipping zone title", description: "add items title",
}, },
}); });
type ShippingZonesCardListFooterProps = ShippingZonesProps; type AssignmentListFooterProps = AssignmentListProps;
const ShippingZonesCardListFooter: React.FC<ShippingZonesCardListFooterProps> = ({ const AssignmentListFooter: React.FC<AssignmentListFooterProps> = ({
shippingZonesChoices, items,
searchShippingZones, itemsChoices,
fetchMoreShippingZones, itemsName,
addShippingZone, inputName,
shippingZones, dataTestId,
addItem,
searchItems,
fetchMoreItems,
}) => { }) => {
const intl = useIntl();
const classes = useStyles(); const classes = useStyles();
const [isChoicesSelectShown, setIsChoicesSelectShown] = useState(false); const [isChoicesSelectShown, setIsChoicesSelectShown] = useState(false);
const shippingZonesRef = useRef<ChannelShippingZones>(shippingZones); const itemsRef = useRef<AssignItem[]>(items);
// select holds value and displays it so it needs remounting // select holds value and displays it so it needs remounting
// to display empty input after adding new zone // to display empty input after adding new zone
useEffect(() => { useEffect(() => {
if (shippingZones.length > shippingZonesRef.current.length) { if (items.length > itemsRef.current.length) {
setIsChoicesSelectShown(true); setIsChoicesSelectShown(true);
} }
shippingZonesRef.current = shippingZones; itemsRef.current = items;
}, [shippingZones]); }, [items]);
const handleChoice = ({ target }) => { const handleChoice = ({ target }) => {
setIsChoicesSelectShown(false); setIsChoicesSelectShown(false);
addShippingZone(target.value); addItem(target.value);
}; };
const handleFooterClickAway = () => { const handleFooterClickAway = () => {
setIsChoicesSelectShown(false); setIsChoicesSelectShown(false);
searchShippingZones(""); searchItems("");
}; };
return isChoicesSelectShown ? ( return isChoicesSelectShown ? (
<ClickAwayListener onClickAway={handleFooterClickAway}> <ClickAwayListener onClickAway={handleFooterClickAway}>
<div className={classes.root}> <div className={classes.root}>
<SingleAutocompleteSelectField <SingleAutocompleteSelectField
data-test-id="shipping-auto-complete-select" data-test-id={`${dataTestId}-auto-complete-select`}
value="" value=""
displayValue="" displayValue=""
nakedInput nakedInput
name="shippingZone" name={inputName}
choices={mapNodeToChoice(shippingZonesChoices)} choices={mapNodeToChoice(itemsChoices)}
fetchChoices={searchShippingZones} fetchChoices={searchItems}
onChange={handleChoice} onChange={handleChoice}
{...fetchMoreShippingZones} {...fetchMoreItems}
/> />
</div> </div>
</ClickAwayListener> </ClickAwayListener>
) : ( ) : (
<CardAddItemsFooter <CardAddItemsFooter
onAdd={() => setIsChoicesSelectShown(true)} onAdd={() => setIsChoicesSelectShown(true)}
title={messages.addZoneTitle} title={intl.formatMessage(messages.addItemTitle, {
itemsName,
})}
testIds={{ testIds={{
link: "add-shipping-zone-link", link: `${dataTestId}-add-link`,
button: "add-shipping-zone-button", button: `${dataTestId}-add-button`,
}} }}
/> />
); );
}; };
export default ShippingZonesCardListFooter; export default AssignmentListFooter;

View file

@ -0,0 +1,33 @@
import { AccordionSummary, Typography } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import IconChevronDown from "@saleor/icons/ChevronDown";
import React from "react";
import { useHeaderStyles } from "./styles";
interface AssignmentListHeaderProps {
assignCount: number;
totalCount: number;
itemsName: string;
}
const AssignmentListHeader: React.FC<AssignmentListHeaderProps> = ({
assignCount,
totalCount,
itemsName,
}) => {
const classes = useHeaderStyles();
return (
<div className={classes.container}>
<AccordionSummary expandIcon={<IconChevronDown />} classes={classes}>
<Typography variant="subtitle2" color="textSecondary">
{`${assignCount} / ${totalCount} ${itemsName.toLowerCase()}`}
</Typography>
</AccordionSummary>
<HorizontalSpacer spacing={1.5} />
</div>
);
};
export default AssignmentListHeader;

View file

@ -1,21 +1,18 @@
import { Divider, Typography } from "@material-ui/core"; import { Divider, Typography } from "@material-ui/core";
import { ChannelShippingZone } from "@saleor/channels/pages/ChannelDetailsPage/types";
import DeletableItem from "@saleor/components/DeletableItem"; import DeletableItem from "@saleor/components/DeletableItem";
import React from "react"; import React from "react";
import useStyles from "./styles"; import { useStyles } from "./styles";
import { AssignItem } from "./types";
interface ShippingZoneItemProps { interface ItemProps {
zone: ChannelShippingZone; item: AssignItem;
onDelete: (id: string) => void; onDelete: (id: string) => void;
} }
const ShippingZoneItem: React.FC<ShippingZoneItemProps> = ({ const Item: React.FC<ItemProps> = ({ item, onDelete }) => {
zone, const { id, name } = item;
onDelete, const classes = useStyles();
}) => {
const { id, name } = zone;
const classes = useStyles({});
return ( return (
<> <>
@ -28,4 +25,4 @@ const ShippingZoneItem: React.FC<ShippingZoneItemProps> = ({
); );
}; };
export default ShippingZoneItem; export default Item;

View file

@ -0,0 +1,2 @@
export * from "./AssignmentList";
export { default } from "./AssignmentList";

View file

@ -0,0 +1,73 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useExpanderStyles = makeStyles(
theme => ({
expanded: {},
root: {
boxShadow: "none",
padding: theme.spacing(1, 4),
"&:before": {
content: "none",
},
"&$expanded": {
margin: 0,
border: "none",
},
},
}),
{ name: "Expander" },
);
export const useHeaderStyles = makeStyles(
theme => ({
container: {
width: "100%",
display: "flex",
flexDirection: "row",
alignItems: "center",
},
// empty expanded needed for mui to use root styles
expanded: {},
root: {
width: "100%",
border: "none",
marginRight: theme.spacing(1),
padding: 0,
paddingBottom: theme.spacing(2),
minHeight: 0,
"&$expanded": {
minHeight: 0,
},
},
content: {
margin: 0,
"&$expanded": {
margin: 0,
},
},
}),
{ name: "AssignmentListHeader" },
);
export const useStyles = makeStyles(
theme => ({
container: {
padding: theme.spacing(1, 0),
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
root: {
paddingRight: theme.spacing(1),
},
infoMessage: {
padding: theme.spacing(3),
},
}),
{ name: "AssignmentList" },
);

View file

@ -0,0 +1,18 @@
import { FetchMoreProps } from "@saleor/types";
export interface AssignItem {
id: string;
name: string;
}
export interface AssignmentListProps {
items: AssignItem[];
itemsChoices: AssignItem[];
itemsName: string;
fetchMoreItems: FetchMoreProps;
inputName: string;
dataTestId: string;
addItem: (id: string) => void;
removeItem: (id: string) => void;
searchItems: (searchPhrase: string) => void;
}

View file

@ -12,6 +12,8 @@ const props: ChannelFormProps = {
currencyCode: "euro", currencyCode: "euro",
shippingZonesIdsToAdd: [], shippingZonesIdsToAdd: [],
shippingZonesIdsToRemove: [], shippingZonesIdsToRemove: [],
warehousesIdsToAdd: [],
warehousesIdsToRemove: [],
name: "Test", name: "Test",
slug: "test", slug: "test",
defaultCountry: CountryCode.PL, defaultCountry: CountryCode.PL,

View file

@ -29,6 +29,8 @@ export interface FormData {
slug: string; slug: string;
shippingZonesIdsToAdd: string[]; shippingZonesIdsToAdd: string[];
shippingZonesIdsToRemove: string[]; shippingZonesIdsToRemove: string[];
warehousesIdsToAdd: string[];
warehousesIdsToRemove: string[];
defaultCountry: CountryCode; defaultCountry: CountryCode;
} }

View file

@ -3,7 +3,7 @@ import CommonDecorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
import ShippingZonesCard from "./ShippingZonesCard"; import ShippingZones from "./ShippingZones";
const shippingZones = [ const shippingZones = [
{ {
@ -32,11 +32,11 @@ const baseProps = {
shippingZonesChoices: shippingZones as ChannelShippingZones, shippingZonesChoices: shippingZones as ChannelShippingZones,
}; };
storiesOf("Shipping zones card", module) storiesOf("Shipping zones", module)
.addDecorator(CommonDecorator) .addDecorator(CommonDecorator)
.add("with no options selected", () => <ShippingZonesCard {...baseProps} />) .add("with no options selected", () => <ShippingZones {...baseProps} />)
.add("with options selected", () => ( .add("with options selected", () => (
<ShippingZonesCard <ShippingZones
{...baseProps} {...baseProps}
shippingZones={shippingZones as ChannelShippingZones} shippingZones={shippingZones as ChannelShippingZones}
/> />

View file

@ -0,0 +1,63 @@
import { Card, CardContent, Typography } from "@material-ui/core";
import { ChannelShippingZones } from "@saleor/channels/pages/ChannelDetailsPage/types";
import CardTitle from "@saleor/components/CardTitle";
import { SearchShippingZonesQuery } from "@saleor/graphql";
import { sectionNames } from "@saleor/intl";
import { FetchMoreProps, RelayToFlat } from "@saleor/types";
import React from "react";
import { defineMessages, useIntl } from "react-intl";
import AssignmentList from "../AssignmentList";
const messages = defineMessages({
subtitle: {
id: "Ic7Wln",
defaultMessage:
"Select shipping zones that will be supplied via this channel. You can assign shipping zones to multiple channels.",
description: "card subtitle",
},
});
interface ShippingZonesProps {
addShippingZone: (id: string) => void;
removeShippingZone: (id: string) => void;
searchShippingZones: (searchPhrase: string) => void;
fetchMoreShippingZones: FetchMoreProps;
shippingZones: ChannelShippingZones;
shippingZonesChoices: RelayToFlat<SearchShippingZonesQuery["search"]>;
}
const ShippingZones: React.FC<ShippingZonesProps> = props => {
const {
addShippingZone,
removeShippingZone,
searchShippingZones,
fetchMoreShippingZones,
shippingZones,
shippingZonesChoices,
} = props;
const intl = useIntl();
return (
<Card>
<CardTitle title={intl.formatMessage(sectionNames.shippingZones)} />
<CardContent>
<Typography>{intl.formatMessage(messages.subtitle)}</Typography>
</CardContent>
<AssignmentList
items={shippingZones}
itemsChoices={shippingZonesChoices}
addItem={addShippingZone}
removeItem={removeShippingZone}
searchItems={searchShippingZones}
fetchMoreItems={fetchMoreShippingZones}
dataTestId="shipping"
inputName="shippingZone"
itemsName={intl.formatMessage(sectionNames.shippingZones)}
/>
</Card>
);
};
export default ShippingZones;

View file

@ -0,0 +1,2 @@
export * from "./ShippingZones";
export { default } from "./ShippingZones";

View file

@ -1,25 +1,5 @@
import { makeStyles } from "@saleor/macaw-ui"; import { makeStyles } from "@saleor/macaw-ui";
export const useExpanderStyles = makeStyles(
theme => ({
expanded: {},
root: {
boxShadow: "none",
padding: theme.spacing(1, 4),
"&:before": {
content: "none",
},
"&$expanded": {
margin: 0,
border: "none",
},
},
}),
{ name: "Expander" },
);
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
container: { container: {
@ -33,7 +13,7 @@ const useStyles = makeStyles(
paddingRight: theme.spacing(1), paddingRight: theme.spacing(1),
}, },
}), }),
{ name: "ShippingZonesCard" }, { name: "ShippingZones" },
); );
export default useStyles; export default useStyles;

View file

@ -1,93 +0,0 @@
import {
Accordion,
Card,
CardContent,
Divider,
makeStyles,
Typography,
} from "@material-ui/core";
import CardTitle from "@saleor/components/CardTitle";
import React from "react";
import { defineMessages, useIntl } from "react-intl";
import ShippingZoneItem from "./ShippingZoneItem";
import ShippingZonesCardListFooter from "./ShippingZonesCardListFooter";
import ShippingZonesListHeader from "./ShippingZonesListHeader";
import { useExpanderStyles } from "./styles";
import { ShippingZonesProps } from "./types";
const messages = defineMessages({
title: {
id: "ANRRpG",
defaultMessage: "Shipping Zones",
description: "card title",
},
subtitle: {
id: "Ic7Wln",
defaultMessage:
"Select shipping zones that will be supplied via this channel. You can assign shipping zones to multiple channels.",
description: "card subtitle",
},
allSelectedMessage: {
id: "+G9l7u",
defaultMessage: "All available shipping zones have been selected",
description: "all selected zones card message",
},
});
const useStyles = makeStyles(
theme => ({
infoMessage: {
padding: theme.spacing(3),
},
}),
{ name: "ShippingZonesCard" },
);
type ShippingZonesCardProps = ShippingZonesProps;
const ShippingZonesCard: React.FC<ShippingZonesCardProps> = props => {
const {
shippingZones,
removeShippingZone,
fetchMoreShippingZones: { totalCount },
} = props;
const expanderClasses = useExpanderStyles({});
const classes = useStyles();
const intl = useIntl();
const hasMoreZonesToBeSelected = totalCount !== shippingZones.length;
return (
<Card>
<CardTitle title={intl.formatMessage(messages.title)} />
<CardContent>
<Typography>{intl.formatMessage(messages.subtitle)}</Typography>
</CardContent>
<Accordion classes={expanderClasses}>
<ShippingZonesListHeader
shippingZones={shippingZones}
totalCount={totalCount}
/>
<Divider />
{shippingZones.map(zone => (
<ShippingZoneItem zone={zone} onDelete={removeShippingZone} />
))}
{hasMoreZonesToBeSelected ? (
<ShippingZonesCardListFooter {...props} />
) : (
<Typography
color="textSecondary"
variant="subtitle1"
className={classes.infoMessage}
>
{intl.formatMessage(messages.allSelectedMessage)}
</Typography>
)}
</Accordion>
</Card>
);
};
export default ShippingZonesCard;

View file

@ -1,77 +0,0 @@
import { AccordionSummary, Typography } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import { ChannelShippingZones } from "@saleor/channels/pages/ChannelDetailsPage/types";
import IconChevronDown from "@saleor/icons/ChevronDown";
import { makeStyles } from "@saleor/macaw-ui";
import React from "react";
import { defineMessages, useIntl } from "react-intl";
const useStyles = makeStyles(
theme => ({
container: {
width: "100%",
display: "flex",
flexDirection: "row",
alignItems: "center",
},
// empty expanded needed for mui to use root styles
expanded: {},
root: {
width: "100%",
border: "none",
marginRight: theme.spacing(1),
padding: 0,
paddingBottom: theme.spacing(2),
minHeight: 0,
"&$expanded": {
minHeight: 0,
},
},
content: {
margin: 0,
"&$expanded": {
margin: 0,
},
},
}),
{ name: "ShippingZonesListHeader" },
);
const messages = defineMessages({
title: {
id: "gtKcPf",
defaultMessage: "{zonesCount} / {totalCount} shipping zones",
description: "title",
},
});
interface ShippingZonesListHeaderProps {
shippingZones: ChannelShippingZones;
totalCount: number;
}
const ShippingZonesListHeader: React.FC<ShippingZonesListHeaderProps> = ({
shippingZones,
totalCount,
}) => {
const classes = useStyles({});
const intl = useIntl();
return (
<div className={classes.container}>
<AccordionSummary expandIcon={<IconChevronDown />} classes={classes}>
<Typography variant="subtitle2" color="textSecondary">
{intl.formatMessage(messages.title, {
zonesCount: shippingZones.length,
totalCount,
})}
</Typography>
</AccordionSummary>
<HorizontalSpacer spacing={1.5} />
</div>
);
};
export default ShippingZonesListHeader;

View file

@ -1,2 +0,0 @@
export * from "./ShippingZonesCard";
export { default } from "./ShippingZonesCard";

View file

@ -1,12 +0,0 @@
import { ChannelShippingZones } from "@saleor/channels/pages/ChannelDetailsPage/types";
import { SearchShippingZonesQuery } from "@saleor/graphql";
import { FetchMoreProps, RelayToFlat } from "@saleor/types";
export interface ShippingZonesProps {
addShippingZone: (id: string) => void;
removeShippingZone: (id: string) => void;
searchShippingZones: (searchPhrase: string) => void;
fetchMoreShippingZones: FetchMoreProps;
shippingZones: ChannelShippingZones;
shippingZonesChoices: RelayToFlat<SearchShippingZonesQuery["search"]>;
}

View file

@ -0,0 +1,40 @@
import { ChannelWarehouses } from "@saleor/channels/pages/ChannelDetailsPage/types";
import CommonDecorator from "@saleor/storybook/Decorator";
import { storiesOf } from "@storybook/react";
import React from "react";
import Warehouses from "./Warehouses";
const warehouses = [
{
__typename: "Warehouse",
id: "2",
name: "Fancy warehouse",
},
{
__typename: "Warehouse",
id: "3",
name: "Nice warehouse",
},
];
const baseProps = {
addWarehouse: () => undefined,
removeWarehouse: () => undefined,
searchWarehouses: () => undefined,
fetchMoreWarehouses: {
loading: false,
hasMore: false,
onFetchMore: () => undefined,
totalCount: 0,
},
warehouses: [],
warehousesChoices: warehouses as ChannelWarehouses,
};
storiesOf("Warehouses", module)
.addDecorator(CommonDecorator)
.add("with no options selected", () => <Warehouses {...baseProps} />)
.add("with options selected", () => (
<Warehouses {...baseProps} warehouses={warehouses as ChannelWarehouses} />
));

View file

@ -0,0 +1,62 @@
import { Card, CardContent, Typography } from "@material-ui/core";
import { ChannelWarehouses } from "@saleor/channels/pages/ChannelDetailsPage/types";
import CardTitle from "@saleor/components/CardTitle";
import { SearchWarehousesQuery } from "@saleor/graphql";
import { sectionNames } from "@saleor/intl";
import { FetchMoreProps, RelayToFlat } from "@saleor/types";
import React from "react";
import { defineMessages, useIntl } from "react-intl";
import AssignmentList from "../AssignmentList";
const messages = defineMessages({
subtitle: {
id: "ImTelT",
defaultMessage:
"Select warehouses that will be used in this channel. You can assign warehouses to multiple channels.",
description: "card subtitle",
},
});
interface WarehousesProps {
addWarehouse: (id: string) => void;
removeWarehouse: (id: string) => void;
searchWarehouses: (searchPhrase: string) => void;
fetchMoreWarehouses: FetchMoreProps;
warehouses: ChannelWarehouses;
warehousesChoices: RelayToFlat<SearchWarehousesQuery["search"]>;
}
const Warehouses: React.FC<WarehousesProps> = props => {
const {
addWarehouse,
removeWarehouse,
searchWarehouses,
fetchMoreWarehouses,
warehouses,
warehousesChoices,
} = props;
const intl = useIntl();
return (
<Card>
<CardTitle title={intl.formatMessage(sectionNames.warehouses)} />
<CardContent>
<Typography>{intl.formatMessage(messages.subtitle)}</Typography>
</CardContent>
<AssignmentList
items={warehouses}
itemsChoices={warehousesChoices}
addItem={addWarehouse}
removeItem={removeWarehouse}
searchItems={searchWarehouses}
fetchMoreItems={fetchMoreWarehouses}
dataTestId="warehouse"
inputName="warehouse"
itemsName={intl.formatMessage(sectionNames.warehouses)}
/>
</Card>
);
};
export default Warehouses;

View file

@ -0,0 +1,2 @@
export * from "./Warehouses";
export { default } from "./Warehouses";

View file

@ -0,0 +1,19 @@
import { makeStyles } from "@saleor/macaw-ui";
const useStyles = makeStyles(
theme => ({
container: {
padding: theme.spacing(1, 0),
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
root: {
paddingRight: theme.spacing(1),
},
}),
{ name: "Warehouses" },
);
export default useStyles;

View file

@ -22,6 +22,8 @@ const props: ChannelDetailsPageProps<ChannelErrorFragment[]> = {
updateChannelStatus: () => undefined, updateChannelStatus: () => undefined,
searchShippingZones: () => undefined, searchShippingZones: () => undefined,
searchShippingZonesData: undefined, searchShippingZonesData: undefined,
searchWarehouses: () => undefined,
searchWarehousesData: undefined,
countries: countries.map(({ name, code }) => ({ countries: countries.map(({ name, code }) => ({
code, code,
country: name, country: name,
@ -45,6 +47,24 @@ const props: ChannelDetailsPageProps<ChannelErrorFragment[]> = {
onFetchMore: () => undefined, onFetchMore: () => undefined,
totalCount: 0, totalCount: 0,
}, },
channelWarehouses: [
{
__typename: "Warehouse",
id: "warehouse-1",
name: "Warehouse 1",
},
{
__typename: "Warehouse",
id: "warehouse-2",
name: "Warehouse 2",
},
],
fetchMoreWarehouses: {
loading: false,
hasMore: false,
onFetchMore: () => undefined,
totalCount: 0,
},
}; };
storiesOf("Views / Channels / Channel details", module) storiesOf("Views / Channels / Channel details", module)

View file

@ -1,4 +1,5 @@
import ShippingZonesCard from "@saleor/channels/components/ShippingZonesCard/ShippingZonesCard"; import ShippingZones from "@saleor/channels/components/ShippingZones";
import Warehouses from "@saleor/channels/components/Warehouses";
import { channelsListUrl } from "@saleor/channels/urls"; import { channelsListUrl } from "@saleor/channels/urls";
import CardSpacer from "@saleor/components/CardSpacer"; import CardSpacer from "@saleor/components/CardSpacer";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
@ -11,6 +12,7 @@ import {
CountryCode, CountryCode,
CountryFragment, CountryFragment,
SearchShippingZonesQuery, SearchShippingZonesQuery,
SearchWarehousesQuery,
} from "@saleor/graphql"; } from "@saleor/graphql";
import { SearchData } from "@saleor/hooks/makeTopLevelSearch"; import { SearchData } from "@saleor/hooks/makeTopLevelSearch";
import { getParsedSearchData } from "@saleor/hooks/makeTopLevelSearch/utils"; import { getParsedSearchData } from "@saleor/hooks/makeTopLevelSearch/utils";
@ -29,7 +31,7 @@ import React, { useState } from "react";
import { ChannelForm, FormData } from "../../components/ChannelForm"; import { ChannelForm, FormData } from "../../components/ChannelForm";
import { ChannelStatus } from "../../components/ChannelStatus/ChannelStatus"; import { ChannelStatus } from "../../components/ChannelStatus/ChannelStatus";
import { ChannelShippingZones } from "./types"; import { ChannelShippingZones, ChannelWarehouses } from "./types";
import { getUpdatedIdsWithNewId, getUpdatedIdsWithoutNewId } from "./utils"; import { getUpdatedIdsWithNewId, getUpdatedIdsWithoutNewId } from "./utils";
export interface ChannelDetailsPageProps<TErrors> { export interface ChannelDetailsPageProps<TErrors> {
@ -42,11 +44,15 @@ export interface ChannelDetailsPageProps<TErrors> {
searchShippingZonesData?: SearchData; searchShippingZonesData?: SearchData;
fetchMoreShippingZones: FetchMoreProps; fetchMoreShippingZones: FetchMoreProps;
channelShippingZones?: ChannelShippingZones; channelShippingZones?: ChannelShippingZones;
searchWarehousesData?: SearchData;
fetchMoreWarehouses: FetchMoreProps;
channelWarehouses?: ChannelWarehouses;
countries: CountryFragment[]; countries: CountryFragment[];
onDelete?: () => void; onDelete?: () => void;
onSubmit: (data: FormData) => SubmitPromise<TErrors[]>; onSubmit: (data: FormData) => SubmitPromise<TErrors[]>;
updateChannelStatus?: () => void; updateChannelStatus?: () => void;
searchShippingZones: (query: string) => void; searchShippingZones: (query: string) => void;
searchWarehouses: (query: string) => void;
} }
const ChannelDetailsPage = function<TErrors>({ const ChannelDetailsPage = function<TErrors>({
@ -62,8 +68,12 @@ const ChannelDetailsPage = function<TErrors>({
searchShippingZones, searchShippingZones,
searchShippingZonesData, searchShippingZonesData,
fetchMoreShippingZones, fetchMoreShippingZones,
countries,
channelShippingZones = [], channelShippingZones = [],
searchWarehouses,
searchWarehousesData,
fetchMoreWarehouses,
channelWarehouses = [],
countries,
}: ChannelDetailsPageProps<TErrors>) { }: ChannelDetailsPageProps<TErrors>) {
const navigate = useNavigator(); const navigate = useNavigator();
@ -76,6 +86,9 @@ const ChannelDetailsPage = function<TErrors>({
const [shippingZonesToDisplay, setShippingZonesToDisplay] = useStateFromProps< const [shippingZonesToDisplay, setShippingZonesToDisplay] = useStateFromProps<
ChannelShippingZones ChannelShippingZones
>(channelShippingZones); >(channelShippingZones);
const [warehousesToDisplay, setWarehousesToDisplay] = useStateFromProps<
ChannelWarehouses
>(channelWarehouses);
const countryChoices = mapCountriesToChoices(countries || []); const countryChoices = mapCountriesToChoices(countries || []);
@ -86,6 +99,8 @@ const ChannelDetailsPage = function<TErrors>({
slug: "", slug: "",
shippingZonesIdsToAdd: [], shippingZonesIdsToAdd: [],
shippingZonesIdsToRemove: [], shippingZonesIdsToRemove: [],
warehousesIdsToAdd: [],
warehousesIdsToRemove: [],
defaultCountry: (defaultCountry?.code || "") as CountryCode, defaultCountry: (defaultCountry?.code || "") as CountryCode,
...formData, ...formData,
}; };
@ -96,6 +111,12 @@ const ChannelDetailsPage = function<TErrors>({
!shippingZonesToDisplay.some(({ id }) => id === searchedZoneId), !shippingZonesToDisplay.some(({ id }) => id === searchedZoneId),
); );
const getFilteredWarehousesChoices = (): RelayToFlat<SearchWarehousesQuery["search"]> =>
getParsedSearchData({ data: searchWarehousesData }).filter(
({ id: searchedWarehouseId }) =>
!warehousesToDisplay.some(({ id }) => id === searchedWarehouseId),
);
const checkIfSaveIsDisabled = (data: FormData) => { const checkIfSaveIsDisabled = (data: FormData) => {
const isValid = const isValid =
!!data.name && !!data.name &&
@ -114,7 +135,7 @@ const ChannelDetailsPage = function<TErrors>({
initial={initialData} initial={initialData}
checkIfSaveIsDisabled={checkIfSaveIsDisabled} checkIfSaveIsDisabled={checkIfSaveIsDisabled}
> >
{({ change, data, submit, set, isSaveDisabled }) => { {({ change, data, submit, set, isSaveDisabled, triggerChange }) => {
const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler( const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler(
change, change,
setSelectedCurrencyCode, setSelectedCurrencyCode,
@ -127,6 +148,8 @@ const ChannelDetailsPage = function<TErrors>({
); );
const addShippingZone = (zoneId: string) => { const addShippingZone = (zoneId: string) => {
triggerChange();
set({ set({
...data, ...data,
shippingZonesIdsToRemove: getUpdatedIdsWithoutNewId( shippingZonesIdsToRemove: getUpdatedIdsWithoutNewId(
@ -148,6 +171,8 @@ const ChannelDetailsPage = function<TErrors>({
}; };
const removeShippingZone = (zoneId: string) => { const removeShippingZone = (zoneId: string) => {
triggerChange();
set({ set({
...data, ...data,
shippingZonesIdsToAdd: getUpdatedIdsWithoutNewId( shippingZonesIdsToAdd: getUpdatedIdsWithoutNewId(
@ -165,6 +190,49 @@ const ChannelDetailsPage = function<TErrors>({
); );
}; };
const addWarehouse = (warehouseId: string) => {
triggerChange();
set({
...data,
warehousesIdsToRemove: getUpdatedIdsWithoutNewId(
data.warehousesIdsToRemove,
warehouseId,
),
warehousesIdsToAdd: getUpdatedIdsWithNewId(
data.warehousesIdsToAdd,
warehouseId,
),
});
setWarehousesToDisplay([
...warehousesToDisplay,
getParsedSearchData({ data: searchWarehousesData }).find(
getById(warehouseId),
),
]);
};
const removeWarehouse = (warehouseId: string) => {
triggerChange();
set({
...data,
warehousesIdsToAdd: getUpdatedIdsWithoutNewId(
data.warehousesIdsToAdd,
warehouseId,
),
warehousesIdsToRemove: getUpdatedIdsWithNewId(
data.warehousesIdsToRemove,
warehouseId,
),
});
setWarehousesToDisplay(
warehousesToDisplay.filter(getByUnmatchingId(warehouseId)),
);
};
return ( return (
<> <>
<Grid> <Grid>
@ -193,7 +261,7 @@ const ChannelDetailsPage = function<TErrors>({
<CardSpacer /> <CardSpacer />
</> </>
)} )}
<ShippingZonesCard <ShippingZones
shippingZonesChoices={getFilteredShippingZonesChoices()} shippingZonesChoices={getFilteredShippingZonesChoices()}
shippingZones={shippingZonesToDisplay} shippingZones={shippingZonesToDisplay}
addShippingZone={addShippingZone} addShippingZone={addShippingZone}
@ -201,6 +269,15 @@ const ChannelDetailsPage = function<TErrors>({
searchShippingZones={searchShippingZones} searchShippingZones={searchShippingZones}
fetchMoreShippingZones={fetchMoreShippingZones} fetchMoreShippingZones={fetchMoreShippingZones}
/> />
<CardSpacer />
<Warehouses
warehousesChoices={getFilteredWarehousesChoices()}
warehouses={warehousesToDisplay}
addWarehouse={addWarehouse}
removeWarehouse={removeWarehouse}
searchWarehouses={searchWarehouses}
fetchMoreWarehouses={fetchMoreWarehouses}
/>
</div> </div>
</Grid> </Grid>
<Savebar <Savebar

View file

@ -1,4 +1,7 @@
import { ChannelShippingZonesQuery } from "@saleor/graphql"; import {
ChannelShippingZonesQuery,
ChannelWarehousesQuery,
} from "@saleor/graphql";
import { RelayToFlat } from "@saleor/types"; import { RelayToFlat } from "@saleor/types";
export type ChannelShippingZones = RelayToFlat< export type ChannelShippingZones = RelayToFlat<
@ -6,3 +9,9 @@ export type ChannelShippingZones = RelayToFlat<
>; >;
export type ChannelShippingZone = ChannelShippingZones[0]; export type ChannelShippingZone = ChannelShippingZones[0];
export type ChannelWarehouses = RelayToFlat<
ChannelWarehousesQuery["warehouses"]
>;
export type ChannelWarehouse = ChannelWarehouses[0];

View file

@ -16,6 +16,7 @@ import useShop from "@saleor/hooks/useShop";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { extractMutationErrors } from "@saleor/misc"; import { extractMutationErrors } from "@saleor/misc";
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch"; import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
import currencyCodes from "currency-codes"; import currencyCodes from "currency-codes";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -44,6 +45,8 @@ export const ChannelCreateView = ({}) => {
const handleSubmit = ({ const handleSubmit = ({
shippingZonesIdsToAdd, shippingZonesIdsToAdd,
shippingZonesIdsToRemove, shippingZonesIdsToRemove,
warehousesIdsToAdd,
warehousesIdsToRemove,
currencyCode, currencyCode,
...rest ...rest
}: FormData) => }: FormData) =>
@ -54,6 +57,7 @@ export const ChannelCreateView = ({}) => {
...rest, ...rest,
currencyCode: currencyCode.toUpperCase(), currencyCode: currencyCode.toUpperCase(),
addShippingZones: shippingZonesIdsToAdd, addShippingZones: shippingZonesIdsToAdd,
addWarehouses: warehousesIdsToAdd,
}, },
}, },
}), }),
@ -67,6 +71,14 @@ export const ChannelCreateView = ({}) => {
variables: DEFAULT_INITIAL_SEARCH_DATA, variables: DEFAULT_INITIAL_SEARCH_DATA,
}); });
const {
loadMore: fetchMoreWarehouses,
search: searchWarehouses,
result: searchWarehousesResult,
} = useWarehouseSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA,
});
const currencyCodeChoices = currencyCodes.data.map(currencyData => ({ const currencyCodeChoices = currencyCodes.data.map(currencyData => ({
label: intl.formatMessage( label: intl.formatMessage(
{ {
@ -109,6 +121,12 @@ export const ChannelCreateView = ({}) => {
searchShippingZonesResult, searchShippingZonesResult,
fetchMoreShippingZones, fetchMoreShippingZones,
)} )}
searchWarehouses={searchWarehouses}
searchWarehousesData={searchWarehousesResult.data}
fetchMoreWarehouses={getSearchFetchMoreProps(
searchWarehousesResult,
fetchMoreWarehouses,
)}
disabled={createChannelOpts.loading} disabled={createChannelOpts.loading}
errors={createChannelOpts?.data?.channelCreate?.errors || []} errors={createChannelOpts?.data?.channelCreate?.errors || []}
currencyCodes={currencyCodeChoices} currencyCodes={currencyCodeChoices}

View file

@ -17,6 +17,7 @@ import {
useChannelShippingZonesQuery, useChannelShippingZonesQuery,
useChannelsQuery, useChannelsQuery,
useChannelUpdateMutation, useChannelUpdateMutation,
useChannelWarehousesQuery,
} from "@saleor/graphql"; } from "@saleor/graphql";
import { getSearchFetchMoreProps } from "@saleor/hooks/makeTopLevelSearch/utils"; import { getSearchFetchMoreProps } from "@saleor/hooks/makeTopLevelSearch/utils";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -26,6 +27,7 @@ import useShop from "@saleor/hooks/useShop";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { extractMutationErrors } from "@saleor/misc"; import { extractMutationErrors } from "@saleor/misc";
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch"; import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
import getChannelsErrorMessage from "@saleor/utils/errors/channels"; import getChannelsErrorMessage from "@saleor/utils/errors/channels";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import React from "react"; import React from "react";
@ -103,6 +105,8 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
slug, slug,
shippingZonesIdsToRemove, shippingZonesIdsToRemove,
shippingZonesIdsToAdd, shippingZonesIdsToAdd,
warehousesIdsToRemove,
warehousesIdsToAdd,
defaultCountry, defaultCountry,
}: FormData) => }: FormData) =>
extractMutationErrors( extractMutationErrors(
@ -115,6 +119,8 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
defaultCountry, defaultCountry,
addShippingZones: shippingZonesIdsToAdd, addShippingZones: shippingZonesIdsToAdd,
removeShippingZones: shippingZonesIdsToRemove, removeShippingZones: shippingZonesIdsToRemove,
addWarehouses: warehousesIdsToAdd,
removeWarehouses: warehousesIdsToRemove,
}, },
}, },
}), }),
@ -176,6 +182,25 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
variables: DEFAULT_INITIAL_SEARCH_DATA, variables: DEFAULT_INITIAL_SEARCH_DATA,
}); });
const {
data: channelWarehousesData,
loading: channelsWarehousesLoading,
} = useChannelWarehousesQuery({
variables: {
filter: {
channels: [id],
},
},
});
const {
loadMore: fetchMoreWarehouses,
search: searchWarehouses,
result: searchWarehousesResult,
} = useWarehouseSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA,
});
return ( return (
<> <>
<WindowTitle <WindowTitle
@ -200,9 +225,21 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
searchShippingZonesResult, searchShippingZonesResult,
fetchMoreShippingZones, fetchMoreShippingZones,
)} )}
channelWarehouses={channelWarehousesData?.warehouses?.edges?.map(
({ node }) => node,
)}
searchWarehouses={searchWarehouses}
searchWarehousesData={searchWarehousesResult.data}
fetchMoreWarehouses={getSearchFetchMoreProps(
searchWarehousesResult,
fetchMoreWarehouses,
)}
channel={data?.channel} channel={data?.channel}
disabled={ disabled={
updateChannelOpts.loading || loading || channelsShippingZonesLoading updateChannelOpts.loading ||
loading ||
channelsShippingZonesLoading ||
channelsWarehousesLoading
} }
disabledStatus={ disabledStatus={
activateChannelOpts.loading || deactivateChannelOpts.loading activateChannelOpts.loading || deactivateChannelOpts.loading

View file

@ -22,6 +22,9 @@
"AttributeCreated", "AttributeCreated",
"AttributeDeleted", "AttributeDeleted",
"AttributeUpdated", "AttributeUpdated",
"AttributeValueCreated",
"AttributeValueDeleted",
"AttributeValueUpdated",
"CategoryCreated", "CategoryCreated",
"CategoryDeleted", "CategoryDeleted",
"CategoryUpdated", "CategoryUpdated",
@ -195,6 +198,7 @@
"Attribute", "Attribute",
"Category", "Category",
"Checkout", "Checkout",
"CheckoutLine",
"Collection", "Collection",
"DigitalContent", "DigitalContent",
"Fulfillment", "Fulfillment",
@ -203,6 +207,7 @@
"Menu", "Menu",
"MenuItem", "MenuItem",
"Order", "Order",
"OrderLine",
"Page", "Page",
"PageType", "PageType",
"Payment", "Payment",

View file

@ -13495,6 +13495,7 @@ export const SearchWarehousesDocument = gql`
sortBy: {direction: ASC, field: NAME} sortBy: {direction: ASC, field: NAME}
filter: {search: $query} filter: {search: $query}
) { ) {
totalCount
edges { edges {
node { node {
id id
@ -16343,6 +16344,46 @@ export function useWarehouseDetailsLazyQuery(baseOptions?: ApolloReactHooks.Lazy
export type WarehouseDetailsQueryHookResult = ReturnType<typeof useWarehouseDetailsQuery>; export type WarehouseDetailsQueryHookResult = ReturnType<typeof useWarehouseDetailsQuery>;
export type WarehouseDetailsLazyQueryHookResult = ReturnType<typeof useWarehouseDetailsLazyQuery>; export type WarehouseDetailsLazyQueryHookResult = ReturnType<typeof useWarehouseDetailsLazyQuery>;
export type WarehouseDetailsQueryResult = Apollo.QueryResult<Types.WarehouseDetailsQuery, Types.WarehouseDetailsQueryVariables>; export type WarehouseDetailsQueryResult = Apollo.QueryResult<Types.WarehouseDetailsQuery, Types.WarehouseDetailsQueryVariables>;
export const ChannelWarehousesDocument = gql`
query ChannelWarehouses($filter: WarehouseFilterInput) {
warehouses(filter: $filter, first: 100) {
edges {
node {
id
name
}
}
}
}
`;
/**
* __useChannelWarehousesQuery__
*
* To run a query within a React component, call `useChannelWarehousesQuery` and pass it any options that fit your needs.
* When your component renders, `useChannelWarehousesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useChannelWarehousesQuery({
* variables: {
* filter: // value for 'filter'
* },
* });
*/
export function useChannelWarehousesQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<Types.ChannelWarehousesQuery, Types.ChannelWarehousesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useQuery<Types.ChannelWarehousesQuery, Types.ChannelWarehousesQueryVariables>(ChannelWarehousesDocument, options);
}
export function useChannelWarehousesLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.ChannelWarehousesQuery, Types.ChannelWarehousesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return ApolloReactHooks.useLazyQuery<Types.ChannelWarehousesQuery, Types.ChannelWarehousesQueryVariables>(ChannelWarehousesDocument, options);
}
export type ChannelWarehousesQueryHookResult = ReturnType<typeof useChannelWarehousesQuery>;
export type ChannelWarehousesLazyQueryHookResult = ReturnType<typeof useChannelWarehousesLazyQuery>;
export type ChannelWarehousesQueryResult = Apollo.QueryResult<Types.ChannelWarehousesQuery, Types.ChannelWarehousesQueryVariables>;
export const WebhookCreateDocument = gql` export const WebhookCreateDocument = gql`
mutation WebhookCreate($input: WebhookCreateInput!) { mutation WebhookCreate($input: WebhookCreateInput!) {
webhookCreate(input: $input) { webhookCreate(input: $input) {

View file

@ -518,6 +518,14 @@ export type AttributeValueCreateFieldPolicy = {
errors?: FieldPolicy<any> | FieldReadFunction<any>, errors?: FieldPolicy<any> | FieldReadFunction<any>,
attributeValue?: FieldPolicy<any> | FieldReadFunction<any> attributeValue?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type AttributeValueCreatedKeySpecifier = ('issuedAt' | 'version' | 'issuingPrincipal' | 'recipient' | 'attributeValue' | AttributeValueCreatedKeySpecifier)[];
export type AttributeValueCreatedFieldPolicy = {
issuedAt?: FieldPolicy<any> | FieldReadFunction<any>,
version?: FieldPolicy<any> | FieldReadFunction<any>,
issuingPrincipal?: FieldPolicy<any> | FieldReadFunction<any>,
recipient?: FieldPolicy<any> | FieldReadFunction<any>,
attributeValue?: FieldPolicy<any> | FieldReadFunction<any>
};
export type AttributeValueDeleteKeySpecifier = ('attribute' | 'attributeErrors' | 'errors' | 'attributeValue' | AttributeValueDeleteKeySpecifier)[]; export type AttributeValueDeleteKeySpecifier = ('attribute' | 'attributeErrors' | 'errors' | 'attributeValue' | AttributeValueDeleteKeySpecifier)[];
export type AttributeValueDeleteFieldPolicy = { export type AttributeValueDeleteFieldPolicy = {
attribute?: FieldPolicy<any> | FieldReadFunction<any>, attribute?: FieldPolicy<any> | FieldReadFunction<any>,
@ -525,6 +533,14 @@ export type AttributeValueDeleteFieldPolicy = {
errors?: FieldPolicy<any> | FieldReadFunction<any>, errors?: FieldPolicy<any> | FieldReadFunction<any>,
attributeValue?: FieldPolicy<any> | FieldReadFunction<any> attributeValue?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type AttributeValueDeletedKeySpecifier = ('issuedAt' | 'version' | 'issuingPrincipal' | 'recipient' | 'attributeValue' | AttributeValueDeletedKeySpecifier)[];
export type AttributeValueDeletedFieldPolicy = {
issuedAt?: FieldPolicy<any> | FieldReadFunction<any>,
version?: FieldPolicy<any> | FieldReadFunction<any>,
issuingPrincipal?: FieldPolicy<any> | FieldReadFunction<any>,
recipient?: FieldPolicy<any> | FieldReadFunction<any>,
attributeValue?: FieldPolicy<any> | FieldReadFunction<any>
};
export type AttributeValueTranslatableContentKeySpecifier = ('id' | 'name' | 'richText' | 'plainText' | 'translation' | 'attributeValue' | AttributeValueTranslatableContentKeySpecifier)[]; export type AttributeValueTranslatableContentKeySpecifier = ('id' | 'name' | 'richText' | 'plainText' | 'translation' | 'attributeValue' | AttributeValueTranslatableContentKeySpecifier)[];
export type AttributeValueTranslatableContentFieldPolicy = { export type AttributeValueTranslatableContentFieldPolicy = {
id?: FieldPolicy<any> | FieldReadFunction<any>, id?: FieldPolicy<any> | FieldReadFunction<any>,
@ -555,6 +571,14 @@ export type AttributeValueUpdateFieldPolicy = {
errors?: FieldPolicy<any> | FieldReadFunction<any>, errors?: FieldPolicy<any> | FieldReadFunction<any>,
attributeValue?: FieldPolicy<any> | FieldReadFunction<any> attributeValue?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type AttributeValueUpdatedKeySpecifier = ('issuedAt' | 'version' | 'issuingPrincipal' | 'recipient' | 'attributeValue' | AttributeValueUpdatedKeySpecifier)[];
export type AttributeValueUpdatedFieldPolicy = {
issuedAt?: FieldPolicy<any> | FieldReadFunction<any>,
version?: FieldPolicy<any> | FieldReadFunction<any>,
issuingPrincipal?: FieldPolicy<any> | FieldReadFunction<any>,
recipient?: FieldPolicy<any> | FieldReadFunction<any>,
attributeValue?: FieldPolicy<any> | FieldReadFunction<any>
};
export type BulkProductErrorKeySpecifier = ('field' | 'message' | 'code' | 'attributes' | 'values' | 'index' | 'warehouses' | 'channels' | BulkProductErrorKeySpecifier)[]; export type BulkProductErrorKeySpecifier = ('field' | 'message' | 'code' | 'attributes' | 'values' | 'index' | 'warehouses' | 'channels' | BulkProductErrorKeySpecifier)[];
export type BulkProductErrorFieldPolicy = { export type BulkProductErrorFieldPolicy = {
field?: FieldPolicy<any> | FieldReadFunction<any>, field?: FieldPolicy<any> | FieldReadFunction<any>,
@ -684,7 +708,7 @@ export type CategoryUpdatedFieldPolicy = {
recipient?: FieldPolicy<any> | FieldReadFunction<any>, recipient?: FieldPolicy<any> | FieldReadFunction<any>,
category?: FieldPolicy<any> | FieldReadFunction<any> category?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type ChannelKeySpecifier = ('id' | 'name' | 'isActive' | 'currencyCode' | 'slug' | 'hasOrders' | 'defaultCountry' | ChannelKeySpecifier)[]; export type ChannelKeySpecifier = ('id' | 'name' | 'isActive' | 'currencyCode' | 'slug' | 'hasOrders' | 'defaultCountry' | 'warehouses' | ChannelKeySpecifier)[];
export type ChannelFieldPolicy = { export type ChannelFieldPolicy = {
id?: FieldPolicy<any> | FieldReadFunction<any>, id?: FieldPolicy<any> | FieldReadFunction<any>,
name?: FieldPolicy<any> | FieldReadFunction<any>, name?: FieldPolicy<any> | FieldReadFunction<any>,
@ -692,7 +716,8 @@ export type ChannelFieldPolicy = {
currencyCode?: FieldPolicy<any> | FieldReadFunction<any>, currencyCode?: FieldPolicy<any> | FieldReadFunction<any>,
slug?: FieldPolicy<any> | FieldReadFunction<any>, slug?: FieldPolicy<any> | FieldReadFunction<any>,
hasOrders?: FieldPolicy<any> | FieldReadFunction<any>, hasOrders?: FieldPolicy<any> | FieldReadFunction<any>,
defaultCountry?: FieldPolicy<any> | FieldReadFunction<any> defaultCountry?: FieldPolicy<any> | FieldReadFunction<any>,
warehouses?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type ChannelActivateKeySpecifier = ('channel' | 'channelErrors' | 'errors' | ChannelActivateKeySpecifier)[]; export type ChannelActivateKeySpecifier = ('channel' | 'channelErrors' | 'errors' | ChannelActivateKeySpecifier)[];
export type ChannelActivateFieldPolicy = { export type ChannelActivateFieldPolicy = {
@ -734,12 +759,13 @@ export type ChannelDeletedFieldPolicy = {
recipient?: FieldPolicy<any> | FieldReadFunction<any>, recipient?: FieldPolicy<any> | FieldReadFunction<any>,
channel?: FieldPolicy<any> | FieldReadFunction<any> channel?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type ChannelErrorKeySpecifier = ('field' | 'message' | 'code' | 'shippingZones' | ChannelErrorKeySpecifier)[]; export type ChannelErrorKeySpecifier = ('field' | 'message' | 'code' | 'shippingZones' | 'warehouses' | ChannelErrorKeySpecifier)[];
export type ChannelErrorFieldPolicy = { export type ChannelErrorFieldPolicy = {
field?: FieldPolicy<any> | FieldReadFunction<any>, field?: FieldPolicy<any> | FieldReadFunction<any>,
message?: FieldPolicy<any> | FieldReadFunction<any>, message?: FieldPolicy<any> | FieldReadFunction<any>,
code?: FieldPolicy<any> | FieldReadFunction<any>, code?: FieldPolicy<any> | FieldReadFunction<any>,
shippingZones?: FieldPolicy<any> | FieldReadFunction<any> shippingZones?: FieldPolicy<any> | FieldReadFunction<any>,
warehouses?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type ChannelStatusChangedKeySpecifier = ('issuedAt' | 'version' | 'issuingPrincipal' | 'recipient' | 'channel' | ChannelStatusChangedKeySpecifier)[]; export type ChannelStatusChangedKeySpecifier = ('issuedAt' | 'version' | 'issuingPrincipal' | 'recipient' | 'channel' | ChannelStatusChangedKeySpecifier)[];
export type ChannelStatusChangedFieldPolicy = { export type ChannelStatusChangedFieldPolicy = {
@ -886,9 +912,15 @@ export type CheckoutLanguageCodeUpdateFieldPolicy = {
checkoutErrors?: FieldPolicy<any> | FieldReadFunction<any>, checkoutErrors?: FieldPolicy<any> | FieldReadFunction<any>,
errors?: FieldPolicy<any> | FieldReadFunction<any> errors?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type CheckoutLineKeySpecifier = ('id' | 'variant' | 'quantity' | 'unitPrice' | 'undiscountedUnitPrice' | 'totalPrice' | 'undiscountedTotalPrice' | 'requiresShipping' | CheckoutLineKeySpecifier)[]; export type CheckoutLineKeySpecifier = ('id' | 'privateMetadata' | 'privateMetafield' | 'privateMetafields' | 'metadata' | 'metafield' | 'metafields' | 'variant' | 'quantity' | 'unitPrice' | 'undiscountedUnitPrice' | 'totalPrice' | 'undiscountedTotalPrice' | 'requiresShipping' | CheckoutLineKeySpecifier)[];
export type CheckoutLineFieldPolicy = { export type CheckoutLineFieldPolicy = {
id?: FieldPolicy<any> | FieldReadFunction<any>, id?: FieldPolicy<any> | FieldReadFunction<any>,
privateMetadata?: FieldPolicy<any> | FieldReadFunction<any>,
privateMetafield?: FieldPolicy<any> | FieldReadFunction<any>,
privateMetafields?: FieldPolicy<any> | FieldReadFunction<any>,
metadata?: FieldPolicy<any> | FieldReadFunction<any>,
metafield?: FieldPolicy<any> | FieldReadFunction<any>,
metafields?: FieldPolicy<any> | FieldReadFunction<any>,
variant?: FieldPolicy<any> | FieldReadFunction<any>, variant?: FieldPolicy<any> | FieldReadFunction<any>,
quantity?: FieldPolicy<any> | FieldReadFunction<any>, quantity?: FieldPolicy<any> | FieldReadFunction<any>,
unitPrice?: FieldPolicy<any> | FieldReadFunction<any>, unitPrice?: FieldPolicy<any> | FieldReadFunction<any>,
@ -2736,9 +2768,15 @@ export type OrderFullyPaidFieldPolicy = {
recipient?: FieldPolicy<any> | FieldReadFunction<any>, recipient?: FieldPolicy<any> | FieldReadFunction<any>,
order?: FieldPolicy<any> | FieldReadFunction<any> order?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type OrderLineKeySpecifier = ('id' | 'productName' | 'variantName' | 'productSku' | 'productVariantId' | 'isShippingRequired' | 'quantity' | 'quantityFulfilled' | 'unitDiscountReason' | 'taxRate' | 'digitalContentUrl' | 'thumbnail' | 'unitPrice' | 'undiscountedUnitPrice' | 'unitDiscount' | 'unitDiscountValue' | 'totalPrice' | 'variant' | 'translatedProductName' | 'translatedVariantName' | 'allocations' | 'quantityToFulfill' | 'unitDiscountType' | OrderLineKeySpecifier)[]; export type OrderLineKeySpecifier = ('id' | 'privateMetadata' | 'privateMetafield' | 'privateMetafields' | 'metadata' | 'metafield' | 'metafields' | 'productName' | 'variantName' | 'productSku' | 'productVariantId' | 'isShippingRequired' | 'quantity' | 'quantityFulfilled' | 'unitDiscountReason' | 'taxRate' | 'digitalContentUrl' | 'thumbnail' | 'unitPrice' | 'undiscountedUnitPrice' | 'unitDiscount' | 'unitDiscountValue' | 'totalPrice' | 'variant' | 'translatedProductName' | 'translatedVariantName' | 'allocations' | 'quantityToFulfill' | 'unitDiscountType' | OrderLineKeySpecifier)[];
export type OrderLineFieldPolicy = { export type OrderLineFieldPolicy = {
id?: FieldPolicy<any> | FieldReadFunction<any>, id?: FieldPolicy<any> | FieldReadFunction<any>,
privateMetadata?: FieldPolicy<any> | FieldReadFunction<any>,
privateMetafield?: FieldPolicy<any> | FieldReadFunction<any>,
privateMetafields?: FieldPolicy<any> | FieldReadFunction<any>,
metadata?: FieldPolicy<any> | FieldReadFunction<any>,
metafield?: FieldPolicy<any> | FieldReadFunction<any>,
metafields?: FieldPolicy<any> | FieldReadFunction<any>,
productName?: FieldPolicy<any> | FieldReadFunction<any>, productName?: FieldPolicy<any> | FieldReadFunction<any>,
variantName?: FieldPolicy<any> | FieldReadFunction<any>, variantName?: FieldPolicy<any> | FieldReadFunction<any>,
productSku?: FieldPolicy<any> | FieldReadFunction<any>, productSku?: FieldPolicy<any> | FieldReadFunction<any>,
@ -5271,10 +5309,18 @@ export type StrictTypedTypePolicies = {
keyFields?: false | AttributeValueCreateKeySpecifier | (() => undefined | AttributeValueCreateKeySpecifier), keyFields?: false | AttributeValueCreateKeySpecifier | (() => undefined | AttributeValueCreateKeySpecifier),
fields?: AttributeValueCreateFieldPolicy, fields?: AttributeValueCreateFieldPolicy,
}, },
AttributeValueCreated?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | AttributeValueCreatedKeySpecifier | (() => undefined | AttributeValueCreatedKeySpecifier),
fields?: AttributeValueCreatedFieldPolicy,
},
AttributeValueDelete?: Omit<TypePolicy, "fields" | "keyFields"> & { AttributeValueDelete?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | AttributeValueDeleteKeySpecifier | (() => undefined | AttributeValueDeleteKeySpecifier), keyFields?: false | AttributeValueDeleteKeySpecifier | (() => undefined | AttributeValueDeleteKeySpecifier),
fields?: AttributeValueDeleteFieldPolicy, fields?: AttributeValueDeleteFieldPolicy,
}, },
AttributeValueDeleted?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | AttributeValueDeletedKeySpecifier | (() => undefined | AttributeValueDeletedKeySpecifier),
fields?: AttributeValueDeletedFieldPolicy,
},
AttributeValueTranslatableContent?: Omit<TypePolicy, "fields" | "keyFields"> & { AttributeValueTranslatableContent?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | AttributeValueTranslatableContentKeySpecifier | (() => undefined | AttributeValueTranslatableContentKeySpecifier), keyFields?: false | AttributeValueTranslatableContentKeySpecifier | (() => undefined | AttributeValueTranslatableContentKeySpecifier),
fields?: AttributeValueTranslatableContentFieldPolicy, fields?: AttributeValueTranslatableContentFieldPolicy,
@ -5291,6 +5337,10 @@ export type StrictTypedTypePolicies = {
keyFields?: false | AttributeValueUpdateKeySpecifier | (() => undefined | AttributeValueUpdateKeySpecifier), keyFields?: false | AttributeValueUpdateKeySpecifier | (() => undefined | AttributeValueUpdateKeySpecifier),
fields?: AttributeValueUpdateFieldPolicy, fields?: AttributeValueUpdateFieldPolicy,
}, },
AttributeValueUpdated?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | AttributeValueUpdatedKeySpecifier | (() => undefined | AttributeValueUpdatedKeySpecifier),
fields?: AttributeValueUpdatedFieldPolicy,
},
BulkProductError?: Omit<TypePolicy, "fields" | "keyFields"> & { BulkProductError?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | BulkProductErrorKeySpecifier | (() => undefined | BulkProductErrorKeySpecifier), keyFields?: false | BulkProductErrorKeySpecifier | (() => undefined | BulkProductErrorKeySpecifier),
fields?: BulkProductErrorFieldPolicy, fields?: BulkProductErrorFieldPolicy,

File diff suppressed because one or more lines are too long

View file

@ -447,6 +447,11 @@ export const sectionNames = defineMessages({
defaultMessage: "Shipping Methods", defaultMessage: "Shipping Methods",
description: "shipping section name", description: "shipping section name",
}, },
shippingZones: {
id: "V1MytH",
defaultMessage: "Shipping Zones",
description: "shipping zones section name",
},
siteSettings: { siteSettings: {
id: "viFkCw", id: "viFkCw",
defaultMessage: "Site Settings", defaultMessage: "Site Settings",

View file

@ -2319,6 +2319,7 @@ export const shopOrderSettings: ShopOrderSettingsFragment = {
}; };
export const warehouseSearch: SearchWarehousesQuery["search"] = { export const warehouseSearch: SearchWarehousesQuery["search"] = {
totalCount: 20,
edges: [ edges: [
{ {
node: { node: {

View file

@ -2,7 +2,6 @@ import AddIcon from "@material-ui/icons/Add";
import Link from "@saleor/components/Link"; import Link from "@saleor/components/Link";
import { IconButton, makeStyles } from "@saleor/macaw-ui"; import { IconButton, makeStyles } from "@saleor/macaw-ui";
import React, { MutableRefObject } from "react"; import React, { MutableRefObject } from "react";
import { MessageDescriptor, useIntl } from "react-intl";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -21,7 +20,7 @@ const useStyles = makeStyles(
); );
interface CardAddItemsFooterProps { interface CardAddItemsFooterProps {
title: MessageDescriptor; title: string;
onAdd: () => void; onAdd: () => void;
testIds: { testIds: {
link: string; link: string;
@ -37,13 +36,12 @@ const CardAddItemsFooter: React.FC<CardAddItemsFooterProps> = ({
ref, ref,
children, children,
}) => { }) => {
const intl = useIntl();
const classes = useStyles({}); const classes = useStyles({});
return ( return (
<div className={classes.container} ref={ref}> <div className={classes.container} ref={ref}>
<Link data-test-id={testIds.link} onClick={onAdd}> <Link data-test-id={testIds.link} onClick={onAdd}>
{intl.formatMessage(title)} {title}
</Link> </Link>
<IconButton <IconButton
variant="secondary" variant="secondary"

View file

@ -28,14 +28,8 @@ import PreviewPill from "@saleor/components/PreviewPill";
import { ProductErrorFragment, WarehouseFragment } from "@saleor/graphql"; import { ProductErrorFragment, WarehouseFragment } from "@saleor/graphql";
import { FormChange, FormErrors } from "@saleor/hooks/useForm"; import { FormChange, FormErrors } from "@saleor/hooks/useForm";
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
import { import { sectionNames } from "@saleor/intl";
Button, import { Button, DeleteIcon, IconButton, PlusIcon } from "@saleor/macaw-ui";
DeleteIcon,
IconButton,
ICONBUTTON_SIZE,
makeStyles,
PlusIcon,
} from "@saleor/macaw-ui";
import { renderCollection } from "@saleor/misc"; import { renderCollection } from "@saleor/misc";
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
import createNonNegativeValueChangeHandler from "@saleor/utils/handlers/nonNegativeValueChangeHandler"; import createNonNegativeValueChangeHandler from "@saleor/utils/handlers/nonNegativeValueChangeHandler";
@ -46,6 +40,8 @@ import { ProductCreateData } from "../ProductCreatePage";
import { ProductUpdateSubmitData } from "../ProductUpdatePage/form"; import { ProductUpdateSubmitData } from "../ProductUpdatePage/form";
import { ProductVariantCreateData } from "../ProductVariantCreatePage/form"; import { ProductVariantCreateData } from "../ProductVariantCreatePage/form";
import { ProductVariantUpdateData } from "../ProductVariantPage/form"; import { ProductVariantUpdateData } from "../ProductVariantPage/form";
import { messages } from "./messages";
import { useStyles } from "./styles";
export interface ProductStockFormsetData { export interface ProductStockFormsetData {
quantityAllocated: number; quantityAllocated: number;
@ -90,98 +86,6 @@ export interface ProductStocksProps {
onWarehouseConfigure: () => void; onWarehouseConfigure: () => void;
} }
const useStyles = makeStyles(
theme => ({
colAction: {
padding: 0,
width: `calc(${ICONBUTTON_SIZE}px + ${theme.spacing(1)})`,
},
colName: {},
colQuantity: {
textAlign: "right",
width: 150,
},
colSoldUnits: {
textAlign: "right",
width: 150,
},
colThreshold: {
textAlign: "right",
width: 180,
},
editWarehouses: {
marginRight: theme.spacing(-1),
},
input: {
padding: theme.spacing(1.5),
textAlign: "right",
},
menuItem: {
"&:not(:last-of-type)": {
marginBottom: theme.spacing(2),
},
},
noWarehouseInfo: {
marginTop: theme.spacing(),
},
paper: {
padding: theme.spacing(2),
},
popper: {
marginTop: theme.spacing(1),
zIndex: 2,
},
quantityContainer: {
paddingTop: theme.spacing(),
},
quantityHeader: {
alignItems: "center",
display: "flex",
justifyContent: "space-between",
},
skuInputContainer: {
display: "grid",
gridColumnGap: theme.spacing(3),
gridTemplateColumns: "repeat(2, 1fr)",
},
dateTimeInputs: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
},
preorderInfo: {
marginBottom: theme.spacing(2),
marginTop: theme.spacing(2),
display: "block",
},
caption: {
fontSize: 14,
},
thresholdRow: {
display: "grid",
gridColumnGap: theme.spacing(3),
gridTemplateColumns: "3fr 1fr",
marginTop: theme.spacing(1),
},
thresholdInput: {
maxWidth: 400,
},
preorderItemsLeftCount: {
fontSize: 14,
paddingTop: theme.spacing(2),
textAlign: "center",
},
preorderLimitInfo: {
marginTop: theme.spacing(3),
},
preview: {
marginLeft: theme.spacing(1),
},
}),
{
name: "ProductStocks",
},
);
const ProductStocks: React.FC<ProductStocksProps> = ({ const ProductStocks: React.FC<ProductStocksProps> = ({
data, data,
disabled, disabled,
@ -200,7 +104,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
onWarehouseStockDelete, onWarehouseStockDelete,
onWarehouseConfigure, onWarehouseConfigure,
}) => { }) => {
const classes = useStyles({}); const classes = useStyles();
const intl = useIntl(); const intl = useIntl();
const anchor = React.useRef<HTMLDivElement>(); const anchor = React.useRef<HTMLDivElement>();
const [isExpanded, setExpansionState] = React.useState(false); const [isExpanded, setExpansionState] = React.useState(false);
@ -218,13 +122,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
return ( return (
<Card> <Card>
<CardTitle <CardTitle title={intl.formatMessage(messages.title)} />
title={intl.formatMessage({
id: "4qe6hO",
defaultMessage: "Inventory",
description: "product stock, section header",
})}
/>
<CardContent> <CardContent>
<div className={classes.skuInputContainer}> <div className={classes.skuInputContainer}>
<TextField <TextField
@ -232,10 +130,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
error={!!formErrors.sku} error={!!formErrors.sku}
fullWidth fullWidth
helperText={getProductErrorMessage(formErrors.sku, intl)} helperText={getProductErrorMessage(formErrors.sku, intl)}
label={intl.formatMessage({ label={intl.formatMessage(messages.sku)}
id: "xB7BTp",
defaultMessage: "SKU (Stock Keeping Unit)",
})}
name="sku" name="sku"
onChange={onFormDataChange} onChange={onFormDataChange}
value={data.sku} value={data.sku}
@ -252,11 +147,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
disabled={disabled} disabled={disabled}
label={ label={
<> <>
<FormattedMessage <FormattedMessage {...messages.variantInPreorder} />
id="eAFU/E"
defaultMessage="Variant currently in preorder"
description="product inventory, checkbox"
/>
<PreviewPill className={classes.preview} /> <PreviewPill className={classes.preview} />
</> </>
} }
@ -272,16 +163,9 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
disabled={disabled} disabled={disabled}
label={ label={
<> <>
<FormattedMessage <FormattedMessage {...messages.trackInventory} />
id="TjGYna"
defaultMessage="Track Inventory"
description="product inventory, checkbox"
/>
<Typography variant="caption"> <Typography variant="caption">
<FormattedMessage <FormattedMessage {...messages.trackInventoryDescription} />
id="jABdx1"
defaultMessage="Active inventory tracking will automatically calculate changes of stock"
/>
</Typography> </Typography>
</> </>
} }
@ -295,14 +179,18 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
<Typography> <Typography>
<div className={classes.quantityHeader}> <div className={classes.quantityHeader}>
<span> <span>
<FormattedMessage <FormattedMessage {...messages.quantity} />
id="bp/i0x"
defaultMessage="Quantity"
description="header"
/>
</span> </span>
</div> </div>
</Typography> </Typography>
{!productVariantChannelListings?.length && (
<>
<FormSpacer />
<Typography variant="caption">
<FormattedMessage {...messages.noChannelWarehousesAllocation} />
</Typography>
</>
)}
{!warehouses?.length && ( {!warehouses?.length && (
<Typography <Typography
@ -312,9 +200,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
{hasVariants ? ( {hasVariants ? (
<> <>
<FormattedMessage <FormattedMessage
id="D8nsBc" {...messages.configureWarehouseForVariant}
defaultMessage="There are no warehouses set up for your store. To add stock quantity to the variant please <a>configure a warehouse</a>"
description="no warehouses info"
values={{ values={{
a: chunks => ( a: chunks => (
<Link onClick={onWarehouseConfigure}>{chunks}</Link> <Link onClick={onWarehouseConfigure}>{chunks}</Link>
@ -325,9 +211,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
) : ( ) : (
<> <>
<FormattedMessage <FormattedMessage
id="RLBLPQ" {...messages.configureWarehouseForProduct}
defaultMessage="There are no warehouses set up for your store. To add stock quantity to the product please <a>configure a warehouse</a>"
description="no warehouses info"
values={{ values={{
a: chunks => ( a: chunks => (
<Link onClick={onWarehouseConfigure}>{chunks}</Link> <Link onClick={onWarehouseConfigure}>{chunks}</Link>
@ -340,7 +224,9 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
)} )}
</CardContent> </CardContent>
)} )}
{warehouses?.length > 0 && !data.isPreorder && ( {productVariantChannelListings?.length > 0 &&
warehouses?.length > 0 &&
!data.isPreorder && (
<Table> <Table>
<colgroup> <colgroup>
<col className={classes.colName} /> <col className={classes.colName} />
@ -350,25 +236,13 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
<FormattedMessage <FormattedMessage {...messages.warehouseName} />
id="KTAg0f"
defaultMessage="Warehouse Name"
description="tabel column header"
/>
</TableCell> </TableCell>
<TableCell className={classes.colQuantity}> <TableCell className={classes.colQuantity}>
<FormattedMessage <FormattedMessage {...messages.allocated} />
id="g/FRtd"
defaultMessage="Allocated"
description="table column header, allocated product quantity"
/>
</TableCell> </TableCell>
<TableCell className={classes.colQuantity}> <TableCell className={classes.colQuantity}>
<FormattedMessage <FormattedMessage {...messages.quantity} />
id="ge/xFX"
defaultMessage="Quantity"
description="table column header"
/>
</TableCell> </TableCell>
<TableCell className={classes.colAction} /> <TableCell className={classes.colAction} />
</TableRow> </TableRow>
@ -417,11 +291,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
<TableRow> <TableRow>
<TableCell colSpan={3}> <TableCell colSpan={3}>
<Typography variant="body2"> <Typography variant="body2">
<FormattedMessage <FormattedMessage {...messages.assignWarehouse} />
id="cBHRxx"
defaultMessage="Assign Warehouse"
description="button"
/>
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell className={classes.colAction}> <TableCell className={classes.colAction}>
@ -477,12 +347,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
{data.isPreorder && ( {data.isPreorder && (
<CardContent> <CardContent>
<Typography variant="caption" className={classes.caption}> <Typography variant="caption" className={classes.caption}>
{intl.formatMessage({ <FormattedMessage {...messages.preorderEndDateSetup} />
id: "REVk27",
defaultMessage:
"Set up an end date of preorder. When end date will be reached product will be automatically taken from preorder to standard selling",
description: "info text",
})}
</Typography> </Typography>
{data.hasPreorderEndDate && ( {data.hasPreorderEndDate && (
@ -519,22 +384,11 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
} }
> >
{data.hasPreorderEndDate {data.hasPreorderEndDate
? intl.formatMessage({ ? intl.formatMessage(messages.endDateCancel)
id: "2qJc9y", : intl.formatMessage(messages.endDateSetup)}
defaultMessage: "CANCEL END DATE",
})
: intl.formatMessage({
id: "7Ii5ZQ",
defaultMessage: "SETUP END DATE",
})}
</Button> </Button>
<Typography variant="caption" className={classes.preorderLimitInfo}> <Typography variant="caption" className={classes.preorderLimitInfo}>
{intl.formatMessage({ <FormattedMessage {...messages.preorderProductsAvailability} />
id: "Gz+4CI",
defaultMessage:
"Preordered products will be available in all warehouses. You can set a threshold for sold quantity. Leaving input blank will be interpreted as no limit to sale. Sold items will be allocated at the warehouse assigned to chosen shipping zone.",
description: "info text",
})}
</Typography> </Typography>
<div className={classes.thresholdRow}> <div className={classes.thresholdRow}>
<TextField <TextField
@ -543,15 +397,10 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
}} }}
disabled={disabled} disabled={disabled}
fullWidth fullWidth
helperText={intl.formatMessage({ helperText={intl.formatMessage(
id: "NcY4ph", messages.preorderTresholdDescription,
defaultMessage: )}
"Threshold that cannot be exceeded even if per channel thresholds are still available", label={intl.formatMessage(messages.preorderTresholdLabel)}
})}
label={intl.formatMessage({
id: "RJ5QxE",
defaultMessage: "Global threshold",
})}
name="globalThreshold" name="globalThreshold"
onChange={onThresholdChange} onChange={onThresholdChange}
value={data.globalThreshold ?? ""} value={data.globalThreshold ?? ""}
@ -563,19 +412,10 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
className={classes.preorderItemsLeftCount} className={classes.preorderItemsLeftCount}
> >
{data.globalThreshold {data.globalThreshold
? intl.formatMessage( ? intl.formatMessage(messages.preorderTresholdUnitsLeft, {
{ unitsLeft,
id: "7wkGxW", })
defaultMessage: "{unitsLeft} units left", : intl.formatMessage(messages.preorderTresholdUnlimited)}
description: "app has been installed",
},
{ unitsLeft },
)
: intl.formatMessage({
id: "CEavJt",
defaultMessage: "Unlimited",
description: "section header",
})}
</Typography> </Typography>
)} )}
</div> </div>
@ -592,25 +432,13 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
<FormattedMessage <FormattedMessage {...sectionNames.channels} />
id="JyQEHU"
defaultMessage="Channels"
description="tabel column header"
/>
</TableCell> </TableCell>
<TableCell className={classes.colSoldUnits}> <TableCell className={classes.colSoldUnits}>
<FormattedMessage <FormattedMessage {...messages.soldUnits} />
id="HcQEUk"
defaultMessage="Sold units"
description="table column header, sold units preorder quantity"
/>
</TableCell> </TableCell>
<TableCell className={classes.colThreshold}> <TableCell className={classes.colThreshold}>
<FormattedMessage <FormattedMessage {...messages.channelTreshold} />
id="MNZY28"
defaultMessage="Channel threshold"
description="table column header"
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
@ -638,10 +466,9 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
min: 0, min: 0,
type: "number", type: "number",
}} }}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage(
id: "ekXood", messages.preorderTresholdUnlimited,
defaultMessage: "Unlimited", )}
})}
onChange={e => { onChange={e => {
onVariantChannelListingChange(listing.id, { onVariantChannelListingChange(listing.id, {
costPrice: listing.costPrice, costPrice: listing.costPrice,

View file

@ -0,0 +1,121 @@
import { defineMessages } from "react-intl";
export const messages = defineMessages({
title: {
id: "4qe6hO",
defaultMessage: "Inventory",
description: "product stock, section header",
},
sku: {
id: "SM+yG0",
defaultMessage: "SKU (Stock Keeping Unit)",
description: "input label",
},
variantInPreorder: {
id: "eAFU/E",
defaultMessage: "Variant currently in preorder",
description: "product inventory, checkbox",
},
trackInventory: {
id: "TjGYna",
defaultMessage: "Track Inventory",
description: "product inventory, checkbox",
},
trackInventoryDescription: {
id: "Wyl25+",
defaultMessage:
"Active inventory tracking will automatically calculate changes of stock",
description: "product inventory, checkbox description",
},
quantity: {
id: "bp/i0x",
defaultMessage: "Quantity",
description: "header",
},
warehouseName: {
id: "ErvPaM",
defaultMessage: "Warehouse Name",
description: "header",
},
allocated: {
id: "/C//FB",
defaultMessage: "Allocated",
description: "header, allocated product quantity",
},
noChannelWarehousesAllocation: {
id: "taS/08",
defaultMessage:
"Assign this variant to a channel in the product channel manager to define warehouses allocation",
description: "variant stocks section subtitle",
},
configureWarehouseForVariant: {
id: "D8nsBc",
defaultMessage:
"There are no warehouses set up for your store. To add stock quantity to the variant please <a>configure a warehouse</a>",
description: "no warehouses info",
},
configureWarehouseForProduct: {
id: "RLBLPQ",
defaultMessage:
"There are no warehouses set up for your store. To add stock quantity to the product please <a>configure a warehouse</a>",
description: "no warehouses info",
},
assignWarehouse: {
id: "cBHRxx",
defaultMessage: "Assign Warehouse",
description: "button",
},
preorderEndDateSetup: {
id: "REVk27",
defaultMessage:
"Set up an end date of preorder. When end date will be reached product will be automatically taken from preorder to standard selling",
description: "info text",
},
endDateCancel: {
id: "v9ILn/",
defaultMessage: "CANCEL END DATE",
description: "button",
},
endDateSetup: {
id: "9IWg/f",
defaultMessage: "SETUP END DATE",
description: "button",
},
preorderProductsAvailability: {
id: "Gz+4CI",
defaultMessage:
"Preordered products will be available in all warehouses. You can set a threshold for sold quantity. Leaving input blank will be interpreted as no limit to sale. Sold items will be allocated at the warehouse assigned to chosen shipping zone.",
description: "info text",
},
preorderTresholdLabel: {
id: "/iijFq",
defaultMessage: "Global threshold",
description: "input label",
},
preorderTresholdDescription: {
id: "HYC6cH",
defaultMessage:
"Threshold that cannot be exceeded even if per channel thresholds are still available",
description: "input description",
},
preorderTresholdUnitsLeft: {
id: "JkO0jp",
defaultMessage: "{unitsLeft} units left",
description: "input description",
},
preorderTresholdUnlimited: {
id: "tlGXkh",
defaultMessage: "Unlimited",
description: "input description",
},
soldUnits: {
id: "HcQEUk",
defaultMessage: "Sold units",
description: "table column header, sold units preorder quantity",
},
channelTreshold: {
id: "MNZY28",
defaultMessage: "Channel threshold",
description: "table column header",
},
});

View file

@ -0,0 +1,93 @@
import { ICONBUTTON_SIZE, makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
theme => ({
colAction: {
padding: 0,
width: `calc(${ICONBUTTON_SIZE}px + ${theme.spacing(1)})`,
},
colName: {},
colQuantity: {
textAlign: "right",
width: 150,
},
colSoldUnits: {
textAlign: "right",
width: 150,
},
colThreshold: {
textAlign: "right",
width: 180,
},
editWarehouses: {
marginRight: theme.spacing(-1),
},
input: {
padding: theme.spacing(1.5),
textAlign: "right",
},
menuItem: {
"&:not(:last-of-type)": {
marginBottom: theme.spacing(2),
},
},
noWarehouseInfo: {
marginTop: theme.spacing(),
},
paper: {
padding: theme.spacing(2),
},
popper: {
marginTop: theme.spacing(1),
zIndex: 2,
},
quantityContainer: {
paddingTop: theme.spacing(),
},
quantityHeader: {
alignItems: "center",
display: "flex",
justifyContent: "space-between",
},
skuInputContainer: {
display: "grid",
gridColumnGap: theme.spacing(3),
gridTemplateColumns: "repeat(2, 1fr)",
},
dateTimeInputs: {
marginTop: theme.spacing(2),
marginBottom: theme.spacing(2),
},
preorderInfo: {
marginBottom: theme.spacing(2),
marginTop: theme.spacing(2),
display: "block",
},
caption: {
fontSize: 14,
},
thresholdRow: {
display: "grid",
gridColumnGap: theme.spacing(3),
gridTemplateColumns: "3fr 1fr",
marginTop: theme.spacing(1),
},
thresholdInput: {
maxWidth: 400,
},
preorderItemsLeftCount: {
fontSize: 14,
paddingTop: theme.spacing(2),
textAlign: "center",
},
preorderLimitInfo: {
marginTop: theme.spacing(3),
},
preview: {
marginLeft: theme.spacing(1),
},
}),
{
name: "ProductStocks",
},
);

View file

@ -112,12 +112,6 @@ export const ProductCreateView: React.FC<ProductCreateProps> = ({ params }) => {
result: searchAttributeValuesOpts, result: searchAttributeValuesOpts,
reset: searchAttributeReset, reset: searchAttributeReset,
} = useAttributeValueSearchHandler(DEFAULT_INITIAL_SEARCH_DATA); } = useAttributeValueSearchHandler(DEFAULT_INITIAL_SEARCH_DATA);
const warehouses = useWarehouseListQuery({
displayLoader: true,
variables: {
first: 50,
},
});
const [updateMetadata] = useUpdateMetadataMutation({}); const [updateMetadata] = useUpdateMetadataMutation({});
const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({}); const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({});
const taxTypes = useTaxTypeListQuery({}); const taxTypes = useTaxTypeListQuery({});
@ -160,6 +154,16 @@ export const ProductCreateView: React.FC<ProductCreateProps> = ({ params }) => {
}, },
); );
const warehouses = useWarehouseListQuery({
displayLoader: true,
variables: {
first: 50,
filter: {
channels: currentChannels.map(channel => channel.id),
},
},
});
const handleSuccess = (productId: string) => { const handleSuccess = (productId: string) => {
notify({ notify({
status: "success", status: "success",

View file

@ -156,12 +156,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
result: searchAttributeValuesOpts, result: searchAttributeValuesOpts,
reset: searchAttributeReset, reset: searchAttributeReset,
} = useAttributeValueSearchHandler(DEFAULT_INITIAL_SEARCH_DATA); } = useAttributeValueSearchHandler(DEFAULT_INITIAL_SEARCH_DATA);
const warehouses = useWarehouseListQuery({
displayLoader: true,
variables: {
first: 50,
},
});
const shop = useShop(); const shop = useShop();
const [updateMetadata] = useUpdateMetadataMutation({}); const [updateMetadata] = useUpdateMetadataMutation({});
const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({}); const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({});
@ -324,6 +318,16 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
{ formId: PRODUCT_UPDATE_FORM_ID }, { formId: PRODUCT_UPDATE_FORM_ID },
); );
const warehouses = useWarehouseListQuery({
displayLoader: true,
variables: {
first: 50,
filter: {
channels: currentChannels.map(channel => channel.id),
},
},
});
const [ const [
updateChannels, updateChannels,
updateChannelsOpts, updateChannelsOpts,

View file

@ -82,13 +82,6 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
setErrors([]); setErrors([]);
}, [variantId]); }, [variantId]);
const warehouses = useWarehouseListQuery({
displayLoader: true,
variables: {
first: 50,
},
});
const { data, loading } = useProductVariantDetailsQuery({ const { data, loading } = useProductVariantDetailsQuery({
displayLoader: true, displayLoader: true,
variables: { variables: {
@ -196,6 +189,16 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
const variant = data?.productVariant; const variant = data?.productVariant;
const channels = createVariantChannels(variant); const channels = createVariantChannels(variant);
const warehouses = useWarehouseListQuery({
displayLoader: true,
variables: {
first: 50,
filter: {
channels: channels.map(channel => channel.id),
},
},
});
const [ const [
deactivatePreorder, deactivatePreorder,
deactivatePreoderOpts, deactivatePreoderOpts,

View file

@ -14,6 +14,7 @@ export const searchWarehouses = gql`
sortBy: { direction: ASC, field: NAME } sortBy: { direction: ASC, field: NAME }
filter: { search: $query } filter: { search: $query }
) { ) {
totalCount
edges { edges {
node { node {
id id

File diff suppressed because it is too large Load diff

View file

@ -36,3 +36,17 @@ export const warehouseDetails = gql`
} }
} }
`; `;
// first: 100 - to be removed when we implement pagintion in ui for this query
export const channelWarehouses = gql`
query ChannelWarehouses($filter: WarehouseFilterInput) {
warehouses(filter: $filter, first: 100) {
edges {
node {
id
name
}
}
}
}
`;