Merge pull request #172 from mirumee/add/search-bars

Add search bars
This commit is contained in:
Dominik Żegleń 2019-09-16 17:07:38 +02:00 committed by GitHub
commit 2b0f2933ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
127 changed files with 6869 additions and 1625 deletions

View file

@ -21,3 +21,4 @@ All notable, unreleased changes to this project will be documented in this file.
- UI improvements - #166 by @benekex2 - UI improvements - #166 by @benekex2
- Fix en locale matching - #165 by @dominik-zeglen - Fix en locale matching - #165 by @dominik-zeglen
- Implement the Credential Management API - #158 by @patrys - Implement the Credential Management API - #158 by @patrys
- Add search bars - #172 by @dominik-zeglen

View file

@ -1,6 +1,6 @@
msgid "" msgid ""
msgstr "" msgstr ""
"POT-Creation-Date: 2019-09-10T11:00:36.829Z\n" "POT-Creation-Date: 2019-09-12T15:12:49.543Z\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -455,8 +455,8 @@ msgctxt "button"
msgid "Add staff member" msgid "Add staff member"
msgstr "" msgstr ""
#: build/locale/src/categories/components/CategoryList/CategoryList.json #: build/locale/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.json
#. [src.categories.components.CategoryList.435697837] - button #. [src.categories.components.CategoryUpdatePage.435697837] - button
#. defaultMessage is: #. defaultMessage is:
#. Add subcategory #. Add subcategory
msgctxt "button" msgctxt "button"
@ -627,6 +627,46 @@ msgctxt "tax rate"
msgid "Agricultural supplies" msgid "Agricultural supplies"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/AttributeListPage/AttributeListPage.json
#. [src.attributes.components.AttributeListPage.2417065806] - tab name
#. defaultMessage is:
#. All Attributes
msgctxt "tab name"
msgid "All Attributes"
msgstr ""
#: build/locale/src/categories/components/CategoryListPage/CategoryListPage.json
#. [src.categories.components.CategoryListPage.4294878092] - tab name
#. defaultMessage is:
#. All Categories
msgctxt "tab name"
msgid "All Categories"
msgstr ""
#: build/locale/src/collections/components/CollectionListPage/CollectionListPage.json
#. [src.collections.components.CollectionListPage.1631917001] - tab name
#. defaultMessage is:
#. All Collections
msgctxt "tab name"
msgid "All Collections"
msgstr ""
#: build/locale/src/customers/components/CustomerListPage/CustomerListPage.json
#. [src.customers.components.CustomerListPage.477293306] - tab name
#. defaultMessage is:
#. All Customers
msgctxt "tab name"
msgid "All Customers"
msgstr ""
#: build/locale/src/orders/components/OrderDraftListPage/OrderDraftListPage.json
#. [src.orders.components.OrderDraftListPage.551325728] - tab name
#. defaultMessage is:
#. All Drafts
msgctxt "tab name"
msgid "All Drafts"
msgstr ""
#: build/locale/src/orders/components/OrderListPage/OrderListPage.json #: build/locale/src/orders/components/OrderListPage/OrderListPage.json
#. [src.orders.components.OrderListPage.875489544] - tab name #. [src.orders.components.OrderListPage.875489544] - tab name
#. defaultMessage is: #. defaultMessage is:
@ -643,22 +683,54 @@ msgctxt "section header"
msgid "All Photos" msgid "All Photos"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListPage/ProductListPage.json #: build/locale/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.json
#. [src.products.components.ProductListPage.821159718] - tab name #. [src.productTypes.components.ProductTypeListPage.1776073799] - tab name
#. defaultMessage is:
#. All Product Types
msgctxt "tab name"
msgid "All Product Types"
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.821159718] - tab name
#. defaultMessage is: #. defaultMessage is:
#. All Products #. All Products
msgctxt "tab name" msgctxt "tab name"
msgid "All Products" msgid "All Products"
msgstr "" msgstr ""
#: build/locale/src/categories/components/CategoryList/CategoryList.json #: build/locale/src/discounts/components/SaleListPage/SaleListPage.json
#. [src.categories.components.CategoryList.3229914152] - section header #. [src.discounts.components.SaleListPage.3907768880] - tab name
#. defaultMessage is:
#. All Sales
msgctxt "tab name"
msgid "All Sales"
msgstr ""
#: build/locale/src/staff/components/StaffListPage/StaffListPage.json
#. [src.staff.components.StaffListPage.2852350932] - tab name
#. defaultMessage is:
#. All Staff Members
msgctxt "tab name"
msgid "All Staff Members"
msgstr ""
#: build/locale/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.json
#. [src.categories.components.CategoryUpdatePage.3229914152] - section header
#. defaultMessage is: #. defaultMessage is:
#. All Subcategories #. All Subcategories
msgctxt "section header" msgctxt "section header"
msgid "All Subcategories" msgid "All Subcategories"
msgstr "" msgstr ""
#: build/locale/src/discounts/components/VoucherListPage/VoucherListPage.json
#. [src.discounts.components.VoucherListPage.1112241061] - tab name
#. defaultMessage is:
#. All Vouchers
msgctxt "tab name"
msgid "All Vouchers"
msgstr ""
#: build/locale/src/taxes/components/TaxConfiguration/TaxConfiguration.json #: build/locale/src/taxes/components/TaxConfiguration/TaxConfiguration.json
#. [src.taxes.components.TaxConfiguration.142803418] #. [src.taxes.components.TaxConfiguration.142803418]
#. defaultMessage is: #. defaultMessage is:
@ -815,10 +887,6 @@ msgstr ""
#. [src.categories.views.299584400] #. [src.categories.views.299584400]
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} categories}}? #. Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} categories}}?
#: build/locale/src/categories/views/CategoryList.json
#. [src.categories.views.844574071]
#. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} categories}}?
msgctxt "description" msgctxt "description"
msgid "Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} categories}}?" msgid "Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} categories}}?"
msgstr "" msgstr ""
@ -831,16 +899,24 @@ msgctxt "description"
msgid "Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} products}}?" msgid "Are you sure you want to delete {counter,plural,one{this attribute} other{{displayQuantity} products}}?"
msgstr "" msgstr ""
#: build/locale/src/collections/views/CollectionList.json #: build/locale/src/categories/views/CategoryList/CategoryList.json
#. [src.collections.views.2497542455] #. [src.categories.views.CategoryList.2144707585]
#. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this category} other{{displayQuantity} categories}}?
msgctxt "description"
msgid "Are you sure you want to delete {counter,plural,one{this category} other{{displayQuantity} categories}}?"
msgstr ""
#: build/locale/src/collections/views/CollectionList/CollectionList.json
#. [src.collections.views.CollectionList.2497542455]
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this collection} other{{displayQuantity} collections}}? #. Are you sure you want to delete {counter,plural,one{this collection} other{{displayQuantity} collections}}?
msgctxt "description" msgctxt "description"
msgid "Are you sure you want to delete {counter,plural,one{this collection} other{{displayQuantity} collections}}?" msgid "Are you sure you want to delete {counter,plural,one{this collection} other{{displayQuantity} collections}}?"
msgstr "" msgstr ""
#: build/locale/src/customers/views/CustomerList.json #: build/locale/src/customers/views/CustomerList/CustomerList.json
#. [src.customers.views.409347866] #. [src.customers.views.CustomerList.409347866]
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this customer} other{{displayQuantity} customers}}? #. Are you sure you want to delete {counter,plural,one{this customer} other{{displayQuantity} customers}}?
msgctxt "description" msgctxt "description"
@ -855,8 +931,8 @@ msgctxt "description"
msgid "Are you sure you want to delete {counter,plural,one{this menu} other{{displayQuantity} menus}}?" msgid "Are you sure you want to delete {counter,plural,one{this menu} other{{displayQuantity} menus}}?"
msgstr "" msgstr ""
#: build/locale/src/orders/views/OrderDraftList.json #: build/locale/src/orders/views/OrderDraftList/OrderDraftList.json
#. [src.orders.views.1389231130] - dialog content #. [src.orders.views.OrderDraftList.1389231130] - dialog content
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this order draft} other{{displayQuantity} orderDrafts}}? #. Are you sure you want to delete {counter,plural,one{this order draft} other{{displayQuantity} orderDrafts}}?
msgctxt "dialog content" msgctxt "dialog content"
@ -871,8 +947,8 @@ msgctxt "dialog content"
msgid "Are you sure you want to delete {counter,plural,one{this page} other{{displayQuantity} pages}}?" msgid "Are you sure you want to delete {counter,plural,one{this page} other{{displayQuantity} pages}}?"
msgstr "" msgstr ""
#: build/locale/src/productTypes/views/ProductTypeList.json #: build/locale/src/productTypes/views/ProductTypeList/ProductTypeList.json
#. [src.productTypes.views.2294091098] - dialog content #. [src.productTypes.views.ProductTypeList.2294091098] - dialog content
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this product type} other{{displayQuantity} product types}}? #. Are you sure you want to delete {counter,plural,one{this product type} other{{displayQuantity} product types}}?
msgctxt "dialog content" msgctxt "dialog content"
@ -887,8 +963,8 @@ msgctxt "dialog content"
msgid "Are you sure you want to delete {counter,plural,one{this product} other{{displayQuantity} products}}?" msgid "Are you sure you want to delete {counter,plural,one{this product} other{{displayQuantity} products}}?"
msgstr "" msgstr ""
#: build/locale/src/discounts/views/SaleList.json #: build/locale/src/discounts/views/SaleList/SaleList.json
#. [src.discounts.views.2516361175] - dialog content #. [src.discounts.views.SaleList.2516361175] - dialog content
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this sale} other{{displayQuantity} sales}}? #. Are you sure you want to delete {counter,plural,one{this sale} other{{displayQuantity} sales}}?
msgctxt "dialog content" msgctxt "dialog content"
@ -911,8 +987,8 @@ msgctxt "dialog content"
msgid "Are you sure you want to delete {counter,plural,one{this variant} other{{displayQuantity} variants}}?" msgid "Are you sure you want to delete {counter,plural,one{this variant} other{{displayQuantity} variants}}?"
msgstr "" msgstr ""
#: build/locale/src/discounts/views/VoucherList.json #: build/locale/src/discounts/views/VoucherList/VoucherList.json
#. [src.discounts.views.1791926983] - dialog content #. [src.discounts.views.VoucherList.1791926983] - dialog content
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to delete {counter,plural,one{this voucher} other{{displayQuantity} vouchers}}? #. Are you sure you want to delete {counter,plural,one{this voucher} other{{displayQuantity} vouchers}}?
msgctxt "dialog content" msgctxt "dialog content"
@ -1031,8 +1107,8 @@ msgctxt "description"
msgid "Are you sure you want to mark this order as paid?" msgid "Are you sure you want to mark this order as paid?"
msgstr "" msgstr ""
#: build/locale/src/collections/views/CollectionList.json #: build/locale/src/collections/views/CollectionList/CollectionList.json
#. [src.collections.views.1348793822] #. [src.collections.views.CollectionList.1348793822]
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to publish {counter,plural,one{this collection} other{{displayQuantity} collections}}? #. Are you sure you want to publish {counter,plural,one{this collection} other{{displayQuantity} collections}}?
msgctxt "description" msgctxt "description"
@ -1155,8 +1231,8 @@ msgctxt "dialog content"
msgid "Are you sure you want to unassign {counter,plural,one{this product} other{{displayQuantity} products}}?" msgid "Are you sure you want to unassign {counter,plural,one{this product} other{{displayQuantity} products}}?"
msgstr "" msgstr ""
#: build/locale/src/collections/views/CollectionList.json #: build/locale/src/collections/views/CollectionList/CollectionList.json
#. [src.collections.views.3944356444] #. [src.collections.views.CollectionList.3944356444]
#. defaultMessage is: #. defaultMessage is:
#. Are you sure you want to unpublish {counter,plural,one{this collection} other{{displayQuantity} collections}}? #. Are you sure you want to unpublish {counter,plural,one{this collection} other{{displayQuantity} collections}}?
msgctxt "description" msgctxt "description"
@ -1439,10 +1515,6 @@ msgctxt "description"
msgid "Availability" msgid "Availability"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.2157131639] - product status
#. defaultMessage is:
#. Available
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json #: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.2157131639] - product status #. [src.products.components.ProductListFilter.2157131639] - product status
#. defaultMessage is: #. defaultMessage is:
@ -2319,6 +2391,10 @@ msgstr ""
#. [src.components.FilterBar.2340527467] #. [src.components.FilterBar.2340527467]
#. defaultMessage is: #. defaultMessage is:
#. Custom Filter #. Custom Filter
#: build/locale/src/components/SearchBar/SearchBar.json
#. [src.components.SearchBar.2340527467]
#. defaultMessage is:
#. Custom Filter
msgctxt "description" msgctxt "description"
msgid "Custom Filter" msgid "Custom Filter"
msgstr "" msgstr ""
@ -2571,8 +2647,8 @@ msgctxt "dialog title"
msgid "Delete Collection" msgid "Delete Collection"
msgstr "" msgstr ""
#: build/locale/src/orders/views/OrderDraftList.json #: build/locale/src/orders/views/OrderDraftList/OrderDraftList.json
#. [src.orders.views.1161115149] - dialog header #. [src.orders.views.OrderDraftList.1161115149] - dialog header
#. defaultMessage is: #. defaultMessage is:
#. Delete Order Drafts #. Delete Order Drafts
msgctxt "dialog header" msgctxt "dialog header"
@ -2611,8 +2687,8 @@ msgctxt "dialog header"
msgid "Delete Product Type" msgid "Delete Product Type"
msgstr "" msgstr ""
#: build/locale/src/productTypes/views/ProductTypeList.json #: build/locale/src/productTypes/views/ProductTypeList/ProductTypeList.json
#. [src.productTypes.views.4080551769] - dialog header #. [src.productTypes.views.ProductTypeList.4080551769] - dialog header
#. defaultMessage is: #. defaultMessage is:
#. Delete Product Types #. Delete Product Types
msgctxt "dialog header" msgctxt "dialog header"
@ -2643,8 +2719,8 @@ msgctxt "dialog header"
msgid "Delete Sale" msgid "Delete Sale"
msgstr "" msgstr ""
#: build/locale/src/discounts/views/SaleList.json #: build/locale/src/discounts/views/SaleList/SaleList.json
#. [src.discounts.views.2809303671] - dialog header #. [src.discounts.views.SaleList.2809303671] - dialog header
#. defaultMessage is: #. defaultMessage is:
#. Delete Sales #. Delete Sales
msgctxt "dialog header" msgctxt "dialog header"
@ -2659,6 +2735,10 @@ msgctxt "custom search delete, dialog header"
msgid "Delete Search" msgid "Delete Search"
msgstr "" msgstr ""
#: build/locale/src/components/Filter/FilterSearch.json
#. [src.components.Filter.2173195312] - button
#. defaultMessage is:
#. Delete Search
#: build/locale/src/components/TableFilter/FilterChips.json #: build/locale/src/components/TableFilter/FilterChips.json
#. [src.components.TableFilter.2173195312] - button #. [src.components.TableFilter.2173195312] - button
#. defaultMessage is: #. defaultMessage is:
@ -2727,8 +2807,8 @@ msgctxt "dialog header"
msgid "Delete Voucher" msgid "Delete Voucher"
msgstr "" msgstr ""
#: build/locale/src/discounts/views/VoucherList.json #: build/locale/src/discounts/views/VoucherList/VoucherList.json
#. [src.discounts.views.367317371] - dialog header #. [src.discounts.views.VoucherList.367317371] - dialog header
#. defaultMessage is: #. defaultMessage is:
#. Delete Vouchers #. Delete Vouchers
msgctxt "dialog header" msgctxt "dialog header"
@ -2763,8 +2843,8 @@ msgstr ""
#. [src.categories.views.712767046] - dialog title #. [src.categories.views.712767046] - dialog title
#. defaultMessage is: #. defaultMessage is:
#. Delete categories #. Delete categories
#: build/locale/src/categories/views/CategoryList.json #: build/locale/src/categories/views/CategoryList/CategoryList.json
#. [src.categories.views.712767046] - dialog title #. [src.categories.views.CategoryList.712767046] - dialog title
#. defaultMessage is: #. defaultMessage is:
#. Delete categories #. Delete categories
msgctxt "dialog title" msgctxt "dialog title"
@ -2783,8 +2863,8 @@ msgctxt "dialog title"
msgid "Delete category" msgid "Delete category"
msgstr "" msgstr ""
#: build/locale/src/collections/views/CollectionList.json #: build/locale/src/collections/views/CollectionList/CollectionList.json
#. [src.collections.views.3817188998] - dialog title #. [src.collections.views.CollectionList.3817188998] - dialog title
#. defaultMessage is: #. defaultMessage is:
#. Delete collections #. Delete collections
msgctxt "dialog title" msgctxt "dialog title"
@ -2799,8 +2879,8 @@ msgctxt "dialog header"
msgid "Delete customer" msgid "Delete customer"
msgstr "" msgstr ""
#: build/locale/src/customers/views/CustomerList.json #: build/locale/src/customers/views/CustomerList/CustomerList.json
#. [src.customers.views.1946482599] - dialog header #. [src.customers.views.CustomerList.1946482599] - dialog header
#. defaultMessage is: #. defaultMessage is:
#. Delete customers #. Delete customers
msgctxt "dialog header" msgctxt "dialog header"
@ -3551,18 +3631,6 @@ msgctxt "subheader"
msgid "Here is some information we gathered about your store" msgid "Here is some information we gathered about your store"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.77815154] - product is hidden
#. defaultMessage is:
#. Hidden
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.77815154] - product is hidden
#. defaultMessage is:
#. Hidden
msgctxt "product is hidden"
msgid "Hidden"
msgstr ""
#: build/locale/src/components/VisibilityCard/VisibilityCard.json #: build/locale/src/components/VisibilityCard/VisibilityCard.json
#. [src.components.VisibilityCard.77815154] #. [src.components.VisibilityCard.77815154]
#. defaultMessage is: #. defaultMessage is:
@ -3571,6 +3639,14 @@ msgctxt "description"
msgid "Hidden" msgid "Hidden"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.77815154] - product is hidden
#. defaultMessage is:
#. Hidden
msgctxt "product is hidden"
msgid "Hidden"
msgstr ""
#: build/locale/src/products/views/ProductList/filters.json #: build/locale/src/products/views/ProductList/filters.json
#. [src.products.views.ProductList.hidden] - filter products by visibility #. [src.products.views.ProductList.hidden] - filter products by visibility
#. defaultMessage is: #. defaultMessage is:
@ -4895,8 +4971,8 @@ msgctxt "order history message"
msgid "Order confirmation was sent to customer" msgid "Order confirmation was sent to customer"
msgstr "" msgstr ""
#: build/locale/src/orders/views/OrderDraftList.json #: build/locale/src/orders/views/OrderDraftList/OrderDraftList.json
#. [src.orders.views.1872939752] #. [src.orders.views.OrderDraftList.1872939752]
#. defaultMessage is: #. defaultMessage is:
#. Order draft succesfully created #. Order draft succesfully created
#: build/locale/src/orders/views/OrderList/OrderList.json #: build/locale/src/orders/views/OrderList/OrderList.json
@ -5035,10 +5111,6 @@ msgctxt "description"
msgid "Original String" msgid "Original String"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1640493122] - product status
#. defaultMessage is:
#. Out Of Stock
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json #: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1640493122] - product status #. [src.products.components.ProductListFilter.1640493122] - product status
#. defaultMessage is: #. defaultMessage is:
@ -5375,18 +5447,6 @@ msgctxt "order payment"
msgid "Preauthorized amount" msgid "Preauthorized amount"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1134347598]
#. defaultMessage is:
#. Price
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1134347598]
#. defaultMessage is:
#. Price
msgctxt "description"
msgid "Price"
msgstr ""
#: build/locale/src/categories/components/CategoryProductList/CategoryProductList.json #: build/locale/src/categories/components/CategoryProductList/CategoryProductList.json
#. [src.categories.components.CategoryProductList.1134347598] - product price #. [src.categories.components.CategoryProductList.1134347598] - product price
#. defaultMessage is: #. defaultMessage is:
@ -5435,6 +5495,14 @@ msgctxt "product unit price"
msgid "Price" msgid "Price"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1134347598]
#. defaultMessage is:
#. Price
msgctxt "description"
msgid "Price"
msgstr ""
#: build/locale/src/products/components/ProductVariants/ProductVariants.json #: build/locale/src/products/components/ProductVariants/ProductVariants.json
#. [src.products.components.ProductVariants.1134347598] - product variant price #. [src.products.components.ProductVariants.1134347598] - product variant price
#. defaultMessage is: #. defaultMessage is:
@ -5747,8 +5815,8 @@ msgctxt "description"
msgid "Provided email address does not exist in our database." msgid "Provided email address does not exist in our database."
msgstr "" msgstr ""
#: build/locale/src/collections/views/CollectionList.json #: build/locale/src/collections/views/CollectionList/CollectionList.json
#. [src.collections.views.1547167026] - publish collections #. [src.collections.views.CollectionList.1547167026] - publish collections
#. defaultMessage is: #. defaultMessage is:
#. Publish #. Publish
msgctxt "publish collections" msgctxt "publish collections"
@ -5787,8 +5855,8 @@ msgctxt "dialog header"
msgid "Publish Products" msgid "Publish Products"
msgstr "" msgstr ""
#: build/locale/src/collections/views/CollectionList.json #: build/locale/src/collections/views/CollectionList/CollectionList.json
#. [src.collections.views.2823425739] - dialog title #. [src.collections.views.CollectionList.2823425739] - dialog title
#. defaultMessage is: #. defaultMessage is:
#. Publish collections #. Publish collections
msgctxt "dialog title" msgctxt "dialog title"
@ -5919,10 +5987,6 @@ msgctxt "description"
msgid "Quick Pick" msgid "Quick Pick"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.2545228781]
#. defaultMessage is:
#. Range
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json #: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.2545228781] #. [src.orders.components.OrderListFilter.2545228781]
#. defaultMessage is: #. defaultMessage is:
@ -6031,8 +6095,8 @@ msgstr ""
#. [src.categories.views.3488150607] #. [src.categories.views.3488150607]
#. defaultMessage is: #. defaultMessage is:
#. Remember this will also delete all products assigned to this category. #. Remember this will also delete all products assigned to this category.
#: build/locale/src/categories/views/CategoryList.json #: build/locale/src/categories/views/CategoryList/CategoryList.json
#. [src.categories.views.3488150607] #. [src.categories.views.CategoryList.3488150607]
#. defaultMessage is: #. defaultMessage is:
#. Remember this will also delete all products assigned to this category. #. Remember this will also delete all products assigned to this category.
msgctxt "description" msgctxt "description"
@ -6071,8 +6135,8 @@ msgctxt "unassign country, dialog header"
msgid "Remove from shipping zone" msgid "Remove from shipping zone"
msgstr "" msgstr ""
#: build/locale/src/orders/views/OrderDraftList.json #: build/locale/src/orders/views/OrderDraftList/OrderDraftList.json
#. [src.orders.views.3880993240] #. [src.orders.views.OrderDraftList.3880993240]
#. defaultMessage is: #. defaultMessage is:
#. Removed draft orders #. Removed draft orders
msgctxt "description" msgctxt "description"
@ -6243,14 +6307,10 @@ msgctxt "button"
msgid "Save" msgid "Save"
msgstr "" msgstr ""
#: build/locale/src/components/SaveFilterTabDialog/SaveFilterTabDialog.json #: build/locale/src/components/Filter/FilterSearch.json
#. [src.components.SaveFilterTabDialog.1514415736] - save filter tab, header #. [src.components.Filter.1514415736] - button
#. defaultMessage is: #. defaultMessage is:
#. Save Custom Search #. Save Custom Search
msgctxt "save filter tab, header"
msgid "Save Custom Search"
msgstr ""
#: build/locale/src/components/TableFilter/FilterChips.json #: build/locale/src/components/TableFilter/FilterChips.json
#. [src.components.TableFilter.1514415736] - button #. [src.components.TableFilter.1514415736] - button
#. defaultMessage is: #. defaultMessage is:
@ -6259,6 +6319,14 @@ msgctxt "button"
msgid "Save Custom Search" msgid "Save Custom Search"
msgstr "" msgstr ""
#: build/locale/src/components/SaveFilterTabDialog/SaveFilterTabDialog.json
#. [src.components.SaveFilterTabDialog.1514415736] - save filter tab, header
#. defaultMessage is:
#. Save Custom Search
msgctxt "save filter tab, header"
msgid "Save Custom Search"
msgstr ""
#: build/locale/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.json #: build/locale/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.json
#. [src.products.components.ProductVariantCreatePage.2853608829] - button #. [src.products.components.ProductVariantCreatePage.2853608829] - button
#. defaultMessage is: #. defaultMessage is:
@ -6275,6 +6343,14 @@ msgctxt "description"
msgid "Saved changes" msgid "Saved changes"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/AttributeListPage/AttributeListPage.json
#. [src.attributes.components.AttributeListPage.3916653510]
#. defaultMessage is:
#. Search Attribute
msgctxt "description"
msgid "Search Attribute"
msgstr ""
#: build/locale/src/productTypes/components/AssignAttributeDialog/AssignAttributeDialog.json #: build/locale/src/productTypes/components/AssignAttributeDialog/AssignAttributeDialog.json
#. [src.productTypes.components.AssignAttributeDialog.902296540] #. [src.productTypes.components.AssignAttributeDialog.902296540]
#. defaultMessage is: #. defaultMessage is:
@ -6291,10 +6367,30 @@ msgctxt "description"
msgid "Search Categories" msgid "Search Categories"
msgstr "" msgstr ""
#: build/locale/src/categories/components/CategoryListPage/CategoryListPage.json
#. [src.categories.components.CategoryListPage.3841025483]
#. defaultMessage is:
#. Search Category
#: build/locale/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.json
#. [src.translations.components.TranslationsEntitiesListPage.3841025483]
#. defaultMessage is:
#. Search Category
msgctxt "description"
msgid "Search Category"
msgstr ""
#: build/locale/src/collections/components/CollectionListPage/CollectionListPage.json
#. [src.collections.components.CollectionListPage.4057224233]
#. defaultMessage is:
#. Search Collection
#: build/locale/src/components/AssignCollectionDialog/AssignCollectionDialog.json #: build/locale/src/components/AssignCollectionDialog/AssignCollectionDialog.json
#. [src.components.AssignCollectionDialog.4057224233] #. [src.components.AssignCollectionDialog.4057224233]
#. defaultMessage is: #. defaultMessage is:
#. Search Collection #. Search Collection
#: build/locale/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.json
#. [src.translations.components.TranslationsEntitiesListPage.4057224233]
#. defaultMessage is:
#. Search Collection
msgctxt "description" msgctxt "description"
msgid "Search Collection" msgid "Search Collection"
msgstr "" msgstr ""
@ -6307,6 +6403,14 @@ msgctxt "description"
msgid "Search Countries" msgid "Search Countries"
msgstr "" msgstr ""
#: build/locale/src/customers/components/CustomerListPage/CustomerListPage.json
#. [src.customers.components.CustomerListPage.1643417013]
#. defaultMessage is:
#. Search Customer
msgctxt "description"
msgid "Search Customer"
msgstr ""
#: build/locale/src/orders/components/OrderCustomer/OrderCustomer.json #: build/locale/src/orders/components/OrderCustomer/OrderCustomer.json
#. [src.orders.components.OrderCustomer.2433460203] #. [src.orders.components.OrderCustomer.2433460203]
#. defaultMessage is: #. defaultMessage is:
@ -6315,6 +6419,14 @@ msgctxt "description"
msgid "Search Customers" msgid "Search Customers"
msgstr "" msgstr ""
#: build/locale/src/orders/components/OrderDraftListPage/OrderDraftListPage.json
#. [src.orders.components.OrderDraftListPage.77765281]
#. defaultMessage is:
#. Search Draft
msgctxt "description"
msgid "Search Draft"
msgstr ""
#: build/locale/src/translations/components/TranslationsCategoriesPage/TranslationsCategoriesPage.json #: build/locale/src/translations/components/TranslationsCategoriesPage/TranslationsCategoriesPage.json
#. [src.translations.components.TranslationsCategoriesPage.1406947243] #. [src.translations.components.TranslationsCategoriesPage.1406947243]
#. defaultMessage is: #. defaultMessage is:
@ -6395,6 +6507,34 @@ msgctxt "description"
msgid "Search Orders..." msgid "Search Orders..."
msgstr "" msgstr ""
#: build/locale/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.json
#. [src.translations.components.TranslationsEntitiesListPage.2559018090]
#. defaultMessage is:
#. Search Page
msgctxt "description"
msgid "Search Page"
msgstr ""
#: build/locale/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.json
#. [src.translations.components.TranslationsEntitiesListPage.2105464697]
#. defaultMessage is:
#. Search Product
msgctxt "description"
msgid "Search Product"
msgstr ""
#: build/locale/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.json
#. [src.productTypes.components.ProductTypeListPage.3420445375]
#. defaultMessage is:
#. Search Product Type
#: build/locale/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.json
#. [src.translations.components.TranslationsEntitiesListPage.3420445375]
#. defaultMessage is:
#. Search Product Type
msgctxt "description"
msgid "Search Product Type"
msgstr ""
#: build/locale/src/components/AssignProductDialog/AssignProductDialog.json #: build/locale/src/components/AssignProductDialog/AssignProductDialog.json
#. [src.components.AssignProductDialog.2850255786] #. [src.components.AssignProductDialog.2850255786]
#. defaultMessage is: #. defaultMessage is:
@ -6407,14 +6547,46 @@ msgctxt "description"
msgid "Search Products" msgid "Search Products"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListPage/ProductListPage.json #: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListPage.3550330425] #. [src.products.components.ProductListFilter.3550330425]
#. defaultMessage is: #. defaultMessage is:
#. Search Products... #. Search Products...
msgctxt "description" msgctxt "description"
msgid "Search Products..." msgid "Search Products..."
msgstr "" msgstr ""
#: build/locale/src/discounts/components/SaleListPage/SaleListPage.json
#. [src.discounts.components.SaleListPage.1866913828]
#. defaultMessage is:
#. Search Sale
#: build/locale/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.json
#. [src.translations.components.TranslationsEntitiesListPage.1866913828]
#. defaultMessage is:
#. Search Sale
msgctxt "description"
msgid "Search Sale"
msgstr ""
#: build/locale/src/staff/components/StaffListPage/StaffListPage.json
#. [src.staff.components.StaffListPage.61043583]
#. defaultMessage is:
#. Search Staff Member
msgctxt "description"
msgid "Search Staff Member"
msgstr ""
#: build/locale/src/discounts/components/VoucherListPage/VoucherListPage.json
#. [src.discounts.components.VoucherListPage.1930485532]
#. defaultMessage is:
#. Search Voucher
#: build/locale/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.json
#. [src.translations.components.TranslationsEntitiesListPage.1930485532]
#. defaultMessage is:
#. Search Voucher
msgctxt "description"
msgid "Search Voucher"
msgstr ""
#: build/locale/src/productTypes/components/AssignAttributeDialog/AssignAttributeDialog.json #: build/locale/src/productTypes/components/AssignAttributeDialog/AssignAttributeDialog.json
#. [src.productTypes.components.AssignAttributeDialog.524117994] #. [src.productTypes.components.AssignAttributeDialog.524117994]
#. defaultMessage is: #. defaultMessage is:
@ -6519,8 +6691,8 @@ msgctxt "description"
msgid "Select all orders where:" msgid "Select all orders where:"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListPage/ProductListPage.json #: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListPage.1421689426] #. [src.products.components.ProductListFilter.1421689426]
#. defaultMessage is: #. defaultMessage is:
#. Select all products where: #. Select all products where:
msgctxt "description" msgctxt "description"
@ -6835,10 +7007,6 @@ msgctxt "description"
msgid "Specific Date" msgid "Specific Date"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.2844426531]
#. defaultMessage is:
#. Specific Price
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json #: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.2844426531] #. [src.products.components.ProductListFilter.2844426531]
#. defaultMessage is: #. defaultMessage is:
@ -6919,18 +7087,6 @@ msgctxt "voucher is active from date"
msgid "Starts" msgid "Starts"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1756106276] - product status
#. defaultMessage is:
#. Status
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1756106276] - product status
#. defaultMessage is:
#. Status
msgctxt "product status"
msgid "Status"
msgstr ""
#: build/locale/src/customers/components/CustomerOrders/CustomerOrders.json #: build/locale/src/customers/components/CustomerOrders/CustomerOrders.json
#. [src.customers.components.CustomerOrders.1756106276] - order status #. [src.customers.components.CustomerOrders.1756106276] - order status
#. defaultMessage is: #. defaultMessage is:
@ -6955,6 +7111,14 @@ msgctxt "plugin status"
msgid "Status" msgid "Status"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1756106276] - product status
#. defaultMessage is:
#. Status
msgctxt "product status"
msgid "Status"
msgstr ""
#: build/locale/src/products/components/ProductVariants/ProductVariants.json #: build/locale/src/products/components/ProductVariants/ProductVariants.json
#. [src.products.components.ProductVariants.1756106276] - product variant status #. [src.products.components.ProductVariants.1756106276] - product variant status
#. defaultMessage is: #. defaultMessage is:
@ -6963,10 +7127,6 @@ msgctxt "product variant status"
msgid "Status" msgid "Status"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.3841616483] - product stock
#. defaultMessage is:
#. Stock
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json #: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.3841616483] - product stock #. [src.products.components.ProductListFilter.3841616483] - product stock
#. defaultMessage is: #. defaultMessage is:
@ -6983,10 +7143,6 @@ msgctxt "product variant stock, section header"
msgid "Stock" msgid "Stock"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.3645081351]
#. defaultMessage is:
#. Stock quantity
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json #: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.3645081351] #. [src.products.components.ProductListFilter.3645081351]
#. defaultMessage is: #. defaultMessage is:
@ -7815,8 +7971,8 @@ msgctxt "payment status"
msgid "Unpaid" msgid "Unpaid"
msgstr "" msgstr ""
#: build/locale/src/collections/views/CollectionList.json #: build/locale/src/collections/views/CollectionList/CollectionList.json
#. [src.collections.views.2237014112] - unpublish collections #. [src.collections.views.CollectionList.2237014112] - unpublish collections
#. defaultMessage is: #. defaultMessage is:
#. Unpublish #. Unpublish
msgctxt "unpublish collections" msgctxt "unpublish collections"
@ -7855,8 +8011,8 @@ msgctxt "dialog header"
msgid "Unpublish Products" msgid "Unpublish Products"
msgstr "" msgstr ""
#: build/locale/src/collections/views/CollectionList.json #: build/locale/src/collections/views/CollectionList/CollectionList.json
#. [src.collections.views.2637364047] - dialog title #. [src.collections.views.CollectionList.2637364047] - dialog title
#. defaultMessage is: #. defaultMessage is:
#. Unpublish collections #. Unpublish collections
msgctxt "dialog title" msgctxt "dialog title"
@ -8155,18 +8311,6 @@ msgctxt "description"
msgid "View and update your site settings" msgid "View and update your site settings"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1459686496] - product visibility
#. defaultMessage is:
#. Visibility
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1459686496] - product visibility
#. defaultMessage is:
#. Visibility
msgctxt "product visibility"
msgid "Visibility"
msgstr ""
#: build/locale/src/components/VisibilityCard/VisibilityCard.json #: build/locale/src/components/VisibilityCard/VisibilityCard.json
#. [src.components.VisibilityCard.1459686496] - section header #. [src.components.VisibilityCard.1459686496] - section header
#. defaultMessage is: #. defaultMessage is:
@ -8183,6 +8327,14 @@ msgctxt "page status"
msgid "Visibility" msgid "Visibility"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1459686496] - product visibility
#. defaultMessage is:
#. Visibility
msgctxt "product visibility"
msgid "Visibility"
msgstr ""
#: build/locale/src/attributes/components/AttributeList/AttributeList.json #: build/locale/src/attributes/components/AttributeList/AttributeList.json
#. [src.attributes.components.AttributeList.643174786] - attribute is visible #. [src.attributes.components.AttributeList.643174786] - attribute is visible
#. defaultMessage is: #. defaultMessage is:
@ -8191,18 +8343,6 @@ msgctxt "attribute is visible"
msgid "Visible" msgid "Visible"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.643174786] - product is visible
#. defaultMessage is:
#. Visible
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.643174786] - product is visible
#. defaultMessage is:
#. Visible
msgctxt "product is visible"
msgid "Visible"
msgstr ""
#: build/locale/src/components/VisibilityCard/VisibilityCard.json #: build/locale/src/components/VisibilityCard/VisibilityCard.json
#. [src.components.VisibilityCard.643174786] #. [src.components.VisibilityCard.643174786]
#. defaultMessage is: #. defaultMessage is:
@ -8211,6 +8351,14 @@ msgctxt "description"
msgid "Visible" msgid "Visible"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.643174786] - product is visible
#. defaultMessage is:
#. Visible
msgctxt "product is visible"
msgid "Visible"
msgstr ""
#: build/locale/src/attributes/components/AttributeProperties/AttributeProperties.json #: build/locale/src/attributes/components/AttributeProperties/AttributeProperties.json
#. [src.attributes.components.AttributeProperties.3876764312] - attribute #. [src.attributes.components.AttributeProperties.3876764312] - attribute
#. defaultMessage is: #. defaultMessage is:
@ -8359,18 +8507,6 @@ msgctxt "order does not require shipping"
msgid "does not apply" msgid "does not apply"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.3477667254] - product price
#. defaultMessage is:
#. equals
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.3477667254] - product price
#. defaultMessage is:
#. equals
msgctxt "product price"
msgid "equals"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json #: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.3477667254] #. [src.orders.components.OrderListFilter.3477667254]
#. defaultMessage is: #. defaultMessage is:
@ -8379,6 +8515,14 @@ msgctxt "description"
msgid "equals" msgid "equals"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.3477667254] - product price
#. defaultMessage is:
#. equals
msgctxt "product price"
msgid "equals"
msgstr ""
#: build/locale/src/components/Filter/FilterElement.json #: build/locale/src/components/Filter/FilterElement.json
#. [src.components.Filter.2755325844] #. [src.components.Filter.2755325844]
#. defaultMessage is: #. defaultMessage is:
@ -8403,18 +8547,6 @@ msgctxt "weight"
msgid "from {value} {unit}" msgid "from {value} {unit}"
msgstr "" msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1438173764] - product status is set as
#. defaultMessage is:
#. is set as
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1438173764] - product status is set as
#. defaultMessage is:
#. is set as
msgctxt "product status is set as"
msgid "is set as"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json #: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.1438173764] - date is set as #. [src.orders.components.OrderListFilter.1438173764] - date is set as
#. defaultMessage is: #. defaultMessage is:
@ -8423,6 +8555,14 @@ msgctxt "date is set as"
msgid "is set as" msgid "is set as"
msgstr "" msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1438173764] - product status is set as
#. defaultMessage is:
#. is set as
msgctxt "product status is set as"
msgid "is set as"
msgstr ""
#: build/locale/src/staff/views/StaffDetails.json #: build/locale/src/staff/views/StaffDetails.json
#. [src.staff.views.2240444792] - dialog header #. [src.staff.views.2240444792] - dialog header
#. defaultMessage is: #. defaultMessage is:

View file

@ -466,6 +466,10 @@ type CategoryDelete {
category: Category category: Category
} }
input CategoryFilterInput {
search: String
}
input CategoryInput { input CategoryInput {
description: String description: String
descriptionJson: JSONString descriptionJson: JSONString
@ -1627,6 +1631,10 @@ type MenuDelete {
menu: Menu menu: Menu
} }
input MenuFilterInput {
search: String
}
input MenuInput { input MenuInput {
name: String name: String
} }
@ -1682,6 +1690,10 @@ type MenuItemDelete {
menuItem: MenuItem menuItem: MenuItem
} }
input MenuItemFilterInput {
search: String
}
input MenuItemInput { input MenuItemInput {
name: String name: String
url: String url: String
@ -1980,6 +1992,11 @@ type Mutations {
userBulkSetActive(ids: [ID]!, isActive: Boolean!): UserBulkSetActive userBulkSetActive(ids: [ID]!, isActive: Boolean!): UserBulkSetActive
userUpdatePrivateMetadata(id: ID!, input: MetaInput!): UserUpdatePrivateMeta userUpdatePrivateMetadata(id: ID!, input: MetaInput!): UserUpdatePrivateMeta
userClearStoredPrivateMetadata(id: ID!, input: MetaPath!): UserClearStoredPrivateMeta userClearStoredPrivateMetadata(id: ID!, input: MetaPath!): UserClearStoredPrivateMeta
serviceAccountCreate(input: ServiceAccountInput!): ServiceAccountCreate
serviceAccountUpdate(id: ID!, input: ServiceAccountInput!): ServiceAccountUpdate
serviceAccountDelete(id: ID!): ServiceAccountDelete
serviceAccountUpdatePrivateMetadata(id: ID!, input: MetaInput!): ServiceAccountUpdatePrivateMeta
serviceAccountClearStoredPrivateMetadata(id: ID!, input: MetaPath!): ServiceAccountClearStoredPrivateMeta
passwordReset(email: String!): PasswordReset passwordReset(email: String!): PasswordReset
} }
@ -2095,6 +2112,7 @@ enum OrderDirection {
input OrderDraftFilterInput { input OrderDraftFilterInput {
customer: String customer: String
created: DateRangeInput created: DateRangeInput
search: String
} }
type OrderEvent implements Node { type OrderEvent implements Node {
@ -2171,12 +2189,13 @@ input OrderFilterInput {
status: [OrderStatusFilter] status: [OrderStatusFilter]
customer: String customer: String
created: DateRangeInput created: DateRangeInput
search: String
} }
type OrderLine implements Node { type OrderLine implements Node {
id: ID! id: ID!
productName: String! productName: String!
translatedProductName: String! variantName: String!
productSku: String! productSku: String!
isShippingRequired: Boolean! isShippingRequired: Boolean!
quantity: Int! quantity: Int!
@ -2186,6 +2205,8 @@ type OrderLine implements Node {
thumbnail(size: Int): Image thumbnail(size: Int): Image
unitPrice: TaxedMoney unitPrice: TaxedMoney
variant: ProductVariant variant: ProductVariant
translatedProductName: String!
translatedVariantName: String!
} }
input OrderLineCreateInput { input OrderLineCreateInput {
@ -2294,6 +2315,10 @@ type PageDelete {
page: Page page: Page
} }
input PageFilterInput {
search: String
}
type PageInfo { type PageInfo {
hasNextPage: Boolean! hasNextPage: Boolean!
hasPreviousPage: Boolean! hasPreviousPage: Boolean!
@ -2430,6 +2455,7 @@ type PermissionDisplay {
enum PermissionEnum { enum PermissionEnum {
MANAGE_USERS MANAGE_USERS
MANAGE_STAFF MANAGE_STAFF
MANAGE_SERVICE_ACCOUNTS
IMPERSONATE_USERS IMPERSONATE_USERS
MANAGE_DISCOUNTS MANAGE_DISCOUNTS
MANAGE_GIFT_CARD MANAGE_GIFT_CARD
@ -2462,6 +2488,11 @@ type PluginCountableEdge {
cursor: String! cursor: String!
} }
input PluginFilterInput {
active: Boolean
search: String
}
type PluginUpdate { type PluginUpdate {
errors: [Error!] errors: [Error!]
plugin: Plugin plugin: Plugin
@ -2661,6 +2692,8 @@ enum ProductOrderField {
PRICE PRICE
MINIMAL_PRICE MINIMAL_PRICE
DATE DATE
TYPE
PUBLISHED
} }
type ProductPricingInfo { type ProductPricingInfo {
@ -2922,7 +2955,7 @@ type Query {
digitalContents(query: String, level: Int, before: String, after: String, first: Int, last: Int): DigitalContentCountableConnection digitalContents(query: String, level: Int, before: String, after: String, first: Int, last: Int): DigitalContentCountableConnection
attributes(query: String, inCategory: ID, inCollection: ID, filter: AttributeFilterInput, sortBy: AttributeSortingInput, before: String, after: String, first: Int, last: Int): AttributeCountableConnection attributes(query: String, inCategory: ID, inCollection: ID, filter: AttributeFilterInput, sortBy: AttributeSortingInput, before: String, after: String, first: Int, last: Int): AttributeCountableConnection
attribute(id: ID!): Attribute attribute(id: ID!): Attribute
categories(query: String, level: Int, before: String, after: String, first: Int, last: Int): CategoryCountableConnection categories(query: String, filter: CategoryFilterInput, level: Int, before: String, after: String, first: Int, last: Int): CategoryCountableConnection
category(id: ID!): Category category(id: ID!): Category
collection(id: ID!): Collection collection(id: ID!): Collection
collections(filter: CollectionFilterInput, query: String, before: String, after: String, first: Int, last: Int): CollectionCountableConnection collections(filter: CollectionFilterInput, query: String, before: String, after: String, first: Int, last: Int): CollectionCountableConnection
@ -2937,7 +2970,7 @@ type Query {
payments(before: String, after: String, first: Int, last: Int): PaymentCountableConnection payments(before: String, after: String, first: Int, last: Int): PaymentCountableConnection
paymentClientToken(gateway: GatewaysEnum): String paymentClientToken(gateway: GatewaysEnum): String
page(id: ID, slug: String): Page page(id: ID, slug: String): Page
pages(query: String, before: String, after: String, first: Int, last: Int): PageCountableConnection pages(query: String, filter: PageFilterInput, before: String, after: String, first: Int, last: Int): PageCountableConnection
homepageEvents(before: String, after: String, first: Int, last: Int): OrderEventCountableConnection homepageEvents(before: String, after: String, first: Int, last: Int): OrderEventCountableConnection
order(id: ID!): Order order(id: ID!): Order
orders(filter: OrderFilterInput, query: String, created: ReportingPeriod, status: OrderStatusFilter, before: String, after: String, first: Int, last: Int): OrderCountableConnection orders(filter: OrderFilterInput, query: String, created: ReportingPeriod, status: OrderStatusFilter, before: String, after: String, first: Int, last: Int): OrderCountableConnection
@ -2945,13 +2978,13 @@ type Query {
ordersTotal(period: ReportingPeriod): TaxedMoney ordersTotal(period: ReportingPeriod): TaxedMoney
orderByToken(token: String!): Order orderByToken(token: String!): Order
menu(id: ID, name: String): Menu menu(id: ID, name: String): Menu
menus(query: String, before: String, after: String, first: Int, last: Int): MenuCountableConnection menus(query: String, filter: MenuFilterInput, before: String, after: String, first: Int, last: Int): MenuCountableConnection
menuItem(id: ID!): MenuItem menuItem(id: ID!): MenuItem
menuItems(query: String, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection menuItems(query: String, filter: MenuItemFilterInput, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection
giftCard(id: ID!): GiftCard giftCard(id: ID!): GiftCard
giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection
plugin(id: ID!): Plugin plugin(id: ID!): Plugin
plugins(before: String, after: String, first: Int, last: Int): PluginCountableConnection plugins(filter: PluginFilterInput, before: String, after: String, first: Int, last: Int): PluginCountableConnection
sale(id: ID!): Sale sale(id: ID!): Sale
sales(filter: SaleFilterInput, query: String, before: String, after: String, first: Int, last: Int): SaleCountableConnection sales(filter: SaleFilterInput, query: String, before: String, after: String, first: Int, last: Int): SaleCountableConnection
voucher(id: ID!): Voucher voucher(id: ID!): Voucher
@ -2965,6 +2998,8 @@ type Query {
customers(filter: CustomerFilterInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection customers(filter: CustomerFilterInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection
me: User me: User
staffUsers(filter: StaffUserInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection staffUsers(filter: StaffUserInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection
serviceAccounts(filter: ServiceAccountFilterInput, before: String, after: String, first: Int, last: Int): ServiceAccountCountableConnection
serviceAccount(id: ID!): ServiceAccount
user(id: ID!): User user(id: ID!): User
node(id: ID!): Node node(id: ID!): Node
} }
@ -3092,6 +3127,65 @@ input SeoInput {
description: String description: String
} }
type ServiceAccount implements Node {
id: ID!
authToken: String
created: DateTime
isActive: Boolean
privateMeta: [MetaStore]!
meta: [MetaStore]!
permissions: [PermissionDisplay]
name: String
}
type ServiceAccountClearStoredPrivateMeta {
errors: [Error!]
serviceAccount: ServiceAccount
}
type ServiceAccountCountableConnection {
pageInfo: PageInfo!
edges: [ServiceAccountCountableEdge!]!
totalCount: Int
}
type ServiceAccountCountableEdge {
node: ServiceAccount!
cursor: String!
}
type ServiceAccountCreate {
errors: [Error!]
authToken: String
serviceAccount: ServiceAccount
}
type ServiceAccountDelete {
errors: [Error!]
serviceAccount: ServiceAccount
}
input ServiceAccountFilterInput {
search: String
isActive: Boolean
}
input ServiceAccountInput {
name: String
isActive: Boolean
permissions: [PermissionEnum]
}
type ServiceAccountUpdate {
errors: [Error!]
serviceAccount: ServiceAccount
}
type ServiceAccountUpdatePrivateMeta {
errors: [Error!]
serviceAccount: ServiceAccount
}
type SetPassword { type SetPassword {
token: String token: String
errors: [Error]! errors: [Error]!
@ -3307,6 +3401,7 @@ input StaffCreateInput {
note: String note: String
permissions: [PermissionEnum] permissions: [PermissionEnum]
sendPasswordEmail: Boolean sendPasswordEmail: Boolean
redirectUrl: String
} }
type StaffDelete { type StaffDelete {
@ -3534,6 +3629,7 @@ input UserCreateInput {
isActive: Boolean isActive: Boolean
note: String note: String
sendPasswordEmail: Boolean sendPasswordEmail: Boolean
redirectUrl: String
} }
type UserUpdateMeta { type UserUpdateMeta {

View file

@ -4,19 +4,37 @@ import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import Container from "../../../components/Container"; import Container from "../../../components/Container";
import PageHeader from "../../../components/PageHeader"; import PageHeader from "../../../components/PageHeader";
import { ListActions, PageListProps } from "../../../types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "../../../types";
import { AttributeList_attributes_edges_node } from "../../types/AttributeList"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
import AttributeList from "../AttributeList/AttributeList"; import AttributeList from "../AttributeList/AttributeList";
export interface AttributeListPageProps extends PageListProps, ListActions { export interface AttributeListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
attributes: AttributeList_attributes_edges_node[]; attributes: AttributeList_attributes_edges_node[];
} }
const AttributeListPage: React.FC<AttributeListPageProps> = ({ const AttributeListPage: React.FC<AttributeListPageProps> = ({
onAdd, onAdd,
initialSearch,
onSearchChange,
currentTab,
onAll,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -32,6 +50,23 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
</Button> </Button>
</PageHeader> </PageHeader>
<Card> <Card>
<SearchBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Attributes",
description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Attribute"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<AttributeList {...listProps} /> <AttributeList {...listProps} />
</Card> </Card>
</Container> </Container>

View file

@ -52,7 +52,7 @@ const attributeList = gql`
${attributeFragment} ${attributeFragment}
${pageInfoFragment} ${pageInfoFragment}
query AttributeList( query AttributeList(
$query: String $filter: AttributeFilterInput
$inCategory: ID $inCategory: ID
$inCollection: ID $inCollection: ID
$before: String $before: String
@ -61,7 +61,7 @@ const attributeList = gql`
$last: Int $last: Int
) { ) {
attributes( attributes(
query: $query filter: $filter
inCategory: $inCategory inCategory: $inCategory
inCollection: $inCollection inCollection: $inCollection
before: $before before: $before

View file

@ -2,6 +2,8 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { AttributeFilterInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: AttributeList // GraphQL query operation: AttributeList
// ==================================================== // ====================================================
@ -40,7 +42,7 @@ export interface AttributeList {
} }
export interface AttributeListVariables { export interface AttributeListVariables {
query?: string | null; filter?: AttributeFilterInput | null;
inCategory?: string | null; inCategory?: string | null;
inCollection?: string | null; inCollection?: string | null;
before?: string | null; before?: string | null;

View file

@ -1,12 +1,26 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination, SingleAction } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
SingleAction,
TabActionDialog
} from "../types";
export const attributeSection = "/attributes/"; export const attributeSection = "/attributes/";
export type AttributeListUrlDialog = "remove"; export enum AttributeListUrlFiltersEnum {
export type AttributeListUrlQueryParams = BulkAction & query = "query"
}
export type AttributeListUrlFilters = Filters<AttributeListUrlFiltersEnum>;
export type AttributeListUrlDialog = "remove" | TabActionDialog;
export type AttributeListUrlQueryParams = ActiveTab &
AttributeListUrlFilters &
BulkAction &
Dialog<AttributeListUrlDialog> & Dialog<AttributeListUrlDialog> &
Pagination; Pagination;
export const attributeListPath = attributeSection; export const attributeListPath = attributeSection;

View file

@ -3,6 +3,18 @@ import DeleteIcon from "@material-ui/icons/Delete";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "@saleor/attributes/views/AttributeList/filters";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, { import usePaginator, {
@ -20,6 +32,7 @@ import {
attributeAddUrl, attributeAddUrl,
attributeListUrl, attributeListUrl,
AttributeListUrlDialog, AttributeListUrlDialog,
AttributeListUrlFilters,
AttributeListUrlQueryParams, AttributeListUrlQueryParams,
attributeUrl attributeUrl
} from "../../urls"; } from "../../urls";
@ -37,6 +50,15 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
); );
const intl = useIntl(); const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const closeModal = () => const closeModal = () =>
navigate( navigate(
attributeListUrl({ attributeListUrl({
@ -56,8 +78,46 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
}) })
); );
const changeFilterField = (filter: AttributeListUrlFilters) => {
reset();
navigate(
attributeListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const handleTabChange = (tab: number) => {
reset();
navigate(
attributeListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(attributeListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(PAGINATE_BY, params); const paginationState = createPaginationState(PAGINATE_BY, params);
const queryVariables = React.useMemo(() => paginationState, [params]); const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return ( return (
<AttributeListQuery variables={queryVariables}> <AttributeListQuery variables={queryVariables}>
@ -99,14 +159,22 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
attributes={maybe(() => attributes={maybe(() =>
data.attributes.edges.map(edge => edge.node) data.attributes.edges.map(edge => edge.node)
)} )}
currentTab={currentTab}
disabled={loading || attributeBulkDeleteOpts.loading} disabled={loading || attributeBulkDeleteOpts.loading}
initialSearch={params.query || ""}
isChecked={isSelected} isChecked={isSelected}
onAdd={() => navigate(attributeAddUrl())} onAdd={() => navigate(attributeAddUrl())}
onAll={() => navigate(attributeListUrl())}
onNextPage={loadNextPage} onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage} onPreviousPage={loadPreviousPage}
onRowClick={id => () => navigate(attributeUrl(id))} onRowClick={id => () => navigate(attributeUrl(id))}
onSearchChange={query => changeFilterField({ query })}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
pageInfo={pageInfo} pageInfo={pageInfo}
selected={listElements.length} selected={listElements.length}
tabs={tabs.map(tab => tab.name)}
toggle={toggle} toggle={toggle}
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={ toolbar={
@ -130,6 +198,19 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
onClose={closeModal} onClose={closeModal}
quantity={maybe(() => params.ids.length)} quantity={maybe(() => params.ids.length)}
/> />
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { AttributeFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
AttributeListUrlFilters,
AttributeListUrlFiltersEnum,
AttributeListUrlQueryParams
} from "../../urls";
export const PRODUCT_FILTERS_KEY = "productFilters";
export function getFilterVariables(
params: AttributeListUrlFilters
): AttributeFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<AttributeListUrlFilters>(PRODUCT_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
AttributeListUrlQueryParams,
AttributeListUrlFilters
>(AttributeListUrlFiltersEnum);

View file

@ -1,5 +1,3 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -12,9 +10,9 @@ import TableCell from "@material-ui/core/TableCell";
import TableFooter from "@material-ui/core/TableFooter"; import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import { CategoryFragment } from "@saleor/categories/types/CategoryFragment";
import Checkbox from "@saleor/components/Checkbox"; import Checkbox from "@saleor/components/Checkbox";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
@ -49,20 +47,8 @@ const styles = (theme: Theme) =>
} }
}); });
interface CategoryListProps interface CategoryListProps extends ListProps, ListActions {
extends ListProps, categories?: CategoryFragment[];
ListActions,
WithStyles<typeof styles> {
categories?: Array<{
id: string;
name: string;
children: {
totalCount: number;
};
products: {
totalCount: number;
};
}>;
isRoot: boolean; isRoot: boolean;
onAdd?(); onAdd?();
} }
@ -75,144 +61,119 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
classes, classes,
disabled, disabled,
settings, settings,
isRoot,
pageInfo, pageInfo,
isChecked, isChecked,
isRoot,
selected, selected,
toggle, toggle,
toggleAll, toggleAll,
toolbar, toolbar,
onAdd,
onNextPage, onNextPage,
onPreviousPage, onPreviousPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick onRowClick
}: CategoryListProps) => { }: CategoryListProps & WithStyles<typeof styles>) => (
const intl = useIntl(); <Table>
<TableHead
return ( colSpan={numberOfColumns}
<Card> selected={selected}
{!isRoot && ( disabled={disabled}
<CardTitle items={categories}
title={intl.formatMessage({ toggleAll={toggleAll}
defaultMessage: "All Subcategories", toolbar={toolbar}
description: "section header" >
})} <TableCell className={classes.colName}>
toolbar={ <FormattedMessage defaultMessage="Category Name" />
<Button color="primary" variant="text" onClick={onAdd}> </TableCell>
<FormattedMessage <TableCell className={classes.colSubcategories}>
defaultMessage="Create subcategory" <FormattedMessage
description="button" defaultMessage="Subcategories"
/> description="number of subcategories"
</Button>
}
/> />
)} </TableCell>
<Table> <TableCell className={classes.colProducts}>
<TableHead <FormattedMessage
defaultMessage="No. of Products"
description="number of products"
/>
</TableCell>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns} colSpan={numberOfColumns}
selected={selected} settings={settings}
disabled={disabled} hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
items={categories} onNextPage={onNextPage}
toggleAll={toggleAll} onUpdateListSettings={onUpdateListSettings}
toolbar={toolbar} hasPreviousPage={
> pageInfo && !disabled ? pageInfo.hasPreviousPage : false
<TableCell className={classes.colName}> }
<FormattedMessage defaultMessage="Category Name" /> onPreviousPage={onPreviousPage}
</TableCell> />
<TableCell className={classes.colSubcategories}> </TableRow>
<FormattedMessage </TableFooter>
defaultMessage="Subcategories" <TableBody>
description="number of subcategories" {renderCollection(
/> categories,
</TableCell> category => {
<TableCell className={classes.colProducts}> const isSelected = category ? isChecked(category.id) : false;
<FormattedMessage
defaultMessage="No. of Products"
description="number of products"
/>
</TableCell>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns}
settings={settings}
hasNextPage={
pageInfo && !disabled ? pageInfo.hasNextPage : false
}
onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
}
onPreviousPage={onPreviousPage}
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
categories,
category => {
const isSelected = category ? isChecked(category.id) : false;
return ( return (
<TableRow <TableRow
className={classes.tableRow} className={classes.tableRow}
hover={!!category} hover={!!category}
onClick={category ? onRowClick(category.id) : undefined} onClick={category ? onRowClick(category.id) : undefined}
key={category ? category.id : "skeleton"} key={category ? category.id : "skeleton"}
selected={isSelected} selected={isSelected}
> >
<TableCell padding="checkbox"> <TableCell padding="checkbox">
<Checkbox <Checkbox
checked={isSelected} checked={isSelected}
disabled={disabled} disabled={disabled}
disableClickPropagation disableClickPropagation
onChange={() => toggle(category.id)} onChange={() => toggle(category.id)}
/> />
</TableCell> </TableCell>
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
{category && category.name ? category.name : <Skeleton />} {category && category.name ? category.name : <Skeleton />}
</TableCell> </TableCell>
<TableCell className={classes.colSubcategories}> <TableCell className={classes.colSubcategories}>
{category && {category &&
category.children && category.children &&
category.children.totalCount !== undefined ? ( category.children.totalCount !== undefined ? (
category.children.totalCount category.children.totalCount
) : ( ) : (
<Skeleton /> <Skeleton />
)} )}
</TableCell> </TableCell>
<TableCell className={classes.colProducts}> <TableCell className={classes.colProducts}>
{category && {category &&
category.products && category.products &&
category.products.totalCount !== undefined ? ( category.products.totalCount !== undefined ? (
category.products.totalCount category.products.totalCount
) : ( ) : (
<Skeleton /> <Skeleton />
)} )}
</TableCell> </TableCell>
</TableRow> </TableRow>
); );
}, },
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={numberOfColumns}> <TableCell colSpan={numberOfColumns}>
{isRoot ? ( {isRoot ? (
<FormattedMessage defaultMessage="No categories found" /> <FormattedMessage defaultMessage="No categories found" />
) : ( ) : (
<FormattedMessage defaultMessage="No subcategories found" /> <FormattedMessage defaultMessage="No subcategories found" />
)} )}
</TableCell> </TableCell>
</TableRow> </TableRow>
) )
)} )}
</TableBody> </TableBody>
</Table> </Table>
</Card> )
);
}
); );
CategoryList.displayName = "CategoryList"; CategoryList.displayName = "CategoryList";
export default CategoryList; export default CategoryList;

View file

@ -1,44 +1,55 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { CategoryFragment } from "@saleor/categories/types/CategoryFragment";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import CategoryList from "../CategoryList"; import CategoryList from "../CategoryList";
export interface CategoryTableProps extends PageListProps, ListActions { export interface CategoryTableProps
categories: Array<{ extends PageListProps,
id: string; ListActions,
name: string; SearchPageProps,
children: { TabPageProps {
totalCount: number; categories: CategoryFragment[];
};
products: {
totalCount: number;
};
}>;
} }
export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({ export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
categories, categories,
currentTab,
disabled, disabled,
settings, initialSearch,
onAdd,
onNextPage,
onPreviousPage,
onUpdateListSettings,
onRowClick,
pageInfo,
isChecked, isChecked,
pageInfo,
selected, selected,
settings,
tabs,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar,
onAdd,
onAll,
onNextPage,
onPreviousPage,
onRowClick,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
onUpdateListSettings
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
return ( return (
<Container> <Container>
<PageHeader title={intl.formatMessage(sectionNames.categories)}> <PageHeader title={intl.formatMessage(sectionNames.categories)}>
@ -49,23 +60,42 @@ export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<CategoryList <Card>
categories={categories} <SearchBar
onAdd={onAdd} allTabLabel={intl.formatMessage({
onRowClick={onRowClick} defaultMessage: "All Categories",
disabled={disabled} description: "tab name"
settings={settings} })}
isRoot={true} currentTab={currentTab}
onNextPage={onNextPage} initialSearch={initialSearch}
onPreviousPage={onPreviousPage} searchPlaceholder={intl.formatMessage({
onUpdateListSettings={onUpdateListSettings} defaultMessage: "Search Category"
pageInfo={pageInfo} })}
isChecked={isChecked} tabs={tabs}
selected={selected} onAll={onAll}
toggle={toggle} onSearchChange={onSearchChange}
toggleAll={toggleAll} onTabChange={onTabChange}
toolbar={toolbar} onTabDelete={onTabDelete}
/> onTabSave={onTabSave}
/>
<CategoryList
categories={categories}
disabled={disabled}
isChecked={isChecked}
isRoot={true}
pageInfo={pageInfo}
selected={selected}
settings={settings}
toggle={toggle}
toggleAll={toggleAll}
toolbar={toolbar}
onAdd={onAdd}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
onRowClick={onRowClick}
onUpdateListSettings={onUpdateListSettings}
/>
</Card>
</Container> </Container>
); );
}; };

View file

@ -1,9 +1,12 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import { RawDraftContentState } from "draft-js"; import { RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import { CardSpacer } from "@saleor/components/CardSpacer"; import { CardSpacer } from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
@ -178,21 +181,40 @@ export const CategoryUpdatePage: React.StatelessComponent<
</TabContainer> </TabContainer>
<CardSpacer /> <CardSpacer />
{currentTab === CategoryPageTab.categories && ( {currentTab === CategoryPageTab.categories && (
<CategoryList <Card>
disabled={disabled} <CardTitle
isRoot={false} title={intl.formatMessage({
categories={subcategories} defaultMessage: "All Subcategories",
onAdd={onAddCategory} description: "section header"
onRowClick={onCategoryClick} })}
onNextPage={onNextPage} toolbar={
onPreviousPage={onPreviousPage} <Button
pageInfo={pageInfo} color="primary"
toggle={toggle} variant="text"
toggleAll={toggleAll} onClick={onAddCategory}
selected={selected} >
isChecked={isChecked} <FormattedMessage
toolbar={subcategoryListToolbar} defaultMessage="Create subcategory"
/> description="button"
/>
</Button>
}
/>
<CategoryList
categories={subcategories}
disabled={disabled}
isChecked={isChecked}
isRoot={false}
pageInfo={pageInfo}
selected={selected}
toggle={toggle}
toggleAll={toggleAll}
toolbar={subcategoryListToolbar}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
onRowClick={onCategoryClick}
/>
</Card>
)} )}
{currentTab === CategoryPageTab.products && ( {currentTab === CategoryPageTab.products && (
<CategoryProducts <CategoryProducts

View file

@ -1,64 +1,83 @@
import { content } from "../storybook/stories/components/RichTextEditor"; import { content } from "../storybook/stories/components/RichTextEditor";
import { CategoryDetails_category } from "./types/CategoryDetails"; import { CategoryDetails_category } from "./types/CategoryDetails";
import { CategoryFragment } from "./types/CategoryFragment";
export const categories = [ export const categories: CategoryFragment[] = [
{ {
__typename: "Category",
children: { children: {
__typename: "CategoryCountableConnection",
totalCount: 2 totalCount: 2
}, },
id: "123123", id: "123123",
name: "Lorem ipsum dolor", name: "Lorem ipsum dolor",
products: { products: {
__typename: "ProductCountableConnection",
totalCount: 4 totalCount: 4
} }
}, },
{ {
__typename: "Category",
children: { children: {
__typename: "CategoryCountableConnection",
totalCount: 54 totalCount: 54
}, },
id: "876752", id: "876752",
name: "Mauris vehicula tortor vulputate", name: "Mauris vehicula tortor vulputate",
products: { products: {
__typename: "ProductCountableConnection",
totalCount: 3 totalCount: 3
} }
}, },
{ {
__typename: "Category",
children: { children: {
__typename: "CategoryCountableConnection",
totalCount: 2 totalCount: 2
}, },
id: "876542", id: "876542",
name: "Excepteur sint occaecat cupidatat non proident", name: "Excepteur sint occaecat cupidatat non proident",
products: { products: {
__typename: "ProductCountableConnection",
totalCount: 6 totalCount: 6
} }
}, },
{ {
__typename: "Category",
children: { children: {
__typename: "CategoryCountableConnection",
totalCount: 6 totalCount: 6
}, },
id: "875352", id: "875352",
name: "Ut enim ad minim veniam", name: "Ut enim ad minim veniam",
products: { products: {
__typename: "ProductCountableConnection",
totalCount: 12 totalCount: 12
} }
}, },
{ {
__typename: "Category",
children: { children: {
__typename: "CategoryCountableConnection",
totalCount: 76 totalCount: 76
}, },
id: "865752", id: "865752",
name: "Duis aute irure dolor in reprehenderit", name: "Duis aute irure dolor in reprehenderit",
products: { products: {
__typename: "ProductCountableConnection",
totalCount: 43 totalCount: 43
} }
}, },
{ {
__typename: "Category",
children: { children: {
__typename: "CategoryCountableConnection",
totalCount: 11 totalCount: 11
}, },
id: "878752", id: "878752",
name: "Neque porro quisquam est", name: "Neque porro quisquam est",
products: { products: {
__typename: "ProductCountableConnection",
totalCount: 21 totalCount: 21
} }
} }

View file

@ -7,6 +7,18 @@ import {
} from "./types/CategoryDetails"; } from "./types/CategoryDetails";
import { RootCategories } from "./types/RootCategories"; import { RootCategories } from "./types/RootCategories";
export const categoryFragment = gql`
fragment CategoryFragment on Category {
id
name
children {
totalCount
}
products {
totalCount
}
}
`;
export const categoryDetailsFragment = gql` export const categoryDetailsFragment = gql`
fragment CategoryDetailsFragment on Category { fragment CategoryDetailsFragment on Category {
id id
@ -25,11 +37,13 @@ export const categoryDetailsFragment = gql`
`; `;
export const rootCategories = gql` export const rootCategories = gql`
${categoryFragment}
query RootCategories( query RootCategories(
$first: Int $first: Int
$after: String $after: String
$last: Int $last: Int
$before: String $before: String
$filter: CategoryFilterInput
) { ) {
categories( categories(
level: 0 level: 0
@ -37,17 +51,11 @@ export const rootCategories = gql`
after: $after after: $after
last: $last last: $last
before: $before before: $before
filter: $filter
) { ) {
edges { edges {
node { node {
id ...CategoryFragment
name
children {
totalCount
}
products {
totalCount
}
} }
} }
pageInfo { pageInfo {
@ -64,6 +72,7 @@ export const TypedRootCategoriesQuery = TypedQuery<RootCategories, {}>(
); );
export const categoryDetails = gql` export const categoryDetails = gql`
${categoryFragment}
${categoryDetailsFragment} ${categoryDetailsFragment}
query CategoryDetails( query CategoryDetails(
$id: ID! $id: ID!
@ -77,14 +86,7 @@ export const categoryDetails = gql`
children(first: 20) { children(first: 20) {
edges { edges {
node { node {
id ...CategoryFragment
name
children {
totalCount
}
products {
totalCount
}
} }
} }
} }

View file

@ -0,0 +1,25 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL fragment: CategoryFragment
// ====================================================
export interface CategoryFragment_children {
__typename: "CategoryCountableConnection";
totalCount: number | null;
}
export interface CategoryFragment_products {
__typename: "ProductCountableConnection";
totalCount: number | null;
}
export interface CategoryFragment {
__typename: "Category";
id: string;
name: string;
children: CategoryFragment_children | null;
products: CategoryFragment_products | null;
}

View file

@ -2,6 +2,8 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { CategoryFilterInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: RootCategories // GraphQL query operation: RootCategories
// ==================================================== // ====================================================
@ -52,4 +54,5 @@ export interface RootCategoriesVariables {
after?: string | null; after?: string | null;
last?: number | null; last?: number | null;
before?: string | null; before?: string | null;
filter?: CategoryFilterInput | null;
} }

View file

@ -1,14 +1,27 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { ActiveTab, BulkAction, Dialog, Pagination } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
TabActionDialog
} from "../types";
import { CategoryPageTab } from "./components/CategoryUpdatePage"; import { CategoryPageTab } from "./components/CategoryUpdatePage";
const categorySectionUrl = "/categories/"; const categorySectionUrl = "/categories/";
export const categoryListPath = categorySectionUrl; export const categoryListPath = categorySectionUrl;
export type CategoryListUrlDialog = "delete"; export enum CategoryListUrlFiltersEnum {
export type CategoryListUrlQueryParams = BulkAction & query = "query"
}
export type CategoryListUrlFilters = Filters<CategoryListUrlFiltersEnum>;
export type CategoryListUrlDialog = "delete" | TabActionDialog;
export type CategoryListUrlQueryParams = ActiveTab &
BulkAction &
CategoryListUrlFilters &
Dialog<CategoryListUrlDialog> & Dialog<CategoryListUrlDialog> &
Pagination; Pagination;
export const categoryListUrl = (params?: CategoryListUrlQueryParams) => export const categoryListUrl = (params?: CategoryListUrlQueryParams) =>

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -13,16 +17,26 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import { CategoryListPage } from "../components/CategoryListPage/CategoryListPage"; import { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage";
import { TypedCategoryBulkDeleteMutation } from "../mutations"; import { TypedCategoryBulkDeleteMutation } from "../../mutations";
import { TypedRootCategoriesQuery } from "../queries"; import { TypedRootCategoriesQuery } from "../../queries";
import { CategoryBulkDelete } from "../types/CategoryBulkDelete"; import { CategoryBulkDelete } from "../../types/CategoryBulkDelete";
import { import {
categoryAddUrl, categoryAddUrl,
categoryListUrl, categoryListUrl,
CategoryListUrlDialog,
CategoryListUrlFilters,
CategoryListUrlQueryParams, CategoryListUrlQueryParams,
categoryUrl categoryUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface CategoryListProps { interface CategoryListProps {
params: CategoryListUrlQueryParams; params: CategoryListUrlQueryParams;
@ -41,9 +55,77 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: CategoryListUrlFilters) => {
reset();
navigate(
categoryListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () =>
navigate(
categoryListUrl({
...params,
action: undefined,
ids: undefined
}),
true
);
const openModal = (action: CategoryListUrlDialog, ids?: string[]) =>
navigate(
categoryListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
categoryListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(categoryListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return ( return (
<TypedRootCategoriesQuery displayLoader variables={paginationState}> <TypedRootCategoriesQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.categories.pageInfo), maybe(() => data.categories.pageInfo),
@ -78,6 +160,14 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
() => data.categories.edges.map(edge => edge.node), () => data.categories.edges.map(edge => edge.node),
[] []
)} )}
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(categoryListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
settings={settings} settings={settings}
onAdd={() => navigate(categoryAddUrl())} onAdd={() => navigate(categoryAddUrl())}
onRowClick={id => () => navigate(categoryUrl(id))} onRowClick={id => () => navigate(categoryUrl(id))}
@ -134,7 +224,7 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
> >
<FormattedMessage <FormattedMessage
defaultMessage="Are you sure you want to delete {counter, plural, defaultMessage="Are you sure you want to delete {counter, plural,
one {this attribute} one {this category}
other {{displayQuantity} categories} other {{displayQuantity} categories}
}?" }?"
values={{ values={{
@ -148,6 +238,19 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." /> <FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { CategoryFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
CategoryListUrlFilters,
CategoryListUrlFiltersEnum,
CategoryListUrlQueryParams
} from "../../urls";
export const CATEGORY_FILTERS_KEY = "categoryFilters";
export function getFilterVariables(
params: CategoryListUrlFilters
): CategoryFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<CategoryListUrlFilters>(CATEGORY_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
CategoryListUrlQueryParams,
CategoryListUrlFilters
>(CategoryListUrlFiltersEnum);

View file

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

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -74,118 +73,110 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
const intl = useIntl(); const intl = useIntl();
return ( return (
<Card> <Table>
<Table> <TableHead
<TableHead colSpan={numberOfColumns}
colSpan={numberOfColumns} selected={selected}
selected={selected} disabled={disabled}
disabled={disabled} items={collections}
items={collections} toggleAll={toggleAll}
toggleAll={toggleAll} toolbar={toolbar}
toolbar={toolbar} >
> <TableCell className={classes.colName}>
<TableCell className={classes.colName}> <FormattedMessage defaultMessage="Category Name" />
<FormattedMessage defaultMessage="Category Name" /> </TableCell>
</TableCell> <TableCell className={classes.colProducts}>
<TableCell className={classes.colProducts}> <FormattedMessage defaultMessage="No. of Products" />
<FormattedMessage defaultMessage="No. of Products" /> </TableCell>
</TableCell> <TableCell className={classes.colAvailability}>
<TableCell className={classes.colAvailability}> <FormattedMessage
<FormattedMessage defaultMessage="Availability"
defaultMessage="Availability" description="collection availability"
description="collection availability" />
/> </TableCell>
</TableCell> </TableHead>
</TableHead> <TableFooter>
<TableFooter> <TableRow>
<TableRow> <TablePagination
<TablePagination colSpan={numberOfColumns}
colSpan={numberOfColumns} settings={settings}
settings={settings} hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
hasNextPage={ onNextPage={onNextPage}
pageInfo && !disabled ? pageInfo.hasNextPage : false onUpdateListSettings={onUpdateListSettings}
} hasPreviousPage={
onNextPage={onNextPage} pageInfo && !disabled ? pageInfo.hasPreviousPage : false
onUpdateListSettings={onUpdateListSettings} }
hasPreviousPage={ onPreviousPage={onPreviousPage}
pageInfo && !disabled ? pageInfo.hasPreviousPage : false />
} </TableRow>
onPreviousPage={onPreviousPage} </TableFooter>
/> <TableBody>
</TableRow> {renderCollection(
</TableFooter> collections,
<TableBody> collection => {
{renderCollection( const isSelected = collection ? isChecked(collection.id) : false;
collections, return (
collection => { <TableRow
const isSelected = collection className={classes.tableRow}
? isChecked(collection.id) hover={!!collection}
: false; onClick={collection ? onRowClick(collection.id) : undefined}
return ( key={collection ? collection.id : "skeleton"}
<TableRow selected={isSelected}
className={classes.tableRow} >
hover={!!collection} <TableCell padding="checkbox">
onClick={collection ? onRowClick(collection.id) : undefined} <Checkbox
key={collection ? collection.id : "skeleton"} checked={isSelected}
selected={isSelected} disabled={disabled}
> disableClickPropagation
<TableCell padding="checkbox"> onChange={() => toggle(collection.id)}
<Checkbox />
checked={isSelected} </TableCell>
disabled={disabled} <TableCell className={classes.colName}>
disableClickPropagation {maybe<React.ReactNode>(
onChange={() => toggle(collection.id)} () => collection.name,
/> <Skeleton />
</TableCell> )}
<TableCell className={classes.colName}> </TableCell>
{maybe<React.ReactNode>( <TableCell className={classes.colProducts}>
() => collection.name, {maybe<React.ReactNode>(
<Skeleton /> () => collection.products.totalCount,
)} <Skeleton />
</TableCell> )}
<TableCell className={classes.colProducts}> </TableCell>
{maybe<React.ReactNode>( <TableCell className={classes.colAvailability}>
() => collection.products.totalCount, {maybe(
<Skeleton /> () => (
)} <StatusLabel
</TableCell> status={collection.isPublished ? "success" : "error"}
<TableCell className={classes.colAvailability}> label={
{maybe( collection.isPublished
() => ( ? intl.formatMessage({
<StatusLabel defaultMessage: "Published",
status={ description: "collection is published"
collection.isPublished ? "success" : "error" })
} : intl.formatMessage({
label={ defaultMessage: "Not published",
collection.isPublished description: "collection is not published"
? intl.formatMessage({ })
defaultMessage: "Published", }
description: "collection is published" />
}) ),
: intl.formatMessage({ <Skeleton />
defaultMessage: "Not published", )}
description: "collection is not published"
})
}
/>
),
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No collections found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) );
)} },
</TableBody> () => (
</Table> <TableRow>
</Card> <TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No collections found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
); );
} }
); );

View file

@ -1,22 +1,40 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { Container } from "@saleor/components/Container"; import { Container } from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import { CollectionList_collections_edges_node } from "../../types/CollectionList"; import { CollectionList_collections_edges_node } from "../../types/CollectionList";
import CollectionList from "../CollectionList/CollectionList"; import CollectionList from "../CollectionList/CollectionList";
export interface CollectionListPageProps extends PageListProps, ListActions { export interface CollectionListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
collections: CollectionList_collections_edges_node[]; collections: CollectionList_collections_edges_node[];
} }
const CollectionListPage: React.StatelessComponent<CollectionListPageProps> = ({ const CollectionListPage: React.StatelessComponent<CollectionListPageProps> = ({
currentTab,
disabled, disabled,
initialSearch,
onAdd, onAdd,
onAll,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -36,7 +54,26 @@ const CollectionListPage: React.StatelessComponent<CollectionListPageProps> = ({
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<CollectionList disabled={disabled} {...listProps} /> <Card>
<SearchBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Collections",
description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Collection"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<CollectionList disabled={disabled} {...listProps} />
</Card>
</Container> </Container>
); );
}; };

View file

@ -60,8 +60,15 @@ export const collectionList = gql`
$after: String $after: String
$last: Int $last: Int
$before: String $before: String
$filter: CollectionFilterInput
) { ) {
collections(first: $first, after: $after, before: $before, last: $last) { collections(
first: $first
after: $after
before: $before
last: $last
filter: $filter
) {
edges { edges {
node { node {
...CollectionFragment ...CollectionFragment

View file

@ -2,6 +2,8 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { CollectionFilterInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: CollectionList // GraphQL query operation: CollectionList
// ==================================================== // ====================================================
@ -47,4 +49,5 @@ export interface CollectionListVariables {
after?: string | null; after?: string | null;
last?: number | null; last?: number | null;
before?: string | null; before?: string | null;
filter?: CollectionFilterInput | null;
} }

View file

@ -1,13 +1,30 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
TabActionDialog
} from "../types";
const collectionSectionUrl = "/collections/"; const collectionSectionUrl = "/collections/";
export const collectionListPath = collectionSectionUrl; export const collectionListPath = collectionSectionUrl;
export type CollectionListUrlDialog = "publish" | "unpublish" | "remove"; export enum CollectionListUrlFiltersEnum {
export type CollectionListUrlQueryParams = BulkAction & query = "query"
}
export type CollectionListUrlFilters = Filters<CollectionListUrlFiltersEnum>;
export type CollectionListUrlDialog =
| "publish"
| "unpublish"
| "remove"
| TabActionDialog;
export type CollectionListUrlQueryParams = ActiveTab &
BulkAction &
CollectionListUrlFilters &
Dialog<CollectionListUrlDialog> & Dialog<CollectionListUrlDialog> &
Pagination; Pagination;
export const collectionListUrl = (params?: CollectionListUrlQueryParams) => export const collectionListUrl = (params?: CollectionListUrlQueryParams) =>

View file

@ -6,6 +6,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -16,21 +20,30 @@ import usePaginator, {
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import CollectionListPage from "../components/CollectionListPage/CollectionListPage"; import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
import { import {
TypedCollectionBulkDelete, TypedCollectionBulkDelete,
TypedCollectionBulkPublish TypedCollectionBulkPublish
} from "../mutations"; } from "../../mutations";
import { TypedCollectionListQuery } from "../queries"; import { TypedCollectionListQuery } from "../../queries";
import { CollectionBulkDelete } from "../types/CollectionBulkDelete"; import { CollectionBulkDelete } from "../../types/CollectionBulkDelete";
import { CollectionBulkPublish } from "../types/CollectionBulkPublish"; import { CollectionBulkPublish } from "../../types/CollectionBulkPublish";
import { import {
collectionAddUrl, collectionAddUrl,
collectionListUrl, collectionListUrl,
CollectionListUrlDialog, CollectionListUrlDialog,
CollectionListUrlFilters,
CollectionListUrlQueryParams, CollectionListUrlQueryParams,
collectionUrl collectionUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface CollectionListProps { interface CollectionListProps {
params: CollectionListUrlQueryParams; params: CollectionListUrlQueryParams;
@ -50,6 +63,26 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: CollectionListUrlFilters) => {
reset();
navigate(
collectionListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () => const closeModal = () =>
navigate( navigate(
collectionListUrl({ collectionListUrl({
@ -60,17 +93,47 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
true true
); );
const openModal = (action: CollectionListUrlDialog, ids: string[]) => const openModal = (action: CollectionListUrlDialog, ids?: string[]) =>
navigate( navigate(
collectionListUrl({ collectionListUrl({
...params,
action, action,
ids ids
}) })
); );
const handleTabChange = (tab: number) => {
reset();
navigate(
collectionListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(collectionListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return ( return (
<TypedCollectionListQuery displayLoader variables={paginationState}> <TypedCollectionListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.collections.pageInfo), maybe(() => data.collections.pageInfo),
@ -130,7 +193,15 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
return ( return (
<> <>
<CollectionListPage <CollectionListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAdd={() => navigate(collectionAddUrl)} onAdd={() => navigate(collectionAddUrl)}
onAll={() => navigate(collectionListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading} disabled={loading}
collections={maybe(() => collections={maybe(() =>
data.collections.edges.map(edge => edge.node) data.collections.edges.map(edge => edge.node)
@ -289,6 +360,19 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
/> />
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { CollectionFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
CollectionListUrlFilters,
CollectionListUrlFiltersEnum,
CollectionListUrlQueryParams
} from "../../urls";
export const COLLECTION_FILTERS_KEY = "collectionFilters";
export function getFilterVariables(
params: CollectionListUrlFilters
): CollectionFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<CollectionListUrlFilters>(COLLECTION_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
CollectionListUrlQueryParams,
CollectionListUrlFilters
>(CollectionListUrlFiltersEnum);

View file

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

View file

@ -0,0 +1,109 @@
import { Theme } from "@material-ui/core/styles";
import TextField, { TextFieldProps } from "@material-ui/core/TextField";
import { makeStyles } from "@material-ui/styles";
import React from "react";
import { FilterContentSubmitData, IFilter } from "../Filter";
import Filter from "./Filter";
const useInputStyles = makeStyles({
input: {
padding: "10px 12px"
},
root: {
flex: 1
}
});
const Search: React.FC<TextFieldProps> = props => {
const classes = useInputStyles({});
return (
<TextField
{...props}
className={classes.root}
inputProps={{
className: classes.input
}}
/>
);
};
const useStyles = makeStyles(
(theme: Theme) => ({
actionContainer: {
display: "flex",
flexWrap: "wrap",
padding: `${theme.spacing.unit * 1.5}px ${theme.spacing.unit * 3}px ${
theme.spacing.unit
}px`
}
}),
{
name: "FilterActions"
}
);
export interface FilterActionsPropsSearch {
placeholder: string;
search: string;
onSearchChange: (event: React.ChangeEvent<any>) => void;
}
export interface FilterActionsPropsFilters<TKeys = string> {
currencySymbol: string;
menu: IFilter<TKeys>;
filterLabel: string;
onFilterAdd: (filter: FilterContentSubmitData<TKeys>) => void;
}
export const FilterActionsOnlySearch: React.FC<
FilterActionsPropsSearch
> = props => {
const { onSearchChange, placeholder, search } = props;
const classes = useStyles(props);
return (
<div className={classes.actionContainer}>
<Search
fullWidth
placeholder={placeholder}
value={search}
onChange={onSearchChange}
/>
</div>
);
};
export type FilterActionsProps = FilterActionsPropsSearch &
FilterActionsPropsFilters;
const FilterActions: React.FC<FilterActionsProps> = props => {
const {
currencySymbol,
filterLabel,
menu,
onFilterAdd,
onSearchChange,
placeholder,
search
} = props;
const classes = useStyles(props);
return (
<div className={classes.actionContainer}>
<Filter
currencySymbol={currencySymbol}
menu={menu}
filterLabel={filterLabel}
onFilterAdd={onFilterAdd}
/>
<Search
fullWidth
placeholder={placeholder}
value={search}
onChange={onSearchChange}
/>
</div>
);
};
FilterActions.displayName = "FilterActions";
export default FilterActions;

View file

@ -0,0 +1,98 @@
import { Theme } from "@material-ui/core/styles";
import { makeStyles } from "@material-ui/styles";
import React from "react";
import { FormattedMessage } from "react-intl";
import { SearchPageProps } from "../../types";
import Debounce from "../Debounce";
import { FilterActionsOnlySearch } from "../Filter/FilterActions";
import Hr from "../Hr";
import Link from "../Link";
export interface FilterSearchProps extends SearchPageProps {
displaySearchAction: "save" | "delete" | null;
searchPlaceholder: string;
onSearchDelete?: () => void;
onSearchSave?: () => void;
}
const useStyles = makeStyles(
(theme: Theme) => ({
tabAction: {
display: "inline-block"
},
tabActionContainer: {
borderBottom: `1px solid ${theme.palette.divider}`,
display: "flex",
justifyContent: "flex-end",
marginTop: theme.spacing.unit,
padding: `0 ${theme.spacing.unit * 3}px ${theme.spacing.unit}px`
}
}),
{
name: "FilterSearch"
}
);
const FilterSearch: React.FC<FilterSearchProps> = props => {
const {
displaySearchAction,
initialSearch,
onSearchChange,
onSearchDelete,
onSearchSave,
searchPlaceholder
} = props;
const classes = useStyles(props);
const [search, setSearch] = React.useState(initialSearch);
React.useEffect(() => setSearch(initialSearch), [initialSearch]);
return (
<Debounce debounceFn={onSearchChange}>
{debounceSearchChange => {
const handleSearchChange = (event: React.ChangeEvent<any>) => {
const value = event.target.value;
setSearch(value);
debounceSearchChange(value);
};
return (
<>
<FilterActionsOnlySearch
{...props}
placeholder={searchPlaceholder}
search={search}
onSearchChange={handleSearchChange}
/>
{!!displaySearchAction ? (
<div className={classes.tabActionContainer}>
<div className={classes.tabAction}>
{displaySearchAction === "save" ? (
<Link onClick={onSearchSave}>
<FormattedMessage
defaultMessage="Save Custom Search"
description="button"
/>
</Link>
) : (
<Link onClick={onSearchDelete}>
<FormattedMessage
defaultMessage="Delete Search"
description="button"
/>
</Link>
)}
</div>
</div>
) : (
<Hr />
)}
</>
);
}}
</Debounce>
);
};
FilterSearch.displayName = "FilterSearch";
export default FilterSearch;

View file

@ -6,9 +6,8 @@ import Debounce from "../Debounce";
import { IFilter } from "../Filter/types"; import { IFilter } from "../Filter/types";
import FilterTabs, { FilterChips, FilterTab } from "../TableFilter"; import FilterTabs, { FilterChips, FilterTab } from "../TableFilter";
export interface FilterBarProps<TUrlFilters = object, TFilterKeys = any> export interface FilterBarProps<TKeys = string> extends FilterProps {
extends FilterProps<TUrlFilters, TFilterKeys> { filterMenu: IFilter<TKeys>;
filterMenu: IFilter<TFilterKeys>;
} }
const FilterBar: React.FC<FilterBarProps> = ({ const FilterBar: React.FC<FilterBarProps> = ({
@ -16,32 +15,32 @@ const FilterBar: React.FC<FilterBarProps> = ({
currencySymbol, currencySymbol,
filterLabel, filterLabel,
filtersList, filtersList,
filterTabs,
filterMenu, filterMenu,
currentTab, currentTab,
initialSearch, initialSearch,
searchPlaceholder, searchPlaceholder,
tabs,
onAll, onAll,
onSearchChange, onSearchChange,
onFilterAdd, onFilterAdd,
onFilterSave,
onTabChange, onTabChange,
onFilterDelete onTabDelete,
onTabSave
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const [search, setSearch] = React.useState(initialSearch); const [search, setSearch] = React.useState(initialSearch);
React.useEffect(() => setSearch(initialSearch), [currentTab, initialSearch]); React.useEffect(() => setSearch(initialSearch), [currentTab, initialSearch]);
const isCustom = currentTab === filterTabs.length + 1; const isCustom = currentTab === tabs.length + 1;
return ( return (
<> <>
<FilterTabs currentTab={currentTab}> <FilterTabs currentTab={currentTab}>
<FilterTab label={allTabLabel} onClick={onAll} /> <FilterTab label={allTabLabel} onClick={onAll} />
{filterTabs.map((tab, tabIndex) => ( {tabs.map((tab, tabIndex) => (
<FilterTab <FilterTab
onClick={() => onTabChange(tabIndex + 1)} onClick={() => onTabChange(tabIndex + 1)}
label={tab.name} label={tab}
key={tabIndex} key={tabIndex}
/> />
))} ))}
@ -65,6 +64,9 @@ const FilterBar: React.FC<FilterBarProps> = ({
return ( return (
<FilterChips <FilterChips
currencySymbol={currencySymbol} currencySymbol={currencySymbol}
displayTabAction={
!!initialSearch ? (isCustom ? "save" : "delete") : null
}
menu={filterMenu} menu={filterMenu}
filtersList={filtersList} filtersList={filtersList}
filterLabel={filterLabel} filterLabel={filterLabel}
@ -72,9 +74,9 @@ const FilterBar: React.FC<FilterBarProps> = ({
search={search} search={search}
onSearchChange={handleSearchChange} onSearchChange={handleSearchChange}
onFilterAdd={onFilterAdd} onFilterAdd={onFilterAdd}
onFilterSave={onFilterSave} onFilterSave={onTabSave}
isCustomSearch={isCustom} isCustomSearch={isCustom}
onFilterDelete={onFilterDelete} onFilterDelete={onTabDelete}
/> />
); );
}} }}

View file

@ -56,6 +56,7 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
<> <>
<DialogContent> <DialogContent>
<TextField <TextField
autoFocus
fullWidth fullWidth
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Search Name", defaultMessage: "Search Name",

View file

@ -0,0 +1,65 @@
import React from "react";
import { useIntl } from "react-intl";
import { SearchPageProps, TabPageProps } from "@saleor/types";
import FilterSearch from "../Filter/FilterSearch";
import FilterTabs, { FilterTab } from "../TableFilter";
export interface SearchBarProps extends SearchPageProps, TabPageProps {
allTabLabel: string;
searchPlaceholder: string;
}
const SearchBar: React.FC<SearchBarProps> = props => {
const {
allTabLabel,
currentTab,
initialSearch,
onSearchChange,
searchPlaceholder,
tabs,
onAll,
onTabChange,
onTabDelete,
onTabSave
} = props;
const intl = useIntl();
const isCustom = currentTab === tabs.length + 1;
return (
<>
<FilterTabs currentTab={currentTab}>
<FilterTab label={allTabLabel} onClick={onAll} />
{tabs.map((tab, tabIndex) => (
<FilterTab
onClick={() => onTabChange(tabIndex + 1)}
label={tab}
key={tabIndex}
/>
))}
{isCustom && (
<FilterTab
onClick={() => undefined}
label={intl.formatMessage({
defaultMessage: "Custom Filter"
})}
/>
)}
</FilterTabs>
<FilterSearch
displaySearchAction={
!!initialSearch ? (isCustom ? "save" : "delete") : null
}
initialSearch={initialSearch}
searchPlaceholder={searchPlaceholder}
onSearchChange={onSearchChange}
onSearchDelete={onTabDelete}
onSearchSave={onTabSave}
/>
</>
);
};
SearchBar.displayName = "SearchBar";
export default SearchBar;

View file

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

View file

@ -1,14 +1,14 @@
import ButtonBase from "@material-ui/core/ButtonBase"; import ButtonBase from "@material-ui/core/ButtonBase";
import { Theme } from "@material-ui/core/styles"; import { Theme } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator"; import { fade } from "@material-ui/core/styles/colorManipulator";
import TextField, { TextFieldProps } from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import ClearIcon from "@material-ui/icons/Clear"; import ClearIcon from "@material-ui/icons/Clear";
import { createStyles, makeStyles, useTheme } from "@material-ui/styles"; import { makeStyles, useTheme } from "@material-ui/styles";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import Filter, { FilterContentSubmitData, IFilter } from "../Filter"; import Filter from "../Filter";
import FilterActions, { FilterActionsProps } from "../Filter/FilterActions";
import Hr from "../Hr"; import Hr from "../Hr";
import Link from "../Link"; import Link from "../Link";
@ -17,110 +17,76 @@ export interface Filter {
onClick: () => void; onClick: () => void;
} }
const useInputStyles = makeStyles({
input: {
padding: "10px 12px"
},
root: {
flex: 1
}
});
const Search: React.FC<TextFieldProps> = props => {
const classes = useInputStyles({});
return (
<TextField
{...props}
className={classes.root}
inputProps={{
className: classes.input
}}
/>
);
};
const useStyles = makeStyles( const useStyles = makeStyles(
(theme: Theme) => (theme: Theme) => ({
createStyles({ filterButton: {
actionContainer: { alignItems: "center",
display: "flex", backgroundColor: fade(theme.palette.primary.main, 0.8),
flexWrap: "wrap", borderRadius: "19px",
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${ display: "flex",
theme.spacing.unit height: "38px",
}px ${theme.spacing.unit * 3}px` justifyContent: "space-around",
margin: `0 ${theme.spacing.unit * 2}px ${theme.spacing.unit}px`,
marginLeft: 0,
padding: `0 ${theme.spacing.unit * 2}px`
},
filterChipContainer: {
display: "flex",
flex: 1,
flexWrap: "wrap"
},
filterContainer: {
"& a": {
paddingBottom: 10
}, },
filterButton: { borderBottom: `1px solid ${theme.palette.divider}`,
alignItems: "center", display: "flex",
backgroundColor: fade(theme.palette.primary.main, 0.8), marginTop: theme.spacing.unit,
borderRadius: "19px", padding: `0 ${theme.spacing.unit * 3}px ${theme.spacing.unit}px`
display: "flex", },
height: "38px", filterIcon: {
justifyContent: "space-around", color: theme.palette.common.white,
margin: `0 ${theme.spacing.unit * 2}px ${theme.spacing.unit}px`, height: 16,
marginLeft: 0, width: 16
padding: "0 16px" },
}, filterIconContainer: {
filterChipContainer: { WebkitAppearance: "none",
display: "flex", background: "transparent",
flex: 1, border: "none",
flexWrap: "wrap" borderRadius: "100%",
}, cursor: "pointer",
filterContainer: { height: 32,
"& a": { marginRight: -13,
paddingBottom: 10 padding: 8,
}, width: 32
borderBottom: `1px solid ${theme.palette.divider}`, },
display: "flex", filterLabel: {
marginTop: theme.spacing.unit, marginBottom: theme.spacing.unit
padding: `0 ${theme.spacing.unit * 3}px ${theme.spacing.unit}px` },
}, filterText: {
filterIcon: { color: theme.palette.common.white,
color: theme.palette.common.white, fontSize: 14,
height: 16, fontWeight: 400 as 400,
width: 16 lineHeight: "38px"
}, }
filterIconContainer: { }),
WebkitAppearance: "none",
background: "transparent",
border: "none",
borderRadius: "100%",
cursor: "pointer",
height: 32,
marginRight: -13,
padding: 8,
width: 32
},
filterLabel: {
marginBottom: theme.spacing.unit
},
filterText: {
color: theme.palette.common.white,
fontSize: 14,
fontWeight: 400 as 400,
lineHeight: "38px"
}
}),
{ {
name: "FilterChips" name: "FilterChips"
} }
); );
interface FilterChipProps<TFilterKeys = string> { interface FilterChipProps extends FilterActionsProps {
currencySymbol: string; displayTabAction: "save" | "delete" | null;
menu: IFilter<TFilterKeys>;
filtersList: Filter[]; filtersList: Filter[];
filterLabel: string;
placeholder: string;
search: string; search: string;
isCustomSearch: boolean; isCustomSearch: boolean;
onSearchChange: (event: React.ChangeEvent<any>) => void;
onFilterAdd: (filter: FilterContentSubmitData<TFilterKeys>) => void;
onFilterDelete: () => void; onFilterDelete: () => void;
onFilterSave: () => void; onFilterSave: () => void;
} }
export const FilterChips: React.FC<FilterChipProps> = ({ export const FilterChips: React.FC<FilterChipProps> = ({
currencySymbol, currencySymbol,
displayTabAction,
filtersList, filtersList,
menu, menu,
filterLabel, filterLabel,
@ -129,29 +95,23 @@ export const FilterChips: React.FC<FilterChipProps> = ({
search, search,
onFilterAdd, onFilterAdd,
onFilterSave, onFilterSave,
onFilterDelete, onFilterDelete
isCustomSearch
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const classes = useStyles({ theme }); const classes = useStyles({ theme });
return ( return (
<> <>
<div className={classes.actionContainer}> <FilterActions
<Filter currencySymbol={currencySymbol}
currencySymbol={currencySymbol} menu={menu}
menu={menu} filterLabel={filterLabel}
filterLabel={filterLabel} placeholder={placeholder}
onFilterAdd={onFilterAdd} search={search}
/> onSearchChange={onSearchChange}
<Search onFilterAdd={onFilterAdd}
fullWidth />
placeholder={placeholder} {search || (filtersList && filtersList.length > 0) ? (
value={search}
onChange={onSearchChange}
/>
</div>
{search || (filtersList && filtersList.length) ? (
<div className={classes.filterContainer}> <div className={classes.filterContainer}>
<div className={classes.filterChipContainer}> <div className={classes.filterChipContainer}>
{filtersList.map(filter => ( {filtersList.map(filter => (
@ -168,7 +128,7 @@ export const FilterChips: React.FC<FilterChipProps> = ({
</div> </div>
))} ))}
</div> </div>
{isCustomSearch ? ( {displayTabAction === "save" ? (
<Link onClick={onFilterSave}> <Link onClick={onFilterSave}>
<FormattedMessage <FormattedMessage
defaultMessage="Save Custom Search" defaultMessage="Save Custom Search"
@ -176,12 +136,14 @@ export const FilterChips: React.FC<FilterChipProps> = ({
/> />
</Link> </Link>
) : ( ) : (
<Link onClick={onFilterDelete}> displayTabAction === "delete" && (
<FormattedMessage <Link onClick={onFilterDelete}>
defaultMessage="Delete Search" <FormattedMessage
description="button" defaultMessage="Delete Search"
/> description="button"
</Link> />
</Link>
)
)} )}
</div> </div>
) : ( ) : (

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -68,89 +67,87 @@ const CustomerList = withStyles(styles, { name: "CustomerList" })(
selected, selected,
isChecked isChecked
}: CustomerListProps) => ( }: CustomerListProps) => (
<Card> <Table>
<Table> <TableHead
<TableHead colSpan={numberOfColumns}
colSpan={numberOfColumns} selected={selected}
selected={selected} disabled={disabled}
disabled={disabled} items={customers}
items={customers} toggleAll={toggleAll}
toggleAll={toggleAll} toolbar={toolbar}
toolbar={toolbar} >
> <TableCell className={classes.colName}>
<TableCell className={classes.colName}> <FormattedMessage defaultMessage="Customer Name" />
<FormattedMessage defaultMessage="Customer Name" /> </TableCell>
</TableCell> <TableCell className={classes.colEmail}>
<TableCell className={classes.colEmail}> <FormattedMessage defaultMessage="Customer Email" />
<FormattedMessage defaultMessage="Customer Email" /> </TableCell>
</TableCell> <TableCell className={classes.colOrders}>
<TableCell className={classes.colOrders}> <FormattedMessage defaultMessage="No. of Orders" />
<FormattedMessage defaultMessage="No. of Orders" /> </TableCell>
</TableCell> </TableHead>
</TableHead> <TableFooter>
<TableFooter> <TableRow>
<TableRow> <TablePagination
<TablePagination colSpan={numberOfColumns}
colSpan={numberOfColumns} settings={settings}
settings={settings} hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false} onNextPage={onNextPage}
onNextPage={onNextPage} onUpdateListSettings={onUpdateListSettings}
onUpdateListSettings={onUpdateListSettings} hasPreviousPage={
hasPreviousPage={ pageInfo && !disabled ? pageInfo.hasPreviousPage : false
pageInfo && !disabled ? pageInfo.hasPreviousPage : false }
} onPreviousPage={onPreviousPage}
onPreviousPage={onPreviousPage} />
/> </TableRow>
</TableRow> </TableFooter>
</TableFooter> <TableBody>
<TableBody> {renderCollection(
{renderCollection( customers,
customers, customer => {
customer => { const isSelected = customer ? isChecked(customer.id) : false;
const isSelected = customer ? isChecked(customer.id) : false;
return ( return (
<TableRow <TableRow
className={!!customer ? classes.tableRow : undefined} className={!!customer ? classes.tableRow : undefined}
hover={!!customer} hover={!!customer}
key={customer ? customer.id : "skeleton"} key={customer ? customer.id : "skeleton"}
selected={isSelected} selected={isSelected}
onClick={customer ? onRowClick(customer.id) : undefined} onClick={customer ? onRowClick(customer.id) : undefined}
> >
<TableCell padding="checkbox"> <TableCell padding="checkbox">
<Checkbox <Checkbox
checked={isSelected} checked={isSelected}
disabled={disabled} disabled={disabled}
disableClickPropagation disableClickPropagation
onChange={() => toggle(customer.id)} onChange={() => toggle(customer.id)}
/> />
</TableCell> </TableCell>
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
{getUserName(customer)} {getUserName(customer)}
</TableCell> </TableCell>
<TableCell className={classes.colEmail}> <TableCell className={classes.colEmail}>
{maybe<React.ReactNode>(() => customer.email, <Skeleton />)} {maybe<React.ReactNode>(() => customer.email, <Skeleton />)}
</TableCell> </TableCell>
<TableCell className={classes.colOrders}> <TableCell className={classes.colOrders}>
{maybe<React.ReactNode>( {maybe<React.ReactNode>(
() => customer.orders.totalCount, () => customer.orders.totalCount,
<Skeleton /> <Skeleton />
)} )}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No customers found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) );
)} },
</TableBody> () => (
</Table> <TableRow>
</Card> <TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No customers found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
) )
); );
CustomerList.displayName = "CustomerList"; CustomerList.displayName = "CustomerList";

View file

@ -1,23 +1,41 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
import CustomerList from "../CustomerList/CustomerList"; import CustomerList from "../CustomerList/CustomerList";
export interface CustomerListPageProps extends PageListProps, ListActions { export interface CustomerListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
customers: ListCustomers_customers_edges_node[]; customers: ListCustomers_customers_edges_node[];
} }
const CustomerListPage: React.StatelessComponent<CustomerListPageProps> = ({ const CustomerListPage: React.StatelessComponent<CustomerListPageProps> = ({
currentTab,
customers, customers,
disabled, disabled,
initialSearch,
onAdd, onAdd,
onAll,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...customerListProps ...customerListProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -37,11 +55,30 @@ const CustomerListPage: React.StatelessComponent<CustomerListPageProps> = ({
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<CustomerList <Card>
customers={customers} <SearchBar
disabled={disabled} allTabLabel={intl.formatMessage({
{...customerListProps} defaultMessage: "All Customers",
/> description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Customer"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<CustomerList
customers={customers}
disabled={disabled}
{...customerListProps}
/>
</Card>
</Container> </Container>
); );
}; };

View file

@ -64,8 +64,15 @@ const customerList = gql`
$before: String $before: String
$first: Int $first: Int
$last: Int $last: Int
$filter: CustomerFilterInput
) { ) {
customers(after: $after, before: $before, first: $first, last: $last) { customers(
after: $after
before: $before
first: $first
last: $last
filter: $filter
) {
edges { edges {
node { node {
...CustomerFragment ...CustomerFragment

View file

@ -2,6 +2,8 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { CustomerFilterInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: ListCustomers // GraphQL query operation: ListCustomers
// ==================================================== // ====================================================
@ -48,4 +50,5 @@ export interface ListCustomersVariables {
before?: string | null; before?: string | null;
first?: number | null; first?: number | null;
last?: number | null; last?: number | null;
filter?: CustomerFilterInput | null;
} }

View file

@ -1,13 +1,27 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination, SingleAction } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
SingleAction,
TabActionDialog
} from "../types";
export const customerSection = "/customers/"; export const customerSection = "/customers/";
export const customerListPath = customerSection; export const customerListPath = customerSection;
export type CustomerListUrlDialog = "remove"; export enum CustomerListUrlFiltersEnum {
export type CustomerListUrlQueryParams = BulkAction & query = "query"
}
export type CustomerListUrlFilters = Filters<CustomerListUrlFiltersEnum>;
export type CustomerListUrlDialog = "remove" | TabActionDialog;
export type CustomerListUrlQueryParams = ActiveTab &
BulkAction &
CustomerListUrlFilters &
Dialog<CustomerListUrlDialog> & Dialog<CustomerListUrlDialog> &
Pagination; Pagination;
export const customerListUrl = (params?: CustomerListUrlQueryParams) => export const customerListUrl = (params?: CustomerListUrlQueryParams) =>

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -15,16 +19,26 @@ import usePaginator, {
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import CustomerListPage from "../components/CustomerListPage"; import CustomerListPage from "../../components/CustomerListPage";
import { TypedBulkRemoveCustomers } from "../mutations"; import { TypedBulkRemoveCustomers } from "../../mutations";
import { TypedCustomerListQuery } from "../queries"; import { TypedCustomerListQuery } from "../../queries";
import { BulkRemoveCustomers } from "../types/BulkRemoveCustomers"; import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers";
import { import {
customerAddUrl, customerAddUrl,
customerListUrl, customerListUrl,
CustomerListUrlDialog,
CustomerListUrlFilters,
CustomerListUrlQueryParams, CustomerListUrlQueryParams,
customerUrl customerUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface CustomerListProps { interface CustomerListProps {
params: CustomerListUrlQueryParams; params: CustomerListUrlQueryParams;
@ -44,6 +58,26 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: CustomerListUrlFilters) => {
reset();
navigate(
customerListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () => const closeModal = () =>
navigate( navigate(
customerListUrl({ customerListUrl({
@ -54,10 +88,47 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
true true
); );
const openModal = (action: CustomerListUrlDialog, ids?: string[]) =>
navigate(
customerListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
customerListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(customerListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return ( return (
<TypedCustomerListQuery displayLoader variables={paginationState}> <TypedCustomerListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.customers.pageInfo), maybe(() => data.customers.pageInfo),
@ -90,6 +161,14 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
return ( return (
<> <>
<CustomerListPage <CustomerListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(customerListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
customers={maybe(() => customers={maybe(() =>
data.customers.edges.map(edge => edge.node) data.customers.edges.map(edge => edge.node)
)} )}
@ -156,6 +235,19 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
/> />
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { CustomerFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
CustomerListUrlFilters,
CustomerListUrlFiltersEnum,
CustomerListUrlQueryParams
} from "../../urls";
export const CUSTOMER_FILTERS_KEY = "customerFilters";
export function getFilterVariables(
params: CustomerListUrlFilters
): CustomerFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<CustomerListUrlFilters>(CUSTOMER_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
CustomerListUrlQueryParams,
CustomerListUrlFilters
>(CustomerListUrlFiltersEnum);

View file

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

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -83,124 +82,119 @@ const SaleList = withStyles(styles, {
toggleAll, toggleAll,
toolbar toolbar
}: SaleListProps & WithStyles<typeof styles>) => ( }: SaleListProps & WithStyles<typeof styles>) => (
<Card> <Table>
<Table> <TableHead
<TableHead colSpan={numberOfColumns}
colSpan={numberOfColumns} selected={selected}
selected={selected} disabled={disabled}
disabled={disabled} items={sales}
items={sales} toggleAll={toggleAll}
toggleAll={toggleAll} toolbar={toolbar}
toolbar={toolbar} >
> <TableCell className={classes.colName}>
<TableCell className={classes.colName}> <FormattedMessage defaultMessage="Name" description="sale name" />
<FormattedMessage defaultMessage="Name" description="sale name" /> </TableCell>
</TableCell> <TableCell className={classes.colStart}>
<TableCell className={classes.colStart}> <FormattedMessage
<FormattedMessage defaultMessage="Starts"
defaultMessage="Starts" description="sale start date"
description="sale start date" />
/> </TableCell>
</TableCell> <TableCell className={classes.colEnd}>
<TableCell className={classes.colEnd}> <FormattedMessage defaultMessage="Ends" description="sale end date" />
<FormattedMessage </TableCell>
defaultMessage="Ends" <TableCell className={classes.colValue}>
description="sale end date" <FormattedMessage defaultMessage="Value" description="sale value" />
/> </TableCell>
</TableCell> </TableHead>
<TableCell className={classes.colValue}> <TableFooter>
<FormattedMessage defaultMessage="Value" description="sale value" /> <TableRow>
</TableCell> <TablePagination
</TableHead> colSpan={numberOfColumns}
<TableFooter> settings={settings}
<TableRow> hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
<TablePagination onNextPage={onNextPage}
colSpan={numberOfColumns} onUpdateListSettings={onUpdateListSettings}
settings={settings} hasPreviousPage={
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false} pageInfo && !disabled ? pageInfo.hasPreviousPage : false
onNextPage={onNextPage} }
onUpdateListSettings={onUpdateListSettings} onPreviousPage={onPreviousPage}
hasPreviousPage={ />
pageInfo && !disabled ? pageInfo.hasPreviousPage : false </TableRow>
} </TableFooter>
onPreviousPage={onPreviousPage} <TableBody>
/> {renderCollection(
</TableRow> sales,
</TableFooter> sale => {
<TableBody> const isSelected = sale ? isChecked(sale.id) : false;
{renderCollection(
sales,
sale => {
const isSelected = sale ? isChecked(sale.id) : false;
return ( return (
<TableRow <TableRow
className={!!sale ? classes.tableRow : undefined} className={!!sale ? classes.tableRow : undefined}
hover={!!sale} hover={!!sale}
key={sale ? sale.id : "skeleton"} key={sale ? sale.id : "skeleton"}
onClick={sale ? onRowClick(sale.id) : undefined}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(sale.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{maybe<React.ReactNode>(() => sale.name, <Skeleton />)}
</TableCell>
<TableCell className={classes.colStart}>
{sale && sale.startDate ? (
<Date date={sale.startDate} />
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colEnd}>
{sale && sale.endDate ? (
<Date date={sale.endDate} />
) : sale && sale.endDate === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colValue}
onClick={sale ? onRowClick(sale.id) : undefined} onClick={sale ? onRowClick(sale.id) : undefined}
selected={isSelected}
> >
<TableCell padding="checkbox"> {sale && sale.type && sale.value ? (
<Checkbox sale.type === SaleType.FIXED ? (
checked={isSelected} <Money
disabled={disabled} money={{
disableClickPropagation amount: sale.value,
onChange={() => toggle(sale.id)} currency: defaultCurrency
/> }}
</TableCell> />
<TableCell className={classes.colName}>
{maybe<React.ReactNode>(() => sale.name, <Skeleton />)}
</TableCell>
<TableCell className={classes.colStart}>
{sale && sale.startDate ? (
<Date date={sale.startDate} />
) : ( ) : (
<Skeleton /> <Percent amount={sale.value} />
)} )
</TableCell> ) : (
<TableCell className={classes.colEnd}> <Skeleton />
{sale && sale.endDate ? ( )}
<Date date={sale.endDate} />
) : sale && sale.endDate === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colValue}
onClick={sale ? onRowClick(sale.id) : undefined}
>
{sale && sale.type && sale.value ? (
sale.type === SaleType.FIXED ? (
<Money
money={{
amount: sale.value,
currency: defaultCurrency
}}
/>
) : (
<Percent amount={sale.value} />
)
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No sales found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) );
)} },
</TableBody> () => (
</Table> <TableRow>
</Card> <TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No sales found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
) )
); );
SaleList.displayName = "SaleList"; SaleList.displayName = "SaleList";

View file

@ -1,22 +1,40 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import { SaleList_sales_edges_node } from "../../types/SaleList"; import { SaleList_sales_edges_node } from "../../types/SaleList";
import SaleList from "../SaleList"; import SaleList from "../SaleList";
export interface SaleListPageProps extends PageListProps, ListActions { export interface SaleListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
defaultCurrency: string; defaultCurrency: string;
sales: SaleList_sales_edges_node[]; sales: SaleList_sales_edges_node[];
} }
const SaleListPage: React.StatelessComponent<SaleListPageProps> = ({ const SaleListPage: React.StatelessComponent<SaleListPageProps> = ({
currentTab,
initialSearch,
onAdd, onAdd,
onAll,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -28,7 +46,26 @@ const SaleListPage: React.StatelessComponent<SaleListPageProps> = ({
<FormattedMessage defaultMessage="Create Sale" description="button" /> <FormattedMessage defaultMessage="Create Sale" description="button" />
</Button> </Button>
</PageHeader> </PageHeader>
<SaleList {...listProps} /> <Card>
<SearchBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Sales",
description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Sale"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<SaleList {...listProps} />
</Card>
</Container> </Container>
); );
}; };

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -98,163 +97,155 @@ const VoucherList = withStyles(styles, {
toggleAll, toggleAll,
toolbar toolbar
}: VoucherListProps & WithStyles<typeof styles>) => ( }: VoucherListProps & WithStyles<typeof styles>) => (
<Card> <Table>
<Table> <TableHead
<TableHead colSpan={numberOfColumns}
colSpan={numberOfColumns} selected={selected}
selected={selected} disabled={disabled}
disabled={disabled} items={vouchers}
items={vouchers} toggleAll={toggleAll}
toggleAll={toggleAll} toolbar={toolbar}
toolbar={toolbar} >
> <TableCell className={classes.colName}>
<TableCell className={classes.colName}> <FormattedMessage defaultMessage="Code" description="voucher code" />
<FormattedMessage </TableCell>
defaultMessage="Code" <TableCell className={classes.colMinSpent}>
description="voucher code" <FormattedMessage
/> defaultMessage="Min. Spent"
</TableCell> description="minimum amount of spent money to activate voucher"
<TableCell className={classes.colMinSpent}> />
<FormattedMessage </TableCell>
defaultMessage="Min. Spent" <TableCell className={classes.colStart}>
description="minimum amount of spent money to activate voucher" <FormattedMessage
/> defaultMessage="Starts"
</TableCell> description="voucher is active from date"
<TableCell className={classes.colStart}> />
<FormattedMessage </TableCell>
defaultMessage="Starts" <TableCell className={classes.colEnd}>
description="voucher is active from date" <FormattedMessage
/> defaultMessage="Ends"
</TableCell> description="voucher is active until date"
<TableCell className={classes.colEnd}> />
<FormattedMessage </TableCell>
defaultMessage="Ends" <TableCell className={classes.colValue}>
description="voucher is active until date" <FormattedMessage
/> defaultMessage="Value"
</TableCell> description="voucher value"
<TableCell className={classes.colValue}> />
<FormattedMessage </TableCell>
defaultMessage="Value" <TableCell className={classes.colUses}>
description="voucher value" <FormattedMessage defaultMessage="Uses" description="voucher uses" />
/> </TableCell>
</TableCell> </TableHead>
<TableCell className={classes.colUses}> <TableFooter>
<FormattedMessage <TableRow>
defaultMessage="Uses" <TablePagination
description="voucher uses" colSpan={numberOfColumns}
/> settings={settings}
</TableCell> hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
</TableHead> onNextPage={onNextPage}
<TableFooter> onUpdateListSettings={onUpdateListSettings}
<TableRow> hasPreviousPage={
<TablePagination pageInfo && !disabled ? pageInfo.hasPreviousPage : false
colSpan={numberOfColumns} }
settings={settings} onPreviousPage={onPreviousPage}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false} />
onNextPage={onNextPage} </TableRow>
onUpdateListSettings={onUpdateListSettings} </TableFooter>
hasPreviousPage={ <TableBody>
pageInfo && !disabled ? pageInfo.hasPreviousPage : false {renderCollection(
} vouchers,
onPreviousPage={onPreviousPage} voucher => {
/> const isSelected = voucher ? isChecked(voucher.id) : false;
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
vouchers,
voucher => {
const isSelected = voucher ? isChecked(voucher.id) : false;
return ( return (
<TableRow <TableRow
className={!!voucher ? classes.tableRow : undefined} className={!!voucher ? classes.tableRow : undefined}
hover={!!voucher} hover={!!voucher}
key={voucher ? voucher.id : "skeleton"} key={voucher ? voucher.id : "skeleton"}
selected={isSelected} selected={isSelected}
onClick={voucher ? onRowClick(voucher.id) : undefined}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(voucher.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{maybe<React.ReactNode>(() => voucher.code, <Skeleton />)}
</TableCell>
<TableCell className={classes.colMinSpent}>
{voucher && voucher.minAmountSpent ? (
<Money money={voucher.minAmountSpent} />
) : voucher && voucher.minAmountSpent === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colStart}>
{voucher && voucher.startDate ? (
<Date date={voucher.startDate} />
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colEnd}>
{voucher && voucher.endDate ? (
<Date date={voucher.endDate} />
) : voucher && voucher.endDate === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colValue}
onClick={voucher ? onRowClick(voucher.id) : undefined} onClick={voucher ? onRowClick(voucher.id) : undefined}
> >
<TableCell padding="checkbox"> {voucher &&
<Checkbox voucher.discountValueType &&
checked={isSelected} voucher.discountValue ? (
disabled={disabled} voucher.discountValueType ===
disableClickPropagation DiscountValueTypeEnum.FIXED ? (
onChange={() => toggle(voucher.id)} <Money
/> money={{
</TableCell> amount: voucher.discountValue,
<TableCell className={classes.colName}> currency: defaultCurrency
{maybe<React.ReactNode>(() => voucher.code, <Skeleton />)} }}
</TableCell> />
<TableCell className={classes.colMinSpent}>
{voucher && voucher.minAmountSpent ? (
<Money money={voucher.minAmountSpent} />
) : voucher && voucher.minAmountSpent === null ? (
"-"
) : ( ) : (
<Skeleton /> <Percent amount={voucher.discountValue} />
)} )
</TableCell> ) : (
<TableCell className={classes.colStart}> <Skeleton />
{voucher && voucher.startDate ? ( )}
<Date date={voucher.startDate} /> </TableCell>
) : ( <TableCell className={classes.colUses}>
<Skeleton /> {voucher && voucher.usageLimit ? (
)} voucher.usageLimit
</TableCell> ) : voucher && voucher.usageLimit === null ? (
<TableCell className={classes.colEnd}> "-"
{voucher && voucher.endDate ? ( ) : (
<Date date={voucher.endDate} /> <Skeleton />
) : voucher && voucher.endDate === null ? ( )}
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colValue}
onClick={voucher ? onRowClick(voucher.id) : undefined}
>
{voucher &&
voucher.discountValueType &&
voucher.discountValue ? (
voucher.discountValueType ===
DiscountValueTypeEnum.FIXED ? (
<Money
money={{
amount: voucher.discountValue,
currency: defaultCurrency
}}
/>
) : (
<Percent amount={voucher.discountValue} />
)
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colUses}>
{voucher && voucher.usageLimit ? (
voucher.usageLimit
) : voucher && voucher.usageLimit === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No vouchers found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) );
)} },
</TableBody> () => (
</Table> <TableRow>
</Card> <TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No vouchers found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
) )
); );
VoucherList.displayName = "VoucherList"; VoucherList.displayName = "VoucherList";

View file

@ -1,36 +1,41 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import { VoucherList_vouchers_edges_node } from "../../types/VoucherList"; import { VoucherList_vouchers_edges_node } from "../../types/VoucherList";
import VoucherList from "../VoucherList"; import VoucherList from "../VoucherList";
export interface VoucherListPageProps extends PageListProps, ListActions { export interface VoucherListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
defaultCurrency: string; defaultCurrency: string;
vouchers: VoucherList_vouchers_edges_node[]; vouchers: VoucherList_vouchers_edges_node[];
} }
const VoucherListPage: React.StatelessComponent<VoucherListPageProps> = ({ const VoucherListPage: React.StatelessComponent<VoucherListPageProps> = ({
defaultCurrency, currentTab,
disabled, initialSearch,
settings,
onAdd, onAdd,
onNextPage, onAll,
onPreviousPage, onSearchChange,
onUpdateListSettings, onTabChange,
onRowClick, onTabDelete,
pageInfo, onTabSave,
vouchers, tabs,
isChecked, ...listProps
selected,
toggle,
toggleAll,
toolbar
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -44,22 +49,26 @@ const VoucherListPage: React.StatelessComponent<VoucherListPageProps> = ({
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<VoucherList <Card>
defaultCurrency={defaultCurrency} <SearchBar
settings={settings} allTabLabel={intl.formatMessage({
disabled={disabled} defaultMessage: "All Vouchers",
onNextPage={onNextPage} description: "tab name"
onPreviousPage={onPreviousPage} })}
onUpdateListSettings={onUpdateListSettings} currentTab={currentTab}
onRowClick={onRowClick} initialSearch={initialSearch}
pageInfo={pageInfo} searchPlaceholder={intl.formatMessage({
vouchers={vouchers} defaultMessage: "Search Voucher"
isChecked={isChecked} })}
selected={selected} tabs={tabs}
toggle={toggle} onAll={onAll}
toggleAll={toggleAll} onSearchChange={onSearchChange}
toolbar={toolbar} onTabChange={onTabChange}
/> onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<VoucherList {...listProps} />
</Card>
</Container> </Container>
); );
}; };

View file

@ -166,8 +166,20 @@ export const voucherDetailsFragment = gql`
export const saleList = gql` export const saleList = gql`
${pageInfoFragment} ${pageInfoFragment}
${saleFragment} ${saleFragment}
query SaleList($after: String, $before: String, $first: Int, $last: Int) { query SaleList(
sales(after: $after, before: $before, first: $first, last: $last) { $after: String
$before: String
$first: Int
$last: Int
$filter: SaleFilterInput
) {
sales(
after: $after
before: $before
first: $first
last: $last
filter: $filter
) {
edges { edges {
node { node {
...SaleFragment ...SaleFragment
@ -184,8 +196,20 @@ export const TypedSaleList = TypedQuery<SaleList, SaleListVariables>(saleList);
export const voucherList = gql` export const voucherList = gql`
${pageInfoFragment} ${pageInfoFragment}
${voucherFragment} ${voucherFragment}
query VoucherList($after: String, $before: String, $first: Int, $last: Int) { query VoucherList(
vouchers(after: $after, before: $before, first: $first, last: $last) { $after: String
$before: String
$first: Int
$last: Int
$filter: VoucherFilterInput
) {
vouchers(
after: $after
before: $before
first: $first
last: $last
filter: $filter
) {
edges { edges {
node { node {
...VoucherFragment ...VoucherFragment

View file

@ -2,7 +2,7 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { SaleType } from "./../../types/globalTypes"; import { SaleFilterInput, SaleType } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: SaleList // GraphQL query operation: SaleList
@ -46,4 +46,5 @@ export interface SaleListVariables {
before?: string | null; before?: string | null;
first?: number | null; first?: number | null;
last?: number | null; last?: number | null;
filter?: SaleFilterInput | null;
} }

View file

@ -2,7 +2,7 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { DiscountValueTypeEnum } from "./../../types/globalTypes"; import { VoucherFilterInput, DiscountValueTypeEnum } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: VoucherList // GraphQL query operation: VoucherList
@ -62,4 +62,5 @@ export interface VoucherListVariables {
before?: string | null; before?: string | null;
first?: number | null; first?: number | null;
last?: number | null; last?: number | null;
filter?: VoucherFilterInput | null;
} }

View file

@ -1,7 +1,14 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { ActiveTab, BulkAction, Dialog, Pagination } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
TabActionDialog
} from "../types";
import { SaleDetailsPageTab } from "./components/SaleDetailsPage"; import { SaleDetailsPageTab } from "./components/SaleDetailsPage";
import { VoucherDetailsPageTab } from "./components/VoucherDetailsPage"; import { VoucherDetailsPageTab } from "./components/VoucherDetailsPage";
@ -9,10 +16,16 @@ export const discountSection = "/discounts/";
export const saleSection = urlJoin(discountSection, "sales"); export const saleSection = urlJoin(discountSection, "sales");
export const saleListPath = saleSection; export const saleListPath = saleSection;
export type SaleListUrlDialog = "remove"; export enum SaleListUrlFiltersEnum {
export type SaleListUrlQueryParams = BulkAction & query = "query"
}
export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum>;
export type SaleListUrlDialog = "remove" | TabActionDialog;
export type SaleListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<SaleListUrlDialog> & Dialog<SaleListUrlDialog> &
Pagination; Pagination &
SaleListUrlFilters;
export const saleListUrl = (params?: SaleListUrlQueryParams) => export const saleListUrl = (params?: SaleListUrlQueryParams) =>
saleListPath + "?" + stringifyQs(params); saleListPath + "?" + stringifyQs(params);
export const salePath = (id: string) => urlJoin(saleSection, id); export const salePath = (id: string) => urlJoin(saleSection, id);
@ -35,10 +48,16 @@ export const saleAddUrl = saleAddPath;
export const voucherSection = urlJoin(discountSection, "vouchers"); export const voucherSection = urlJoin(discountSection, "vouchers");
export const voucherListPath = voucherSection; export const voucherListPath = voucherSection;
export type VoucherListUrlDialog = "remove"; export enum VoucherListUrlFiltersEnum {
export type VoucherListUrlQueryParams = BulkAction & query = "query"
}
export type VoucherListUrlFilters = Filters<VoucherListUrlFiltersEnum>;
export type VoucherListUrlDialog = "remove" | TabActionDialog;
export type VoucherListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<VoucherListUrlDialog> & Dialog<VoucherListUrlDialog> &
Pagination; Pagination &
VoucherListUrlFilters;
export const voucherListUrl = (params?: VoucherListUrlQueryParams) => export const voucherListUrl = (params?: VoucherListUrlQueryParams) =>
voucherListPath + "?" + stringifyQs(params); voucherListPath + "?" + stringifyQs(params);
export const voucherPath = (id: string) => urlJoin(voucherSection, id); export const voucherPath = (id: string) => urlJoin(voucherSection, id);

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
@ -17,16 +21,26 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl"; import { commonMessages, sectionNames } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import SaleListPage from "../components/SaleListPage"; import SaleListPage from "../../components/SaleListPage";
import { TypedSaleBulkDelete } from "../mutations"; import { TypedSaleBulkDelete } from "../../mutations";
import { TypedSaleList } from "../queries"; import { TypedSaleList } from "../../queries";
import { SaleBulkDelete } from "../types/SaleBulkDelete"; import { SaleBulkDelete } from "../../types/SaleBulkDelete";
import { import {
saleAddUrl, saleAddUrl,
saleListUrl, saleListUrl,
SaleListUrlDialog,
SaleListUrlFilters,
SaleListUrlQueryParams, SaleListUrlQueryParams,
saleUrl saleUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface SaleListProps { interface SaleListProps {
params: SaleListUrlQueryParams; params: SaleListUrlQueryParams;
@ -47,13 +61,79 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const closeModal = () => navigate(saleListUrl(), true); const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: SaleListUrlFilters) => {
reset();
navigate(
saleListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () =>
navigate(
saleListUrl({
...params,
action: undefined,
ids: undefined
}),
true
);
const openModal = (action: SaleListUrlDialog, ids?: string[]) =>
navigate(
saleListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
saleListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(saleListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0); const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
return ( return (
<TypedSaleList displayLoader variables={paginationState}> <TypedSaleList displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.sales.pageInfo), maybe(() => data.sales.pageInfo),
@ -91,6 +171,14 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
<> <>
<WindowTitle title={intl.formatMessage(sectionNames.sales)} /> <WindowTitle title={intl.formatMessage(sectionNames.sales)} />
<SaleListPage <SaleListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(saleListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
defaultCurrency={maybe(() => shop.defaultCurrency)} defaultCurrency={maybe(() => shop.defaultCurrency)}
sales={maybe(() => data.sales.edges.map(edge => edge.node))} sales={maybe(() => data.sales.edges.map(edge => edge.node))}
settings={settings} settings={settings}
@ -150,6 +238,19 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
</DialogContentText> </DialogContentText>
)} )}
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { SaleFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
SaleListUrlFilters,
SaleListUrlFiltersEnum,
SaleListUrlQueryParams
} from "../../urls";
export const SALE_FILTERS_KEY = "saleFilters";
export function getFilterVariables(
params: SaleListUrlFilters
): SaleFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<SaleListUrlFilters>(SALE_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
SaleListUrlQueryParams,
SaleListUrlFilters
>(SaleListUrlFiltersEnum);

View file

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

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
@ -17,16 +21,26 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl"; import { commonMessages, sectionNames } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import VoucherListPage from "../components/VoucherListPage"; import VoucherListPage from "../../components/VoucherListPage";
import { TypedVoucherBulkDelete } from "../mutations"; import { TypedVoucherBulkDelete } from "../../mutations";
import { TypedVoucherList } from "../queries"; import { TypedVoucherList } from "../../queries";
import { VoucherBulkDelete } from "../types/VoucherBulkDelete"; import { VoucherBulkDelete } from "../../types/VoucherBulkDelete";
import { import {
voucherAddUrl, voucherAddUrl,
voucherListUrl, voucherListUrl,
VoucherListUrlDialog,
VoucherListUrlFilters,
VoucherListUrlQueryParams, VoucherListUrlQueryParams,
voucherUrl voucherUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface VoucherListProps { interface VoucherListProps {
params: VoucherListUrlQueryParams; params: VoucherListUrlQueryParams;
@ -47,13 +61,79 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const closeModal = () => navigate(voucherListUrl(), true); const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: VoucherListUrlFilters) => {
reset();
navigate(
voucherListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () =>
navigate(
voucherListUrl({
...params,
action: undefined,
ids: undefined
}),
true
);
const openModal = (action: VoucherListUrlDialog, ids?: string[]) =>
navigate(
voucherListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
voucherListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(voucherListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0); const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
return ( return (
<TypedVoucherList displayLoader variables={paginationState}> <TypedVoucherList displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.vouchers.pageInfo), maybe(() => data.vouchers.pageInfo),
@ -92,6 +172,14 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
title={intl.formatMessage(sectionNames.vouchers)} title={intl.formatMessage(sectionNames.vouchers)}
/> />
<VoucherListPage <VoucherListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(voucherListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
defaultCurrency={maybe(() => shop.defaultCurrency)} defaultCurrency={maybe(() => shop.defaultCurrency)}
settings={settings} settings={settings}
vouchers={maybe(() => vouchers={maybe(() =>
@ -153,6 +241,19 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
</DialogContentText> </DialogContentText>
)} )}
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { VoucherFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
VoucherListUrlFilters,
VoucherListUrlFiltersEnum,
VoucherListUrlQueryParams
} from "../../urls";
export const VOUCHER_FILTERS_KEY = "VoucherFilters";
export function getFilterVariables(
params: VoucherListUrlFilters
): VoucherFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<VoucherListUrlFilters>(VOUCHER_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
VoucherListUrlQueryParams,
VoucherListUrlFilters
>(VoucherListUrlFiltersEnum);

View file

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

View file

@ -3,7 +3,9 @@ import {
FetchMoreProps, FetchMoreProps,
FilterPageProps, FilterPageProps,
ListActions, ListActions,
PageListProps PageListProps,
SearchPageProps,
TabPageProps
} from "./types"; } from "./types";
const pageInfo = { const pageInfo = {
@ -46,23 +48,26 @@ export const countries = [
{ code: "AS", label: "American Samoa" } { code: "AS", label: "American Samoa" }
]; ];
export const filterPageProps: FilterPageProps<{}, unknown> = { export const tabPageProps: TabPageProps = {
currencySymbol: "USD",
currentTab: 0, currentTab: 0,
filterTabs: [
{
data: {},
name: "Tab X"
}
],
filtersList: [],
initialSearch: "",
onAll: () => undefined, onAll: () => undefined,
onFilterAdd: () => undefined, onTabChange: () => undefined,
onFilterDelete: () => undefined, onTabDelete: () => undefined,
onFilterSave: () => undefined, onTabSave: () => undefined,
onSearchChange: () => undefined, tabs: ["Tab X"]
onTabChange: () => undefined };
export const searchPageProps: SearchPageProps = {
initialSearch: "",
onSearchChange: () => undefined
};
export const filterPageProps: FilterPageProps = {
...searchPageProps,
...tabPageProps,
currencySymbol: "USD",
filtersList: [],
onFilterAdd: () => undefined
}; };
export const filters: Filter[] = [ export const filters: Filter[] = [

View file

@ -6,18 +6,36 @@ import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList"; import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList";
import OrderDraftList from "../OrderDraftList"; import OrderDraftList from "../OrderDraftList";
export interface OrderDraftListPageProps extends PageListProps, ListActions { export interface OrderDraftListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
orders: OrderDraftList_draftOrders_edges_node[]; orders: OrderDraftList_draftOrders_edges_node[];
} }
const OrderDraftListPage: React.StatelessComponent<OrderDraftListPageProps> = ({ const OrderDraftListPage: React.StatelessComponent<OrderDraftListPageProps> = ({
currentTab,
disabled, disabled,
initialSearch,
onAdd, onAdd,
onAll,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -38,6 +56,23 @@ const OrderDraftListPage: React.StatelessComponent<OrderDraftListPageProps> = ({
</Button> </Button>
</PageHeader> </PageHeader>
<Card> <Card>
<SearchBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Drafts",
description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Draft"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<OrderDraftList disabled={disabled} {...listProps} /> <OrderDraftList disabled={disabled} {...listProps} />
</Card> </Card>
</Container> </Container>

View file

@ -8,9 +8,8 @@ import FilterBar from "@saleor/components/FilterBar";
import TimezoneContext from "@saleor/components/Timezone"; import TimezoneContext from "@saleor/components/Timezone";
import { FilterProps } from "../../../types"; import { FilterProps } from "../../../types";
import { OrderStatusFilter } from "../../../types/globalTypes"; import { OrderStatusFilter } from "../../../types/globalTypes";
import { OrderListUrlFilters } from "../../urls";
type OrderListFilterProps = FilterProps<OrderListUrlFilters, OrderFilterKeys>; type OrderListFilterProps = FilterProps<OrderFilterKeys>;
export enum OrderFilterKeys { export enum OrderFilterKeys {
date = "date", date = "date",

View file

@ -7,17 +7,15 @@ import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { OrderFilterKeys } from "@saleor/orders/components/OrderListFilter";
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types"; import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
import { OrderList_orders_edges_node } from "../../types/OrderList"; import { OrderList_orders_edges_node } from "../../types/OrderList";
import { OrderListUrlFilters } from "../../urls";
import OrderList from "../OrderList"; import OrderList from "../OrderList";
import OrderListFilter from "../OrderListFilter"; import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter";
export interface OrderListPageProps export interface OrderListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
FilterPageProps<OrderListUrlFilters, OrderFilterKeys> { FilterPageProps<OrderFilterKeys> {
orders: OrderList_orders_edges_node[]; orders: OrderList_orders_edges_node[];
} }
@ -25,15 +23,15 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
currencySymbol, currencySymbol,
currentTab, currentTab,
filtersList, filtersList,
filterTabs,
initialSearch, initialSearch,
tabs,
onAdd, onAdd,
onAll, onAll,
onSearchChange, onSearchChange,
onFilterAdd, onFilterAdd,
onFilterSave,
onTabChange, onTabChange,
onFilterDelete, onTabDelete,
onTabSave,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -59,7 +57,7 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
filterLabel={intl.formatMessage({ filterLabel={intl.formatMessage({
defaultMessage: "Select all orders where:" defaultMessage: "Select all orders where:"
})} })}
filterTabs={filterTabs} tabs={tabs}
filtersList={filtersList} filtersList={filtersList}
initialSearch={initialSearch} initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({ searchPlaceholder={intl.formatMessage({
@ -68,9 +66,9 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
onAll={onAll} onAll={onAll}
onSearchChange={onSearchChange} onSearchChange={onSearchChange}
onFilterAdd={onFilterAdd} onFilterAdd={onFilterAdd}
onFilterSave={onFilterSave}
onTabChange={onTabChange} onTabChange={onTabChange}
onFilterDelete={onFilterDelete} onTabDelete={onTabDelete}
onTabSave={onTabSave}
/> />
<OrderList {...listProps} /> <OrderList {...listProps} />
</Card> </Card>

View file

@ -168,7 +168,6 @@ export const orderListQuery = gql`
$after: String $after: String
$last: Int $last: Int
$before: String $before: String
$status: OrderStatusFilter
$filter: OrderFilterInput $filter: OrderFilterInput
) { ) {
orders( orders(
@ -176,7 +175,6 @@ export const orderListQuery = gql`
after: $after after: $after
first: $first first: $first
last: $last last: $last
status: $status
filter: $filter filter: $filter
) { ) {
edges { edges {
@ -221,8 +219,15 @@ export const orderDraftListQuery = gql`
$after: String $after: String
$last: Int $last: Int
$before: String $before: String
$filter: OrderDraftFilterInput
) { ) {
draftOrders(before: $before, after: $after, first: $first, last: $last) { draftOrders(
before: $before
after: $after
first: $first
last: $last
filter: $filter
) {
edges { edges {
node { node {
__typename __typename

View file

@ -2,7 +2,7 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes"; import { OrderDraftFilterInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: OrderDraftList // GraphQL query operation: OrderDraftList
@ -81,4 +81,5 @@ export interface OrderDraftListVariables {
after?: string | null; after?: string | null;
last?: number | null; last?: number | null;
before?: string | null; before?: string | null;
filter?: OrderDraftFilterInput | null;
} }

View file

@ -2,7 +2,7 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { OrderStatusFilter, OrderFilterInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes"; import { OrderFilterInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: OrderList // GraphQL query operation: OrderList
@ -81,6 +81,5 @@ export interface OrderListVariables {
after?: string | null; after?: string | null;
last?: number | null; last?: number | null;
before?: string | null; before?: string | null;
status?: OrderStatusFilter | null;
filter?: OrderFilterInput | null; filter?: OrderFilterInput | null;
} }

View file

@ -8,7 +8,8 @@ import {
Filters, Filters,
FiltersWithMultipleValues, FiltersWithMultipleValues,
Pagination, Pagination,
SingleAction SingleAction,
TabActionDialog
} from "../types"; } from "../types";
const orderSectionUrl = "/orders"; const orderSectionUrl = "/orders";
@ -18,14 +19,15 @@ export enum OrderListUrlFiltersEnum {
dateFrom = "dateFrom", dateFrom = "dateFrom",
dateTo = "dateTo", dateTo = "dateTo",
email = "email", email = "email",
payment = "payment" payment = "payment",
query = "query"
} }
export enum OrderListUrlFiltersWithMultipleValuesEnum { export enum OrderListUrlFiltersWithMultipleValuesEnum {
status = "status" status = "status"
} }
export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> & export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> &
FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValuesEnum>; FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValuesEnum>;
export type OrderListUrlDialog = "cancel" | "save-search" | "delete-search"; export type OrderListUrlDialog = "cancel" | TabActionDialog;
export type OrderListUrlQueryParams = BulkAction & export type OrderListUrlQueryParams = BulkAction &
Dialog<OrderListUrlDialog> & Dialog<OrderListUrlDialog> &
OrderListUrlFilters & OrderListUrlFilters &
@ -41,9 +43,15 @@ export const orderListUrl = (params?: OrderListUrlQueryParams): string => {
}; };
export const orderDraftListPath = urlJoin(orderSectionUrl, "drafts"); export const orderDraftListPath = urlJoin(orderSectionUrl, "drafts");
export type OrderDraftListUrlDialog = "remove"; export enum OrderDraftListUrlFiltersEnum {
export type OrderDraftListUrlQueryParams = BulkAction & query = "query"
}
export type OrderDraftListUrlFilters = Filters<OrderDraftListUrlFiltersEnum>;
export type OrderDraftListUrlDialog = "remove" | TabActionDialog;
export type OrderDraftListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<OrderDraftListUrlDialog> & Dialog<OrderDraftListUrlDialog> &
OrderDraftListUrlFilters &
Pagination; Pagination;
export const orderDraftListUrl = ( export const orderDraftListUrl = (
params?: OrderDraftListUrlQueryParams params?: OrderDraftListUrlQueryParams

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -14,19 +18,29 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import OrderDraftListPage from "../components/OrderDraftListPage"; import OrderDraftListPage from "../../components/OrderDraftListPage";
import { import {
TypedOrderDraftBulkCancelMutation, TypedOrderDraftBulkCancelMutation,
TypedOrderDraftCreateMutation TypedOrderDraftCreateMutation
} from "../mutations"; } from "../../mutations";
import { TypedOrderDraftListQuery } from "../queries"; import { TypedOrderDraftListQuery } from "../../queries";
import { OrderDraftBulkCancel } from "../types/OrderDraftBulkCancel"; import { OrderDraftBulkCancel } from "../../types/OrderDraftBulkCancel";
import { OrderDraftCreate } from "../types/OrderDraftCreate"; import { OrderDraftCreate } from "../../types/OrderDraftCreate";
import { import {
orderDraftListUrl, orderDraftListUrl,
OrderDraftListUrlDialog,
OrderDraftListUrlFilters,
OrderDraftListUrlQueryParams, OrderDraftListUrlQueryParams,
orderUrl orderUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface OrderDraftListProps { interface OrderDraftListProps {
params: OrderDraftListUrlQueryParams; params: OrderDraftListUrlQueryParams;
@ -46,13 +60,34 @@ export const OrderDraftList: React.StatelessComponent<OrderDraftListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: OrderDraftListUrlFilters) => {
reset();
navigate(
orderDraftListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () => const closeModal = () =>
navigate( navigate(
orderDraftListUrl({ orderDraftListUrl({
...params, ...params,
action: undefined, action: undefined,
ids: undefined ids: undefined
}) }),
true
); );
const handleCreateOrderCreateSuccess = (data: OrderDraftCreate) => { const handleCreateOrderCreateSuccess = (data: OrderDraftCreate) => {
@ -64,12 +99,49 @@ export const OrderDraftList: React.StatelessComponent<OrderDraftListProps> = ({
navigate(orderUrl(data.draftOrderCreate.order.id)); navigate(orderUrl(data.draftOrderCreate.order.id));
}; };
const openModal = (action: OrderDraftListUrlDialog, ids?: string[]) =>
navigate(
orderDraftListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
orderDraftListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(orderDraftListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return ( return (
<TypedOrderDraftCreateMutation onCompleted={handleCreateOrderCreateSuccess}> <TypedOrderDraftCreateMutation onCompleted={handleCreateOrderCreateSuccess}>
{createOrder => ( {createOrder => (
<TypedOrderDraftListQuery displayLoader variables={paginationState}> <TypedOrderDraftListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.draftOrders.pageInfo), maybe(() => data.draftOrders.pageInfo),
@ -114,6 +186,14 @@ export const OrderDraftList: React.StatelessComponent<OrderDraftListProps> = ({
return ( return (
<> <>
<OrderDraftListPage <OrderDraftListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(orderDraftListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading} disabled={loading}
settings={settings} settings={settings}
orders={maybe(() => orders={maybe(() =>
@ -174,6 +254,19 @@ export const OrderDraftList: React.StatelessComponent<OrderDraftListProps> = ({
/> />
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { OrderDraftFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
OrderDraftListUrlFilters,
OrderDraftListUrlFiltersEnum,
OrderDraftListUrlQueryParams
} from "../../urls";
export const ORDER_DRAFT_FILTERS_KEY = "orderDraftFilters";
export function getFilterVariables(
params: OrderDraftListUrlFilters
): OrderDraftFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<OrderDraftListUrlFilters>(ORDER_DRAFT_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
OrderDraftListUrlQueryParams,
OrderDraftListUrlFilters
>(OrderDraftListUrlFiltersEnum);

View file

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

View file

@ -228,15 +228,15 @@ export const OrderList: React.StatelessComponent<OrderListProps> = ({
/> />
</Button> </Button>
} }
onSearchChange={email => changeFilterField({ email })} onSearchChange={query => changeFilterField({ query })}
onFilterAdd={data => onFilterAdd={data =>
changeFilterField(createFilter(params, data)) changeFilterField(createFilter(params, data))
} }
onFilterSave={() => openModal("save-search")} onTabSave={() => openModal("save-search")}
onFilterDelete={() => openModal("delete-search")} onTabDelete={() => openModal("delete-search")}
onTabChange={handleTabChange} onTabChange={handleTabChange}
initialSearch={params.email || ""} initialSearch={params.query || ""}
filterTabs={getFilterTabs()} tabs={getFilterTabs().map(tab => tab.name)}
onAll={() => onAll={() =>
changeFilters({ changeFilters({
status: undefined status: undefined

View file

@ -10,6 +10,10 @@ Array [
"label": "Date to 2019-09-10", "label": "Date to 2019-09-10",
"onClick": [Function], "onClick": [Function],
}, },
Object {
"label": "email@example.com",
"onClick": [Function],
},
Object { Object {
"label": "Fulfilled", "label": "Fulfilled",
"onClick": [Function], "onClick": [Function],
@ -88,6 +92,7 @@ Object {
"lte": "2019-09-10", "lte": "2019-09-10",
}, },
"customer": "email@example.com", "customer": "email@example.com",
"search": "24",
"status": Array [ "status": Array [
"FULFILLED", "FULFILLED",
"PARTIALLY_FULFILLED", "PARTIALLY_FULFILLED",
@ -102,6 +107,7 @@ Object {
"lte": "2019-09-10", "lte": "2019-09-10",
}, },
"customer": "email@example.com", "customer": "email@example.com",
"search": "24",
"status": Array [ "status": Array [
"FULFILLED", "FULFILLED",
], ],

View file

@ -115,6 +115,7 @@ test("Crate filter chips", () => {
{ {
dateFrom: "2019-09-01", dateFrom: "2019-09-01",
dateTo: "2019-09-10", dateTo: "2019-09-10",
email: "email@example.com",
status: [OrderStatus.FULFILLED, OrderStatus.PARTIALLY_FULFILLED] status: [OrderStatus.FULFILLED, OrderStatus.PARTIALLY_FULFILLED]
}, },
{ {
@ -133,6 +134,7 @@ describe("Get filter variables", () => {
dateFrom: "2019-09-01", dateFrom: "2019-09-01",
dateTo: "2019-09-10", dateTo: "2019-09-10",
email: "email@example.com", email: "email@example.com",
query: "24",
status: OrderStatus.FULFILLED.toString() status: OrderStatus.FULFILLED.toString()
}); });
@ -144,6 +146,7 @@ describe("Get filter variables", () => {
dateFrom: "2019-09-01", dateFrom: "2019-09-01",
dateTo: "2019-09-10", dateTo: "2019-09-10",
email: "email@example.com", email: "email@example.com",
query: "24",
status: [ status: [
OrderStatus.FULFILLED.toString(), OrderStatus.FULFILLED.toString(),
OrderStatus.PARTIALLY_FULFILLED.toString() OrderStatus.PARTIALLY_FULFILLED.toString()

View file

@ -20,6 +20,7 @@ import { OrderFilterKeys } from "../../components/OrderListFilter";
import { import {
OrderListUrlFilters, OrderListUrlFilters,
OrderListUrlFiltersEnum, OrderListUrlFiltersEnum,
OrderListUrlFiltersWithMultipleValuesEnum,
OrderListUrlQueryParams OrderListUrlQueryParams
} from "../../urls"; } from "../../urls";
@ -83,6 +84,7 @@ export function getFilterVariables(
lte: params.dateTo lte: params.dateTo
}, },
customer: params.email, customer: params.email,
search: params.query,
status: Array.isArray(params.status) status: Array.isArray(params.status)
? params.status.map(status => findInEnum(status, OrderStatusFilter)) ? params.status.map(status => findInEnum(status, OrderStatusFilter))
: params.status : params.status
@ -190,6 +192,20 @@ export function createFilterChips(
} }
} }
if (!!filters.email) {
filterChips = [
...filterChips,
{
label: filters.email,
onClick: () =>
onFilterDelete({
...filters,
email: undefined
})
}
];
}
if (!!filters.status) { if (!!filters.status) {
const statusFilterChips = Array.isArray(filters.status) const statusFilterChips = Array.isArray(filters.status)
? filters.status.map((status, statusIndex) => ({ ? filters.status.map((status, statusIndex) => ({
@ -228,4 +244,7 @@ export const {
export const { areFiltersApplied, getActiveFilters } = createFilterUtils< export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
OrderListUrlQueryParams, OrderListUrlQueryParams,
OrderListUrlFilters OrderListUrlFilters
>(OrderListUrlFiltersEnum); >({
...OrderListUrlFiltersEnum,
...OrderListUrlFiltersWithMultipleValuesEnum
});

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -70,138 +69,132 @@ const ProductTypeList = withStyles(styles, { name: "ProductTypeList" })(
const intl = useIntl(); const intl = useIntl();
return ( return (
<Card> <Table>
<Table> <TableHead
<TableHead colSpan={numberOfColumns}
colSpan={numberOfColumns} selected={selected}
selected={selected} disabled={disabled}
disabled={disabled} items={productTypes}
items={productTypes} toggleAll={toggleAll}
toggleAll={toggleAll} toolbar={toolbar}
toolbar={toolbar} >
> <TableCell className={classes.colName}>
<TableCell className={classes.colName}> <FormattedMessage
<FormattedMessage defaultMessage="Type Name"
defaultMessage="Type Name" description="product type name"
description="product type name" />
/> </TableCell>
</TableCell> <TableCell className={classes.colType}>
<TableCell className={classes.colType}> <FormattedMessage
<FormattedMessage defaultMessage="Type"
defaultMessage="Type" description="product type is either simple or configurable"
description="product type is either simple or configurable" />
/> </TableCell>
</TableCell> <TableCell className={classes.colTax}>
<TableCell className={classes.colTax}> <FormattedMessage
<FormattedMessage defaultMessage="Tax"
defaultMessage="Tax" description="tax rate for a product type"
description="tax rate for a product type" />
/> </TableCell>
</TableCell> </TableHead>
</TableHead> <TableFooter>
<TableFooter> <TableRow>
<TableRow> <TablePagination
<TablePagination colSpan={numberOfColumns}
colSpan={numberOfColumns} hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
hasNextPage={ onNextPage={onNextPage}
pageInfo && !disabled ? pageInfo.hasNextPage : false hasPreviousPage={
} pageInfo && !disabled ? pageInfo.hasPreviousPage : false
onNextPage={onNextPage} }
hasPreviousPage={ onPreviousPage={onPreviousPage}
pageInfo && !disabled ? pageInfo.hasPreviousPage : false />
} </TableRow>
onPreviousPage={onPreviousPage} </TableFooter>
/> <TableBody>
</TableRow> {renderCollection(
</TableFooter> productTypes,
<TableBody> productType => {
{renderCollection( const isSelected = productType
productTypes, ? isChecked(productType.id)
productType => { : false;
const isSelected = productType return (
? isChecked(productType.id) <TableRow
: false; className={!!productType ? classes.link : undefined}
return ( hover={!!productType}
<TableRow key={productType ? productType.id : "skeleton"}
className={!!productType ? classes.link : undefined} onClick={productType ? onRowClick(productType.id) : undefined}
hover={!!productType} selected={isSelected}
key={productType ? productType.id : "skeleton"} >
onClick={ <TableCell padding="checkbox">
productType ? onRowClick(productType.id) : undefined <Checkbox
} checked={isSelected}
selected={isSelected} disabled={disabled}
> disableClickPropagation
<TableCell padding="checkbox"> onChange={() => toggle(productType.id)}
<Checkbox />
checked={isSelected} </TableCell>
disabled={disabled} <TableCell className={classes.colName}>
disableClickPropagation {productType ? (
onChange={() => toggle(productType.id)} <>
/> {productType.name}
</TableCell> <Typography variant="caption">
<TableCell className={classes.colName}> {maybe(() => productType.hasVariants)
{productType ? ( ? intl.formatMessage({
defaultMessage: "Configurable",
description: "product type"
})
: intl.formatMessage({
defaultMessage: "Simple product",
description: "product type"
})}
</Typography>
</>
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colType}>
{maybe(() => productType.isShippingRequired) !==
undefined ? (
productType.isShippingRequired ? (
<> <>
{productType.name} <FormattedMessage
<Typography variant="caption"> defaultMessage="Physical"
{maybe(() => productType.hasVariants) description="product type"
? intl.formatMessage({ />
defaultMessage: "Configurable",
description: "product type"
})
: intl.formatMessage({
defaultMessage: "Simple product",
description: "product type"
})}
</Typography>
</> </>
) : ( ) : (
<Skeleton /> <>
)} <FormattedMessage
</TableCell> defaultMessage="Digital"
<TableCell className={classes.colType}> description="product type"
{maybe(() => productType.isShippingRequired) !== />
undefined ? ( </>
productType.isShippingRequired ? ( )
<> ) : (
<FormattedMessage <Skeleton />
defaultMessage="Physical" )}
description="product type" </TableCell>
/> <TableCell className={classes.colTax}>
</> {maybe(() => productType.taxType) ? (
) : ( productType.taxType.description
<> ) : (
<FormattedMessage <Skeleton />
defaultMessage="Digital" )}
description="product type"
/>
</>
)
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colTax}>
{maybe(() => productType.taxType) ? (
productType.taxType.description
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No product types found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) );
)} },
</TableBody> () => (
</Table> <TableRow>
</Card> <TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No product types found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
); );
} }
); );

View file

@ -1,23 +1,46 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "../../../types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "../../../types";
import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList"; import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList";
import ProductTypeList from "../ProductTypeList"; import ProductTypeList from "../ProductTypeList";
interface ProductTypeListPageProps extends PageListProps, ListActions { export interface ProductTypeListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
productTypes: ProductTypeList_productTypes_edges_node[]; productTypes: ProductTypeList_productTypes_edges_node[];
onBack: () => void; onBack: () => void;
} }
const ProductTypeListPage: React.StatelessComponent< const ProductTypeListPage: React.StatelessComponent<
ProductTypeListPageProps ProductTypeListPageProps
> = ({ disabled, onAdd, onBack, ...listProps }) => { > = ({
currentTab,
initialSearch,
onAdd,
onAll,
onBack,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps
}) => {
const intl = useIntl(); const intl = useIntl();
return ( return (
@ -26,19 +49,33 @@ const ProductTypeListPage: React.StatelessComponent<
{intl.formatMessage(sectionNames.configuration)} {intl.formatMessage(sectionNames.configuration)}
</AppHeader> </AppHeader>
<PageHeader title={intl.formatMessage(sectionNames.productTypes)}> <PageHeader title={intl.formatMessage(sectionNames.productTypes)}>
<Button <Button color="primary" variant="contained" onClick={onAdd}>
color="primary"
variant="contained"
disabled={disabled}
onClick={onAdd}
>
<FormattedMessage <FormattedMessage
defaultMessage="create product type" defaultMessage="create product type"
description="button" description="button"
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<ProductTypeList disabled={disabled} {...listProps} /> <Card>
<SearchBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Product Types",
description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Product Type"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<ProductTypeList {...listProps} />
</Card>
</Container> </Container>
); );
}; };

View file

@ -51,8 +51,15 @@ export const productTypeListQuery = gql`
$before: String $before: String
$first: Int $first: Int
$last: Int $last: Int
$filter: ProductTypeFilterInput
) { ) {
productTypes(after: $after, before: $before, first: $first, last: $last) { productTypes(
after: $after
before: $before
first: $first
last: $last
filter: $filter
) {
edges { edges {
node { node {
...ProductTypeFragment ...ProductTypeFragment

View file

@ -2,6 +2,8 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductTypeFilterInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: ProductTypeList // GraphQL query operation: ProductTypeList
// ==================================================== // ====================================================
@ -49,4 +51,5 @@ export interface ProductTypeListVariables {
before?: string | null; before?: string | null;
first?: number | null; first?: number | null;
last?: number | null; last?: number | null;
filter?: ProductTypeFilterInput | null;
} }

View file

@ -1,15 +1,29 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination, SingleAction } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
SingleAction,
TabActionDialog
} from "../types";
const productTypeSection = "/product-types/"; const productTypeSection = "/product-types/";
export const productTypeListPath = productTypeSection; export const productTypeListPath = productTypeSection;
export type ProductTypeListUrlDialog = "remove"; export enum ProductTypeListUrlFiltersEnum {
export type ProductTypeListUrlQueryParams = BulkAction & query = "query"
}
export type ProductTypeListUrlFilters = Filters<ProductTypeListUrlFiltersEnum>;
export type ProductTypeListUrlDialog = "remove" | TabActionDialog;
export type ProductTypeListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<ProductTypeListUrlDialog> & Dialog<ProductTypeListUrlDialog> &
Pagination; Pagination &
ProductTypeListUrlFilters;
export const productTypeListUrl = (params?: ProductTypeListUrlQueryParams) => export const productTypeListUrl = (params?: ProductTypeListUrlQueryParams) =>
productTypeListPath + "?" + stringifyQs(params); productTypeListPath + "?" + stringifyQs(params);

View file

@ -5,26 +5,41 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, { import usePaginator, {
createPaginationState createPaginationState
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { PAGINATE_BY } from "../../config"; import { ListViews } from "@saleor/types";
import { configurationMenuUrl } from "../../configuration"; import { configurationMenuUrl } from "../../../configuration";
import { getMutationState, maybe } from "../../misc"; import { getMutationState, maybe } from "../../../misc";
import ProductTypeListPage from "../components/ProductTypeListPage"; import ProductTypeListPage from "../../components/ProductTypeListPage";
import { TypedProductTypeBulkDeleteMutation } from "../mutations"; import { TypedProductTypeBulkDeleteMutation } from "../../mutations";
import { TypedProductTypeListQuery } from "../queries"; import { TypedProductTypeListQuery } from "../../queries";
import { ProductTypeBulkDelete } from "../types/ProductTypeBulkDelete"; import { ProductTypeBulkDelete } from "../../types/ProductTypeBulkDelete";
import { import {
productTypeAddUrl, productTypeAddUrl,
productTypeListUrl, productTypeListUrl,
ProductTypeListUrlDialog,
ProductTypeListUrlFilters,
ProductTypeListUrlQueryParams, ProductTypeListUrlQueryParams,
productTypeUrl productTypeUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface ProductTypeListProps { interface ProductTypeListProps {
params: ProductTypeListUrlQueryParams; params: ProductTypeListUrlQueryParams;
@ -39,13 +54,80 @@ export const ProductTypeList: React.StatelessComponent<
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids params.ids
); );
const { settings } = useListSettings(ListViews.PRODUCT_LIST);
const intl = useIntl(); const intl = useIntl();
const closeModal = () => navigate(productTypeListUrl(), true); const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: ProductTypeListUrlFilters) => {
reset();
navigate(
productTypeListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () =>
navigate(
productTypeListUrl({
...params,
action: undefined,
ids: undefined
}),
true
);
const openModal = (action: ProductTypeListUrlDialog, ids?: string[]) =>
navigate(
productTypeListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
productTypeListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(productTypeListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const paginationState = createPaginationState(PAGINATE_BY, params);
return ( return (
<TypedProductTypeListQuery displayLoader variables={paginationState}> <TypedProductTypeListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.productTypes.pageInfo), maybe(() => data.productTypes.pageInfo),
@ -93,6 +175,14 @@ export const ProductTypeList: React.StatelessComponent<
return ( return (
<> <>
<ProductTypeListPage <ProductTypeListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(productTypeListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading} disabled={loading}
productTypes={maybe(() => productTypes={maybe(() =>
data.productTypes.edges.map(edge => edge.node) data.productTypes.edges.map(edge => edge.node)
@ -150,6 +240,19 @@ export const ProductTypeList: React.StatelessComponent<
/> />
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { ProductTypeFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
ProductTypeListUrlFilters,
ProductTypeListUrlFiltersEnum,
ProductTypeListUrlQueryParams
} from "../../urls";
export const PRODUCT_TYPE_FILTERS_KEY = "productTypeFilters";
export function getFilterVariables(
params: ProductTypeListUrlFilters
): ProductTypeFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<ProductTypeListUrlFilters>(PRODUCT_TYPE_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
ProductTypeListUrlQueryParams,
ProductTypeListUrlFilters
>(ProductTypeListUrlFiltersEnum);

View file

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

View file

@ -5,11 +5,10 @@ import { FieldType, IFilter } from "@saleor/components/Filter";
import FilterBar from "@saleor/components/FilterBar"; import FilterBar from "@saleor/components/FilterBar";
import { FilterProps } from "@saleor/types"; import { FilterProps } from "@saleor/types";
import { StockAvailability } from "@saleor/types/globalTypes"; import { StockAvailability } from "@saleor/types/globalTypes";
import { ProductListUrlFilters } from "../../urls";
type ProductListFilterProps = FilterProps< type ProductListFilterProps = Omit<
ProductListUrlFilters, FilterProps,
ProductFilterKeys "allTabLabel" | "filterLabel" | "searchPlaceholder"
>; >;
export enum ProductFilterKeys { export enum ProductFilterKeys {
@ -133,7 +132,22 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
} }
]; ];
return <FilterBar {...props} filterMenu={filterMenu} />; return (
<FilterBar
{...props}
allTabLabel={intl.formatMessage({
defaultMessage: "All Products",
description: "tab name"
})}
filterMenu={filterMenu}
filterLabel={intl.formatMessage({
defaultMessage: "Select all products where:"
})}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Products..."
})}
/>
);
}; };
ProductListFilter.displayName = "ProductListFilter"; ProductListFilter.displayName = "ProductListFilter";
export default ProductListFilter; export default ProductListFilter;

View file

@ -24,14 +24,13 @@ import {
ListActions, ListActions,
PageListProps PageListProps
} from "@saleor/types"; } from "@saleor/types";
import { ProductListUrlFilters } from "../../urls";
import ProductList from "../ProductList"; import ProductList from "../ProductList";
import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter"; import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter";
export interface ProductListPageProps export interface ProductListPageProps
extends PageListProps<ProductListColumns>, extends PageListProps<ProductListColumns>,
ListActions, ListActions,
FilterPageProps<ProductListUrlFilters, ProductFilterKeys>, FilterPageProps<ProductFilterKeys>,
FetchMoreProps { FetchMoreProps {
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[]; availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
currencySymbol: string; currencySymbol: string;
@ -52,22 +51,22 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
currentTab, currentTab,
defaultSettings, defaultSettings,
filtersList, filtersList,
filterTabs,
gridAttributes, gridAttributes,
availableInGridAttributes, availableInGridAttributes,
hasMore, hasMore,
initialSearch, initialSearch,
loading, loading,
settings, settings,
tabs,
totalGridAttributes, totalGridAttributes,
onAdd, onAdd,
onAll, onAll,
onFetchMore, onFetchMore,
onSearchChange, onSearchChange,
onFilterAdd, onFilterAdd,
onFilterSave,
onTabChange, onTabChange,
onFilterDelete, onTabDelete,
onTabSave,
onUpdateListSettings, onUpdateListSettings,
...listProps ...listProps
} = props; } = props;
@ -137,27 +136,17 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
</PageHeader> </PageHeader>
<Card> <Card>
<ProductListFilter <ProductListFilter
allTabLabel={intl.formatMessage({
defaultMessage: "All Products",
description: "tab name"
})}
currencySymbol={currencySymbol} currencySymbol={currencySymbol}
currentTab={currentTab} currentTab={currentTab}
filterLabel={intl.formatMessage({
defaultMessage: "Select all products where:"
})}
filterTabs={filterTabs}
filtersList={filtersList} filtersList={filtersList}
initialSearch={initialSearch} initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Products..."
})}
onAll={onAll} onAll={onAll}
onSearchChange={onSearchChange}
onFilterAdd={onFilterAdd} onFilterAdd={onFilterAdd}
onFilterSave={onFilterSave} onSearchChange={onSearchChange}
onTabChange={onTabChange} onTabChange={onTabChange}
onFilterDelete={onFilterDelete} onTabDelete={onTabDelete}
onTabSave={onTabSave}
tabs={tabs}
/> />
<ProductList <ProductList
{...listProps} {...listProps}

View file

@ -1,7 +1,14 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { ActiveTab, BulkAction, Dialog, Filters, Pagination } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
TabActionDialog
} from "../types";
const productSection = "/products/"; const productSection = "/products/";
@ -13,8 +20,7 @@ export type ProductListUrlDialog =
| "publish" | "publish"
| "unpublish" | "unpublish"
| "delete" | "delete"
| "save-search" | TabActionDialog;
| "delete-search";
export enum ProductListUrlFiltersEnum { export enum ProductListUrlFiltersEnum {
isPublished = "isPublished", isPublished = "isPublished",
priceFrom = "priceFrom", priceFrom = "priceFrom",

View file

@ -351,11 +351,11 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
onFilterAdd={filter => onFilterAdd={filter =>
changeFilterField(createFilter(filter)) changeFilterField(createFilter(filter))
} }
onFilterSave={() => openModal("save-search")} onTabSave={() => openModal("save-search")}
onFilterDelete={() => openModal("delete-search")} onTabDelete={() => openModal("delete-search")}
onTabChange={handleTabChange} onTabChange={handleTabChange}
initialSearch={params.query || ""} initialSearch={params.query || ""}
filterTabs={getFilterTabs()} tabs={getFilterTabs().map(tab => tab.name)}
/> />
<ActionDialog <ActionDialog
open={params.action === "delete"} open={params.action === "delete"}

View file

@ -1,4 +1,5 @@
import { defineMessages, IntlShape } from "react-intl"; import { defineMessages, IntlShape } from "react-intl";
import { FilterContentSubmitData } from "../../../components/Filter"; import { FilterContentSubmitData } from "../../../components/Filter";
import { Filter } from "../../../components/TableFilter"; import { Filter } from "../../../components/TableFilter";
import { import {

View file

@ -33,7 +33,7 @@ const ShippingZoneDetails: React.StatelessComponent<
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl(); const intl = useIntl();
const closeModal = () => navigate(shippingZoneUrl(id)); const closeModal = () => navigate(shippingZoneUrl(id), true);
const onShippingRateCreate = (data: CreateShippingRate) => { const onShippingRateCreate = (data: CreateShippingRate) => {
if (data.shippingPriceCreate.errors.length === 0) { if (data.shippingPriceCreate.errors.length === 0) {

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -84,108 +83,102 @@ const StaffList = withStyles(styles, { name: "StaffList" })(
const intl = useIntl(); const intl = useIntl();
return ( return (
<Card> <Table>
<Table> <TableHead>
<TableHead> <TableRow>
<TableRow> <TableCell className={classes.wideColumn}>
<TableCell className={classes.wideColumn}> <FormattedMessage
<FormattedMessage defaultMessage="Name"
defaultMessage="Name" description="staff member full name"
description="staff member full name"
/>
</TableCell>
<TableCell>
<FormattedMessage defaultMessage="Email Address" />
</TableCell>
</TableRow>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={3}
settings={settings}
hasNextPage={
pageInfo && !disabled ? pageInfo.hasNextPage : undefined
}
onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : undefined
}
onPreviousPage={onPreviousPage}
/> />
</TableRow> </TableCell>
</TableFooter> <TableCell>
<TableBody> <FormattedMessage defaultMessage="Email Address" />
{renderCollection( </TableCell>
staffMembers, </TableRow>
staffMember => ( </TableHead>
<TableRow <TableFooter>
className={classNames({ <TableRow>
[classes.tableRow]: !!staffMember <TablePagination
})} colSpan={3}
hover={!!staffMember} settings={settings}
onClick={ hasNextPage={
!!staffMember ? onRowClick(staffMember.id) : undefined pageInfo && !disabled ? pageInfo.hasNextPage : undefined
} }
key={staffMember ? staffMember.id : "skeleton"} onNextPage={onNextPage}
> onUpdateListSettings={onUpdateListSettings}
<TableCell> hasPreviousPage={
<div className={classes.avatar}> pageInfo && !disabled ? pageInfo.hasPreviousPage : undefined
{maybe(() => staffMember.avatar.url) ? ( }
<img onPreviousPage={onPreviousPage}
className={classes.avatarImage} />
src={maybe(() => staffMember.avatar.url)} </TableRow>
/> </TableFooter>
) : ( <TableBody>
<div className={classes.avatarDefault}> {renderCollection(
<Typography> staffMembers,
{getUserInitials(staffMember)} staffMember => (
</Typography> <TableRow
</div> className={classNames({
)} [classes.tableRow]: !!staffMember
</div> })}
<Typography> hover={!!staffMember}
{getUserName(staffMember) || <Skeleton />} onClick={!!staffMember ? onRowClick(staffMember.id) : undefined}
</Typography> key={staffMember ? staffMember.id : "skeleton"}
<Typography >
variant={"caption"} <TableCell>
className={classes.statusText} <div className={classes.avatar}>
> {maybe(() => staffMember.avatar.url) ? (
{maybe<React.ReactNode>( <img
() => className={classes.avatarImage}
staffMember.isActive src={maybe(() => staffMember.avatar.url)}
? intl.formatMessage({ />
defaultMessage: "Active", ) : (
description: "staff member status" <div className={classes.avatarDefault}>
}) <Typography>{getUserInitials(staffMember)}</Typography>
: intl.formatMessage({ </div>
defaultMessage: "Inactive", )}
description: "staff member status" </div>
}), <Typography>
<Skeleton /> {getUserName(staffMember) || <Skeleton />}
)} </Typography>
</Typography> <Typography
</TableCell> variant={"caption"}
<TableCell> className={classes.statusText}
>
{maybe<React.ReactNode>( {maybe<React.ReactNode>(
() => staffMember.email, () =>
staffMember.isActive
? intl.formatMessage({
defaultMessage: "Active",
description: "staff member status"
})
: intl.formatMessage({
defaultMessage: "Inactive",
description: "staff member status"
}),
<Skeleton /> <Skeleton />
)} )}
</TableCell> </Typography>
</TableRow> </TableCell>
), <TableCell>
() => ( {maybe<React.ReactNode>(
<TableRow> () => staffMember.email,
<TableCell colSpan={3}> <Skeleton />
<FormattedMessage defaultMessage="No staff members found" /> )}
</TableCell> </TableCell>
</TableRow> </TableRow>
) ),
)} () => (
</TableBody> <TableRow>
</Table> <TableCell colSpan={3}>
</Card> <FormattedMessage defaultMessage="No staff members found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
); );
} }
); );

View file

@ -1,26 +1,37 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import { Container } from "@saleor/components/Container"; import { Container } from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListProps } from "@saleor/types"; import { ListProps, SearchPageProps, TabPageProps } from "@saleor/types";
import { StaffList_staffUsers_edges_node } from "../../types/StaffList"; import { StaffList_staffUsers_edges_node } from "../../types/StaffList";
import StaffList from "../StaffList/StaffList"; import StaffList from "../StaffList/StaffList";
export interface StaffListPageProps extends ListProps { export interface StaffListPageProps
extends ListProps,
SearchPageProps,
TabPageProps {
staffMembers: StaffList_staffUsers_edges_node[]; staffMembers: StaffList_staffUsers_edges_node[];
onAdd: () => void; onAdd: () => void;
onBack: () => void; onBack: () => void;
} }
const StaffListPage: React.StatelessComponent<StaffListPageProps> = ({ const StaffListPage: React.StatelessComponent<StaffListPageProps> = ({
disabled, currentTab,
initialSearch,
onAdd, onAdd,
onAll,
onBack, onBack,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps ...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -31,19 +42,33 @@ const StaffListPage: React.StatelessComponent<StaffListPageProps> = ({
{intl.formatMessage(sectionNames.configuration)} {intl.formatMessage(sectionNames.configuration)}
</AppHeader> </AppHeader>
<PageHeader title={intl.formatMessage(sectionNames.staff)}> <PageHeader title={intl.formatMessage(sectionNames.staff)}>
<Button <Button color="primary" variant="contained" onClick={onAdd}>
color="primary"
disabled={disabled}
variant="contained"
onClick={onAdd}
>
<FormattedMessage <FormattedMessage
defaultMessage="Invite staff member" defaultMessage="Invite staff member"
description="button" description="button"
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<StaffList disabled={disabled} {...listProps} /> <Card>
<SearchBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Staff Members",
description: "tab name"
})}
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Staff Member"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<StaffList {...listProps} />
</Card>
</Container> </Container>
); );
}; };

View file

@ -30,8 +30,20 @@ export const staffMemberDetailsFragment = gql`
`; `;
const staffList = gql` const staffList = gql`
${staffMemberFragment} ${staffMemberFragment}
query StaffList($first: Int, $after: String, $last: Int, $before: String) { query StaffList(
staffUsers(before: $before, after: $after, first: $first, last: $last) { $first: Int
$after: String
$last: Int
$before: String
$filter: StaffUserInput
) {
staffUsers(
before: $before
after: $after
first: $first
last: $last
filter: $filter
) {
edges { edges {
cursor cursor
node { node {

View file

@ -2,7 +2,7 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { PermissionEnum } from "./../../types/globalTypes"; import { StaffUserInput, PermissionEnum } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: StaffList // GraphQL query operation: StaffList
@ -64,4 +64,5 @@ export interface StaffListVariables {
after?: string | null; after?: string | null;
last?: number | null; last?: number | null;
before?: string | null; before?: string | null;
filter?: StaffUserInput | null;
} }

View file

@ -1,15 +1,28 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
TabActionDialog
} from "../types";
const staffSection = "/staff/"; const staffSection = "/staff/";
export const staffListPath = staffSection; export const staffListPath = staffSection;
export type StaffListUrlDialog = "add" | "remove"; export enum StaffListUrlFiltersEnum {
export type StaffListUrlQueryParams = BulkAction & query = "query"
}
export type StaffListUrlFilters = Filters<StaffListUrlFiltersEnum>;
export type StaffListUrlDialog = "add" | "remove" | TabActionDialog;
export type StaffListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<StaffListUrlDialog> & Dialog<StaffListUrlDialog> &
Pagination; Pagination &
StaffListUrlFilters;
export const staffListUrl = (params?: StaffListUrlQueryParams) => export const staffListUrl = (params?: StaffListUrlQueryParams) =>
staffListPath + "?" + stringifyQs(params); staffListPath + "?" + stringifyQs(params);

View file

@ -8,22 +8,36 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import { configurationMenuUrl } from "@saleor/configuration"; import { configurationMenuUrl } from "@saleor/configuration";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import StaffAddMemberDialog, { import StaffAddMemberDialog, {
FormData as AddStaffMemberForm FormData as AddStaffMemberForm
} from "../components/StaffAddMemberDialog"; } from "../../components/StaffAddMemberDialog";
import StaffListPage from "../components/StaffListPage"; import StaffListPage from "../../components/StaffListPage";
import { TypedStaffMemberAddMutation } from "../mutations"; import { TypedStaffMemberAddMutation } from "../../mutations";
import { TypedStaffListQuery } from "../queries"; import { TypedStaffListQuery } from "../../queries";
import { StaffMemberAdd } from "../types/StaffMemberAdd"; import { StaffMemberAdd } from "../../types/StaffMemberAdd";
import { import {
staffListUrl, staffListUrl,
StaffListUrlDialog,
StaffListUrlFilters,
StaffListUrlQueryParams, StaffListUrlQueryParams,
staffMemberDetailsUrl staffMemberDetailsUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface StaffListProps { interface StaffListProps {
params: StaffListUrlQueryParams; params: StaffListUrlQueryParams;
@ -40,6 +54,24 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: StaffListUrlFilters) =>
navigate(
staffListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
const closeModal = () => const closeModal = () =>
navigate( navigate(
staffListUrl({ staffListUrl({
@ -50,9 +82,45 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
true true
); );
const openModal = (action: StaffListUrlDialog, ids?: string[]) =>
navigate(
staffListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
navigate(
staffListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
navigate(staffListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return ( return (
<TypedStaffListQuery displayLoader variables={paginationState}> <TypedStaffListQuery displayLoader variables={queryVariables}>
{({ data, loading }) => { {({ data, loading }) => {
const handleStaffMemberAddSuccess = (data: StaffMemberAdd) => { const handleStaffMemberAddSuccess = (data: StaffMemberAdd) => {
if (data.staffCreate.errors.length === 0) { if (data.staffCreate.errors.length === 0) {
@ -97,6 +165,14 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
return ( return (
<> <>
<StaffListPage <StaffListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(staffListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading || addStaffMemberData.loading} disabled={loading || addStaffMemberData.loading}
settings={settings} settings={settings}
pageInfo={pageInfo} pageInfo={pageInfo}
@ -126,6 +202,19 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
onClose={closeModal} onClose={closeModal}
onConfirm={handleStaffMemberAdd} onConfirm={handleStaffMemberAdd}
/> />
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { StaffUserInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
StaffListUrlFilters,
StaffListUrlFiltersEnum,
StaffListUrlQueryParams
} from "../../urls";
export const STAFF_FILTERS_KEY = "staffFilters";
export function getFilterVariables(
params: StaffListUrlFilters
): StaffUserInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<StaffListUrlFilters>(STAFF_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
StaffListUrlQueryParams,
StaffListUrlFilters
>(StaffListUrlFiltersEnum);

View file

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

File diff suppressed because it is too large Load diff

View file

@ -5,12 +5,19 @@ import AttributeListPage, {
AttributeListPageProps AttributeListPageProps
} from "@saleor/attributes/components/AttributeListPage"; } from "@saleor/attributes/components/AttributeListPage";
import { attributes } from "@saleor/attributes/fixtures"; import { attributes } from "@saleor/attributes/fixtures";
import { listActionsProps, pageListProps } from "@saleor/fixtures"; import {
listActionsProps,
pageListProps,
searchPageProps,
tabPageProps
} from "@saleor/fixtures";
import Decorator from "../../Decorator"; import Decorator from "../../Decorator";
const props: AttributeListPageProps = { const props: AttributeListPageProps = {
...pageListProps.default, ...pageListProps.default,
...listActionsProps, ...listActionsProps,
...tabPageProps,
...searchPageProps,
attributes attributes
}; };

View file

@ -3,7 +3,12 @@ import React from "react";
import CategoryListPage from "../../../categories/components/CategoryListPage"; import CategoryListPage from "../../../categories/components/CategoryListPage";
import { categories } from "../../../categories/fixtures"; import { categories } from "../../../categories/fixtures";
import { listActionsProps, pageListProps } from "../../../fixtures"; import {
listActionsProps,
pageListProps,
searchPageProps,
tabPageProps
} from "../../../fixtures";
import Decorator from "../../Decorator"; import Decorator from "../../Decorator";
const categoryTableProps = { const categoryTableProps = {
@ -11,6 +16,8 @@ const categoryTableProps = {
onAddCategory: undefined, onAddCategory: undefined,
onCategoryClick: () => undefined, onCategoryClick: () => undefined,
...listActionsProps, ...listActionsProps,
...tabPageProps,
...searchPageProps,
...pageListProps.default ...pageListProps.default
}; };

View file

@ -5,12 +5,19 @@ import CollectionListPage, {
CollectionListPageProps CollectionListPageProps
} from "../../../collections/components/CollectionListPage"; } from "../../../collections/components/CollectionListPage";
import { collections } from "../../../collections/fixtures"; import { collections } from "../../../collections/fixtures";
import { listActionsProps, pageListProps } from "../../../fixtures"; import {
listActionsProps,
pageListProps,
searchPageProps,
tabPageProps
} from "../../../fixtures";
import Decorator from "../../Decorator"; import Decorator from "../../Decorator";
const props: CollectionListPageProps = { const props: CollectionListPageProps = {
...listActionsProps, ...listActionsProps,
...pageListProps.default, ...pageListProps.default,
...searchPageProps,
...tabPageProps,
collections collections
}; };
@ -19,4 +26,5 @@ storiesOf("Views / Collections / Collection list", module)
.add("default", () => <CollectionListPage {...props} />) .add("default", () => <CollectionListPage {...props} />)
.add("loading", () => ( .add("loading", () => (
<CollectionListPage {...props} collections={undefined} disabled={true} /> <CollectionListPage {...props} collections={undefined} disabled={true} />
)); ))
.add("no data", () => <CollectionListPage {...props} collections={[]} />);

View file

@ -5,12 +5,19 @@ import CustomerListPage, {
CustomerListPageProps CustomerListPageProps
} from "../../../customers/components/CustomerListPage"; } from "../../../customers/components/CustomerListPage";
import { customerList } from "../../../customers/fixtures"; import { customerList } from "../../../customers/fixtures";
import { listActionsProps, pageListProps } from "../../../fixtures"; import {
listActionsProps,
pageListProps,
searchPageProps,
tabPageProps
} from "../../../fixtures";
import Decorator from "../../Decorator"; import Decorator from "../../Decorator";
const props: CustomerListPageProps = { const props: CustomerListPageProps = {
...listActionsProps, ...listActionsProps,
...pageListProps.default, ...pageListProps.default,
...searchPageProps,
...tabPageProps,
customers: customerList customers: customerList
}; };

View file

@ -5,12 +5,19 @@ import SaleListPage, {
SaleListPageProps SaleListPageProps
} from "../../../discounts/components/SaleListPage"; } from "../../../discounts/components/SaleListPage";
import { saleList } from "../../../discounts/fixtures"; import { saleList } from "../../../discounts/fixtures";
import { listActionsProps, pageListProps } from "../../../fixtures"; import {
listActionsProps,
pageListProps,
searchPageProps,
tabPageProps
} from "../../../fixtures";
import Decorator from "../../Decorator"; import Decorator from "../../Decorator";
const props: SaleListPageProps = { const props: SaleListPageProps = {
...listActionsProps, ...listActionsProps,
...pageListProps.default, ...pageListProps.default,
...searchPageProps,
...tabPageProps,
defaultCurrency: "USD", defaultCurrency: "USD",
sales: saleList sales: saleList
}; };

Some files were not shown because too many files have changed in this diff Show more