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:
parent
eb58e2c8ed
commit
f1ffb5093f
46 changed files with 4263 additions and 1560 deletions
|
@ -9,7 +9,10 @@ export const ADD_CHANNEL_FORM_SELECTORS = {
|
|||
slugValidationMessage: "[data-test-id='slug-text-input-helper-text']",
|
||||
currencyAutocompleteDropdown:
|
||||
"[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']",
|
||||
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
|
@ -27,10 +27,6 @@
|
|||
"context": "order shipping method name",
|
||||
"string": "Shipping"
|
||||
},
|
||||
"+G9l7u": {
|
||||
"context": "all selected zones card message",
|
||||
"string": "All available shipping zones have been selected"
|
||||
},
|
||||
"+HuipK": {
|
||||
"context": "variant sku",
|
||||
"string": "SKU {sku}"
|
||||
|
@ -170,6 +166,10 @@
|
|||
"context": "dialog header",
|
||||
"string": "Add Tracking Code"
|
||||
},
|
||||
"/C//FB": {
|
||||
"context": "header, allocated product quantity",
|
||||
"string": "Allocated"
|
||||
},
|
||||
"/JENWS": {
|
||||
"string": "Reduced Tax Rates"
|
||||
},
|
||||
|
@ -224,6 +224,10 @@
|
|||
"/glQgs": {
|
||||
"string": "No channels found"
|
||||
},
|
||||
"/iijFq": {
|
||||
"context": "input label",
|
||||
"string": "Global threshold"
|
||||
},
|
||||
"/kWzY1": {
|
||||
"string": "Are you sure you want to delete this address from users address book?"
|
||||
},
|
||||
|
@ -601,9 +605,6 @@
|
|||
"context": "payment status",
|
||||
"string": "Fully paid"
|
||||
},
|
||||
"2qJc9y": {
|
||||
"string": "CANCEL END DATE"
|
||||
},
|
||||
"2r4cTE": {
|
||||
"context": "button",
|
||||
"string": "Enable Dark Mode"
|
||||
|
@ -1126,9 +1127,6 @@
|
|||
"context": "attribute value deleted",
|
||||
"string": "Value deleted"
|
||||
},
|
||||
"7Ii5ZQ": {
|
||||
"string": "SETUP END DATE"
|
||||
},
|
||||
"7JAAul": {
|
||||
"context": "product field",
|
||||
"string": "Export Product Weight"
|
||||
|
@ -1206,10 +1204,6 @@
|
|||
"context": "info text",
|
||||
"string": "This rate will apply to all orders"
|
||||
},
|
||||
"7wkGxW": {
|
||||
"context": "app has been installed",
|
||||
"string": "{unitsLeft} units left"
|
||||
},
|
||||
"7yKZvp": {
|
||||
"context": "section header",
|
||||
"string": "User Status"
|
||||
|
@ -1226,10 +1220,6 @@
|
|||
"context": "navigator placeholder",
|
||||
"string": "Order Number"
|
||||
},
|
||||
"8CbACQ": {
|
||||
"context": "add shipping zone title",
|
||||
"string": "Add Shipping Zones"
|
||||
},
|
||||
"8EGagh": {
|
||||
"context": "search box label",
|
||||
"string": "Filter Countries"
|
||||
|
@ -1331,6 +1321,10 @@
|
|||
"context": "option",
|
||||
"string": "Create single variant"
|
||||
},
|
||||
"9IWg/f": {
|
||||
"context": "button",
|
||||
"string": "SETUP END DATE"
|
||||
},
|
||||
"9OtpHt": {
|
||||
"string": "Order line deleted"
|
||||
},
|
||||
|
@ -1462,10 +1456,6 @@
|
|||
"context": "order refund subtitle",
|
||||
"string": "Refunded items can't be fulfilled"
|
||||
},
|
||||
"ANRRpG": {
|
||||
"context": "card title",
|
||||
"string": "Shipping Zones"
|
||||
},
|
||||
"AOI4LW": {
|
||||
"context": "navigator placeholder",
|
||||
"string": "Search in Catalog"
|
||||
|
@ -1710,10 +1700,6 @@
|
|||
"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?}}"
|
||||
},
|
||||
"CEavJt": {
|
||||
"context": "section header",
|
||||
"string": "Unlimited"
|
||||
},
|
||||
"CG+awx": {
|
||||
"context": "dialog content",
|
||||
"string": "Which address would you like to use as shipping address for selected customer:"
|
||||
|
@ -2033,6 +2019,10 @@
|
|||
"ErNH3D": {
|
||||
"string": "Define where this attribute should be used in Saleor system"
|
||||
},
|
||||
"ErvPaM": {
|
||||
"context": "header",
|
||||
"string": "Warehouse Name"
|
||||
},
|
||||
"EsZH44": {
|
||||
"context": "VariantDetailsChannelsAvailabilityCard item subtitle hidden",
|
||||
"string": "Hidden"
|
||||
|
@ -2041,6 +2031,10 @@
|
|||
"context": "header",
|
||||
"string": "{pluginName} Details"
|
||||
},
|
||||
"EuOXmr": {
|
||||
"context": "add items title",
|
||||
"string": "Add {itemsName}"
|
||||
},
|
||||
"Ev6SEF": {
|
||||
"string": "New Password"
|
||||
},
|
||||
|
@ -2428,6 +2422,10 @@
|
|||
"context": "number of variants",
|
||||
"string": "Variants ({quantity})"
|
||||
},
|
||||
"HYC6cH": {
|
||||
"context": "input description",
|
||||
"string": "Threshold that cannot be exceeded even if per channel thresholds are still available"
|
||||
},
|
||||
"HYHLsB": {
|
||||
"context": "product field",
|
||||
"string": "Export Variant ID"
|
||||
|
@ -2585,6 +2583,10 @@
|
|||
"context": "header",
|
||||
"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": {
|
||||
"context": "no collections",
|
||||
"string": "No collections found"
|
||||
|
@ -2748,6 +2750,10 @@
|
|||
"context": "section header",
|
||||
"string": "Organize Product"
|
||||
},
|
||||
"JkO0jp": {
|
||||
"context": "input description",
|
||||
"string": "{unitsLeft} units left"
|
||||
},
|
||||
"JnzDrI": {
|
||||
"context": "discount type",
|
||||
"string": "Fixed Amount"
|
||||
|
@ -2779,10 +2785,6 @@
|
|||
"context": "select product informations to be exported",
|
||||
"string": "Information exported:"
|
||||
},
|
||||
"JyQEHU": {
|
||||
"context": "tabel column header",
|
||||
"string": "Channels"
|
||||
},
|
||||
"JyQoES": {
|
||||
"context": "delete attribute value",
|
||||
"string": "Are you sure you want to delete \"{name}\" value?"
|
||||
|
@ -2847,10 +2849,6 @@
|
|||
"context": "product available for purchase date",
|
||||
"string": "will become available on {date}"
|
||||
},
|
||||
"KTAg0f": {
|
||||
"context": "tabel column header",
|
||||
"string": "Warehouse Name"
|
||||
},
|
||||
"KXkdMH": {
|
||||
"context": "order discount removed title",
|
||||
"string": "Order discount was removed by"
|
||||
|
@ -3202,9 +3200,6 @@
|
|||
"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."
|
||||
},
|
||||
"NcY4ph": {
|
||||
"string": "Threshold that cannot be exceeded even if per channel thresholds are still available"
|
||||
},
|
||||
"Nfh9QM": {
|
||||
"context": "checkbox gift cards label description",
|
||||
"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",
|
||||
"string": "Use in Filtering"
|
||||
},
|
||||
"RJ5QxE": {
|
||||
"string": "Global threshold"
|
||||
},
|
||||
"RLBLPQ": {
|
||||
"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>"
|
||||
|
@ -3833,6 +3825,10 @@
|
|||
"SKFr04": {
|
||||
"string": "Attribute not found."
|
||||
},
|
||||
"SM+yG0": {
|
||||
"context": "input label",
|
||||
"string": "SKU (Stock Keeping Unit)"
|
||||
},
|
||||
"SMakqb": {
|
||||
"context": "customer contact section, header",
|
||||
"string": "Contact Information"
|
||||
|
@ -4208,6 +4204,10 @@
|
|||
"context": "dialog header",
|
||||
"string": "Edit Shipping Method"
|
||||
},
|
||||
"V1MytH": {
|
||||
"context": "shipping zones section name",
|
||||
"string": "Shipping Zones"
|
||||
},
|
||||
"V1mqpZ": {
|
||||
"context": "button",
|
||||
"string": "Create Permission Group"
|
||||
|
@ -4466,6 +4466,10 @@
|
|||
"context": "gift card bulk create success dialog title",
|
||||
"string": "Bulk Issue Gift Cards"
|
||||
},
|
||||
"Wyl25+": {
|
||||
"context": "product inventory, checkbox description",
|
||||
"string": "Active inventory tracking will automatically calculate changes of stock"
|
||||
},
|
||||
"WzA5Ll": {
|
||||
"string": "Cannot remove user from last group"
|
||||
},
|
||||
|
@ -5395,9 +5399,6 @@
|
|||
"context": "button",
|
||||
"string": "Choose file"
|
||||
},
|
||||
"ekXood": {
|
||||
"string": "Unlimited"
|
||||
},
|
||||
"erC44f": {
|
||||
"context": "filters error messages dependencies missing",
|
||||
"string": "Filter requires other filters: {dependencies}"
|
||||
|
@ -5550,10 +5551,6 @@
|
|||
"g/BrOt": {
|
||||
"string": "Url has invalid format"
|
||||
},
|
||||
"g/FRtd": {
|
||||
"context": "table column header, allocated product quantity",
|
||||
"string": "Allocated"
|
||||
},
|
||||
"g1WQlC": {
|
||||
"context": "page title",
|
||||
"string": "Summary"
|
||||
|
@ -5612,10 +5609,6 @@
|
|||
"context": "plugin filters error messages channels",
|
||||
"string": "No channels selected"
|
||||
},
|
||||
"ge/xFX": {
|
||||
"context": "table column header",
|
||||
"string": "Quantity"
|
||||
},
|
||||
"ghGLbJ": {
|
||||
"context": "OrderPayment click&collect shipping method",
|
||||
"string": "click&collect"
|
||||
|
@ -5643,10 +5636,6 @@
|
|||
"context": "page header",
|
||||
"string": "Create Page"
|
||||
},
|
||||
"gtKcPf": {
|
||||
"context": "title",
|
||||
"string": "{zonesCount} / {totalCount} shipping zones"
|
||||
},
|
||||
"gvOzOl": {
|
||||
"string": "Page Title"
|
||||
},
|
||||
|
@ -5890,9 +5879,6 @@
|
|||
"context": "attribute values",
|
||||
"string": "Values"
|
||||
},
|
||||
"jABdx1": {
|
||||
"string": "Active inventory tracking will automatically calculate changes of stock"
|
||||
},
|
||||
"jBu2yj": {
|
||||
"context": "acre-inch unit",
|
||||
"string": "acre-inch"
|
||||
|
@ -7174,10 +7160,18 @@
|
|||
"context": "Header row stock label",
|
||||
"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": {
|
||||
"context": "order total amount",
|
||||
"string": "Total"
|
||||
},
|
||||
"tlGXkh": {
|
||||
"context": "input description",
|
||||
"string": "Unlimited"
|
||||
},
|
||||
"toDL5R": {
|
||||
"context": "order status",
|
||||
"string": "Draft"
|
||||
|
@ -7247,6 +7241,10 @@
|
|||
"context": "app has been removed",
|
||||
"string": "App successfully removed"
|
||||
},
|
||||
"uKlrEk": {
|
||||
"context": "all selected items message",
|
||||
"string": "All available {itemsName} have been selected"
|
||||
},
|
||||
"uMpv1v": {
|
||||
"string": "Fulfillment successfully cancelled"
|
||||
},
|
||||
|
@ -7364,6 +7362,10 @@
|
|||
"v3WWK+": {
|
||||
"string": "Status is invalid"
|
||||
},
|
||||
"v9ILn/": {
|
||||
"context": "button",
|
||||
"string": "CANCEL END DATE"
|
||||
},
|
||||
"vC8vyb": {
|
||||
"context": "enabled status option label",
|
||||
"string": "Enabled"
|
||||
|
@ -7628,9 +7630,6 @@
|
|||
"context": "dialog header",
|
||||
"string": "Assign Variant"
|
||||
},
|
||||
"xB7BTp": {
|
||||
"string": "SKU (Stock Keeping Unit)"
|
||||
},
|
||||
"xHj9Qe": {
|
||||
"context": "gift card settings header",
|
||||
"string": "Gift Cards Settings"
|
||||
|
|
257
schema.graphql
257
schema.graphql
|
@ -493,7 +493,7 @@ type App implements Node & ObjectWithMetadata {
|
|||
"""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.")
|
||||
|
||||
"""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
|
||||
|
||||
"""Homepage of the app."""
|
||||
|
@ -502,14 +502,14 @@ type App implements Node & ObjectWithMetadata {
|
|||
"""Support page for the app."""
|
||||
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.")
|
||||
|
||||
"""Url to iframe with the app."""
|
||||
"""URL to iframe with the app."""
|
||||
appUrl: String
|
||||
|
||||
"""
|
||||
Url to manifest used during app's installation.
|
||||
URL to manifest used during app's installation.
|
||||
|
||||
Added in Saleor 3.5.
|
||||
"""
|
||||
|
@ -1711,6 +1711,29 @@ input AttributeValueCreateInput {
|
|||
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.
|
||||
|
||||
|
@ -1724,6 +1747,29 @@ type AttributeValueDelete {
|
|||
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 {
|
||||
search: String
|
||||
ids: [ID!]
|
||||
|
@ -1871,6 +1917,29 @@ input AttributeValueUpdateInput {
|
|||
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 {
|
||||
"""ID of the selected attribute."""
|
||||
id: ID
|
||||
|
@ -2373,6 +2442,15 @@ type Channel implements Node {
|
|||
Added in Saleor 3.1.
|
||||
"""
|
||||
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: 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: 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.
|
||||
|
||||
Added in Saleor 3.1.
|
||||
|
||||
Note: this API is currently in Feature Preview and can be subject to changes at later point.
|
||||
"""
|
||||
defaultCountry: CountryCode!
|
||||
|
||||
"""List of shipping zones to assign to the channel."""
|
||||
addShippingZones: [ID!]
|
||||
}
|
||||
|
||||
type ChannelCreated implements Event {
|
||||
|
@ -2510,6 +2599,9 @@ type ChannelError {
|
|||
|
||||
"""List of shipping zone IDs which causes the error."""
|
||||
shippingZones: [ID!]
|
||||
|
||||
"""List of warehouses IDs which causes the error."""
|
||||
warehouses: [ID!]
|
||||
}
|
||||
|
||||
"""An enumeration."""
|
||||
|
@ -2563,6 +2655,18 @@ input ChannelUpdateInput {
|
|||
"""isActive flag."""
|
||||
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: String
|
||||
|
||||
|
@ -2576,11 +2680,17 @@ input ChannelUpdateInput {
|
|||
"""
|
||||
defaultCountry: CountryCode
|
||||
|
||||
"""List of shipping zones to assign to the channel."""
|
||||
addShippingZones: [ID!]
|
||||
|
||||
"""List of shipping zones to unassign from the channel."""
|
||||
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 {
|
||||
|
@ -2972,8 +3082,54 @@ type CheckoutLanguageCodeUpdate {
|
|||
}
|
||||
|
||||
"""Represents an item in the checkout."""
|
||||
type CheckoutLine implements Node {
|
||||
type CheckoutLine implements Node & ObjectWithMetadata {
|
||||
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!
|
||||
quantity: Int!
|
||||
|
||||
|
@ -7520,7 +7676,7 @@ type Manifest {
|
|||
permissions: [Permission!]
|
||||
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.")
|
||||
tokenTargetUrl: String
|
||||
|
||||
|
@ -12818,8 +12974,54 @@ type OrderFullyPaid implements Event {
|
|||
}
|
||||
|
||||
"""Represents order line of particular order."""
|
||||
type OrderLine implements Node {
|
||||
type OrderLine implements Node & ObjectWithMetadata {
|
||||
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!
|
||||
variantName: String!
|
||||
productSku: String
|
||||
|
@ -21946,6 +22148,7 @@ input WarehouseFilterInput {
|
|||
search: String
|
||||
ids: [ID!]
|
||||
isPrivate: Boolean
|
||||
channels: [ID!]
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -22247,6 +22450,15 @@ enum WebhookEventTypeAsyncEnum {
|
|||
"""An attribute is 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."""
|
||||
CATEGORY_CREATED
|
||||
|
||||
|
@ -22341,6 +22553,9 @@ enum WebhookEventTypeAsyncEnum {
|
|||
"""A customer account is updated."""
|
||||
CUSTOMER_UPDATED
|
||||
|
||||
"""A customer account is deleted."""
|
||||
CUSTOMER_DELETED
|
||||
|
||||
"""A new collection is created."""
|
||||
COLLECTION_CREATED
|
||||
|
||||
|
@ -22488,6 +22703,15 @@ enum WebhookEventTypeEnum {
|
|||
"""An attribute is 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."""
|
||||
CATEGORY_CREATED
|
||||
|
||||
|
@ -22582,6 +22806,9 @@ enum WebhookEventTypeEnum {
|
|||
"""A customer account is updated."""
|
||||
CUSTOMER_UPDATED
|
||||
|
||||
"""A customer account is deleted."""
|
||||
CUSTOMER_DELETED
|
||||
|
||||
"""A new collection is created."""
|
||||
COLLECTION_CREATED
|
||||
|
||||
|
@ -22730,6 +22957,9 @@ enum WebhookSampleEventTypeEnum {
|
|||
ATTRIBUTE_CREATED
|
||||
ATTRIBUTE_UPDATED
|
||||
ATTRIBUTE_DELETED
|
||||
ATTRIBUTE_VALUE_CREATED
|
||||
ATTRIBUTE_VALUE_UPDATED
|
||||
ATTRIBUTE_VALUE_DELETED
|
||||
CATEGORY_CREATED
|
||||
CATEGORY_UPDATED
|
||||
CATEGORY_DELETED
|
||||
|
@ -22764,6 +22994,7 @@ enum WebhookSampleEventTypeEnum {
|
|||
INVOICE_SENT
|
||||
CUSTOMER_CREATED
|
||||
CUSTOMER_UPDATED
|
||||
CUSTOMER_DELETED
|
||||
COLLECTION_CREATED
|
||||
COLLECTION_UPDATED
|
||||
COLLECTION_DELETED
|
||||
|
|
60
src/channels/components/AssignmentList/AssignmentList.tsx
Normal file
60
src/channels/components/AssignmentList/AssignmentList.tsx
Normal 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;
|
|
@ -1,82 +1,87 @@
|
|||
import { ClickAwayListener } from "@material-ui/core";
|
||||
import { ChannelShippingZones } from "@saleor/channels/pages/ChannelDetailsPage/types";
|
||||
import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import CardAddItemsFooter from "@saleor/products/components/ProductStocks/CardAddItemsFooter";
|
||||
import { mapNodeToChoice } from "@saleor/utils/maps";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { defineMessages } from "react-intl";
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
import useStyles from "./styles";
|
||||
import { ShippingZonesProps } from "./types";
|
||||
import { useStyles } from "./styles";
|
||||
import { AssignItem, AssignmentListProps } from "./types";
|
||||
|
||||
const messages = defineMessages({
|
||||
addZoneTitle: {
|
||||
id: "8CbACQ",
|
||||
defaultMessage: "Add Shipping Zones",
|
||||
description: "add shipping zone title",
|
||||
addItemTitle: {
|
||||
id: "EuOXmr",
|
||||
defaultMessage: "Add {itemsName}",
|
||||
description: "add items title",
|
||||
},
|
||||
});
|
||||
|
||||
type ShippingZonesCardListFooterProps = ShippingZonesProps;
|
||||
type AssignmentListFooterProps = AssignmentListProps;
|
||||
|
||||
const ShippingZonesCardListFooter: React.FC<ShippingZonesCardListFooterProps> = ({
|
||||
shippingZonesChoices,
|
||||
searchShippingZones,
|
||||
fetchMoreShippingZones,
|
||||
addShippingZone,
|
||||
shippingZones,
|
||||
const AssignmentListFooter: React.FC<AssignmentListFooterProps> = ({
|
||||
items,
|
||||
itemsChoices,
|
||||
itemsName,
|
||||
inputName,
|
||||
dataTestId,
|
||||
addItem,
|
||||
searchItems,
|
||||
fetchMoreItems,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles();
|
||||
|
||||
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
|
||||
// to display empty input after adding new zone
|
||||
useEffect(() => {
|
||||
if (shippingZones.length > shippingZonesRef.current.length) {
|
||||
if (items.length > itemsRef.current.length) {
|
||||
setIsChoicesSelectShown(true);
|
||||
}
|
||||
|
||||
shippingZonesRef.current = shippingZones;
|
||||
}, [shippingZones]);
|
||||
itemsRef.current = items;
|
||||
}, [items]);
|
||||
|
||||
const handleChoice = ({ target }) => {
|
||||
setIsChoicesSelectShown(false);
|
||||
addShippingZone(target.value);
|
||||
addItem(target.value);
|
||||
};
|
||||
|
||||
const handleFooterClickAway = () => {
|
||||
setIsChoicesSelectShown(false);
|
||||
searchShippingZones("");
|
||||
searchItems("");
|
||||
};
|
||||
|
||||
return isChoicesSelectShown ? (
|
||||
<ClickAwayListener onClickAway={handleFooterClickAway}>
|
||||
<div className={classes.root}>
|
||||
<SingleAutocompleteSelectField
|
||||
data-test-id="shipping-auto-complete-select"
|
||||
data-test-id={`${dataTestId}-auto-complete-select`}
|
||||
value=""
|
||||
displayValue=""
|
||||
nakedInput
|
||||
name="shippingZone"
|
||||
choices={mapNodeToChoice(shippingZonesChoices)}
|
||||
fetchChoices={searchShippingZones}
|
||||
name={inputName}
|
||||
choices={mapNodeToChoice(itemsChoices)}
|
||||
fetchChoices={searchItems}
|
||||
onChange={handleChoice}
|
||||
{...fetchMoreShippingZones}
|
||||
{...fetchMoreItems}
|
||||
/>
|
||||
</div>
|
||||
</ClickAwayListener>
|
||||
) : (
|
||||
<CardAddItemsFooter
|
||||
onAdd={() => setIsChoicesSelectShown(true)}
|
||||
title={messages.addZoneTitle}
|
||||
title={intl.formatMessage(messages.addItemTitle, {
|
||||
itemsName,
|
||||
})}
|
||||
testIds={{
|
||||
link: "add-shipping-zone-link",
|
||||
button: "add-shipping-zone-button",
|
||||
link: `${dataTestId}-add-link`,
|
||||
button: `${dataTestId}-add-button`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingZonesCardListFooter;
|
||||
export default AssignmentListFooter;
|
|
@ -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;
|
|
@ -1,21 +1,18 @@
|
|||
import { Divider, Typography } from "@material-ui/core";
|
||||
import { ChannelShippingZone } from "@saleor/channels/pages/ChannelDetailsPage/types";
|
||||
import DeletableItem from "@saleor/components/DeletableItem";
|
||||
import React from "react";
|
||||
|
||||
import useStyles from "./styles";
|
||||
import { useStyles } from "./styles";
|
||||
import { AssignItem } from "./types";
|
||||
|
||||
interface ShippingZoneItemProps {
|
||||
zone: ChannelShippingZone;
|
||||
interface ItemProps {
|
||||
item: AssignItem;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
const ShippingZoneItem: React.FC<ShippingZoneItemProps> = ({
|
||||
zone,
|
||||
onDelete,
|
||||
}) => {
|
||||
const { id, name } = zone;
|
||||
const classes = useStyles({});
|
||||
const Item: React.FC<ItemProps> = ({ item, onDelete }) => {
|
||||
const { id, name } = item;
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -28,4 +25,4 @@ const ShippingZoneItem: React.FC<ShippingZoneItemProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default ShippingZoneItem;
|
||||
export default Item;
|
2
src/channels/components/AssignmentList/index.tsx
Normal file
2
src/channels/components/AssignmentList/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./AssignmentList";
|
||||
export { default } from "./AssignmentList";
|
73
src/channels/components/AssignmentList/styles.ts
Normal file
73
src/channels/components/AssignmentList/styles.ts
Normal 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" },
|
||||
);
|
18
src/channels/components/AssignmentList/types.ts
Normal file
18
src/channels/components/AssignmentList/types.ts
Normal 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;
|
||||
}
|
|
@ -12,6 +12,8 @@ const props: ChannelFormProps = {
|
|||
currencyCode: "euro",
|
||||
shippingZonesIdsToAdd: [],
|
||||
shippingZonesIdsToRemove: [],
|
||||
warehousesIdsToAdd: [],
|
||||
warehousesIdsToRemove: [],
|
||||
name: "Test",
|
||||
slug: "test",
|
||||
defaultCountry: CountryCode.PL,
|
||||
|
|
|
@ -29,6 +29,8 @@ export interface FormData {
|
|||
slug: string;
|
||||
shippingZonesIdsToAdd: string[];
|
||||
shippingZonesIdsToRemove: string[];
|
||||
warehousesIdsToAdd: string[];
|
||||
warehousesIdsToRemove: string[];
|
||||
defaultCountry: CountryCode;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import CommonDecorator from "@saleor/storybook/Decorator";
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ShippingZonesCard from "./ShippingZonesCard";
|
||||
import ShippingZones from "./ShippingZones";
|
||||
|
||||
const shippingZones = [
|
||||
{
|
||||
|
@ -32,11 +32,11 @@ const baseProps = {
|
|||
shippingZonesChoices: shippingZones as ChannelShippingZones,
|
||||
};
|
||||
|
||||
storiesOf("Shipping zones card", module)
|
||||
storiesOf("Shipping zones", module)
|
||||
.addDecorator(CommonDecorator)
|
||||
.add("with no options selected", () => <ShippingZonesCard {...baseProps} />)
|
||||
.add("with no options selected", () => <ShippingZones {...baseProps} />)
|
||||
.add("with options selected", () => (
|
||||
<ShippingZonesCard
|
||||
<ShippingZones
|
||||
{...baseProps}
|
||||
shippingZones={shippingZones as ChannelShippingZones}
|
||||
/>
|
63
src/channels/components/ShippingZones/ShippingZones.tsx
Normal file
63
src/channels/components/ShippingZones/ShippingZones.tsx
Normal 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;
|
2
src/channels/components/ShippingZones/index.tsx
Normal file
2
src/channels/components/ShippingZones/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ShippingZones";
|
||||
export { default } from "./ShippingZones";
|
|
@ -1,25 +1,5 @@
|
|||
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(
|
||||
theme => ({
|
||||
container: {
|
||||
|
@ -33,7 +13,7 @@ const useStyles = makeStyles(
|
|||
paddingRight: theme.spacing(1),
|
||||
},
|
||||
}),
|
||||
{ name: "ShippingZonesCard" },
|
||||
{ name: "ShippingZones" },
|
||||
);
|
||||
|
||||
export default useStyles;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./ShippingZonesCard";
|
||||
export { default } from "./ShippingZonesCard";
|
|
@ -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"]>;
|
||||
}
|
40
src/channels/components/Warehouses/Warehouses.stories.tsx
Normal file
40
src/channels/components/Warehouses/Warehouses.stories.tsx
Normal 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} />
|
||||
));
|
62
src/channels/components/Warehouses/Warehouses.tsx
Normal file
62
src/channels/components/Warehouses/Warehouses.tsx
Normal 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;
|
2
src/channels/components/Warehouses/index.tsx
Normal file
2
src/channels/components/Warehouses/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./Warehouses";
|
||||
export { default } from "./Warehouses";
|
19
src/channels/components/Warehouses/styles.ts
Normal file
19
src/channels/components/Warehouses/styles.ts
Normal 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;
|
|
@ -22,6 +22,8 @@ const props: ChannelDetailsPageProps<ChannelErrorFragment[]> = {
|
|||
updateChannelStatus: () => undefined,
|
||||
searchShippingZones: () => undefined,
|
||||
searchShippingZonesData: undefined,
|
||||
searchWarehouses: () => undefined,
|
||||
searchWarehousesData: undefined,
|
||||
countries: countries.map(({ name, code }) => ({
|
||||
code,
|
||||
country: name,
|
||||
|
@ -45,6 +47,24 @@ const props: ChannelDetailsPageProps<ChannelErrorFragment[]> = {
|
|||
onFetchMore: () => undefined,
|
||||
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)
|
||||
|
|
|
@ -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 CardSpacer from "@saleor/components/CardSpacer";
|
||||
import Form from "@saleor/components/Form";
|
||||
|
@ -11,6 +12,7 @@ import {
|
|||
CountryCode,
|
||||
CountryFragment,
|
||||
SearchShippingZonesQuery,
|
||||
SearchWarehousesQuery,
|
||||
} from "@saleor/graphql";
|
||||
import { SearchData } from "@saleor/hooks/makeTopLevelSearch";
|
||||
import { getParsedSearchData } from "@saleor/hooks/makeTopLevelSearch/utils";
|
||||
|
@ -29,7 +31,7 @@ import React, { useState } from "react";
|
|||
|
||||
import { ChannelForm, FormData } from "../../components/ChannelForm";
|
||||
import { ChannelStatus } from "../../components/ChannelStatus/ChannelStatus";
|
||||
import { ChannelShippingZones } from "./types";
|
||||
import { ChannelShippingZones, ChannelWarehouses } from "./types";
|
||||
import { getUpdatedIdsWithNewId, getUpdatedIdsWithoutNewId } from "./utils";
|
||||
|
||||
export interface ChannelDetailsPageProps<TErrors> {
|
||||
|
@ -42,11 +44,15 @@ export interface ChannelDetailsPageProps<TErrors> {
|
|||
searchShippingZonesData?: SearchData;
|
||||
fetchMoreShippingZones: FetchMoreProps;
|
||||
channelShippingZones?: ChannelShippingZones;
|
||||
searchWarehousesData?: SearchData;
|
||||
fetchMoreWarehouses: FetchMoreProps;
|
||||
channelWarehouses?: ChannelWarehouses;
|
||||
countries: CountryFragment[];
|
||||
onDelete?: () => void;
|
||||
onSubmit: (data: FormData) => SubmitPromise<TErrors[]>;
|
||||
updateChannelStatus?: () => void;
|
||||
searchShippingZones: (query: string) => void;
|
||||
searchWarehouses: (query: string) => void;
|
||||
}
|
||||
|
||||
const ChannelDetailsPage = function<TErrors>({
|
||||
|
@ -62,8 +68,12 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
searchShippingZones,
|
||||
searchShippingZonesData,
|
||||
fetchMoreShippingZones,
|
||||
countries,
|
||||
channelShippingZones = [],
|
||||
searchWarehouses,
|
||||
searchWarehousesData,
|
||||
fetchMoreWarehouses,
|
||||
channelWarehouses = [],
|
||||
countries,
|
||||
}: ChannelDetailsPageProps<TErrors>) {
|
||||
const navigate = useNavigator();
|
||||
|
||||
|
@ -76,6 +86,9 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
const [shippingZonesToDisplay, setShippingZonesToDisplay] = useStateFromProps<
|
||||
ChannelShippingZones
|
||||
>(channelShippingZones);
|
||||
const [warehousesToDisplay, setWarehousesToDisplay] = useStateFromProps<
|
||||
ChannelWarehouses
|
||||
>(channelWarehouses);
|
||||
|
||||
const countryChoices = mapCountriesToChoices(countries || []);
|
||||
|
||||
|
@ -86,6 +99,8 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
slug: "",
|
||||
shippingZonesIdsToAdd: [],
|
||||
shippingZonesIdsToRemove: [],
|
||||
warehousesIdsToAdd: [],
|
||||
warehousesIdsToRemove: [],
|
||||
defaultCountry: (defaultCountry?.code || "") as CountryCode,
|
||||
...formData,
|
||||
};
|
||||
|
@ -96,6 +111,12 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
!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 isValid =
|
||||
!!data.name &&
|
||||
|
@ -114,7 +135,7 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
initial={initialData}
|
||||
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
|
||||
>
|
||||
{({ change, data, submit, set, isSaveDisabled }) => {
|
||||
{({ change, data, submit, set, isSaveDisabled, triggerChange }) => {
|
||||
const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler(
|
||||
change,
|
||||
setSelectedCurrencyCode,
|
||||
|
@ -127,6 +148,8 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
);
|
||||
|
||||
const addShippingZone = (zoneId: string) => {
|
||||
triggerChange();
|
||||
|
||||
set({
|
||||
...data,
|
||||
shippingZonesIdsToRemove: getUpdatedIdsWithoutNewId(
|
||||
|
@ -148,6 +171,8 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
};
|
||||
|
||||
const removeShippingZone = (zoneId: string) => {
|
||||
triggerChange();
|
||||
|
||||
set({
|
||||
...data,
|
||||
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 (
|
||||
<>
|
||||
<Grid>
|
||||
|
@ -193,7 +261,7 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
<CardSpacer />
|
||||
</>
|
||||
)}
|
||||
<ShippingZonesCard
|
||||
<ShippingZones
|
||||
shippingZonesChoices={getFilteredShippingZonesChoices()}
|
||||
shippingZones={shippingZonesToDisplay}
|
||||
addShippingZone={addShippingZone}
|
||||
|
@ -201,6 +269,15 @@ const ChannelDetailsPage = function<TErrors>({
|
|||
searchShippingZones={searchShippingZones}
|
||||
fetchMoreShippingZones={fetchMoreShippingZones}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Warehouses
|
||||
warehousesChoices={getFilteredWarehousesChoices()}
|
||||
warehouses={warehousesToDisplay}
|
||||
addWarehouse={addWarehouse}
|
||||
removeWarehouse={removeWarehouse}
|
||||
searchWarehouses={searchWarehouses}
|
||||
fetchMoreWarehouses={fetchMoreWarehouses}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Savebar
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { ChannelShippingZonesQuery } from "@saleor/graphql";
|
||||
import {
|
||||
ChannelShippingZonesQuery,
|
||||
ChannelWarehousesQuery,
|
||||
} from "@saleor/graphql";
|
||||
import { RelayToFlat } from "@saleor/types";
|
||||
|
||||
export type ChannelShippingZones = RelayToFlat<
|
||||
|
@ -6,3 +9,9 @@ export type ChannelShippingZones = RelayToFlat<
|
|||
>;
|
||||
|
||||
export type ChannelShippingZone = ChannelShippingZones[0];
|
||||
|
||||
export type ChannelWarehouses = RelayToFlat<
|
||||
ChannelWarehousesQuery["warehouses"]
|
||||
>;
|
||||
|
||||
export type ChannelWarehouse = ChannelWarehouses[0];
|
||||
|
|
|
@ -16,6 +16,7 @@ import useShop from "@saleor/hooks/useShop";
|
|||
import { sectionNames } from "@saleor/intl";
|
||||
import { extractMutationErrors } from "@saleor/misc";
|
||||
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
|
||||
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
|
||||
import currencyCodes from "currency-codes";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
@ -44,6 +45,8 @@ export const ChannelCreateView = ({}) => {
|
|||
const handleSubmit = ({
|
||||
shippingZonesIdsToAdd,
|
||||
shippingZonesIdsToRemove,
|
||||
warehousesIdsToAdd,
|
||||
warehousesIdsToRemove,
|
||||
currencyCode,
|
||||
...rest
|
||||
}: FormData) =>
|
||||
|
@ -54,6 +57,7 @@ export const ChannelCreateView = ({}) => {
|
|||
...rest,
|
||||
currencyCode: currencyCode.toUpperCase(),
|
||||
addShippingZones: shippingZonesIdsToAdd,
|
||||
addWarehouses: warehousesIdsToAdd,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
@ -67,6 +71,14 @@ export const ChannelCreateView = ({}) => {
|
|||
variables: DEFAULT_INITIAL_SEARCH_DATA,
|
||||
});
|
||||
|
||||
const {
|
||||
loadMore: fetchMoreWarehouses,
|
||||
search: searchWarehouses,
|
||||
result: searchWarehousesResult,
|
||||
} = useWarehouseSearch({
|
||||
variables: DEFAULT_INITIAL_SEARCH_DATA,
|
||||
});
|
||||
|
||||
const currencyCodeChoices = currencyCodes.data.map(currencyData => ({
|
||||
label: intl.formatMessage(
|
||||
{
|
||||
|
@ -109,6 +121,12 @@ export const ChannelCreateView = ({}) => {
|
|||
searchShippingZonesResult,
|
||||
fetchMoreShippingZones,
|
||||
)}
|
||||
searchWarehouses={searchWarehouses}
|
||||
searchWarehousesData={searchWarehousesResult.data}
|
||||
fetchMoreWarehouses={getSearchFetchMoreProps(
|
||||
searchWarehousesResult,
|
||||
fetchMoreWarehouses,
|
||||
)}
|
||||
disabled={createChannelOpts.loading}
|
||||
errors={createChannelOpts?.data?.channelCreate?.errors || []}
|
||||
currencyCodes={currencyCodeChoices}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
useChannelShippingZonesQuery,
|
||||
useChannelsQuery,
|
||||
useChannelUpdateMutation,
|
||||
useChannelWarehousesQuery,
|
||||
} from "@saleor/graphql";
|
||||
import { getSearchFetchMoreProps } from "@saleor/hooks/makeTopLevelSearch/utils";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
|
@ -26,6 +27,7 @@ import useShop from "@saleor/hooks/useShop";
|
|||
import { sectionNames } from "@saleor/intl";
|
||||
import { extractMutationErrors } from "@saleor/misc";
|
||||
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
|
||||
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
|
||||
import getChannelsErrorMessage from "@saleor/utils/errors/channels";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import React from "react";
|
||||
|
@ -103,6 +105,8 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
|||
slug,
|
||||
shippingZonesIdsToRemove,
|
||||
shippingZonesIdsToAdd,
|
||||
warehousesIdsToRemove,
|
||||
warehousesIdsToAdd,
|
||||
defaultCountry,
|
||||
}: FormData) =>
|
||||
extractMutationErrors(
|
||||
|
@ -115,6 +119,8 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
|||
defaultCountry,
|
||||
addShippingZones: shippingZonesIdsToAdd,
|
||||
removeShippingZones: shippingZonesIdsToRemove,
|
||||
addWarehouses: warehousesIdsToAdd,
|
||||
removeWarehouses: warehousesIdsToRemove,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
@ -176,6 +182,25 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
|||
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 (
|
||||
<>
|
||||
<WindowTitle
|
||||
|
@ -200,9 +225,21 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
|||
searchShippingZonesResult,
|
||||
fetchMoreShippingZones,
|
||||
)}
|
||||
channelWarehouses={channelWarehousesData?.warehouses?.edges?.map(
|
||||
({ node }) => node,
|
||||
)}
|
||||
searchWarehouses={searchWarehouses}
|
||||
searchWarehousesData={searchWarehousesResult.data}
|
||||
fetchMoreWarehouses={getSearchFetchMoreProps(
|
||||
searchWarehousesResult,
|
||||
fetchMoreWarehouses,
|
||||
)}
|
||||
channel={data?.channel}
|
||||
disabled={
|
||||
updateChannelOpts.loading || loading || channelsShippingZonesLoading
|
||||
updateChannelOpts.loading ||
|
||||
loading ||
|
||||
channelsShippingZonesLoading ||
|
||||
channelsWarehousesLoading
|
||||
}
|
||||
disabledStatus={
|
||||
activateChannelOpts.loading || deactivateChannelOpts.loading
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
"AttributeCreated",
|
||||
"AttributeDeleted",
|
||||
"AttributeUpdated",
|
||||
"AttributeValueCreated",
|
||||
"AttributeValueDeleted",
|
||||
"AttributeValueUpdated",
|
||||
"CategoryCreated",
|
||||
"CategoryDeleted",
|
||||
"CategoryUpdated",
|
||||
|
@ -195,6 +198,7 @@
|
|||
"Attribute",
|
||||
"Category",
|
||||
"Checkout",
|
||||
"CheckoutLine",
|
||||
"Collection",
|
||||
"DigitalContent",
|
||||
"Fulfillment",
|
||||
|
@ -203,6 +207,7 @@
|
|||
"Menu",
|
||||
"MenuItem",
|
||||
"Order",
|
||||
"OrderLine",
|
||||
"Page",
|
||||
"PageType",
|
||||
"Payment",
|
||||
|
|
|
@ -13495,6 +13495,7 @@ export const SearchWarehousesDocument = gql`
|
|||
sortBy: {direction: ASC, field: NAME}
|
||||
filter: {search: $query}
|
||||
) {
|
||||
totalCount
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
|
@ -16343,6 +16344,46 @@ export function useWarehouseDetailsLazyQuery(baseOptions?: ApolloReactHooks.Lazy
|
|||
export type WarehouseDetailsQueryHookResult = ReturnType<typeof useWarehouseDetailsQuery>;
|
||||
export type WarehouseDetailsLazyQueryHookResult = ReturnType<typeof useWarehouseDetailsLazyQuery>;
|
||||
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`
|
||||
mutation WebhookCreate($input: WebhookCreateInput!) {
|
||||
webhookCreate(input: $input) {
|
||||
|
|
|
@ -518,6 +518,14 @@ export type AttributeValueCreateFieldPolicy = {
|
|||
errors?: 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 AttributeValueDeleteFieldPolicy = {
|
||||
attribute?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
|
@ -525,6 +533,14 @@ export type AttributeValueDeleteFieldPolicy = {
|
|||
errors?: 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 AttributeValueTranslatableContentFieldPolicy = {
|
||||
id?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
|
@ -555,6 +571,14 @@ export type AttributeValueUpdateFieldPolicy = {
|
|||
errors?: 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 BulkProductErrorFieldPolicy = {
|
||||
field?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
|
@ -684,7 +708,7 @@ export type CategoryUpdatedFieldPolicy = {
|
|||
recipient?: 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 = {
|
||||
id?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
name?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
|
@ -692,7 +716,8 @@ export type ChannelFieldPolicy = {
|
|||
currencyCode?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
slug?: 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 ChannelActivateFieldPolicy = {
|
||||
|
@ -734,12 +759,13 @@ export type ChannelDeletedFieldPolicy = {
|
|||
recipient?: 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 = {
|
||||
field?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
message?: 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 ChannelStatusChangedFieldPolicy = {
|
||||
|
@ -886,9 +912,15 @@ export type CheckoutLanguageCodeUpdateFieldPolicy = {
|
|||
checkoutErrors?: 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 = {
|
||||
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>,
|
||||
quantity?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
unitPrice?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
|
@ -2736,9 +2768,15 @@ export type OrderFullyPaidFieldPolicy = {
|
|||
recipient?: 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 = {
|
||||
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>,
|
||||
variantName?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
productSku?: FieldPolicy<any> | FieldReadFunction<any>,
|
||||
|
@ -5271,10 +5309,18 @@ export type StrictTypedTypePolicies = {
|
|||
keyFields?: false | AttributeValueCreateKeySpecifier | (() => undefined | AttributeValueCreateKeySpecifier),
|
||||
fields?: AttributeValueCreateFieldPolicy,
|
||||
},
|
||||
AttributeValueCreated?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | AttributeValueCreatedKeySpecifier | (() => undefined | AttributeValueCreatedKeySpecifier),
|
||||
fields?: AttributeValueCreatedFieldPolicy,
|
||||
},
|
||||
AttributeValueDelete?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | AttributeValueDeleteKeySpecifier | (() => undefined | AttributeValueDeleteKeySpecifier),
|
||||
fields?: AttributeValueDeleteFieldPolicy,
|
||||
},
|
||||
AttributeValueDeleted?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | AttributeValueDeletedKeySpecifier | (() => undefined | AttributeValueDeletedKeySpecifier),
|
||||
fields?: AttributeValueDeletedFieldPolicy,
|
||||
},
|
||||
AttributeValueTranslatableContent?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | AttributeValueTranslatableContentKeySpecifier | (() => undefined | AttributeValueTranslatableContentKeySpecifier),
|
||||
fields?: AttributeValueTranslatableContentFieldPolicy,
|
||||
|
@ -5291,6 +5337,10 @@ export type StrictTypedTypePolicies = {
|
|||
keyFields?: false | AttributeValueUpdateKeySpecifier | (() => undefined | AttributeValueUpdateKeySpecifier),
|
||||
fields?: AttributeValueUpdateFieldPolicy,
|
||||
},
|
||||
AttributeValueUpdated?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | AttributeValueUpdatedKeySpecifier | (() => undefined | AttributeValueUpdatedKeySpecifier),
|
||||
fields?: AttributeValueUpdatedFieldPolicy,
|
||||
},
|
||||
BulkProductError?: Omit<TypePolicy, "fields" | "keyFields"> & {
|
||||
keyFields?: false | BulkProductErrorKeySpecifier | (() => undefined | BulkProductErrorKeySpecifier),
|
||||
fields?: BulkProductErrorFieldPolicy,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -447,6 +447,11 @@ export const sectionNames = defineMessages({
|
|||
defaultMessage: "Shipping Methods",
|
||||
description: "shipping section name",
|
||||
},
|
||||
shippingZones: {
|
||||
id: "V1MytH",
|
||||
defaultMessage: "Shipping Zones",
|
||||
description: "shipping zones section name",
|
||||
},
|
||||
siteSettings: {
|
||||
id: "viFkCw",
|
||||
defaultMessage: "Site Settings",
|
||||
|
|
|
@ -2319,6 +2319,7 @@ export const shopOrderSettings: ShopOrderSettingsFragment = {
|
|||
};
|
||||
|
||||
export const warehouseSearch: SearchWarehousesQuery["search"] = {
|
||||
totalCount: 20,
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
|
|
|
@ -2,7 +2,6 @@ import AddIcon from "@material-ui/icons/Add";
|
|||
import Link from "@saleor/components/Link";
|
||||
import { IconButton, makeStyles } from "@saleor/macaw-ui";
|
||||
import React, { MutableRefObject } from "react";
|
||||
import { MessageDescriptor, useIntl } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
|
@ -21,7 +20,7 @@ const useStyles = makeStyles(
|
|||
);
|
||||
|
||||
interface CardAddItemsFooterProps {
|
||||
title: MessageDescriptor;
|
||||
title: string;
|
||||
onAdd: () => void;
|
||||
testIds: {
|
||||
link: string;
|
||||
|
@ -37,13 +36,12 @@ const CardAddItemsFooter: React.FC<CardAddItemsFooterProps> = ({
|
|||
ref,
|
||||
children,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<div className={classes.container} ref={ref}>
|
||||
<Link data-test-id={testIds.link} onClick={onAdd}>
|
||||
{intl.formatMessage(title)}
|
||||
{title}
|
||||
</Link>
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
|
|
|
@ -28,14 +28,8 @@ import PreviewPill from "@saleor/components/PreviewPill";
|
|||
import { ProductErrorFragment, WarehouseFragment } from "@saleor/graphql";
|
||||
import { FormChange, FormErrors } from "@saleor/hooks/useForm";
|
||||
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
|
||||
import {
|
||||
Button,
|
||||
DeleteIcon,
|
||||
IconButton,
|
||||
ICONBUTTON_SIZE,
|
||||
makeStyles,
|
||||
PlusIcon,
|
||||
} from "@saleor/macaw-ui";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { Button, DeleteIcon, IconButton, PlusIcon } from "@saleor/macaw-ui";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||
import createNonNegativeValueChangeHandler from "@saleor/utils/handlers/nonNegativeValueChangeHandler";
|
||||
|
@ -46,6 +40,8 @@ import { ProductCreateData } from "../ProductCreatePage";
|
|||
import { ProductUpdateSubmitData } from "../ProductUpdatePage/form";
|
||||
import { ProductVariantCreateData } from "../ProductVariantCreatePage/form";
|
||||
import { ProductVariantUpdateData } from "../ProductVariantPage/form";
|
||||
import { messages } from "./messages";
|
||||
import { useStyles } from "./styles";
|
||||
|
||||
export interface ProductStockFormsetData {
|
||||
quantityAllocated: number;
|
||||
|
@ -90,98 +86,6 @@ export interface ProductStocksProps {
|
|||
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> = ({
|
||||
data,
|
||||
disabled,
|
||||
|
@ -200,7 +104,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
onWarehouseStockDelete,
|
||||
onWarehouseConfigure,
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const classes = useStyles();
|
||||
const intl = useIntl();
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
const [isExpanded, setExpansionState] = React.useState(false);
|
||||
|
@ -218,13 +122,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
id: "4qe6hO",
|
||||
defaultMessage: "Inventory",
|
||||
description: "product stock, section header",
|
||||
})}
|
||||
/>
|
||||
<CardTitle title={intl.formatMessage(messages.title)} />
|
||||
<CardContent>
|
||||
<div className={classes.skuInputContainer}>
|
||||
<TextField
|
||||
|
@ -232,10 +130,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
error={!!formErrors.sku}
|
||||
fullWidth
|
||||
helperText={getProductErrorMessage(formErrors.sku, intl)}
|
||||
label={intl.formatMessage({
|
||||
id: "xB7BTp",
|
||||
defaultMessage: "SKU (Stock Keeping Unit)",
|
||||
})}
|
||||
label={intl.formatMessage(messages.sku)}
|
||||
name="sku"
|
||||
onChange={onFormDataChange}
|
||||
value={data.sku}
|
||||
|
@ -252,11 +147,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
disabled={disabled}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="eAFU/E"
|
||||
defaultMessage="Variant currently in preorder"
|
||||
description="product inventory, checkbox"
|
||||
/>
|
||||
<FormattedMessage {...messages.variantInPreorder} />
|
||||
<PreviewPill className={classes.preview} />
|
||||
</>
|
||||
}
|
||||
|
@ -272,16 +163,9 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
disabled={disabled}
|
||||
label={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="TjGYna"
|
||||
defaultMessage="Track Inventory"
|
||||
description="product inventory, checkbox"
|
||||
/>
|
||||
<FormattedMessage {...messages.trackInventory} />
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage
|
||||
id="jABdx1"
|
||||
defaultMessage="Active inventory tracking will automatically calculate changes of stock"
|
||||
/>
|
||||
<FormattedMessage {...messages.trackInventoryDescription} />
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
|
@ -295,14 +179,18 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
<Typography>
|
||||
<div className={classes.quantityHeader}>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="bp/i0x"
|
||||
defaultMessage="Quantity"
|
||||
description="header"
|
||||
/>
|
||||
<FormattedMessage {...messages.quantity} />
|
||||
</span>
|
||||
</div>
|
||||
</Typography>
|
||||
{!productVariantChannelListings?.length && (
|
||||
<>
|
||||
<FormSpacer />
|
||||
<Typography variant="caption">
|
||||
<FormattedMessage {...messages.noChannelWarehousesAllocation} />
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!warehouses?.length && (
|
||||
<Typography
|
||||
|
@ -312,9 +200,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
{hasVariants ? (
|
||||
<>
|
||||
<FormattedMessage
|
||||
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"
|
||||
{...messages.configureWarehouseForVariant}
|
||||
values={{
|
||||
a: chunks => (
|
||||
<Link onClick={onWarehouseConfigure}>{chunks}</Link>
|
||||
|
@ -325,9 +211,7 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
) : (
|
||||
<>
|
||||
<FormattedMessage
|
||||
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"
|
||||
{...messages.configureWarehouseForProduct}
|
||||
values={{
|
||||
a: chunks => (
|
||||
<Link onClick={onWarehouseConfigure}>{chunks}</Link>
|
||||
|
@ -340,149 +224,130 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
)}
|
||||
</CardContent>
|
||||
)}
|
||||
{warehouses?.length > 0 && !data.isPreorder && (
|
||||
<Table>
|
||||
<colgroup>
|
||||
<col className={classes.colName} />
|
||||
<col className={classes.colQuantity} />
|
||||
<col className={classes.colQuantity} />
|
||||
</colgroup>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage
|
||||
id="KTAg0f"
|
||||
defaultMessage="Warehouse Name"
|
||||
description="tabel column header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<FormattedMessage
|
||||
id="g/FRtd"
|
||||
defaultMessage="Allocated"
|
||||
description="table column header, allocated product quantity"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<FormattedMessage
|
||||
id="ge/xFX"
|
||||
defaultMessage="Quantity"
|
||||
description="table column header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(stocks, stock => {
|
||||
const handleQuantityChange = createNonNegativeValueChangeHandler(
|
||||
event => onChange(stock.id, event.target.value),
|
||||
);
|
||||
{productVariantChannelListings?.length > 0 &&
|
||||
warehouses?.length > 0 &&
|
||||
!data.isPreorder && (
|
||||
<Table>
|
||||
<colgroup>
|
||||
<col className={classes.colName} />
|
||||
<col className={classes.colQuantity} />
|
||||
<col className={classes.colQuantity} />
|
||||
</colgroup>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage {...messages.warehouseName} />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<FormattedMessage {...messages.allocated} />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<FormattedMessage {...messages.quantity} />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(stocks, stock => {
|
||||
const handleQuantityChange = createNonNegativeValueChangeHandler(
|
||||
event => onChange(stock.id, event.target.value),
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow key={stock.id}>
|
||||
<TableCell className={classes.colName}>
|
||||
{stock.label}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
{stock.data?.quantityAllocated || 0}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<TextField
|
||||
data-test-id="stock-input"
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
inputProps={{
|
||||
className: classes.input,
|
||||
min: 0,
|
||||
type: "number",
|
||||
}}
|
||||
onChange={handleQuantityChange}
|
||||
value={stock.value}
|
||||
/>
|
||||
return (
|
||||
<TableRow key={stock.id}>
|
||||
<TableCell className={classes.colName}>
|
||||
{stock.label}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
{stock.data?.quantityAllocated || 0}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<TextField
|
||||
data-test-id="stock-input"
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
inputProps={{
|
||||
className: classes.input,
|
||||
min: 0,
|
||||
type: "number",
|
||||
}}
|
||||
onChange={handleQuantityChange}
|
||||
value={stock.value}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction}>
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
color="primary"
|
||||
onClick={() => onWarehouseStockDelete(stock.id)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{warehousesToAssign.length > 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3}>
|
||||
<Typography variant="body2">
|
||||
<FormattedMessage {...messages.assignWarehouse} />
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction}>
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
color="primary"
|
||||
onClick={() => onWarehouseStockDelete(stock.id)}
|
||||
<ClickAwayListener
|
||||
onClickAway={() => setExpansionState(false)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<div ref={anchor}>
|
||||
<IconButton
|
||||
data-test-id="add-warehouse"
|
||||
color="primary"
|
||||
variant="secondary"
|
||||
onClick={() => setExpansionState(!isExpanded)}
|
||||
>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
<Popper
|
||||
className={classes.popper}
|
||||
open={isExpanded}
|
||||
anchorEl={anchor.current}
|
||||
transition
|
||||
placement="top-end"
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin: "right top",
|
||||
}}
|
||||
>
|
||||
<Paper className={classes.paper} elevation={8}>
|
||||
{warehousesToAssign.map(warehouse => (
|
||||
<MenuItem
|
||||
className={classes.menuItem}
|
||||
onClick={() =>
|
||||
onWarehouseStockAdd(warehouse.id)
|
||||
}
|
||||
>
|
||||
{warehouse.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</div>
|
||||
</ClickAwayListener>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{warehousesToAssign.length > 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3}>
|
||||
<Typography variant="body2">
|
||||
<FormattedMessage
|
||||
id="cBHRxx"
|
||||
defaultMessage="Assign Warehouse"
|
||||
description="button"
|
||||
/>
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction}>
|
||||
<ClickAwayListener
|
||||
onClickAway={() => setExpansionState(false)}
|
||||
>
|
||||
<div ref={anchor}>
|
||||
<IconButton
|
||||
data-test-id="add-warehouse"
|
||||
color="primary"
|
||||
variant="secondary"
|
||||
onClick={() => setExpansionState(!isExpanded)}
|
||||
>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
<Popper
|
||||
className={classes.popper}
|
||||
open={isExpanded}
|
||||
anchorEl={anchor.current}
|
||||
transition
|
||||
placement="top-end"
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin: "right top",
|
||||
}}
|
||||
>
|
||||
<Paper className={classes.paper} elevation={8}>
|
||||
{warehousesToAssign.map(warehouse => (
|
||||
<MenuItem
|
||||
className={classes.menuItem}
|
||||
onClick={() =>
|
||||
onWarehouseStockAdd(warehouse.id)
|
||||
}
|
||||
>
|
||||
{warehouse.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</div>
|
||||
</ClickAwayListener>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
{data.isPreorder && (
|
||||
<CardContent>
|
||||
<Typography variant="caption" className={classes.caption}>
|
||||
{intl.formatMessage({
|
||||
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",
|
||||
})}
|
||||
<FormattedMessage {...messages.preorderEndDateSetup} />
|
||||
</Typography>
|
||||
|
||||
{data.hasPreorderEndDate && (
|
||||
|
@ -519,22 +384,11 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
}
|
||||
>
|
||||
{data.hasPreorderEndDate
|
||||
? intl.formatMessage({
|
||||
id: "2qJc9y",
|
||||
defaultMessage: "CANCEL END DATE",
|
||||
})
|
||||
: intl.formatMessage({
|
||||
id: "7Ii5ZQ",
|
||||
defaultMessage: "SETUP END DATE",
|
||||
})}
|
||||
? intl.formatMessage(messages.endDateCancel)
|
||||
: intl.formatMessage(messages.endDateSetup)}
|
||||
</Button>
|
||||
<Typography variant="caption" className={classes.preorderLimitInfo}>
|
||||
{intl.formatMessage({
|
||||
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",
|
||||
})}
|
||||
<FormattedMessage {...messages.preorderProductsAvailability} />
|
||||
</Typography>
|
||||
<div className={classes.thresholdRow}>
|
||||
<TextField
|
||||
|
@ -543,15 +397,10 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
}}
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
helperText={intl.formatMessage({
|
||||
id: "NcY4ph",
|
||||
defaultMessage:
|
||||
"Threshold that cannot be exceeded even if per channel thresholds are still available",
|
||||
})}
|
||||
label={intl.formatMessage({
|
||||
id: "RJ5QxE",
|
||||
defaultMessage: "Global threshold",
|
||||
})}
|
||||
helperText={intl.formatMessage(
|
||||
messages.preorderTresholdDescription,
|
||||
)}
|
||||
label={intl.formatMessage(messages.preorderTresholdLabel)}
|
||||
name="globalThreshold"
|
||||
onChange={onThresholdChange}
|
||||
value={data.globalThreshold ?? ""}
|
||||
|
@ -563,19 +412,10 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
className={classes.preorderItemsLeftCount}
|
||||
>
|
||||
{data.globalThreshold
|
||||
? intl.formatMessage(
|
||||
{
|
||||
id: "7wkGxW",
|
||||
defaultMessage: "{unitsLeft} units left",
|
||||
description: "app has been installed",
|
||||
},
|
||||
{ unitsLeft },
|
||||
)
|
||||
: intl.formatMessage({
|
||||
id: "CEavJt",
|
||||
defaultMessage: "Unlimited",
|
||||
description: "section header",
|
||||
})}
|
||||
? intl.formatMessage(messages.preorderTresholdUnitsLeft, {
|
||||
unitsLeft,
|
||||
})
|
||||
: intl.formatMessage(messages.preorderTresholdUnlimited)}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
|
@ -592,25 +432,13 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage
|
||||
id="JyQEHU"
|
||||
defaultMessage="Channels"
|
||||
description="tabel column header"
|
||||
/>
|
||||
<FormattedMessage {...sectionNames.channels} />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSoldUnits}>
|
||||
<FormattedMessage
|
||||
id="HcQEUk"
|
||||
defaultMessage="Sold units"
|
||||
description="table column header, sold units preorder quantity"
|
||||
/>
|
||||
<FormattedMessage {...messages.soldUnits} />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colThreshold}>
|
||||
<FormattedMessage
|
||||
id="MNZY28"
|
||||
defaultMessage="Channel threshold"
|
||||
description="table column header"
|
||||
/>
|
||||
<FormattedMessage {...messages.channelTreshold} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
@ -638,10 +466,9 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
min: 0,
|
||||
type: "number",
|
||||
}}
|
||||
placeholder={intl.formatMessage({
|
||||
id: "ekXood",
|
||||
defaultMessage: "Unlimited",
|
||||
})}
|
||||
placeholder={intl.formatMessage(
|
||||
messages.preorderTresholdUnlimited,
|
||||
)}
|
||||
onChange={e => {
|
||||
onVariantChannelListingChange(listing.id, {
|
||||
costPrice: listing.costPrice,
|
||||
|
|
121
src/products/components/ProductStocks/messages.ts
Normal file
121
src/products/components/ProductStocks/messages.ts
Normal 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",
|
||||
},
|
||||
});
|
93
src/products/components/ProductStocks/styles.ts
Normal file
93
src/products/components/ProductStocks/styles.ts
Normal 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",
|
||||
},
|
||||
);
|
|
@ -112,12 +112,6 @@ export const ProductCreateView: React.FC<ProductCreateProps> = ({ params }) => {
|
|||
result: searchAttributeValuesOpts,
|
||||
reset: searchAttributeReset,
|
||||
} = useAttributeValueSearchHandler(DEFAULT_INITIAL_SEARCH_DATA);
|
||||
const warehouses = useWarehouseListQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
first: 50,
|
||||
},
|
||||
});
|
||||
const [updateMetadata] = useUpdateMetadataMutation({});
|
||||
const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({});
|
||||
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) => {
|
||||
notify({
|
||||
status: "success",
|
||||
|
|
|
@ -156,12 +156,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
result: searchAttributeValuesOpts,
|
||||
reset: searchAttributeReset,
|
||||
} = useAttributeValueSearchHandler(DEFAULT_INITIAL_SEARCH_DATA);
|
||||
const warehouses = useWarehouseListQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
first: 50,
|
||||
},
|
||||
});
|
||||
const shop = useShop();
|
||||
const [updateMetadata] = useUpdateMetadataMutation({});
|
||||
const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({});
|
||||
|
@ -324,6 +318,16 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
{ formId: PRODUCT_UPDATE_FORM_ID },
|
||||
);
|
||||
|
||||
const warehouses = useWarehouseListQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
first: 50,
|
||||
filter: {
|
||||
channels: currentChannels.map(channel => channel.id),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [
|
||||
updateChannels,
|
||||
updateChannelsOpts,
|
||||
|
|
|
@ -82,13 +82,6 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
setErrors([]);
|
||||
}, [variantId]);
|
||||
|
||||
const warehouses = useWarehouseListQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
first: 50,
|
||||
},
|
||||
});
|
||||
|
||||
const { data, loading } = useProductVariantDetailsQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
|
@ -196,6 +189,16 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
const variant = data?.productVariant;
|
||||
const channels = createVariantChannels(variant);
|
||||
|
||||
const warehouses = useWarehouseListQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
first: 50,
|
||||
filter: {
|
||||
channels: channels.map(channel => channel.id),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [
|
||||
deactivatePreorder,
|
||||
deactivatePreoderOpts,
|
||||
|
|
|
@ -14,6 +14,7 @@ export const searchWarehouses = gql`
|
|||
sortBy: { direction: ASC, field: NAME }
|
||||
filter: { search: $query }
|
||||
) {
|
||||
totalCount
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
Loading…
Reference in a new issue