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
- Fix en locale matching - #165 by @dominik-zeglen
- Implement the Credential Management API - #158 by @patrys
- Add search bars - #172 by @dominik-zeglen

View file

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

View file

@ -466,6 +466,10 @@ type CategoryDelete {
category: Category
}
input CategoryFilterInput {
search: String
}
input CategoryInput {
description: String
descriptionJson: JSONString
@ -1627,6 +1631,10 @@ type MenuDelete {
menu: Menu
}
input MenuFilterInput {
search: String
}
input MenuInput {
name: String
}
@ -1682,6 +1690,10 @@ type MenuItemDelete {
menuItem: MenuItem
}
input MenuItemFilterInput {
search: String
}
input MenuItemInput {
name: String
url: String
@ -1980,6 +1992,11 @@ type Mutations {
userBulkSetActive(ids: [ID]!, isActive: Boolean!): UserBulkSetActive
userUpdatePrivateMetadata(id: ID!, input: MetaInput!): UserUpdatePrivateMeta
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
}
@ -2095,6 +2112,7 @@ enum OrderDirection {
input OrderDraftFilterInput {
customer: String
created: DateRangeInput
search: String
}
type OrderEvent implements Node {
@ -2171,12 +2189,13 @@ input OrderFilterInput {
status: [OrderStatusFilter]
customer: String
created: DateRangeInput
search: String
}
type OrderLine implements Node {
id: ID!
productName: String!
translatedProductName: String!
variantName: String!
productSku: String!
isShippingRequired: Boolean!
quantity: Int!
@ -2186,6 +2205,8 @@ type OrderLine implements Node {
thumbnail(size: Int): Image
unitPrice: TaxedMoney
variant: ProductVariant
translatedProductName: String!
translatedVariantName: String!
}
input OrderLineCreateInput {
@ -2294,6 +2315,10 @@ type PageDelete {
page: Page
}
input PageFilterInput {
search: String
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
@ -2430,6 +2455,7 @@ type PermissionDisplay {
enum PermissionEnum {
MANAGE_USERS
MANAGE_STAFF
MANAGE_SERVICE_ACCOUNTS
IMPERSONATE_USERS
MANAGE_DISCOUNTS
MANAGE_GIFT_CARD
@ -2462,6 +2488,11 @@ type PluginCountableEdge {
cursor: String!
}
input PluginFilterInput {
active: Boolean
search: String
}
type PluginUpdate {
errors: [Error!]
plugin: Plugin
@ -2661,6 +2692,8 @@ enum ProductOrderField {
PRICE
MINIMAL_PRICE
DATE
TYPE
PUBLISHED
}
type ProductPricingInfo {
@ -2922,7 +2955,7 @@ type Query {
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
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
collection(id: ID!): Collection
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
paymentClientToken(gateway: GatewaysEnum): String
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
order(id: ID!): Order
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
orderByToken(token: String!): Order
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
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
giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection
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
sales(filter: SaleFilterInput, query: String, before: String, after: String, first: Int, last: Int): SaleCountableConnection
voucher(id: ID!): Voucher
@ -2965,6 +2998,8 @@ type Query {
customers(filter: CustomerFilterInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection
me: User
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
node(id: ID!): Node
}
@ -3092,6 +3127,65 @@ input SeoInput {
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 {
token: String
errors: [Error]!
@ -3307,6 +3401,7 @@ input StaffCreateInput {
note: String
permissions: [PermissionEnum]
sendPasswordEmail: Boolean
redirectUrl: String
}
type StaffDelete {
@ -3534,6 +3629,7 @@ input UserCreateInput {
isActive: Boolean
note: String
sendPasswordEmail: Boolean
redirectUrl: String
}
type UserUpdateMeta {

View file

@ -4,19 +4,37 @@ import Card from "@material-ui/core/Card";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl";
import Container from "../../../components/Container";
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 from "../AttributeList/AttributeList";
export interface AttributeListPageProps extends PageListProps, ListActions {
export interface AttributeListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
attributes: AttributeList_attributes_edges_node[];
}
const AttributeListPage: React.FC<AttributeListPageProps> = ({
onAdd,
initialSearch,
onSearchChange,
currentTab,
onAll,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps
}) => {
const intl = useIntl();
@ -32,6 +50,23 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
</Button>
</PageHeader>
<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} />
</Card>
</Container>

View file

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

View file

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

View file

@ -1,12 +1,26 @@
import { stringify as stringifyQs } from "qs";
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 type AttributeListUrlDialog = "remove";
export type AttributeListUrlQueryParams = BulkAction &
export enum AttributeListUrlFiltersEnum {
query = "query"
}
export type AttributeListUrlFilters = Filters<AttributeListUrlFiltersEnum>;
export type AttributeListUrlDialog = "remove" | TabActionDialog;
export type AttributeListUrlQueryParams = ActiveTab &
AttributeListUrlFilters &
BulkAction &
Dialog<AttributeListUrlDialog> &
Pagination;
export const attributeListPath = attributeSection;

View file

@ -3,6 +3,18 @@ import DeleteIcon from "@material-ui/icons/Delete";
import React from "react";
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 useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, {
@ -20,6 +32,7 @@ import {
attributeAddUrl,
attributeListUrl,
AttributeListUrlDialog,
AttributeListUrlFilters,
AttributeListUrlQueryParams,
attributeUrl
} from "../../urls";
@ -37,6 +50,15 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
);
const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const closeModal = () =>
navigate(
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 queryVariables = React.useMemo(() => paginationState, [params]);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return (
<AttributeListQuery variables={queryVariables}>
@ -99,14 +159,22 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
attributes={maybe(() =>
data.attributes.edges.map(edge => edge.node)
)}
currentTab={currentTab}
disabled={loading || attributeBulkDeleteOpts.loading}
initialSearch={params.query || ""}
isChecked={isSelected}
onAdd={() => navigate(attributeAddUrl())}
onAll={() => navigate(attributeListUrl())}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onRowClick={id => () => navigate(attributeUrl(id))}
onSearchChange={query => changeFilterField({ query })}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
pageInfo={pageInfo}
selected={listElements.length}
tabs={tabs.map(tab => tab.name)}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
@ -130,6 +198,19 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
onClose={closeModal}
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 {
createStyles,
Theme,
@ -12,9 +10,9 @@ import TableCell from "@material-ui/core/TableCell";
import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow";
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 Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead";
@ -49,20 +47,8 @@ const styles = (theme: Theme) =>
}
});
interface CategoryListProps
extends ListProps,
ListActions,
WithStyles<typeof styles> {
categories?: Array<{
id: string;
name: string;
children: {
totalCount: number;
};
products: {
totalCount: number;
};
}>;
interface CategoryListProps extends ListProps, ListActions {
categories?: CategoryFragment[];
isRoot: boolean;
onAdd?();
}
@ -75,144 +61,119 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
classes,
disabled,
settings,
isRoot,
pageInfo,
isChecked,
isRoot,
selected,
toggle,
toggleAll,
toolbar,
onAdd,
onNextPage,
onPreviousPage,
onUpdateListSettings,
onRowClick
}: CategoryListProps) => {
const intl = useIntl();
return (
<Card>
{!isRoot && (
<CardTitle
title={intl.formatMessage({
defaultMessage: "All Subcategories",
description: "section header"
})}
toolbar={
<Button color="primary" variant="text" onClick={onAdd}>
<FormattedMessage
defaultMessage="Create subcategory"
description="button"
/>
</Button>
}
}: CategoryListProps & WithStyles<typeof styles>) => (
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={categories}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Category Name" />
</TableCell>
<TableCell className={classes.colSubcategories}>
<FormattedMessage
defaultMessage="Subcategories"
description="number of subcategories"
/>
)}
<Table>
<TableHead
</TableCell>
<TableCell className={classes.colProducts}>
<FormattedMessage
defaultMessage="No. of Products"
description="number of products"
/>
</TableCell>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={categories}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Category Name" />
</TableCell>
<TableCell className={classes.colSubcategories}>
<FormattedMessage
defaultMessage="Subcategories"
description="number of subcategories"
/>
</TableCell>
<TableCell className={classes.colProducts}>
<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;
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 (
<TableRow
className={classes.tableRow}
hover={!!category}
onClick={category ? onRowClick(category.id) : undefined}
key={category ? category.id : "skeleton"}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(category.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{category && category.name ? category.name : <Skeleton />}
</TableCell>
<TableCell className={classes.colSubcategories}>
{category &&
category.children &&
category.children.totalCount !== undefined ? (
category.children.totalCount
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colProducts}>
{category &&
category.products &&
category.products.totalCount !== undefined ? (
category.products.totalCount
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
{isRoot ? (
<FormattedMessage defaultMessage="No categories found" />
) : (
<FormattedMessage defaultMessage="No subcategories found" />
)}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</Card>
);
}
return (
<TableRow
className={classes.tableRow}
hover={!!category}
onClick={category ? onRowClick(category.id) : undefined}
key={category ? category.id : "skeleton"}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(category.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{category && category.name ? category.name : <Skeleton />}
</TableCell>
<TableCell className={classes.colSubcategories}>
{category &&
category.children &&
category.children.totalCount !== undefined ? (
category.children.totalCount
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colProducts}>
{category &&
category.products &&
category.products.totalCount !== undefined ? (
category.products.totalCount
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
{isRoot ? (
<FormattedMessage defaultMessage="No categories found" />
) : (
<FormattedMessage defaultMessage="No subcategories found" />
)}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
)
);
CategoryList.displayName = "CategoryList";
export default CategoryList;

View file

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

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

View file

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

View file

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

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

View file

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

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
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 useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
@ -13,16 +17,26 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import { CategoryListPage } from "../components/CategoryListPage/CategoryListPage";
import { TypedCategoryBulkDeleteMutation } from "../mutations";
import { TypedRootCategoriesQuery } from "../queries";
import { CategoryBulkDelete } from "../types/CategoryBulkDelete";
import { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage";
import { TypedCategoryBulkDeleteMutation } from "../../mutations";
import { TypedRootCategoriesQuery } from "../../queries";
import { CategoryBulkDelete } from "../../types/CategoryBulkDelete";
import {
categoryAddUrl,
categoryListUrl,
CategoryListUrlDialog,
CategoryListUrlFilters,
CategoryListUrlQueryParams,
categoryUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface CategoryListProps {
params: CategoryListUrlQueryParams;
@ -41,9 +55,77 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
);
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 queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return (
<TypedRootCategoriesQuery displayLoader variables={paginationState}>
<TypedRootCategoriesQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.categories.pageInfo),
@ -78,6 +160,14 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
() => 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}
onAdd={() => navigate(categoryAddUrl())}
onRowClick={id => () => navigate(categoryUrl(id))}
@ -134,7 +224,7 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter, plural,
one {this attribute}
one {this category}
other {{displayQuantity} categories}
}?"
values={{
@ -148,6 +238,19 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
</DialogContentText>
</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 {
createStyles,
Theme,
@ -74,118 +73,110 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
const intl = useIntl();
return (
<Card>
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={collections}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Category Name" />
</TableCell>
<TableCell className={classes.colProducts}>
<FormattedMessage defaultMessage="No. of Products" />
</TableCell>
<TableCell className={classes.colAvailability}>
<FormattedMessage
defaultMessage="Availability"
description="collection availability"
/>
</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(
collections,
collection => {
const isSelected = collection
? isChecked(collection.id)
: false;
return (
<TableRow
className={classes.tableRow}
hover={!!collection}
onClick={collection ? onRowClick(collection.id) : undefined}
key={collection ? collection.id : "skeleton"}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(collection.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{maybe<React.ReactNode>(
() => collection.name,
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colProducts}>
{maybe<React.ReactNode>(
() => collection.products.totalCount,
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colAvailability}>
{maybe(
() => (
<StatusLabel
status={
collection.isPublished ? "success" : "error"
}
label={
collection.isPublished
? intl.formatMessage({
defaultMessage: "Published",
description: "collection is published"
})
: intl.formatMessage({
defaultMessage: "Not published",
description: "collection is not published"
})
}
/>
),
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No collections found" />
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={collections}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Category Name" />
</TableCell>
<TableCell className={classes.colProducts}>
<FormattedMessage defaultMessage="No. of Products" />
</TableCell>
<TableCell className={classes.colAvailability}>
<FormattedMessage
defaultMessage="Availability"
description="collection availability"
/>
</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(
collections,
collection => {
const isSelected = collection ? isChecked(collection.id) : false;
return (
<TableRow
className={classes.tableRow}
hover={!!collection}
onClick={collection ? onRowClick(collection.id) : undefined}
key={collection ? collection.id : "skeleton"}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(collection.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{maybe<React.ReactNode>(
() => collection.name,
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colProducts}>
{maybe<React.ReactNode>(
() => collection.products.totalCount,
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colAvailability}>
{maybe(
() => (
<StatusLabel
status={collection.isPublished ? "success" : "error"}
label={
collection.isPublished
? intl.formatMessage({
defaultMessage: "Published",
description: "collection is published"
})
: intl.formatMessage({
defaultMessage: "Not published",
description: "collection is not published"
})
}
/>
),
<Skeleton />
)}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</Card>
);
},
() => (
<TableRow>
<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 Card from "@material-ui/core/Card";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { Container } from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
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 from "../CollectionList/CollectionList";
export interface CollectionListPageProps extends PageListProps, ListActions {
export interface CollectionListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
collections: CollectionList_collections_edges_node[];
}
const CollectionListPage: React.StatelessComponent<CollectionListPageProps> = ({
currentTab,
disabled,
initialSearch,
onAdd,
onAll,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps
}) => {
const intl = useIntl();
@ -36,7 +54,26 @@ const CollectionListPage: React.StatelessComponent<CollectionListPageProps> = ({
/>
</Button>
</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>
);
};

View file

@ -60,8 +60,15 @@ export const collectionList = gql`
$after: String
$last: Int
$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 {
node {
...CollectionFragment

View file

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

View file

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

View file

@ -6,6 +6,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
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 useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
@ -16,21 +20,30 @@ import usePaginator, {
import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import CollectionListPage from "../components/CollectionListPage/CollectionListPage";
import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
import {
TypedCollectionBulkDelete,
TypedCollectionBulkPublish
} from "../mutations";
import { TypedCollectionListQuery } from "../queries";
import { CollectionBulkDelete } from "../types/CollectionBulkDelete";
import { CollectionBulkPublish } from "../types/CollectionBulkPublish";
} from "../../mutations";
import { TypedCollectionListQuery } from "../../queries";
import { CollectionBulkDelete } from "../../types/CollectionBulkDelete";
import { CollectionBulkPublish } from "../../types/CollectionBulkPublish";
import {
collectionAddUrl,
collectionListUrl,
CollectionListUrlDialog,
CollectionListUrlFilters,
CollectionListUrlQueryParams,
collectionUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface CollectionListProps {
params: CollectionListUrlQueryParams;
@ -50,6 +63,26 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
);
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 = () =>
navigate(
collectionListUrl({
@ -60,17 +93,47 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
true
);
const openModal = (action: CollectionListUrlDialog, ids: string[]) =>
const openModal = (action: CollectionListUrlDialog, ids?: string[]) =>
navigate(
collectionListUrl({
...params,
action,
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 queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return (
<TypedCollectionListQuery displayLoader variables={paginationState}>
<TypedCollectionListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.collections.pageInfo),
@ -130,7 +193,15 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
return (
<>
<CollectionListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAdd={() => navigate(collectionAddUrl)}
onAll={() => navigate(collectionListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading}
collections={maybe(() =>
data.collections.edges.map(edge => edge.node)
@ -289,6 +360,19 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
/>
</DialogContentText>
</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 FilterTabs, { FilterChips, FilterTab } from "../TableFilter";
export interface FilterBarProps<TUrlFilters = object, TFilterKeys = any>
extends FilterProps<TUrlFilters, TFilterKeys> {
filterMenu: IFilter<TFilterKeys>;
export interface FilterBarProps<TKeys = string> extends FilterProps {
filterMenu: IFilter<TKeys>;
}
const FilterBar: React.FC<FilterBarProps> = ({
@ -16,32 +15,32 @@ const FilterBar: React.FC<FilterBarProps> = ({
currencySymbol,
filterLabel,
filtersList,
filterTabs,
filterMenu,
currentTab,
initialSearch,
searchPlaceholder,
tabs,
onAll,
onSearchChange,
onFilterAdd,
onFilterSave,
onTabChange,
onFilterDelete
onTabDelete,
onTabSave
}) => {
const intl = useIntl();
const [search, setSearch] = React.useState(initialSearch);
React.useEffect(() => setSearch(initialSearch), [currentTab, initialSearch]);
const isCustom = currentTab === filterTabs.length + 1;
const isCustom = currentTab === tabs.length + 1;
return (
<>
<FilterTabs currentTab={currentTab}>
<FilterTab label={allTabLabel} onClick={onAll} />
{filterTabs.map((tab, tabIndex) => (
{tabs.map((tab, tabIndex) => (
<FilterTab
onClick={() => onTabChange(tabIndex + 1)}
label={tab.name}
label={tab}
key={tabIndex}
/>
))}
@ -65,6 +64,9 @@ const FilterBar: React.FC<FilterBarProps> = ({
return (
<FilterChips
currencySymbol={currencySymbol}
displayTabAction={
!!initialSearch ? (isCustom ? "save" : "delete") : null
}
menu={filterMenu}
filtersList={filtersList}
filterLabel={filterLabel}
@ -72,9 +74,9 @@ const FilterBar: React.FC<FilterBarProps> = ({
search={search}
onSearchChange={handleSearchChange}
onFilterAdd={onFilterAdd}
onFilterSave={onFilterSave}
onFilterSave={onTabSave}
isCustomSearch={isCustom}
onFilterDelete={onFilterDelete}
onFilterDelete={onTabDelete}
/>
);
}}

View file

@ -56,6 +56,7 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
<>
<DialogContent>
<TextField
autoFocus
fullWidth
label={intl.formatMessage({
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 { Theme } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import TextField, { TextFieldProps } from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
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 { 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 Link from "../Link";
@ -17,110 +17,76 @@ export interface Filter {
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(
(theme: Theme) =>
createStyles({
actionContainer: {
display: "flex",
flexWrap: "wrap",
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${
theme.spacing.unit
}px ${theme.spacing.unit * 3}px`
(theme: Theme) => ({
filterButton: {
alignItems: "center",
backgroundColor: fade(theme.palette.primary.main, 0.8),
borderRadius: "19px",
display: "flex",
height: "38px",
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: {
alignItems: "center",
backgroundColor: fade(theme.palette.primary.main, 0.8),
borderRadius: "19px",
display: "flex",
height: "38px",
justifyContent: "space-around",
margin: `0 ${theme.spacing.unit * 2}px ${theme.spacing.unit}px`,
marginLeft: 0,
padding: "0 16px"
},
filterChipContainer: {
display: "flex",
flex: 1,
flexWrap: "wrap"
},
filterContainer: {
"& a": {
paddingBottom: 10
},
borderBottom: `1px solid ${theme.palette.divider}`,
display: "flex",
marginTop: theme.spacing.unit,
padding: `0 ${theme.spacing.unit * 3}px ${theme.spacing.unit}px`
},
filterIcon: {
color: theme.palette.common.white,
height: 16,
width: 16
},
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"
}
}),
borderBottom: `1px solid ${theme.palette.divider}`,
display: "flex",
marginTop: theme.spacing.unit,
padding: `0 ${theme.spacing.unit * 3}px ${theme.spacing.unit}px`
},
filterIcon: {
color: theme.palette.common.white,
height: 16,
width: 16
},
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"
}
);
interface FilterChipProps<TFilterKeys = string> {
currencySymbol: string;
menu: IFilter<TFilterKeys>;
interface FilterChipProps extends FilterActionsProps {
displayTabAction: "save" | "delete" | null;
filtersList: Filter[];
filterLabel: string;
placeholder: string;
search: string;
isCustomSearch: boolean;
onSearchChange: (event: React.ChangeEvent<any>) => void;
onFilterAdd: (filter: FilterContentSubmitData<TFilterKeys>) => void;
onFilterDelete: () => void;
onFilterSave: () => void;
}
export const FilterChips: React.FC<FilterChipProps> = ({
currencySymbol,
displayTabAction,
filtersList,
menu,
filterLabel,
@ -129,29 +95,23 @@ export const FilterChips: React.FC<FilterChipProps> = ({
search,
onFilterAdd,
onFilterSave,
onFilterDelete,
isCustomSearch
onFilterDelete
}) => {
const theme = useTheme();
const classes = useStyles({ theme });
return (
<>
<div className={classes.actionContainer}>
<Filter
currencySymbol={currencySymbol}
menu={menu}
filterLabel={filterLabel}
onFilterAdd={onFilterAdd}
/>
<Search
fullWidth
placeholder={placeholder}
value={search}
onChange={onSearchChange}
/>
</div>
{search || (filtersList && filtersList.length) ? (
<FilterActions
currencySymbol={currencySymbol}
menu={menu}
filterLabel={filterLabel}
placeholder={placeholder}
search={search}
onSearchChange={onSearchChange}
onFilterAdd={onFilterAdd}
/>
{search || (filtersList && filtersList.length > 0) ? (
<div className={classes.filterContainer}>
<div className={classes.filterChipContainer}>
{filtersList.map(filter => (
@ -168,7 +128,7 @@ export const FilterChips: React.FC<FilterChipProps> = ({
</div>
))}
</div>
{isCustomSearch ? (
{displayTabAction === "save" ? (
<Link onClick={onFilterSave}>
<FormattedMessage
defaultMessage="Save Custom Search"
@ -176,12 +136,14 @@ export const FilterChips: React.FC<FilterChipProps> = ({
/>
</Link>
) : (
<Link onClick={onFilterDelete}>
<FormattedMessage
defaultMessage="Delete Search"
description="button"
/>
</Link>
displayTabAction === "delete" && (
<Link onClick={onFilterDelete}>
<FormattedMessage
defaultMessage="Delete Search"
description="button"
/>
</Link>
)
)}
</div>
) : (

View file

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

View file

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

View file

@ -64,8 +64,15 @@ const customerList = gql`
$before: String
$first: 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 {
node {
...CustomerFragment

View file

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

View file

@ -1,13 +1,27 @@
import { stringify as stringifyQs } from "qs";
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 customerListPath = customerSection;
export type CustomerListUrlDialog = "remove";
export type CustomerListUrlQueryParams = BulkAction &
export enum CustomerListUrlFiltersEnum {
query = "query"
}
export type CustomerListUrlFilters = Filters<CustomerListUrlFiltersEnum>;
export type CustomerListUrlDialog = "remove" | TabActionDialog;
export type CustomerListUrlQueryParams = ActiveTab &
BulkAction &
CustomerListUrlFilters &
Dialog<CustomerListUrlDialog> &
Pagination;
export const customerListUrl = (params?: CustomerListUrlQueryParams) =>

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
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 useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
@ -15,16 +19,26 @@ import usePaginator, {
import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import CustomerListPage from "../components/CustomerListPage";
import { TypedBulkRemoveCustomers } from "../mutations";
import { TypedCustomerListQuery } from "../queries";
import { BulkRemoveCustomers } from "../types/BulkRemoveCustomers";
import CustomerListPage from "../../components/CustomerListPage";
import { TypedBulkRemoveCustomers } from "../../mutations";
import { TypedCustomerListQuery } from "../../queries";
import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers";
import {
customerAddUrl,
customerListUrl,
CustomerListUrlDialog,
CustomerListUrlFilters,
CustomerListUrlQueryParams,
customerUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface CustomerListProps {
params: CustomerListUrlQueryParams;
@ -44,6 +58,26 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
);
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 = () =>
navigate(
customerListUrl({
@ -54,10 +88,47 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
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 queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return (
<TypedCustomerListQuery displayLoader variables={paginationState}>
<TypedCustomerListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.customers.pageInfo),
@ -90,6 +161,14 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
return (
<>
<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(() =>
data.customers.edges.map(edge => edge.node)
)}
@ -156,6 +235,19 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
/>
</DialogContentText>
</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 {
createStyles,
Theme,
@ -83,124 +82,119 @@ const SaleList = withStyles(styles, {
toggleAll,
toolbar
}: SaleListProps & WithStyles<typeof styles>) => (
<Card>
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={sales}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Name" description="sale name" />
</TableCell>
<TableCell className={classes.colStart}>
<FormattedMessage
defaultMessage="Starts"
description="sale start date"
/>
</TableCell>
<TableCell className={classes.colEnd}>
<FormattedMessage
defaultMessage="Ends"
description="sale end date"
/>
</TableCell>
<TableCell className={classes.colValue}>
<FormattedMessage defaultMessage="Value" description="sale value" />
</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(
sales,
sale => {
const isSelected = sale ? isChecked(sale.id) : false;
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={sales}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Name" description="sale name" />
</TableCell>
<TableCell className={classes.colStart}>
<FormattedMessage
defaultMessage="Starts"
description="sale start date"
/>
</TableCell>
<TableCell className={classes.colEnd}>
<FormattedMessage defaultMessage="Ends" description="sale end date" />
</TableCell>
<TableCell className={classes.colValue}>
<FormattedMessage defaultMessage="Value" description="sale value" />
</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(
sales,
sale => {
const isSelected = sale ? isChecked(sale.id) : false;
return (
<TableRow
className={!!sale ? classes.tableRow : undefined}
hover={!!sale}
key={sale ? sale.id : "skeleton"}
return (
<TableRow
className={!!sale ? classes.tableRow : undefined}
hover={!!sale}
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}
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} />
{sale && sale.type && sale.value ? (
sale.type === SaleType.FIXED ? (
<Money
money={{
amount: sale.value,
currency: defaultCurrency
}}
/>
) : (
<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}
>
{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" />
<Percent amount={sale.value} />
)
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</Card>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No sales found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
)
);
SaleList.displayName = "SaleList";

View file

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

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import {
createStyles,
Theme,
@ -98,163 +97,155 @@ const VoucherList = withStyles(styles, {
toggleAll,
toolbar
}: VoucherListProps & WithStyles<typeof styles>) => (
<Card>
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={vouchers}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage
defaultMessage="Code"
description="voucher code"
/>
</TableCell>
<TableCell className={classes.colMinSpent}>
<FormattedMessage
defaultMessage="Min. Spent"
description="minimum amount of spent money to activate voucher"
/>
</TableCell>
<TableCell className={classes.colStart}>
<FormattedMessage
defaultMessage="Starts"
description="voucher is active from date"
/>
</TableCell>
<TableCell className={classes.colEnd}>
<FormattedMessage
defaultMessage="Ends"
description="voucher is active until date"
/>
</TableCell>
<TableCell className={classes.colValue}>
<FormattedMessage
defaultMessage="Value"
description="voucher value"
/>
</TableCell>
<TableCell className={classes.colUses}>
<FormattedMessage
defaultMessage="Uses"
description="voucher uses"
/>
</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(
vouchers,
voucher => {
const isSelected = voucher ? isChecked(voucher.id) : false;
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={vouchers}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Code" description="voucher code" />
</TableCell>
<TableCell className={classes.colMinSpent}>
<FormattedMessage
defaultMessage="Min. Spent"
description="minimum amount of spent money to activate voucher"
/>
</TableCell>
<TableCell className={classes.colStart}>
<FormattedMessage
defaultMessage="Starts"
description="voucher is active from date"
/>
</TableCell>
<TableCell className={classes.colEnd}>
<FormattedMessage
defaultMessage="Ends"
description="voucher is active until date"
/>
</TableCell>
<TableCell className={classes.colValue}>
<FormattedMessage
defaultMessage="Value"
description="voucher value"
/>
</TableCell>
<TableCell className={classes.colUses}>
<FormattedMessage defaultMessage="Uses" description="voucher uses" />
</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(
vouchers,
voucher => {
const isSelected = voucher ? isChecked(voucher.id) : false;
return (
<TableRow
className={!!voucher ? classes.tableRow : undefined}
hover={!!voucher}
key={voucher ? voucher.id : "skeleton"}
selected={isSelected}
return (
<TableRow
className={!!voucher ? classes.tableRow : undefined}
hover={!!voucher}
key={voucher ? voucher.id : "skeleton"}
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}
>
<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 ? (
"-"
{voucher &&
voucher.discountValueType &&
voucher.discountValue ? (
voucher.discountValueType ===
DiscountValueTypeEnum.FIXED ? (
<Money
money={{
amount: voucher.discountValue,
currency: defaultCurrency
}}
/>
) : (
<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}
>
{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" />
<Percent amount={voucher.discountValue} />
)
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colUses}>
{voucher && voucher.usageLimit ? (
voucher.usageLimit
) : voucher && voucher.usageLimit === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</Card>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No vouchers found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
)
);
VoucherList.displayName = "VoucherList";

View file

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

View file

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

View file

@ -2,7 +2,7 @@
/* eslint-disable */
// 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
@ -46,4 +46,5 @@ export interface SaleListVariables {
before?: string | null;
first?: number | null;
last?: number | null;
filter?: SaleFilterInput | null;
}

View file

@ -2,7 +2,7 @@
/* eslint-disable */
// 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
@ -62,4 +62,5 @@ export interface VoucherListVariables {
before?: string | null;
first?: number | null;
last?: number | null;
filter?: VoucherFilterInput | null;
}

View file

@ -1,7 +1,14 @@
import { stringify as stringifyQs } from "qs";
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 { VoucherDetailsPageTab } from "./components/VoucherDetailsPage";
@ -9,10 +16,16 @@ export const discountSection = "/discounts/";
export const saleSection = urlJoin(discountSection, "sales");
export const saleListPath = saleSection;
export type SaleListUrlDialog = "remove";
export type SaleListUrlQueryParams = BulkAction &
export enum SaleListUrlFiltersEnum {
query = "query"
}
export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum>;
export type SaleListUrlDialog = "remove" | TabActionDialog;
export type SaleListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<SaleListUrlDialog> &
Pagination;
Pagination &
SaleListUrlFilters;
export const saleListUrl = (params?: SaleListUrlQueryParams) =>
saleListPath + "?" + stringifyQs(params);
export const salePath = (id: string) => urlJoin(saleSection, id);
@ -35,10 +48,16 @@ export const saleAddUrl = saleAddPath;
export const voucherSection = urlJoin(discountSection, "vouchers");
export const voucherListPath = voucherSection;
export type VoucherListUrlDialog = "remove";
export type VoucherListUrlQueryParams = BulkAction &
export enum VoucherListUrlFiltersEnum {
query = "query"
}
export type VoucherListUrlFilters = Filters<VoucherListUrlFiltersEnum>;
export type VoucherListUrlDialog = "remove" | TabActionDialog;
export type VoucherListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<VoucherListUrlDialog> &
Pagination;
Pagination &
VoucherListUrlFilters;
export const voucherListUrl = (params?: VoucherListUrlQueryParams) =>
voucherListPath + "?" + stringifyQs(params);
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 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 useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings";
@ -17,16 +21,26 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import SaleListPage from "../components/SaleListPage";
import { TypedSaleBulkDelete } from "../mutations";
import { TypedSaleList } from "../queries";
import { SaleBulkDelete } from "../types/SaleBulkDelete";
import SaleListPage from "../../components/SaleListPage";
import { TypedSaleBulkDelete } from "../../mutations";
import { TypedSaleList } from "../../queries";
import { SaleBulkDelete } from "../../types/SaleBulkDelete";
import {
saleAddUrl,
saleListUrl,
SaleListUrlDialog,
SaleListUrlFilters,
SaleListUrlQueryParams,
saleUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface SaleListProps {
params: SaleListUrlQueryParams;
@ -47,13 +61,79 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
);
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 queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
return (
<TypedSaleList displayLoader variables={paginationState}>
<TypedSaleList displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.sales.pageInfo),
@ -91,6 +171,14 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
<>
<WindowTitle title={intl.formatMessage(sectionNames.sales)} />
<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)}
sales={maybe(() => data.sales.edges.map(edge => edge.node))}
settings={settings}
@ -150,6 +238,19 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
</DialogContentText>
)}
</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 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 useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings";
@ -17,16 +21,26 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import VoucherListPage from "../components/VoucherListPage";
import { TypedVoucherBulkDelete } from "../mutations";
import { TypedVoucherList } from "../queries";
import { VoucherBulkDelete } from "../types/VoucherBulkDelete";
import VoucherListPage from "../../components/VoucherListPage";
import { TypedVoucherBulkDelete } from "../../mutations";
import { TypedVoucherList } from "../../queries";
import { VoucherBulkDelete } from "../../types/VoucherBulkDelete";
import {
voucherAddUrl,
voucherListUrl,
VoucherListUrlDialog,
VoucherListUrlFilters,
VoucherListUrlQueryParams,
voucherUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface VoucherListProps {
params: VoucherListUrlQueryParams;
@ -47,13 +61,79 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
);
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 queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
return (
<TypedVoucherList displayLoader variables={paginationState}>
<TypedVoucherList displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.vouchers.pageInfo),
@ -92,6 +172,14 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
title={intl.formatMessage(sectionNames.vouchers)}
/>
<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)}
settings={settings}
vouchers={maybe(() =>
@ -153,6 +241,19 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
</DialogContentText>
)}
</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,
FilterPageProps,
ListActions,
PageListProps
PageListProps,
SearchPageProps,
TabPageProps
} from "./types";
const pageInfo = {
@ -46,23 +48,26 @@ export const countries = [
{ code: "AS", label: "American Samoa" }
];
export const filterPageProps: FilterPageProps<{}, unknown> = {
currencySymbol: "USD",
export const tabPageProps: TabPageProps = {
currentTab: 0,
filterTabs: [
{
data: {},
name: "Tab X"
}
],
filtersList: [],
initialSearch: "",
onAll: () => undefined,
onFilterAdd: () => undefined,
onFilterDelete: () => undefined,
onFilterSave: () => undefined,
onSearchChange: () => undefined,
onTabChange: () => undefined
onTabChange: () => undefined,
onTabDelete: () => undefined,
onTabSave: () => undefined,
tabs: ["Tab X"]
};
export const searchPageProps: SearchPageProps = {
initialSearch: "",
onSearchChange: () => undefined
};
export const filterPageProps: FilterPageProps = {
...searchPageProps,
...tabPageProps,
currencySymbol: "USD",
filtersList: [],
onFilterAdd: () => undefined
};
export const filters: Filter[] = [

View file

@ -6,18 +6,36 @@ import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
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 from "../OrderDraftList";
export interface OrderDraftListPageProps extends PageListProps, ListActions {
export interface OrderDraftListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
orders: OrderDraftList_draftOrders_edges_node[];
}
const OrderDraftListPage: React.StatelessComponent<OrderDraftListPageProps> = ({
currentTab,
disabled,
initialSearch,
onAdd,
onAll,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps
}) => {
const intl = useIntl();
@ -38,6 +56,23 @@ const OrderDraftListPage: React.StatelessComponent<OrderDraftListPageProps> = ({
</Button>
</PageHeader>
<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} />
</Card>
</Container>

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
/* eslint-disable */
// 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
@ -81,4 +81,5 @@ export interface OrderDraftListVariables {
after?: string | null;
last?: number | null;
before?: string | null;
filter?: OrderDraftFilterInput | null;
}

View file

@ -2,7 +2,7 @@
/* eslint-disable */
// 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
@ -81,6 +81,5 @@ export interface OrderListVariables {
after?: string | null;
last?: number | null;
before?: string | null;
status?: OrderStatusFilter | null;
filter?: OrderFilterInput | null;
}

View file

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

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
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 useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
@ -14,19 +18,29 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import OrderDraftListPage from "../components/OrderDraftListPage";
import OrderDraftListPage from "../../components/OrderDraftListPage";
import {
TypedOrderDraftBulkCancelMutation,
TypedOrderDraftCreateMutation
} from "../mutations";
import { TypedOrderDraftListQuery } from "../queries";
import { OrderDraftBulkCancel } from "../types/OrderDraftBulkCancel";
import { OrderDraftCreate } from "../types/OrderDraftCreate";
} from "../../mutations";
import { TypedOrderDraftListQuery } from "../../queries";
import { OrderDraftBulkCancel } from "../../types/OrderDraftBulkCancel";
import { OrderDraftCreate } from "../../types/OrderDraftCreate";
import {
orderDraftListUrl,
OrderDraftListUrlDialog,
OrderDraftListUrlFilters,
OrderDraftListUrlQueryParams,
orderUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface OrderDraftListProps {
params: OrderDraftListUrlQueryParams;
@ -46,13 +60,34 @@ export const OrderDraftList: React.StatelessComponent<OrderDraftListProps> = ({
);
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 = () =>
navigate(
orderDraftListUrl({
...params,
action: undefined,
ids: undefined
})
}),
true
);
const handleCreateOrderCreateSuccess = (data: OrderDraftCreate) => {
@ -64,12 +99,49 @@ export const OrderDraftList: React.StatelessComponent<OrderDraftListProps> = ({
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 queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return (
<TypedOrderDraftCreateMutation onCompleted={handleCreateOrderCreateSuccess}>
{createOrder => (
<TypedOrderDraftListQuery displayLoader variables={paginationState}>
<TypedOrderDraftListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.draftOrders.pageInfo),
@ -114,6 +186,14 @@ export const OrderDraftList: React.StatelessComponent<OrderDraftListProps> = ({
return (
<>
<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}
settings={settings}
orders={maybe(() =>
@ -174,6 +254,19 @@ export const OrderDraftList: React.StatelessComponent<OrderDraftListProps> = ({
/>
</DialogContentText>
</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>
}
onSearchChange={email => changeFilterField({ email })}
onSearchChange={query => changeFilterField({ query })}
onFilterAdd={data =>
changeFilterField(createFilter(params, data))
}
onFilterSave={() => openModal("save-search")}
onFilterDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
onTabDelete={() => openModal("delete-search")}
onTabChange={handleTabChange}
initialSearch={params.email || ""}
filterTabs={getFilterTabs()}
initialSearch={params.query || ""}
tabs={getFilterTabs().map(tab => tab.name)}
onAll={() =>
changeFilters({
status: undefined

View file

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

View file

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

View file

@ -20,6 +20,7 @@ import { OrderFilterKeys } from "../../components/OrderListFilter";
import {
OrderListUrlFilters,
OrderListUrlFiltersEnum,
OrderListUrlFiltersWithMultipleValuesEnum,
OrderListUrlQueryParams
} from "../../urls";
@ -83,6 +84,7 @@ export function getFilterVariables(
lte: params.dateTo
},
customer: params.email,
search: params.query,
status: Array.isArray(params.status)
? params.status.map(status => findInEnum(status, OrderStatusFilter))
: 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) {
const statusFilterChips = Array.isArray(filters.status)
? filters.status.map((status, statusIndex) => ({
@ -228,4 +244,7 @@ export const {
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
OrderListUrlQueryParams,
OrderListUrlFilters
>(OrderListUrlFiltersEnum);
>({
...OrderListUrlFiltersEnum,
...OrderListUrlFiltersWithMultipleValuesEnum
});

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import {
createStyles,
Theme,
@ -70,138 +69,132 @@ const ProductTypeList = withStyles(styles, { name: "ProductTypeList" })(
const intl = useIntl();
return (
<Card>
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={productTypes}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage
defaultMessage="Type Name"
description="product type name"
/>
</TableCell>
<TableCell className={classes.colType}>
<FormattedMessage
defaultMessage="Type"
description="product type is either simple or configurable"
/>
</TableCell>
<TableCell className={classes.colTax}>
<FormattedMessage
defaultMessage="Tax"
description="tax rate for a product type"
/>
</TableCell>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns}
hasNextPage={
pageInfo && !disabled ? pageInfo.hasNextPage : false
}
onNextPage={onNextPage}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
}
onPreviousPage={onPreviousPage}
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
productTypes,
productType => {
const isSelected = productType
? isChecked(productType.id)
: false;
return (
<TableRow
className={!!productType ? classes.link : undefined}
hover={!!productType}
key={productType ? productType.id : "skeleton"}
onClick={
productType ? onRowClick(productType.id) : undefined
}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(productType.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{productType ? (
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={productTypes}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage
defaultMessage="Type Name"
description="product type name"
/>
</TableCell>
<TableCell className={classes.colType}>
<FormattedMessage
defaultMessage="Type"
description="product type is either simple or configurable"
/>
</TableCell>
<TableCell className={classes.colTax}>
<FormattedMessage
defaultMessage="Tax"
description="tax rate for a product type"
/>
</TableCell>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
onNextPage={onNextPage}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
}
onPreviousPage={onPreviousPage}
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
productTypes,
productType => {
const isSelected = productType
? isChecked(productType.id)
: false;
return (
<TableRow
className={!!productType ? classes.link : undefined}
hover={!!productType}
key={productType ? productType.id : "skeleton"}
onClick={productType ? onRowClick(productType.id) : undefined}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(productType.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{productType ? (
<>
{productType.name}
<Typography variant="caption">
{maybe(() => productType.hasVariants)
? 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}
<Typography variant="caption">
{maybe(() => productType.hasVariants)
? intl.formatMessage({
defaultMessage: "Configurable",
description: "product type"
})
: intl.formatMessage({
defaultMessage: "Simple product",
description: "product type"
})}
</Typography>
<FormattedMessage
defaultMessage="Physical"
description="product type"
/>
</>
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colType}>
{maybe(() => productType.isShippingRequired) !==
undefined ? (
productType.isShippingRequired ? (
<>
<FormattedMessage
defaultMessage="Physical"
description="product type"
/>
</>
) : (
<>
<FormattedMessage
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" />
<>
<FormattedMessage
defaultMessage="Digital"
description="product type"
/>
</>
)
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colTax}>
{maybe(() => productType.taxType) ? (
productType.taxType.description
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</Card>
);
},
() => (
<TableRow>
<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 Card from "@material-ui/core/Card";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader";
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
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 from "../ProductTypeList";
interface ProductTypeListPageProps extends PageListProps, ListActions {
export interface ProductTypeListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
productTypes: ProductTypeList_productTypes_edges_node[];
onBack: () => void;
}
const ProductTypeListPage: React.StatelessComponent<
ProductTypeListPageProps
> = ({ disabled, onAdd, onBack, ...listProps }) => {
> = ({
currentTab,
initialSearch,
onAdd,
onAll,
onBack,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps
}) => {
const intl = useIntl();
return (
@ -26,19 +49,33 @@ const ProductTypeListPage: React.StatelessComponent<
{intl.formatMessage(sectionNames.configuration)}
</AppHeader>
<PageHeader title={intl.formatMessage(sectionNames.productTypes)}>
<Button
color="primary"
variant="contained"
disabled={disabled}
onClick={onAdd}
>
<Button color="primary" variant="contained" onClick={onAdd}>
<FormattedMessage
defaultMessage="create product type"
description="button"
/>
</Button>
</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>
);
};

View file

@ -51,8 +51,15 @@ export const productTypeListQuery = gql`
$before: String
$first: 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 {
node {
...ProductTypeFragment

View file

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

View file

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

View file

@ -5,26 +5,41 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
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 useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import { commonMessages } from "@saleor/intl";
import { PAGINATE_BY } from "../../config";
import { configurationMenuUrl } from "../../configuration";
import { getMutationState, maybe } from "../../misc";
import ProductTypeListPage from "../components/ProductTypeListPage";
import { TypedProductTypeBulkDeleteMutation } from "../mutations";
import { TypedProductTypeListQuery } from "../queries";
import { ProductTypeBulkDelete } from "../types/ProductTypeBulkDelete";
import { ListViews } from "@saleor/types";
import { configurationMenuUrl } from "../../../configuration";
import { getMutationState, maybe } from "../../../misc";
import ProductTypeListPage from "../../components/ProductTypeListPage";
import { TypedProductTypeBulkDeleteMutation } from "../../mutations";
import { TypedProductTypeListQuery } from "../../queries";
import { ProductTypeBulkDelete } from "../../types/ProductTypeBulkDelete";
import {
productTypeAddUrl,
productTypeListUrl,
ProductTypeListUrlDialog,
ProductTypeListUrlFilters,
ProductTypeListUrlQueryParams,
productTypeUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface ProductTypeListProps {
params: ProductTypeListUrlQueryParams;
@ -39,13 +54,80 @@ export const ProductTypeList: React.StatelessComponent<
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids
);
const { settings } = useListSettings(ListViews.PRODUCT_LIST);
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 (
<TypedProductTypeListQuery displayLoader variables={paginationState}>
<TypedProductTypeListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.productTypes.pageInfo),
@ -93,6 +175,14 @@ export const ProductTypeList: React.StatelessComponent<
return (
<>
<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}
productTypes={maybe(() =>
data.productTypes.edges.map(edge => edge.node)
@ -150,6 +240,19 @@ export const ProductTypeList: React.StatelessComponent<
/>
</DialogContentText>
</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 { FilterProps } from "@saleor/types";
import { StockAvailability } from "@saleor/types/globalTypes";
import { ProductListUrlFilters } from "../../urls";
type ProductListFilterProps = FilterProps<
ProductListUrlFilters,
ProductFilterKeys
type ProductListFilterProps = Omit<
FilterProps,
"allTabLabel" | "filterLabel" | "searchPlaceholder"
>;
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";
export default ProductListFilter;

View file

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

View file

@ -1,7 +1,14 @@
import { stringify as stringifyQs } from "qs";
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/";
@ -13,8 +20,7 @@ export type ProductListUrlDialog =
| "publish"
| "unpublish"
| "delete"
| "save-search"
| "delete-search";
| TabActionDialog;
export enum ProductListUrlFiltersEnum {
isPublished = "isPublished",
priceFrom = "priceFrom",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
/* eslint-disable */
// 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
@ -64,4 +64,5 @@ export interface StaffListVariables {
after?: string | null;
last?: number | null;
before?: string | null;
filter?: StaffUserInput | null;
}

View file

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

View file

@ -8,22 +8,36 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator";
import { useIntl } from "react-intl";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import { configurationMenuUrl } from "@saleor/configuration";
import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import StaffAddMemberDialog, {
FormData as AddStaffMemberForm
} from "../components/StaffAddMemberDialog";
import StaffListPage from "../components/StaffListPage";
import { TypedStaffMemberAddMutation } from "../mutations";
import { TypedStaffListQuery } from "../queries";
import { StaffMemberAdd } from "../types/StaffMemberAdd";
} from "../../components/StaffAddMemberDialog";
import StaffListPage from "../../components/StaffListPage";
import { TypedStaffMemberAddMutation } from "../../mutations";
import { TypedStaffListQuery } from "../../queries";
import { StaffMemberAdd } from "../../types/StaffMemberAdd";
import {
staffListUrl,
StaffListUrlDialog,
StaffListUrlFilters,
StaffListUrlQueryParams,
staffMemberDetailsUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface StaffListProps {
params: StaffListUrlQueryParams;
@ -40,6 +54,24 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
);
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 = () =>
navigate(
staffListUrl({
@ -50,9 +82,45 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
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 queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return (
<TypedStaffListQuery displayLoader variables={paginationState}>
<TypedStaffListQuery displayLoader variables={queryVariables}>
{({ data, loading }) => {
const handleStaffMemberAddSuccess = (data: StaffMemberAdd) => {
if (data.staffCreate.errors.length === 0) {
@ -97,6 +165,14 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
return (
<>
<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}
settings={settings}
pageInfo={pageInfo}
@ -126,6 +202,19 @@ export const StaffList: React.StatelessComponent<StaffListProps> = ({
onClose={closeModal}
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
} from "@saleor/attributes/components/AttributeListPage";
import { attributes } from "@saleor/attributes/fixtures";
import { listActionsProps, pageListProps } from "@saleor/fixtures";
import {
listActionsProps,
pageListProps,
searchPageProps,
tabPageProps
} from "@saleor/fixtures";
import Decorator from "../../Decorator";
const props: AttributeListPageProps = {
...pageListProps.default,
...listActionsProps,
...tabPageProps,
...searchPageProps,
attributes
};

View file

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

View file

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

View file

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

View file

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

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