diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac2eedc9..1b0ec0154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/locale/messages.pot b/locale/messages.pot index 8197eddc2..a054058f8 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -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: diff --git a/schema.graphql b/schema.graphql index a71cfaefc..2329832e6 100644 --- a/schema.graphql +++ b/schema.graphql @@ -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 { diff --git a/src/attributes/components/AttributeListPage/AttributeListPage.tsx b/src/attributes/components/AttributeListPage/AttributeListPage.tsx index e9482704a..df735bd07 100644 --- a/src/attributes/components/AttributeListPage/AttributeListPage.tsx +++ b/src/attributes/components/AttributeListPage/AttributeListPage.tsx @@ -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 = ({ onAdd, + initialSearch, + onSearchChange, + currentTab, + onAll, + onTabChange, + onTabDelete, + onTabSave, + tabs, ...listProps }) => { const intl = useIntl(); @@ -32,6 +50,23 @@ const AttributeListPage: React.FC = ({ + diff --git a/src/attributes/queries.ts b/src/attributes/queries.ts index 9b91f4723..66f82d08d 100644 --- a/src/attributes/queries.ts +++ b/src/attributes/queries.ts @@ -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 diff --git a/src/attributes/types/AttributeList.ts b/src/attributes/types/AttributeList.ts index 938c0098d..d5f296707 100644 --- a/src/attributes/types/AttributeList.ts +++ b/src/attributes/types/AttributeList.ts @@ -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; diff --git a/src/attributes/urls.ts b/src/attributes/urls.ts index ccd6c28be..80bef3768 100644 --- a/src/attributes/urls.ts +++ b/src/attributes/urls.ts @@ -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; +export type AttributeListUrlDialog = "remove" | TabActionDialog; +export type AttributeListUrlQueryParams = ActiveTab & + AttributeListUrlFilters & + BulkAction & Dialog & Pagination; export const attributeListPath = attributeSection; diff --git a/src/attributes/views/AttributeList/AttributeList.tsx b/src/attributes/views/AttributeList/AttributeList.tsx index 262d4e727..c8b8241d1 100644 --- a/src/attributes/views/AttributeList/AttributeList.tsx +++ b/src/attributes/views/AttributeList/AttributeList.tsx @@ -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 = ({ 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 = ({ 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 ( @@ -99,14 +159,22 @@ const AttributeList: React.FC = ({ 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 = ({ params }) => { onClose={closeModal} quantity={maybe(() => params.ids.length)} /> + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/attributes/views/AttributeList/filters.ts b/src/attributes/views/AttributeList/filters.ts new file mode 100644 index 000000000..51db99dc9 --- /dev/null +++ b/src/attributes/views/AttributeList/filters.ts @@ -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(PRODUCT_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + AttributeListUrlQueryParams, + AttributeListUrlFilters +>(AttributeListUrlFiltersEnum); diff --git a/src/categories/components/CategoryList/CategoryList.tsx b/src/categories/components/CategoryList/CategoryList.tsx index aff5a0851..dc18b965a 100644 --- a/src/categories/components/CategoryList/CategoryList.tsx +++ b/src/categories/components/CategoryList/CategoryList.tsx @@ -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 { - 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 ( - - {!isRoot && ( - - - - } + }: CategoryListProps & WithStyles) => ( + + + + + + + - )} -
- + + + + + + + - - - - - - - - - - - - - - - - - {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} + /> + + + + {renderCollection( + categories, + category => { + const isSelected = category ? isChecked(category.id) : false; - return ( - - - toggle(category.id)} - /> - - - {category && category.name ? category.name : } - - - {category && - category.children && - category.children.totalCount !== undefined ? ( - category.children.totalCount - ) : ( - - )} - - - {category && - category.products && - category.products.totalCount !== undefined ? ( - category.products.totalCount - ) : ( - - )} - - - ); - }, - () => ( - - - {isRoot ? ( - - ) : ( - - )} - - - ) - )} - -
-
- ); - } + return ( + + + toggle(category.id)} + /> + + + {category && category.name ? category.name : } + + + {category && + category.children && + category.children.totalCount !== undefined ? ( + category.children.totalCount + ) : ( + + )} + + + {category && + category.products && + category.products.totalCount !== undefined ? ( + category.products.totalCount + ) : ( + + )} + + + ); + }, + () => ( + + + {isRoot ? ( + + ) : ( + + )} + + + ) + )} + + + ) ); CategoryList.displayName = "CategoryList"; export default CategoryList; diff --git a/src/categories/components/CategoryListPage/CategoryListPage.tsx b/src/categories/components/CategoryListPage/CategoryListPage.tsx index b552a482e..db377fe64 100644 --- a/src/categories/components/CategoryListPage/CategoryListPage.tsx +++ b/src/categories/components/CategoryListPage/CategoryListPage.tsx @@ -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 = ({ 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 ( @@ -49,23 +60,42 @@ export const CategoryListPage: React.StatelessComponent = ({ /> - + + + + ); }; diff --git a/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx b/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx index 0e33eef2c..aa3f7d715 100644 --- a/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx +++ b/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx @@ -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< {currentTab === CategoryPageTab.categories && ( - + + + + + } + /> + + )} {currentTab === CategoryPageTab.products && ( ( ); 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 } } } diff --git a/src/categories/types/CategoryFragment.ts b/src/categories/types/CategoryFragment.ts new file mode 100644 index 000000000..d01e37e25 --- /dev/null +++ b/src/categories/types/CategoryFragment.ts @@ -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; +} diff --git a/src/categories/types/RootCategories.ts b/src/categories/types/RootCategories.ts index 0c55c71df..2517b129f 100644 --- a/src/categories/types/RootCategories.ts +++ b/src/categories/types/RootCategories.ts @@ -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; } diff --git a/src/categories/urls.ts b/src/categories/urls.ts index 6d49a8a85..1c06252e7 100644 --- a/src/categories/urls.ts +++ b/src/categories/urls.ts @@ -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; +export type CategoryListUrlDialog = "delete" | TabActionDialog; +export type CategoryListUrlQueryParams = ActiveTab & + BulkAction & + CategoryListUrlFilters & Dialog & Pagination; export const categoryListUrl = (params?: CategoryListUrlQueryParams) => diff --git a/src/categories/views/CategoryList.tsx b/src/categories/views/CategoryList/CategoryList.tsx similarity index 62% rename from src/categories/views/CategoryList.tsx rename to src/categories/views/CategoryList/CategoryList.tsx index 1064d9ccc..be57e3bda 100644 --- a/src/categories/views/CategoryList.tsx +++ b/src/categories/views/CategoryList/CategoryList.tsx @@ -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 = ({ ); 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 ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.categories.pageInfo), @@ -78,6 +160,14 @@ export const CategoryList: React.StatelessComponent = ({ () => 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 = ({ > = ({ + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/categories/views/CategoryList/filter.ts b/src/categories/views/CategoryList/filter.ts new file mode 100644 index 000000000..f61179a63 --- /dev/null +++ b/src/categories/views/CategoryList/filter.ts @@ -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(CATEGORY_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + CategoryListUrlQueryParams, + CategoryListUrlFilters +>(CategoryListUrlFiltersEnum); diff --git a/src/categories/views/CategoryList/index.ts b/src/categories/views/CategoryList/index.ts new file mode 100644 index 000000000..52c4017ff --- /dev/null +++ b/src/categories/views/CategoryList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./CategoryList"; +export * from "./CategoryList"; diff --git a/src/collections/components/CollectionList/CollectionList.tsx b/src/collections/components/CollectionList/CollectionList.tsx index 3690c39ea..68d7302df 100644 --- a/src/collections/components/CollectionList/CollectionList.tsx +++ b/src/collections/components/CollectionList/CollectionList.tsx @@ -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 ( - - - - - - - - - - - - - - - - - - - - {renderCollection( - collections, - collection => { - const isSelected = collection - ? isChecked(collection.id) - : false; - return ( - - - toggle(collection.id)} - /> - - - {maybe( - () => collection.name, - - )} - - - {maybe( - () => collection.products.totalCount, - - )} - - - {maybe( - () => ( - - ), - - )} - - - ); - }, - () => ( - - - +
+ + + + + + + + + + + + + + + + + + {renderCollection( + collections, + collection => { + const isSelected = collection ? isChecked(collection.id) : false; + return ( + + + toggle(collection.id)} + /> + + + {maybe( + () => collection.name, + + )} + + + {maybe( + () => collection.products.totalCount, + + )} + + + {maybe( + () => ( + + ), + + )} - ) - )} - -
-
+ ); + }, + () => ( + + + + + + ) + )} + + ); } ); diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index 5138840db..2426eede8 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -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 = ({ + currentTab, disabled, + initialSearch, onAdd, + onAll, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, ...listProps }) => { const intl = useIntl(); @@ -36,7 +54,26 @@ const CollectionListPage: React.StatelessComponent = ({ /> - + + + + ); }; diff --git a/src/collections/queries.ts b/src/collections/queries.ts index f13bcc24e..bc2a67d1a 100644 --- a/src/collections/queries.ts +++ b/src/collections/queries.ts @@ -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 diff --git a/src/collections/types/CollectionList.ts b/src/collections/types/CollectionList.ts index c17574dc0..93c2af1c7 100644 --- a/src/collections/types/CollectionList.ts +++ b/src/collections/types/CollectionList.ts @@ -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; } diff --git a/src/collections/urls.ts b/src/collections/urls.ts index e6770071e..e17cfca21 100644 --- a/src/collections/urls.ts +++ b/src/collections/urls.ts @@ -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; +export type CollectionListUrlDialog = + | "publish" + | "unpublish" + | "remove" + | TabActionDialog; +export type CollectionListUrlQueryParams = ActiveTab & + BulkAction & + CollectionListUrlFilters & Dialog & Pagination; export const collectionListUrl = (params?: CollectionListUrlQueryParams) => diff --git a/src/collections/views/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx similarity index 78% rename from src/collections/views/CollectionList.tsx rename to src/collections/views/CollectionList/CollectionList.tsx index 0b7345126..c36809f8a 100644 --- a/src/collections/views/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -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 = ({ ); 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 = ({ 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 ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.collections.pageInfo), @@ -130,7 +193,15 @@ export const CollectionList: React.StatelessComponent = ({ return ( <> 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 = ({ /> + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/collections/views/CollectionList/filter.ts b/src/collections/views/CollectionList/filter.ts new file mode 100644 index 000000000..47a710945 --- /dev/null +++ b/src/collections/views/CollectionList/filter.ts @@ -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(COLLECTION_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + CollectionListUrlQueryParams, + CollectionListUrlFilters +>(CollectionListUrlFiltersEnum); diff --git a/src/collections/views/CollectionList/index.ts b/src/collections/views/CollectionList/index.ts new file mode 100644 index 000000000..4f5db2333 --- /dev/null +++ b/src/collections/views/CollectionList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./CollectionList"; +export * from "./CollectionList"; diff --git a/src/components/Filter/FilterActions.tsx b/src/components/Filter/FilterActions.tsx new file mode 100644 index 000000000..21266ae12 --- /dev/null +++ b/src/components/Filter/FilterActions.tsx @@ -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 = props => { + const classes = useInputStyles({}); + return ( + + ); +}; + +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) => void; +} +export interface FilterActionsPropsFilters { + currencySymbol: string; + menu: IFilter; + filterLabel: string; + onFilterAdd: (filter: FilterContentSubmitData) => void; +} + +export const FilterActionsOnlySearch: React.FC< + FilterActionsPropsSearch +> = props => { + const { onSearchChange, placeholder, search } = props; + const classes = useStyles(props); + + return ( +
+ +
+ ); +}; + +export type FilterActionsProps = FilterActionsPropsSearch & + FilterActionsPropsFilters; +const FilterActions: React.FC = props => { + const { + currencySymbol, + filterLabel, + menu, + onFilterAdd, + onSearchChange, + placeholder, + search + } = props; + const classes = useStyles(props); + + return ( +
+ + +
+ ); +}; + +FilterActions.displayName = "FilterActions"; +export default FilterActions; diff --git a/src/components/Filter/FilterSearch.tsx b/src/components/Filter/FilterSearch.tsx new file mode 100644 index 000000000..950bfa58d --- /dev/null +++ b/src/components/Filter/FilterSearch.tsx @@ -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 = 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 ( + + {debounceSearchChange => { + const handleSearchChange = (event: React.ChangeEvent) => { + const value = event.target.value; + setSearch(value); + debounceSearchChange(value); + }; + + return ( + <> + + {!!displaySearchAction ? ( +
+
+ {displaySearchAction === "save" ? ( + + + + ) : ( + + + + )} +
+
+ ) : ( +
+ )} + + ); + }} +
+ ); +}; + +FilterSearch.displayName = "FilterSearch"; +export default FilterSearch; diff --git a/src/components/FilterBar/FilterBar.tsx b/src/components/FilterBar/FilterBar.tsx index e55ca25da..f4a71b815 100644 --- a/src/components/FilterBar/FilterBar.tsx +++ b/src/components/FilterBar/FilterBar.tsx @@ -6,9 +6,8 @@ import Debounce from "../Debounce"; import { IFilter } from "../Filter/types"; import FilterTabs, { FilterChips, FilterTab } from "../TableFilter"; -export interface FilterBarProps - extends FilterProps { - filterMenu: IFilter; +export interface FilterBarProps extends FilterProps { + filterMenu: IFilter; } const FilterBar: React.FC = ({ @@ -16,32 +15,32 @@ const FilterBar: React.FC = ({ 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.map((tab, tabIndex) => ( + {tabs.map((tab, tabIndex) => ( onTabChange(tabIndex + 1)} - label={tab.name} + label={tab} key={tabIndex} /> ))} @@ -65,6 +64,9 @@ const FilterBar: React.FC = ({ return ( = ({ search={search} onSearchChange={handleSearchChange} onFilterAdd={onFilterAdd} - onFilterSave={onFilterSave} + onFilterSave={onTabSave} isCustomSearch={isCustom} - onFilterDelete={onFilterDelete} + onFilterDelete={onTabDelete} /> ); }} diff --git a/src/components/SaveFilterTabDialog/SaveFilterTabDialog.tsx b/src/components/SaveFilterTabDialog/SaveFilterTabDialog.tsx index 060dc247d..2527fdae6 100644 --- a/src/components/SaveFilterTabDialog/SaveFilterTabDialog.tsx +++ b/src/components/SaveFilterTabDialog/SaveFilterTabDialog.tsx @@ -56,6 +56,7 @@ const SaveFilterTabDialog: React.FC = ({ <> = props => { + const { + allTabLabel, + currentTab, + initialSearch, + onSearchChange, + searchPlaceholder, + tabs, + onAll, + onTabChange, + onTabDelete, + onTabSave + } = props; + const intl = useIntl(); + + const isCustom = currentTab === tabs.length + 1; + + return ( + <> + + + {tabs.map((tab, tabIndex) => ( + onTabChange(tabIndex + 1)} + label={tab} + key={tabIndex} + /> + ))} + {isCustom && ( + undefined} + label={intl.formatMessage({ + defaultMessage: "Custom Filter" + })} + /> + )} + + + + ); +}; + +SearchBar.displayName = "SearchBar"; +export default SearchBar; diff --git a/src/components/SearchBar/index.ts b/src/components/SearchBar/index.ts new file mode 100644 index 000000000..169b18c9e --- /dev/null +++ b/src/components/SearchBar/index.ts @@ -0,0 +1,2 @@ +export { default } from "./SearchBar"; +export * from "./SearchBar"; diff --git a/src/components/TableFilter/FilterChips.tsx b/src/components/TableFilter/FilterChips.tsx index b32de4652..6508f9f10 100644 --- a/src/components/TableFilter/FilterChips.tsx +++ b/src/components/TableFilter/FilterChips.tsx @@ -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 = props => { - const classes = useInputStyles({}); - return ( - - ); -}; - 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 { - currencySymbol: string; - menu: IFilter; +interface FilterChipProps extends FilterActionsProps { + displayTabAction: "save" | "delete" | null; filtersList: Filter[]; - filterLabel: string; - placeholder: string; search: string; isCustomSearch: boolean; - onSearchChange: (event: React.ChangeEvent) => void; - onFilterAdd: (filter: FilterContentSubmitData) => void; onFilterDelete: () => void; onFilterSave: () => void; } export const FilterChips: React.FC = ({ currencySymbol, + displayTabAction, filtersList, menu, filterLabel, @@ -129,29 +95,23 @@ export const FilterChips: React.FC = ({ search, onFilterAdd, onFilterSave, - onFilterDelete, - isCustomSearch + onFilterDelete }) => { const theme = useTheme(); const classes = useStyles({ theme }); return ( <> -
- - -
- {search || (filtersList && filtersList.length) ? ( + + {search || (filtersList && filtersList.length > 0) ? (
{filtersList.map(filter => ( @@ -168,7 +128,7 @@ export const FilterChips: React.FC = ({
))}
- {isCustomSearch ? ( + {displayTabAction === "save" ? ( = ({ /> ) : ( - - - + displayTabAction === "delete" && ( + + + + ) )} ) : ( diff --git a/src/customers/components/CustomerList/CustomerList.tsx b/src/customers/components/CustomerList/CustomerList.tsx index 67b3622dd..c3c886440 100644 --- a/src/customers/components/CustomerList/CustomerList.tsx +++ b/src/customers/components/CustomerList/CustomerList.tsx @@ -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) => ( - - - - - - - - - - - - - - - - - - - - {renderCollection( - customers, - customer => { - const isSelected = customer ? isChecked(customer.id) : false; +
+ + + + + + + + + + + + + + + + + + {renderCollection( + customers, + customer => { + const isSelected = customer ? isChecked(customer.id) : false; - return ( - - - toggle(customer.id)} - /> - - - {getUserName(customer)} - - - {maybe(() => customer.email, )} - - - {maybe( - () => customer.orders.totalCount, - - )} - - - ); - }, - () => ( - - - + return ( + + + toggle(customer.id)} + /> + + + {getUserName(customer)} + + + {maybe(() => customer.email, )} + + + {maybe( + () => customer.orders.totalCount, + + )} - ) - )} - -
-
+ ); + }, + () => ( + + + + + + ) + )} + + ) ); CustomerList.displayName = "CustomerList"; diff --git a/src/customers/components/CustomerListPage/CustomerListPage.tsx b/src/customers/components/CustomerListPage/CustomerListPage.tsx index d9fa44acb..3b88e0601 100644 --- a/src/customers/components/CustomerListPage/CustomerListPage.tsx +++ b/src/customers/components/CustomerListPage/CustomerListPage.tsx @@ -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 = ({ + currentTab, customers, disabled, + initialSearch, onAdd, + onAll, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, ...customerListProps }) => { const intl = useIntl(); @@ -37,11 +55,30 @@ const CustomerListPage: React.StatelessComponent = ({ /> - + + + + ); }; diff --git a/src/customers/queries.ts b/src/customers/queries.ts index 9030ecbdf..60a00e56e 100644 --- a/src/customers/queries.ts +++ b/src/customers/queries.ts @@ -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 diff --git a/src/customers/types/ListCustomers.ts b/src/customers/types/ListCustomers.ts index aeaf81931..06a65a999 100644 --- a/src/customers/types/ListCustomers.ts +++ b/src/customers/types/ListCustomers.ts @@ -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; } diff --git a/src/customers/urls.ts b/src/customers/urls.ts index baab66be7..afb55862b 100644 --- a/src/customers/urls.ts +++ b/src/customers/urls.ts @@ -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; +export type CustomerListUrlDialog = "remove" | TabActionDialog; +export type CustomerListUrlQueryParams = ActiveTab & + BulkAction & + CustomerListUrlFilters & Dialog & Pagination; export const customerListUrl = (params?: CustomerListUrlQueryParams) => diff --git a/src/customers/views/CustomerList.tsx b/src/customers/views/CustomerList/CustomerList.tsx similarity index 64% rename from src/customers/views/CustomerList.tsx rename to src/customers/views/CustomerList/CustomerList.tsx index 0ee6d578b..68348ee99 100644 --- a/src/customers/views/CustomerList.tsx +++ b/src/customers/views/CustomerList/CustomerList.tsx @@ -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 = ({ ); 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 = ({ 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 ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.customers.pageInfo), @@ -90,6 +161,14 @@ export const CustomerList: React.StatelessComponent = ({ return ( <> 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 = ({ /> + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/customers/views/CustomerList/filter.ts b/src/customers/views/CustomerList/filter.ts new file mode 100644 index 000000000..98e29cf77 --- /dev/null +++ b/src/customers/views/CustomerList/filter.ts @@ -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(CUSTOMER_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + CustomerListUrlQueryParams, + CustomerListUrlFilters +>(CustomerListUrlFiltersEnum); diff --git a/src/customers/views/CustomerList/index.ts b/src/customers/views/CustomerList/index.ts new file mode 100644 index 000000000..c5517efd0 --- /dev/null +++ b/src/customers/views/CustomerList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./CustomerList"; +export * from "./CustomerList"; diff --git a/src/discounts/components/SaleList/SaleList.tsx b/src/discounts/components/SaleList/SaleList.tsx index 5f0a7bb80..0573355c8 100644 --- a/src/discounts/components/SaleList/SaleList.tsx +++ b/src/discounts/components/SaleList/SaleList.tsx @@ -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) => ( - - - - - - - - - - - - - - - - - - - - - - - {renderCollection( - sales, - sale => { - const isSelected = sale ? isChecked(sale.id) : false; +
+ + + + + + + + + + + + + + + + + + + + + {renderCollection( + sales, + sale => { + const isSelected = sale ? isChecked(sale.id) : false; - return ( - + + toggle(sale.id)} + /> + + + {maybe(() => sale.name, )} + + + {sale && sale.startDate ? ( + + ) : ( + + )} + + + {sale && sale.endDate ? ( + + ) : sale && sale.endDate === null ? ( + "-" + ) : ( + + )} + + - - toggle(sale.id)} - /> - - - {maybe(() => sale.name, )} - - - {sale && sale.startDate ? ( - + {sale && sale.type && sale.value ? ( + sale.type === SaleType.FIXED ? ( + ) : ( - - )} - - - {sale && sale.endDate ? ( - - ) : sale && sale.endDate === null ? ( - "-" - ) : ( - - )} - - - {sale && sale.type && sale.value ? ( - sale.type === SaleType.FIXED ? ( - - ) : ( - - ) - ) : ( - - )} - - - ); - }, - () => ( - - - + + ) + ) : ( + + )} - ) - )} - -
-
+ ); + }, + () => ( + + + + + + ) + )} + + ) ); SaleList.displayName = "SaleList"; diff --git a/src/discounts/components/SaleListPage/SaleListPage.tsx b/src/discounts/components/SaleListPage/SaleListPage.tsx index a7861b4c1..cf567cfa9 100644 --- a/src/discounts/components/SaleListPage/SaleListPage.tsx +++ b/src/discounts/components/SaleListPage/SaleListPage.tsx @@ -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 = ({ + currentTab, + initialSearch, onAdd, + onAll, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, ...listProps }) => { const intl = useIntl(); @@ -28,7 +46,26 @@ const SaleListPage: React.StatelessComponent = ({ - + + + + ); }; diff --git a/src/discounts/components/VoucherList/VoucherList.tsx b/src/discounts/components/VoucherList/VoucherList.tsx index 50b4eaff7..4107c2794 100644 --- a/src/discounts/components/VoucherList/VoucherList.tsx +++ b/src/discounts/components/VoucherList/VoucherList.tsx @@ -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) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {renderCollection( - vouchers, - voucher => { - const isSelected = voucher ? isChecked(voucher.id) : false; +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + {renderCollection( + vouchers, + voucher => { + const isSelected = voucher ? isChecked(voucher.id) : false; - return ( - + + toggle(voucher.id)} + /> + + + {maybe(() => voucher.code, )} + + + {voucher && voucher.minAmountSpent ? ( + + ) : voucher && voucher.minAmountSpent === null ? ( + "-" + ) : ( + + )} + + + {voucher && voucher.startDate ? ( + + ) : ( + + )} + + + {voucher && voucher.endDate ? ( + + ) : voucher && voucher.endDate === null ? ( + "-" + ) : ( + + )} + + - - toggle(voucher.id)} - /> - - - {maybe(() => voucher.code, )} - - - {voucher && voucher.minAmountSpent ? ( - - ) : voucher && voucher.minAmountSpent === null ? ( - "-" + {voucher && + voucher.discountValueType && + voucher.discountValue ? ( + voucher.discountValueType === + DiscountValueTypeEnum.FIXED ? ( + ) : ( - - )} - - - {voucher && voucher.startDate ? ( - - ) : ( - - )} - - - {voucher && voucher.endDate ? ( - - ) : voucher && voucher.endDate === null ? ( - "-" - ) : ( - - )} - - - {voucher && - voucher.discountValueType && - voucher.discountValue ? ( - voucher.discountValueType === - DiscountValueTypeEnum.FIXED ? ( - - ) : ( - - ) - ) : ( - - )} - - - {voucher && voucher.usageLimit ? ( - voucher.usageLimit - ) : voucher && voucher.usageLimit === null ? ( - "-" - ) : ( - - )} - - - ); - }, - () => ( - - - + + ) + ) : ( + + )} + + + {voucher && voucher.usageLimit ? ( + voucher.usageLimit + ) : voucher && voucher.usageLimit === null ? ( + "-" + ) : ( + + )} - ) - )} - -
-
+ ); + }, + () => ( + + + + + + ) + )} + + ) ); VoucherList.displayName = "VoucherList"; diff --git a/src/discounts/components/VoucherListPage/VoucherListPage.tsx b/src/discounts/components/VoucherListPage/VoucherListPage.tsx index 3b3fea014..4d32b59f2 100644 --- a/src/discounts/components/VoucherListPage/VoucherListPage.tsx +++ b/src/discounts/components/VoucherListPage/VoucherListPage.tsx @@ -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 = ({ - 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 = ({ /> - + + + + ); }; diff --git a/src/discounts/queries.ts b/src/discounts/queries.ts index 693c691c0..54e98a5bc 100644 --- a/src/discounts/queries.ts +++ b/src/discounts/queries.ts @@ -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); 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 diff --git a/src/discounts/types/SaleList.ts b/src/discounts/types/SaleList.ts index 0667b9b72..c1c9874b0 100644 --- a/src/discounts/types/SaleList.ts +++ b/src/discounts/types/SaleList.ts @@ -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; } diff --git a/src/discounts/types/VoucherList.ts b/src/discounts/types/VoucherList.ts index 9b421ef41..476dff395 100644 --- a/src/discounts/types/VoucherList.ts +++ b/src/discounts/types/VoucherList.ts @@ -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; } diff --git a/src/discounts/urls.ts b/src/discounts/urls.ts index 0e3833d99..44877e748 100644 --- a/src/discounts/urls.ts +++ b/src/discounts/urls.ts @@ -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; +export type SaleListUrlDialog = "remove" | TabActionDialog; +export type SaleListUrlQueryParams = ActiveTab & + BulkAction & Dialog & - 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; +export type VoucherListUrlDialog = "remove" | TabActionDialog; +export type VoucherListUrlQueryParams = ActiveTab & + BulkAction & Dialog & - Pagination; + Pagination & + VoucherListUrlFilters; export const voucherListUrl = (params?: VoucherListUrlQueryParams) => voucherListPath + "?" + stringifyQs(params); export const voucherPath = (id: string) => urlJoin(voucherSection, id); diff --git a/src/discounts/views/SaleList.tsx b/src/discounts/views/SaleList/SaleList.tsx similarity index 64% rename from src/discounts/views/SaleList.tsx rename to src/discounts/views/SaleList/SaleList.tsx index 319b908dc..0bfded879 100644 --- a/src/discounts/views/SaleList.tsx +++ b/src/discounts/views/SaleList/SaleList.tsx @@ -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 = ({ ); 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 ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.sales.pageInfo), @@ -91,6 +171,14 @@ export const SaleList: React.StatelessComponent = ({ <> 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 = ({ )} + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/discounts/views/SaleList/filter.ts b/src/discounts/views/SaleList/filter.ts new file mode 100644 index 000000000..69362c3d3 --- /dev/null +++ b/src/discounts/views/SaleList/filter.ts @@ -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(SALE_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + SaleListUrlQueryParams, + SaleListUrlFilters +>(SaleListUrlFiltersEnum); diff --git a/src/discounts/views/SaleList/index.ts b/src/discounts/views/SaleList/index.ts new file mode 100644 index 000000000..20d8fef01 --- /dev/null +++ b/src/discounts/views/SaleList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./SaleList"; +export * from "./SaleList"; diff --git a/src/discounts/views/VoucherList.tsx b/src/discounts/views/VoucherList/VoucherList.tsx similarity index 64% rename from src/discounts/views/VoucherList.tsx rename to src/discounts/views/VoucherList/VoucherList.tsx index e70830261..9de0be51c 100644 --- a/src/discounts/views/VoucherList.tsx +++ b/src/discounts/views/VoucherList/VoucherList.tsx @@ -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 = ({ ); 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 ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.vouchers.pageInfo), @@ -92,6 +172,14 @@ export const VoucherList: React.StatelessComponent = ({ title={intl.formatMessage(sectionNames.vouchers)} /> 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 = ({ )} + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/discounts/views/VoucherList/filter.ts b/src/discounts/views/VoucherList/filter.ts new file mode 100644 index 000000000..6f5d7ce57 --- /dev/null +++ b/src/discounts/views/VoucherList/filter.ts @@ -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(VOUCHER_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + VoucherListUrlQueryParams, + VoucherListUrlFilters +>(VoucherListUrlFiltersEnum); diff --git a/src/discounts/views/VoucherList/index.ts b/src/discounts/views/VoucherList/index.ts new file mode 100644 index 000000000..c84409632 --- /dev/null +++ b/src/discounts/views/VoucherList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./VoucherList"; +export * from "./VoucherList"; diff --git a/src/fixtures.ts b/src/fixtures.ts index 2b290708b..e0c4676db 100644 --- a/src/fixtures.ts +++ b/src/fixtures.ts @@ -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[] = [ diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index 47c5383f7..8c35be9c4 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -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 = ({ + currentTab, disabled, + initialSearch, onAdd, + onAll, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, ...listProps }) => { const intl = useIntl(); @@ -38,6 +56,23 @@ const OrderDraftListPage: React.StatelessComponent = ({ + diff --git a/src/orders/components/OrderListFilter/OrderListFilter.tsx b/src/orders/components/OrderListFilter/OrderListFilter.tsx index ca7547276..df399abcf 100644 --- a/src/orders/components/OrderListFilter/OrderListFilter.tsx +++ b/src/orders/components/OrderListFilter/OrderListFilter.tsx @@ -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; +type OrderListFilterProps = FilterProps; export enum OrderFilterKeys { date = "date", diff --git a/src/orders/components/OrderListPage/OrderListPage.tsx b/src/orders/components/OrderListPage/OrderListPage.tsx index b05d08c59..0e89cabd2 100644 --- a/src/orders/components/OrderListPage/OrderListPage.tsx +++ b/src/orders/components/OrderListPage/OrderListPage.tsx @@ -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 { + FilterPageProps { orders: OrderList_orders_edges_node[]; } @@ -25,15 +23,15 @@ const OrderListPage: React.FC = ({ 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 = ({ 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 = ({ onAll={onAll} onSearchChange={onSearchChange} onFilterAdd={onFilterAdd} - onFilterSave={onFilterSave} onTabChange={onTabChange} - onFilterDelete={onFilterDelete} + onTabDelete={onTabDelete} + onTabSave={onTabSave} /> diff --git a/src/orders/queries.ts b/src/orders/queries.ts index 87a006b28..2eab79cd8 100644 --- a/src/orders/queries.ts +++ b/src/orders/queries.ts @@ -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 diff --git a/src/orders/types/OrderDraftList.ts b/src/orders/types/OrderDraftList.ts index c3567edca..ee2f0ff82 100644 --- a/src/orders/types/OrderDraftList.ts +++ b/src/orders/types/OrderDraftList.ts @@ -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; } diff --git a/src/orders/types/OrderList.ts b/src/orders/types/OrderList.ts index 959ca2309..95c4e4a4f 100644 --- a/src/orders/types/OrderList.ts +++ b/src/orders/types/OrderList.ts @@ -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; } diff --git a/src/orders/urls.ts b/src/orders/urls.ts index b53fdd2e5..ce0ce9a0e 100644 --- a/src/orders/urls.ts +++ b/src/orders/urls.ts @@ -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 & FiltersWithMultipleValues; -export type OrderListUrlDialog = "cancel" | "save-search" | "delete-search"; +export type OrderListUrlDialog = "cancel" | TabActionDialog; export type OrderListUrlQueryParams = BulkAction & Dialog & 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; +export type OrderDraftListUrlDialog = "remove" | TabActionDialog; +export type OrderDraftListUrlQueryParams = ActiveTab & + BulkAction & Dialog & + OrderDraftListUrlFilters & Pagination; export const orderDraftListUrl = ( params?: OrderDraftListUrlQueryParams diff --git a/src/orders/views/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx similarity index 67% rename from src/orders/views/OrderDraftList.tsx rename to src/orders/views/OrderDraftList/OrderDraftList.tsx index bd5eaa168..826e942c6 100644 --- a/src/orders/views/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -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 = ({ ); 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 = ({ 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 ( {createOrder => ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.draftOrders.pageInfo), @@ -114,6 +186,14 @@ export const OrderDraftList: React.StatelessComponent = ({ return ( <> 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 = ({ /> + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/orders/views/OrderDraftList/filter.ts b/src/orders/views/OrderDraftList/filter.ts new file mode 100644 index 000000000..76e72e29e --- /dev/null +++ b/src/orders/views/OrderDraftList/filter.ts @@ -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(ORDER_DRAFT_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + OrderDraftListUrlQueryParams, + OrderDraftListUrlFilters +>(OrderDraftListUrlFiltersEnum); diff --git a/src/orders/views/OrderDraftList/index.ts b/src/orders/views/OrderDraftList/index.ts new file mode 100644 index 000000000..5733dc916 --- /dev/null +++ b/src/orders/views/OrderDraftList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./OrderDraftList"; +export * from "./OrderDraftList"; diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 5b84e64b2..69e572e29 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -228,15 +228,15 @@ export const OrderList: React.StatelessComponent = ({ /> } - 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 diff --git a/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap b/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap index 8c70c08ce..77dd07779 100644 --- a/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap +++ b/src/orders/views/OrderList/__snapshots__/filters.test.ts.snap @@ -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", ], diff --git a/src/orders/views/OrderList/filters.test.ts b/src/orders/views/OrderList/filters.test.ts index 7864d9455..a8bb1f032 100644 --- a/src/orders/views/OrderList/filters.test.ts +++ b/src/orders/views/OrderList/filters.test.ts @@ -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() diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index db290ab37..c9441161b 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -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 +}); diff --git a/src/productTypes/components/ProductTypeList/ProductTypeList.tsx b/src/productTypes/components/ProductTypeList/ProductTypeList.tsx index 5ba7d21d3..536ec3b90 100644 --- a/src/productTypes/components/ProductTypeList/ProductTypeList.tsx +++ b/src/productTypes/components/ProductTypeList/ProductTypeList.tsx @@ -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 ( - - - - - - - - - - - - - - - - - - - - {renderCollection( - productTypes, - productType => { - const isSelected = productType - ? isChecked(productType.id) - : false; - return ( - - - toggle(productType.id)} - /> - - - {productType ? ( +
+ + + + + + + + + + + + + + + + + + {renderCollection( + productTypes, + productType => { + const isSelected = productType + ? isChecked(productType.id) + : false; + return ( + + + toggle(productType.id)} + /> + + + {productType ? ( + <> + {productType.name} + + {maybe(() => productType.hasVariants) + ? intl.formatMessage({ + defaultMessage: "Configurable", + description: "product type" + }) + : intl.formatMessage({ + defaultMessage: "Simple product", + description: "product type" + })} + + + ) : ( + + )} + + + {maybe(() => productType.isShippingRequired) !== + undefined ? ( + productType.isShippingRequired ? ( <> - {productType.name} - - {maybe(() => productType.hasVariants) - ? intl.formatMessage({ - defaultMessage: "Configurable", - description: "product type" - }) - : intl.formatMessage({ - defaultMessage: "Simple product", - description: "product type" - })} - + ) : ( - - )} - - - {maybe(() => productType.isShippingRequired) !== - undefined ? ( - productType.isShippingRequired ? ( - <> - - - ) : ( - <> - - - ) - ) : ( - - )} - - - {maybe(() => productType.taxType) ? ( - productType.taxType.description - ) : ( - - )} - - - ); - }, - () => ( - - - + <> + + + ) + ) : ( + + )} + + + {maybe(() => productType.taxType) ? ( + productType.taxType.description + ) : ( + + )} - ) - )} - -
-
+ ); + }, + () => ( + + + + + + ) + )} + + ); } ); diff --git a/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx b/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx index 747712b57..846a3aaa0 100644 --- a/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx +++ b/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx @@ -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)} - - + + + + ); }; diff --git a/src/productTypes/queries.ts b/src/productTypes/queries.ts index 63bbbbec0..d67b628d7 100644 --- a/src/productTypes/queries.ts +++ b/src/productTypes/queries.ts @@ -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 diff --git a/src/productTypes/types/ProductTypeList.ts b/src/productTypes/types/ProductTypeList.ts index 64fa7e917..6a83fa4de 100644 --- a/src/productTypes/types/ProductTypeList.ts +++ b/src/productTypes/types/ProductTypeList.ts @@ -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; } diff --git a/src/productTypes/urls.ts b/src/productTypes/urls.ts index 86251789d..34733f1c4 100644 --- a/src/productTypes/urls.ts +++ b/src/productTypes/urls.ts @@ -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; +export type ProductTypeListUrlDialog = "remove" | TabActionDialog; +export type ProductTypeListUrlQueryParams = ActiveTab & + BulkAction & Dialog & - Pagination; + Pagination & + ProductTypeListUrlFilters; export const productTypeListUrl = (params?: ProductTypeListUrlQueryParams) => productTypeListPath + "?" + stringifyQs(params); diff --git a/src/productTypes/views/ProductTypeList.tsx b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx similarity index 59% rename from src/productTypes/views/ProductTypeList.tsx rename to src/productTypes/views/ProductTypeList/ProductTypeList.tsx index ca83ccc45..542d3dd64 100644 --- a/src/productTypes/views/ProductTypeList.tsx +++ b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx @@ -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 ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.productTypes.pageInfo), @@ -93,6 +175,14 @@ export const ProductTypeList: React.StatelessComponent< return ( <> 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< /> + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/productTypes/views/ProductTypeList/filter.ts b/src/productTypes/views/ProductTypeList/filter.ts new file mode 100644 index 000000000..fffcd0cd8 --- /dev/null +++ b/src/productTypes/views/ProductTypeList/filter.ts @@ -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(PRODUCT_TYPE_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + ProductTypeListUrlQueryParams, + ProductTypeListUrlFilters +>(ProductTypeListUrlFiltersEnum); diff --git a/src/productTypes/views/ProductTypeList/index.ts b/src/productTypes/views/ProductTypeList/index.ts new file mode 100644 index 000000000..9c1624523 --- /dev/null +++ b/src/productTypes/views/ProductTypeList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ProductTypeList"; +export * from "./ProductTypeList"; diff --git a/src/products/components/ProductListFilter/ProductListFilter.tsx b/src/products/components/ProductListFilter/ProductListFilter.tsx index 6bd04e778..52074f5ed 100644 --- a/src/products/components/ProductListFilter/ProductListFilter.tsx +++ b/src/products/components/ProductListFilter/ProductListFilter.tsx @@ -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 = props => { } ]; - return ; + return ( + + ); }; ProductListFilter.displayName = "ProductListFilter"; export default ProductListFilter; diff --git a/src/products/components/ProductListPage/ProductListPage.tsx b/src/products/components/ProductListPage/ProductListPage.tsx index 9187a7f92..7f89ff158 100644 --- a/src/products/components/ProductListPage/ProductListPage.tsx +++ b/src/products/components/ProductListPage/ProductListPage.tsx @@ -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, ListActions, - FilterPageProps, + FilterPageProps, FetchMoreProps { availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[]; currencySymbol: string; @@ -52,22 +51,22 @@ export const ProductListPage: React.FC = 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 = props => { = ({ 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)} /> navigate(shippingZoneUrl(id)); + const closeModal = () => navigate(shippingZoneUrl(id), true); const onShippingRateCreate = (data: CreateShippingRate) => { if (data.shippingPriceCreate.errors.length === 0) { diff --git a/src/staff/components/StaffList/StaffList.tsx b/src/staff/components/StaffList/StaffList.tsx index eeefb9f97..f359027c6 100644 --- a/src/staff/components/StaffList/StaffList.tsx +++ b/src/staff/components/StaffList/StaffList.tsx @@ -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 ( - - - - - - - - - - - - - - - + + + + - - - - {renderCollection( - staffMembers, - staffMember => ( - - -
- {maybe(() => staffMember.avatar.url) ? ( - staffMember.avatar.url)} - /> - ) : ( -
- - {getUserInitials(staffMember)} - -
- )} -
- - {getUserName(staffMember) || } - - - {maybe( - () => - staffMember.isActive - ? intl.formatMessage({ - defaultMessage: "Active", - description: "staff member status" - }) - : intl.formatMessage({ - defaultMessage: "Inactive", - description: "staff member status" - }), - - )} - -
- + + + + +
+ + + + + + + + {renderCollection( + staffMembers, + staffMember => ( + + +
+ {maybe(() => staffMember.avatar.url) ? ( + staffMember.avatar.url)} + /> + ) : ( +
+ {getUserInitials(staffMember)} +
+ )} +
+ + {getUserName(staffMember) || } + + {maybe( - () => staffMember.email, + () => + staffMember.isActive + ? intl.formatMessage({ + defaultMessage: "Active", + description: "staff member status" + }) + : intl.formatMessage({ + defaultMessage: "Inactive", + description: "staff member status" + }), )} -
-
- ), - () => ( - - - - - - ) - )} -
-
-
+ + + + {maybe( + () => staffMember.email, + + )} + + + ), + () => ( + + + + + + ) + )} + + ); } ); diff --git a/src/staff/components/StaffListPage/StaffListPage.tsx b/src/staff/components/StaffListPage/StaffListPage.tsx index 5f4faa668..f83335e56 100644 --- a/src/staff/components/StaffListPage/StaffListPage.tsx +++ b/src/staff/components/StaffListPage/StaffListPage.tsx @@ -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 = ({ - disabled, + currentTab, + initialSearch, onAdd, + onAll, onBack, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, ...listProps }) => { const intl = useIntl(); @@ -31,19 +42,33 @@ const StaffListPage: React.StatelessComponent = ({ {intl.formatMessage(sectionNames.configuration)} - - + + + + ); }; diff --git a/src/staff/queries.ts b/src/staff/queries.ts index 994b2f47c..5bb538eb4 100644 --- a/src/staff/queries.ts +++ b/src/staff/queries.ts @@ -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 { diff --git a/src/staff/types/StaffList.ts b/src/staff/types/StaffList.ts index 2a3663628..08f0a6471 100644 --- a/src/staff/types/StaffList.ts +++ b/src/staff/types/StaffList.ts @@ -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; } diff --git a/src/staff/urls.ts b/src/staff/urls.ts index 4508afdfd..7bdf6e201 100644 --- a/src/staff/urls.ts +++ b/src/staff/urls.ts @@ -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; +export type StaffListUrlDialog = "add" | "remove" | TabActionDialog; +export type StaffListUrlQueryParams = ActiveTab & + BulkAction & Dialog & - Pagination; + Pagination & + StaffListUrlFilters; export const staffListUrl = (params?: StaffListUrlQueryParams) => staffListPath + "?" + stringifyQs(params); diff --git a/src/staff/views/StaffList.tsx b/src/staff/views/StaffList/StaffList.tsx similarity index 59% rename from src/staff/views/StaffList.tsx rename to src/staff/views/StaffList/StaffList.tsx index 0cc4ad33e..0c07ecb56 100644 --- a/src/staff/views/StaffList.tsx +++ b/src/staff/views/StaffList/StaffList.tsx @@ -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 = ({ ); 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 = ({ 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 ( - + {({ data, loading }) => { const handleStaffMemberAddSuccess = (data: StaffMemberAdd) => { if (data.staffCreate.errors.length === 0) { @@ -97,6 +165,14 @@ export const StaffList: React.StatelessComponent = ({ return ( <> 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 = ({ onClose={closeModal} onConfirm={handleStaffMemberAdd} /> + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/staff/views/StaffList/filter.ts b/src/staff/views/StaffList/filter.ts new file mode 100644 index 000000000..9223a0372 --- /dev/null +++ b/src/staff/views/StaffList/filter.ts @@ -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(STAFF_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + StaffListUrlQueryParams, + StaffListUrlFilters +>(StaffListUrlFiltersEnum); diff --git a/src/staff/views/StaffList/index.ts b/src/staff/views/StaffList/index.ts new file mode 100644 index 000000000..524579391 --- /dev/null +++ b/src/staff/views/StaffList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./StaffList"; +export * from "./StaffList"; diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 3bcd5cd99..e25d6d037 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -10368,6 +10368,100 @@ exports[`Storyshots Views / Attributes / Attribute list default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -11078,6 +11172,100 @@ exports[`Storyshots Views / Attributes / Attribute list loading 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -11329,6 +11517,100 @@ exports[`Storyshots Views / Attributes / Attribute list no data 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -12435,6 +12717,100 @@ exports[`Storyshots Views / Categories / Category list default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -12849,6 +13225,100 @@ exports[`Storyshots Views / Categories / Category list empty 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -13053,6 +13523,100 @@ exports[`Storyshots Views / Categories / Category list loading 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -24167,6 +24731,100 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -24606,6 +25264,100 @@ exports[`Storyshots Views / Collections / Collection list loading 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -24829,6 +25581,304 @@ exports[`Storyshots Views / Collections / Collection list loading 1`] = ` `; +exports[`Storyshots Views / Collections / Collection list no data 1`] = ` +
+
+
+
+ Collections +
+
+
+ +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + + + + +
+ Category Name + + No. of Products + + Availability +
+ No collections found +
+
+ + +`; + exports[`Storyshots Views / Collections / Create collection default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -35429,6 +36573,100 @@ exports[`Storyshots Views / Customers / Customer list loading 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -35684,6 +36922,100 @@ exports[`Storyshots Views / Customers / Customer list no data 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -40016,6 +41348,100 @@ exports[`Storyshots Views / Discounts / Sale list default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -40457,6 +41883,100 @@ exports[`Storyshots Views / Discounts / Sale list loading 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -40728,6 +42248,100 @@ exports[`Storyshots Views / Discounts / Sale list no data 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -45256,6 +46870,100 @@ exports[`Storyshots Views / Discounts / Voucher list default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -45594,6 +47302,100 @@ exports[`Storyshots Views / Discounts / Voucher list loading 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -45895,6 +47697,100 @@ exports[`Storyshots Views / Discounts / Voucher list no data 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -49534,6 +51430,100 @@ exports[`Storyshots Views / Orders / Draft order list default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -50651,6 +52641,100 @@ exports[`Storyshots Views / Orders / Draft order list loading 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -50927,6 +53011,100 @@ exports[`Storyshots Views / Orders / Draft order list when no data 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -66433,7 +68611,7 @@ exports[`Storyshots Views / Orders / Order list default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -78622,9 +80889,8 @@ exports[`Storyshots Views / Product types / Product types list loading 1`] = ` class="Component-root-id" > + + + + + +
+
+
+ + +
+
+
+
@@ -78822,6 +81182,264 @@ exports[`Storyshots Views / Product types / Product types list loading 1`] = ` `; +exports[`Storyshots Views / Product types / Product types list no data 1`] = ` +
+
+
+
+ Product Types +
+
+
+ +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + + + + +
+ Type Name + + Type + + Tax +
+ No product types found +
+
+ + +`; + exports[`Storyshots Views / Product types / Unassign attribute default 1`] = `
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
@@ -104819,9 +107526,8 @@ exports[`Storyshots Views / Staff / Staff members when loading 1`] = ` class="Component-root-id" > + + + + + +
+
+
+ + +
+
+
+
@@ -106438,6 +109238,42 @@ exports[`Storyshots Views / Translations / Entity list default 1`] = ` +
+
+
+ + +
+
+
+
diff --git a/src/storybook/stories/attributes/AttributeListPage.tsx b/src/storybook/stories/attributes/AttributeListPage.tsx index e757661f3..c685a4313 100644 --- a/src/storybook/stories/attributes/AttributeListPage.tsx +++ b/src/storybook/stories/attributes/AttributeListPage.tsx @@ -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 }; diff --git a/src/storybook/stories/categories/CategoryListPage.tsx b/src/storybook/stories/categories/CategoryListPage.tsx index 4fa805709..d488c2175 100644 --- a/src/storybook/stories/categories/CategoryListPage.tsx +++ b/src/storybook/stories/categories/CategoryListPage.tsx @@ -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 }; diff --git a/src/storybook/stories/collections/CollectionListPage.tsx b/src/storybook/stories/collections/CollectionListPage.tsx index cc0e44a4d..c3847a220 100644 --- a/src/storybook/stories/collections/CollectionListPage.tsx +++ b/src/storybook/stories/collections/CollectionListPage.tsx @@ -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", () => ) .add("loading", () => ( - )); + )) + .add("no data", () => ); diff --git a/src/storybook/stories/customers/CustomerListPage.tsx b/src/storybook/stories/customers/CustomerListPage.tsx index cfa8e462e..ec00a8bc9 100644 --- a/src/storybook/stories/customers/CustomerListPage.tsx +++ b/src/storybook/stories/customers/CustomerListPage.tsx @@ -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 }; diff --git a/src/storybook/stories/discounts/SaleListPage.tsx b/src/storybook/stories/discounts/SaleListPage.tsx index d8b88b81a..3494bb9ef 100644 --- a/src/storybook/stories/discounts/SaleListPage.tsx +++ b/src/storybook/stories/discounts/SaleListPage.tsx @@ -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 }; diff --git a/src/storybook/stories/discounts/VoucherListPage.tsx b/src/storybook/stories/discounts/VoucherListPage.tsx index fa10118b0..7e53c7211 100644 --- a/src/storybook/stories/discounts/VoucherListPage.tsx +++ b/src/storybook/stories/discounts/VoucherListPage.tsx @@ -5,12 +5,19 @@ import VoucherListPage, { VoucherListPageProps } from "../../../discounts/components/VoucherListPage"; import { voucherList } from "../../../discounts/fixtures"; -import { listActionsProps, pageListProps } from "../../../fixtures"; +import { + listActionsProps, + pageListProps, + searchPageProps, + tabPageProps +} from "../../../fixtures"; import Decorator from "../../Decorator"; const props: VoucherListPageProps = { ...listActionsProps, ...pageListProps.default, + ...searchPageProps, + ...tabPageProps, defaultCurrency: "USD", vouchers: voucherList }; diff --git a/src/storybook/stories/orders/OrderDraftListPage.tsx b/src/storybook/stories/orders/OrderDraftListPage.tsx index 7cc1d4596..5837e1f34 100644 --- a/src/storybook/stories/orders/OrderDraftListPage.tsx +++ b/src/storybook/stories/orders/OrderDraftListPage.tsx @@ -1,7 +1,12 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import { listActionsProps, pageListProps } from "../../../fixtures"; +import { + listActionsProps, + pageListProps, + searchPageProps, + tabPageProps +} from "../../../fixtures"; import OrderDraftListPage, { OrderDraftListPageProps } from "../../../orders/components/OrderDraftListPage"; @@ -11,6 +16,8 @@ import Decorator from "../../Decorator"; const props: OrderDraftListPageProps = { ...listActionsProps, ...pageListProps.default, + ...searchPageProps, + ...tabPageProps, onAdd: () => undefined, orders }; diff --git a/src/storybook/stories/productTypes/ProductTypeListPage.tsx b/src/storybook/stories/productTypes/ProductTypeListPage.tsx index 8d97d165f..5f1387a3a 100644 --- a/src/storybook/stories/productTypes/ProductTypeListPage.tsx +++ b/src/storybook/stories/productTypes/ProductTypeListPage.tsx @@ -1,26 +1,31 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import { listActionsProps, pageListProps } from "../../../fixtures"; -import ProductTypeListPage from "../../../productTypes/components/ProductTypeListPage"; +import { + listActionsProps, + pageListProps, + searchPageProps, + tabPageProps +} from "../../../fixtures"; +import ProductTypeListPage, { + ProductTypeListPageProps +} from "../../../productTypes/components/ProductTypeListPage"; import { productTypes } from "../../../productTypes/fixtures"; import Decorator from "../../Decorator"; +const props: ProductTypeListPageProps = { + ...listActionsProps, + ...pageListProps.default, + ...searchPageProps, + ...tabPageProps, + onBack: () => undefined, + productTypes +}; + storiesOf("Views / Product types / Product types list", module) .addDecorator(Decorator) - .add("default", () => ( - undefined} - productTypes={productTypes} - {...listActionsProps} - {...pageListProps.default} - /> - )) + .add("default", () => ) .add("loading", () => ( - undefined} - productTypes={undefined} - {...listActionsProps} - {...pageListProps.loading} - /> - )); + + )) + .add("no data", () => ); diff --git a/src/storybook/stories/staff/StaffListPage.tsx b/src/storybook/stories/staff/StaffListPage.tsx index 95edeb46f..e18bb96f6 100644 --- a/src/storybook/stories/staff/StaffListPage.tsx +++ b/src/storybook/stories/staff/StaffListPage.tsx @@ -1,7 +1,11 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import { pageListProps } from "../../../fixtures"; +import { + pageListProps, + searchPageProps, + tabPageProps +} from "../../../fixtures"; import StaffListPage, { StaffListPageProps } from "../../../staff/components/StaffListPage"; @@ -9,10 +13,12 @@ import { staffMembers } from "../../../staff/fixtures"; import Decorator from "../../Decorator"; const props: StaffListPageProps = { + ...pageListProps.default, + ...searchPageProps, + ...tabPageProps, onAdd: undefined, onBack: () => undefined, - staffMembers, - ...pageListProps.default + staffMembers }; storiesOf("Views / Staff / Staff members", module) diff --git a/src/storybook/stories/translations/TranslationsEntitiesListPage.tsx b/src/storybook/stories/translations/TranslationsEntitiesListPage.tsx index 1dd1cba50..e58bca6bc 100644 --- a/src/storybook/stories/translations/TranslationsEntitiesListPage.tsx +++ b/src/storybook/stories/translations/TranslationsEntitiesListPage.tsx @@ -1,7 +1,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import { pageListProps } from "../../../fixtures"; +import { pageListProps, searchPageProps } from "../../../fixtures"; import TranslationsEntitiesList from "../../../translations/components/TranslationsEntitiesList"; import TranslationsEntitiesListPage, { TranslationsEntitiesListPageProps @@ -11,6 +11,7 @@ import Decorator from "../../Decorator"; const props: TranslationsEntitiesListPageProps = { ...pageListProps.default, + ...searchPageProps, children: null, filters: { current: "products", diff --git a/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx b/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx index cf76b23c5..4fe4301c9 100644 --- a/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx +++ b/src/translations/components/TranslationsEntitiesListPage/TranslationsEntitiesListPage.tsx @@ -1,17 +1,19 @@ import Card from "@material-ui/core/Card"; import React from "react"; -import { useIntl } from "react-intl"; +import { IntlShape, useIntl } from "react-intl"; import AppHeader from "@saleor/components/AppHeader"; import Container from "@saleor/components/Container"; +import FilterSearch from "@saleor/components/Filter/FilterSearch"; import PageHeader from "@saleor/components/PageHeader"; // tslint:disable no-submodule-imports import { ShopInfo_shop_languages } from "@saleor/components/Shop/types/ShopInfo"; import FilterTabs, { FilterTab } from "@saleor/components/TableFilter"; import { maybe } from "@saleor/misc"; +import { SearchPageProps } from "@saleor/types"; import { TranslatableEntities } from "../../urls"; -export interface TranslationsEntitiesListPageProps { +export interface TranslationsEntitiesListPageProps extends SearchPageProps { children: React.ReactNode; filters: TranslationsEntitiesFilters; language: ShopInfo_shop_languages; @@ -31,10 +33,66 @@ export interface TranslationsEntitiesFilters { export type TranslationsEntitiesListFilterTab = keyof typeof TranslatableEntities; +function getSearchPlaceholder( + tab: TranslationsEntitiesListFilterTab, + intl: IntlShape +): string { + switch (tab) { + case "categories": + return intl.formatMessage({ + defaultMessage: "Search Category" + }); + + case "collections": + return intl.formatMessage({ + defaultMessage: "Search Collection" + }); + + case "products": + return intl.formatMessage({ + defaultMessage: "Search Product" + }); + + case "sales": + return intl.formatMessage({ + defaultMessage: "Search Sale" + }); + + case "vouchers": + return intl.formatMessage({ + defaultMessage: "Search Voucher" + }); + + case "pages": + return intl.formatMessage({ + defaultMessage: "Search Page" + }); + + case "productTypes": + return intl.formatMessage({ + defaultMessage: "Search Product Type" + }); + + default: + return "..."; + } +} + +const tabs: TranslationsEntitiesListFilterTab[] = [ + "categories", + "collections", + "products", + "sales", + "vouchers", + "pages", + "productTypes" +]; + const TranslationsEntitiesListPage: React.StatelessComponent< TranslationsEntitiesListPageProps -> = ({ filters, language, onBack, children }) => { +> = ({ filters, language, onBack, children, ...searchProps }) => { const intl = useIntl(); + const currentTab = tabs.indexOf(filters.current); return ( @@ -55,17 +113,7 @@ const TranslationsEntitiesListPage: React.StatelessComponent< )} /> - + + {children} diff --git a/src/translations/index.tsx b/src/translations/index.tsx index b6d05f925..0863ae1bc 100644 --- a/src/translations/index.tsx +++ b/src/translations/index.tsx @@ -18,9 +18,7 @@ import TranslationsCategoriesComponent, { import TranslationsCollectionsComponent, { TranslationsCollectionsQueryParams } from "./views/TranslationsCollections"; -import TranslationsEntitiesComponent, { - TranslationsEntitiesListQueryParams -} from "./views/TranslationsEntities"; +import TranslationsEntitiesComponent from "./views/TranslationsEntities"; import TranslationsLanguageList from "./views/TranslationsLanguageList"; import TranslationsPagesComponent, { TranslationsPagesQueryParams @@ -46,15 +44,11 @@ const TranslationsEntities: React.FC = ({ match }) => { const qs = parseQs(location.search.substr(1)); - const params: TranslationsEntitiesListQueryParams = { - after: qs.after, - before: qs.before, - tab: qs.tab - }; + return ( ); }; diff --git a/src/translations/queries.ts b/src/translations/queries.ts index b03e208c0..f63d72960 100644 --- a/src/translations/queries.ts +++ b/src/translations/queries.ts @@ -217,8 +217,15 @@ const categoryTranslations = gql` $after: String $last: Int $before: String + $filter: CategoryFilterInput ) { - categories(before: $before, after: $after, first: $first, last: $last) { + categories( + before: $before + after: $after + first: $first + last: $last + filter: $filter + ) { edges { node { ...CategoryTranslationFragment @@ -244,8 +251,15 @@ const collectionTranslations = gql` $after: String $last: Int $before: String + $filter: CollectionFilterInput ) { - collections(before: $before, after: $after, first: $first, last: $last) { + collections( + before: $before + after: $after + first: $first + last: $last + filter: $filter + ) { edges { node { ...CollectionTranslationFragment @@ -271,8 +285,15 @@ const productTranslations = gql` $after: String $last: Int $before: String + $filter: ProductFilterInput ) { - products(before: $before, after: $after, first: $first, last: $last) { + products( + before: $before + after: $after + first: $first + last: $last + filter: $filter + ) { edges { node { ...ProductTranslationFragment @@ -298,8 +319,15 @@ const pageTranslations = gql` $after: String $last: Int $before: String + $filter: PageFilterInput ) { - pages(before: $before, after: $after, first: $first, last: $last) { + pages( + before: $before + after: $after + first: $first + last: $last + filter: $filter + ) { edges { node { ...PageTranslationFragment @@ -325,8 +353,15 @@ const voucherTranslations = gql` $after: String $last: Int $before: String + $filter: VoucherFilterInput ) { - vouchers(before: $before, after: $after, first: $first, last: $last) { + vouchers( + before: $before + after: $after + first: $first + last: $last + filter: $filter + ) { edges { node { ...VoucherTranslationFragment @@ -352,8 +387,15 @@ const saleTranslations = gql` $after: String $last: Int $before: String + $filter: SaleFilterInput ) { - sales(before: $before, after: $after, first: $first, last: $last) { + sales( + before: $before + after: $after + first: $first + last: $last + filter: $filter + ) { edges { node { ...SaleTranslationFragment @@ -379,8 +421,15 @@ const productTypeTranslations = gql` $after: String $last: Int $before: String + $filter: ProductTypeFilterInput ) { - productTypes(before: $before, after: $after, first: $first, last: $last) { + productTypes( + before: $before + after: $after + first: $first + last: $last + filter: $filter + ) { edges { node { ...ProductTypeTranslationFragment diff --git a/src/translations/types/CategoryTranslations.ts b/src/translations/types/CategoryTranslations.ts index e450a738a..3605c7f5c 100644 --- a/src/translations/types/CategoryTranslations.ts +++ b/src/translations/types/CategoryTranslations.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { LanguageCodeEnum } from "./../../types/globalTypes"; +import { LanguageCodeEnum, CategoryFilterInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: CategoryTranslations @@ -62,4 +62,5 @@ export interface CategoryTranslationsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: CategoryFilterInput | null; } diff --git a/src/translations/types/CollectionTranslations.ts b/src/translations/types/CollectionTranslations.ts index 5ff3fe9ba..4cc85dd9e 100644 --- a/src/translations/types/CollectionTranslations.ts +++ b/src/translations/types/CollectionTranslations.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { LanguageCodeEnum } from "./../../types/globalTypes"; +import { LanguageCodeEnum, CollectionFilterInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: CollectionTranslations @@ -62,4 +62,5 @@ export interface CollectionTranslationsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: CollectionFilterInput | null; } diff --git a/src/translations/types/PageTranslations.ts b/src/translations/types/PageTranslations.ts index d9ac20221..877d183a8 100644 --- a/src/translations/types/PageTranslations.ts +++ b/src/translations/types/PageTranslations.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { LanguageCodeEnum } from "./../../types/globalTypes"; +import { LanguageCodeEnum, PageFilterInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: PageTranslations @@ -63,4 +63,5 @@ export interface PageTranslationsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: PageFilterInput | null; } diff --git a/src/translations/types/ProductTranslations.ts b/src/translations/types/ProductTranslations.ts index 1d1595fee..0acd1fcae 100644 --- a/src/translations/types/ProductTranslations.ts +++ b/src/translations/types/ProductTranslations.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { LanguageCodeEnum } from "./../../types/globalTypes"; +import { LanguageCodeEnum, ProductFilterInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: ProductTranslations @@ -63,4 +63,5 @@ export interface ProductTranslationsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: ProductFilterInput | null; } diff --git a/src/translations/types/ProductTypeTranslations.ts b/src/translations/types/ProductTypeTranslations.ts index 17ba27fed..82a04ad24 100644 --- a/src/translations/types/ProductTypeTranslations.ts +++ b/src/translations/types/ProductTypeTranslations.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { LanguageCodeEnum } from "./../../types/globalTypes"; +import { LanguageCodeEnum, ProductTypeFilterInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: ProductTypeTranslations @@ -99,4 +99,5 @@ export interface ProductTypeTranslationsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: ProductTypeFilterInput | null; } diff --git a/src/translations/types/SaleTranslations.ts b/src/translations/types/SaleTranslations.ts index d014353ca..22d6004f1 100644 --- a/src/translations/types/SaleTranslations.ts +++ b/src/translations/types/SaleTranslations.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { LanguageCodeEnum } from "./../../types/globalTypes"; +import { LanguageCodeEnum, SaleFilterInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: SaleTranslations @@ -57,4 +57,5 @@ export interface SaleTranslationsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: SaleFilterInput | null; } diff --git a/src/translations/types/VoucherTranslations.ts b/src/translations/types/VoucherTranslations.ts index 5a1614908..10aa39ac4 100644 --- a/src/translations/types/VoucherTranslations.ts +++ b/src/translations/types/VoucherTranslations.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { LanguageCodeEnum } from "./../../types/globalTypes"; +import { LanguageCodeEnum, VoucherFilterInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: VoucherTranslations @@ -57,4 +57,5 @@ export interface VoucherTranslationsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: VoucherFilterInput | null; } diff --git a/src/translations/urls.ts b/src/translations/urls.ts index e8e7fefcf..d21254bfc 100644 --- a/src/translations/urls.ts +++ b/src/translations/urls.ts @@ -1,6 +1,9 @@ import { stringify as stringifyQs } from "qs"; import urlJoin from "url-join"; +import { Pagination } from "@saleor/types"; +import { TranslationsEntitiesListFilterTab } from "./components/TranslationsEntitiesListPage"; + export enum TranslatableEntities { categories = "categories", products = "products", @@ -18,12 +21,15 @@ export const languageListUrl = translationsSection; export const languageEntitiesPath = (code: string) => urlJoin(translationsSection, code); -export const languageEntitiesUrl = (code: string, tab?: TranslatableEntities) => - languageEntitiesPath(code) + - "?" + - stringifyQs({ - tab - }); +export type LanguageEntitiesUrlQueryParams = Pagination & + Partial<{ + query: string; + tab: TranslationsEntitiesListFilterTab; + }>; +export const languageEntitiesUrl = ( + code: string, + params: LanguageEntitiesUrlQueryParams +) => languageEntitiesPath(code) + "?" + stringifyQs(params); export const languageEntityPath = ( code: string, diff --git a/src/translations/views/TranslationsCategories.tsx b/src/translations/views/TranslationsCategories.tsx index 57bbb82b6..3af3970e0 100644 --- a/src/translations/views/TranslationsCategories.tsx +++ b/src/translations/views/TranslationsCategories.tsx @@ -104,10 +104,9 @@ const TranslationsCategories: React.FC = ({ saveButtonState={saveButtonState} onBack={() => navigate( - languageEntitiesUrl( - languageCode, - TranslatableEntities.categories - ) + languageEntitiesUrl(languageCode, { + tab: TranslatableEntities.categories + }) ) } onEdit={onEdit} diff --git a/src/translations/views/TranslationsCollections.tsx b/src/translations/views/TranslationsCollections.tsx index 1dd5c68ad..8ccf84531 100644 --- a/src/translations/views/TranslationsCollections.tsx +++ b/src/translations/views/TranslationsCollections.tsx @@ -109,10 +109,9 @@ const TranslationsCollections: React.FC = ({ onDiscard={onDiscard} onBack={() => navigate( - languageEntitiesUrl( - languageCode, - TranslatableEntities.collections - ) + languageEntitiesUrl(languageCode, { + tab: TranslatableEntities.collections + }) ) } onLanguageChange={lang => diff --git a/src/translations/views/TranslationsEntities.tsx b/src/translations/views/TranslationsEntities.tsx index fb34cce00..30f47d8bf 100644 --- a/src/translations/views/TranslationsEntities.tsx +++ b/src/translations/views/TranslationsEntities.tsx @@ -8,11 +8,8 @@ import usePaginator, { import useShop from "@saleor/hooks/useShop"; import { PAGINATE_BY } from "../../config"; import { maybe } from "../../misc"; -import { Pagination } from "../../types"; import TranslationsEntitiesList from "../components/TranslationsEntitiesList"; -import TranslationsEntitiesListPage, { - TranslationsEntitiesListFilterTab -} from "../components/TranslationsEntitiesListPage"; +import TranslationsEntitiesListPage from "../components/TranslationsEntitiesListPage"; import { TypedCategoryTranslations, TypedCollectionTranslations, @@ -24,18 +21,16 @@ import { } from "../queries"; import { AttributeTranslationFragment } from "../types/AttributeTranslationFragment"; import { + languageEntitiesUrl, + LanguageEntitiesUrlQueryParams, languageEntityUrl, languageListUrl, TranslatableEntities } from "../urls"; -export type TranslationsEntitiesListQueryParams = Pagination & { - tab: TranslationsEntitiesListFilterTab; -}; - interface TranslationsEntitiesProps { language: string; - params: TranslationsEntitiesListQueryParams; + params: LanguageEntitiesUrlQueryParams; } function sumTranslations( @@ -128,9 +123,29 @@ const TranslationsEntities: React.FC = ({ shop.languages.find(languageFromList => languageFromList.code === language) ); const paginationState = createPaginationState(PAGINATE_BY, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: { + search: params.query + }, + language: language as any + }), + [params] + ); return ( + navigate( + languageEntitiesUrl(language, { + ...params, + query + }), + true + ) + } filters={{ current: params.tab, ...filterCallbacks @@ -139,9 +154,7 @@ const TranslationsEntities: React.FC = ({ onBack={() => navigate(languageListUrl)} > {params.tab === "categories" ? ( - + {({ data, loading }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.categories.pageInfo), @@ -191,9 +204,7 @@ const TranslationsEntities: React.FC = ({ }} ) : params.tab === "products" ? ( - + {({ data, loading }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.products.pageInfo), @@ -243,9 +254,7 @@ const TranslationsEntities: React.FC = ({ }} ) : params.tab === "collections" ? ( - + {({ data, loading }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.collections.pageInfo), @@ -295,9 +304,7 @@ const TranslationsEntities: React.FC = ({ }} ) : params.tab === "sales" ? ( - + {({ data, loading }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.sales.pageInfo), @@ -335,9 +342,7 @@ const TranslationsEntities: React.FC = ({ }} ) : params.tab === "vouchers" ? ( - + {({ data, loading }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.vouchers.pageInfo), @@ -379,9 +384,7 @@ const TranslationsEntities: React.FC = ({ }} ) : params.tab === "pages" ? ( - + {({ data, loading }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.pages.pageInfo), @@ -427,9 +430,7 @@ const TranslationsEntities: React.FC = ({ }} ) : params.tab === "productTypes" ? ( - + {({ data, loading }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.productTypes.pageInfo), diff --git a/src/translations/views/TranslationsLanguageList.tsx b/src/translations/views/TranslationsLanguageList.tsx index a00b115d3..fc3f010c2 100644 --- a/src/translations/views/TranslationsLanguageList.tsx +++ b/src/translations/views/TranslationsLanguageList.tsx @@ -14,7 +14,7 @@ const TranslationsLanguageList: React.FC = () => { shop.languages)} // onAdd={undefined} - onRowClick={code => navigate(languageEntitiesUrl(code))} + onRowClick={code => navigate(languageEntitiesUrl(code, {}))} /> ); }; diff --git a/src/translations/views/TranslationsPages.tsx b/src/translations/views/TranslationsPages.tsx index 6cffea703..301ea8198 100644 --- a/src/translations/views/TranslationsPages.tsx +++ b/src/translations/views/TranslationsPages.tsx @@ -104,10 +104,9 @@ const TranslationsPages: React.FC = ({ saveButtonState={saveButtonState} onBack={() => navigate( - languageEntitiesUrl( - languageCode, - TranslatableEntities.pages - ) + languageEntitiesUrl(languageCode, { + tab: TranslatableEntities.pages + }) ) } onEdit={onEdit} diff --git a/src/translations/views/TranslationsProductTypes.tsx b/src/translations/views/TranslationsProductTypes.tsx index 02a1b8599..3f859ebed 100644 --- a/src/translations/views/TranslationsProductTypes.tsx +++ b/src/translations/views/TranslationsProductTypes.tsx @@ -144,10 +144,9 @@ const TranslationsProductTypes: React.FC = ({ saveButtonState={saveButtonState} onBack={() => navigate( - languageEntitiesUrl( - languageCode, - TranslatableEntities.productTypes - ) + languageEntitiesUrl(languageCode, { + tab: TranslatableEntities.productTypes + }) ) } onEdit={onEdit} diff --git a/src/translations/views/TranslationsProducts.tsx b/src/translations/views/TranslationsProducts.tsx index b343cc87b..2231835e9 100644 --- a/src/translations/views/TranslationsProducts.tsx +++ b/src/translations/views/TranslationsProducts.tsx @@ -104,10 +104,9 @@ const TranslationsProducts: React.FC = ({ saveButtonState={saveButtonState} onBack={() => navigate( - languageEntitiesUrl( - languageCode, - TranslatableEntities.products - ) + languageEntitiesUrl(languageCode, { + tab: TranslatableEntities.products + }) ) } onEdit={onEdit} diff --git a/src/translations/views/TranslationsSales.tsx b/src/translations/views/TranslationsSales.tsx index 942e044c5..d2d003bb3 100644 --- a/src/translations/views/TranslationsSales.tsx +++ b/src/translations/views/TranslationsSales.tsx @@ -98,10 +98,9 @@ const TranslationsSales: React.FC = ({ saveButtonState={saveButtonState} onBack={() => navigate( - languageEntitiesUrl( - languageCode, - TranslatableEntities.sales - ) + languageEntitiesUrl(languageCode, { + tab: TranslatableEntities.sales + }) ) } onEdit={onEdit} diff --git a/src/translations/views/TranslationsVouchers.tsx b/src/translations/views/TranslationsVouchers.tsx index 4deb1e366..2a0564c9b 100644 --- a/src/translations/views/TranslationsVouchers.tsx +++ b/src/translations/views/TranslationsVouchers.tsx @@ -101,10 +101,9 @@ const TranslationsVouchers: React.FC = ({ saveButtonState={saveButtonState} onBack={() => navigate( - languageEntitiesUrl( - languageCode, - TranslatableEntities.vouchers - ) + languageEntitiesUrl(languageCode, { + tab: TranslatableEntities.vouchers + }) ) } onEdit={onEdit} diff --git a/src/types.ts b/src/types.ts index 68de233fb..f47628fa7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,6 @@ import { MutationResult } from "react-apollo"; import { FilterContentSubmitData } from "./components/Filter"; import { Filter } from "./components/TableFilter"; -import { GetFilterTabsOutput } from "./utils/filters"; export interface UserError { field: string; @@ -25,6 +24,7 @@ export enum ListViews { PAGES_LIST = "PAGES_LIST", PLUGINS_LIST = "PLUGIN_LIST", PRODUCT_LIST = "PRODUCT_LIST", + PRODUCT_TYPE_LIST = "PRODUCT_TYPE_LIST", SALES_LIST = "SALES_LIST", SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST", STAFF_MEMBERS_LIST = "STAFF_MEMBERS_LIST", @@ -65,24 +65,36 @@ export interface PageListProps defaultSettings?: ListSettings; onAdd: () => void; } -export interface FilterPageProps { - currencySymbol: string; - currentTab: number; - filterTabs: GetFilterTabsOutput; - filtersList: Filter[]; + +export interface SearchPageProps { initialSearch: string; - onAll: () => void; onSearchChange: (value: string) => void; - onFilterAdd: (filter: FilterContentSubmitData) => void; - onFilterDelete: () => void; - onFilterSave: () => void; - onTabChange: (tab: number) => void; } -export interface FilterProps - extends FilterPageProps { +export interface FilterPageProps + extends SearchPageProps, + TabPageProps { + currencySymbol: string; + filtersList: Filter[]; + onFilterAdd: (filter: FilterContentSubmitData) => void; +} + +export interface SearchProps { + searchPlaceholder: string; +} +export interface FilterProps + extends FilterPageProps, + SearchProps { allTabLabel: string; filterLabel: string; - searchPlaceholder: string; +} + +export interface TabPageProps { + currentTab: number; + tabs: string[]; + onAll: () => void; + onTabChange: (tab: number) => void; + onTabDelete: () => void; + onTabSave: () => void; } export interface PartialMutationProviderOutput< @@ -134,3 +146,5 @@ export interface FetchMoreProps { hasMore: boolean; onFetchMore: () => void; } + +export type TabActionDialog = "save-search" | "delete-search"; diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 9d49dd07d..420441a4f 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -33,11 +33,22 @@ export enum AuthorizationKeyType { GOOGLE_OAUTH2 = "GOOGLE_OAUTH2", } +export enum CollectionPublished { + HIDDEN = "HIDDEN", + PUBLISHED = "PUBLISHED", +} + export enum ConfigurationTypeFieldEnum { BOOLEAN = "BOOLEAN", STRING = "STRING", } +export enum DiscountStatusEnum { + ACTIVE = "ACTIVE", + EXPIRED = "EXPIRED", + SCHEDULED = "SCHEDULED", +} + export enum DiscountValueTypeEnum { FIXED = "FIXED", PERCENTAGE = "PERCENTAGE", @@ -168,6 +179,7 @@ export enum PermissionEnum { MANAGE_PAGES = "MANAGE_PAGES", MANAGE_PLUGINS = "MANAGE_PLUGINS", MANAGE_PRODUCTS = "MANAGE_PRODUCTS", + MANAGE_SERVICE_ACCOUNTS = "MANAGE_SERVICE_ACCOUNTS", MANAGE_SETTINGS = "MANAGE_SETTINGS", MANAGE_SHIPPING = "MANAGE_SHIPPING", MANAGE_STAFF = "MANAGE_STAFF", @@ -175,6 +187,16 @@ export enum PermissionEnum { MANAGE_USERS = "MANAGE_USERS", } +export enum ProductTypeConfigurable { + CONFIGURABLE = "CONFIGURABLE", + SIMPLE = "SIMPLE", +} + +export enum ProductTypeEnum { + DIGITAL = "DIGITAL", + SHIPPABLE = "SHIPPABLE", +} + export enum SaleType { FIXED = "FIXED", PERCENTAGE = "PERCENTAGE", @@ -185,6 +207,11 @@ export enum ShippingMethodTypeEnum { WEIGHT = "WEIGHT", } +export enum StaffMemberStatus { + ACTIVE = "ACTIVE", + DEACTIVATED = "DEACTIVATED", +} + export enum StockAvailability { IN_STOCK = "IN_STOCK", OUT_OF_STOCK = "OUT_OF_STOCK", @@ -218,6 +245,12 @@ export enum TaxRateType { WINE = "WINE", } +export enum VoucherDiscountType { + FIXED = "FIXED", + PERCENTAGE = "PERCENTAGE", + SHIPPING = "SHIPPING", +} + export enum VoucherTypeEnum { ENTIRE_ORDER = "ENTIRE_ORDER", SHIPPING = "SHIPPING", @@ -264,6 +297,17 @@ export interface AttributeCreateInput { availableInGrid?: boolean | null; } +export interface AttributeFilterInput { + valueRequired?: boolean | null; + isVariantOnly?: boolean | null; + visibleInStorefront?: boolean | null; + filterableInStorefront?: boolean | null; + filterableInDashboard?: boolean | null; + availableInGrid?: boolean | null; + search?: string | null; + ids?: (string | null)[] | null; +} + export interface AttributeInput { slug: string; value: string; @@ -304,6 +348,10 @@ export interface CatalogueInput { collections?: (string | null)[] | null; } +export interface CategoryFilterInput { + search?: string | null; +} + export interface CategoryInput { description?: string | null; descriptionJson?: any | null; @@ -327,6 +375,11 @@ export interface CollectionCreateInput { products?: (string | null)[] | null; } +export interface CollectionFilterInput { + published?: CollectionPublished | null; + search?: string | null; +} + export interface CollectionInput { isPublished?: boolean | null; name?: string | null; @@ -344,6 +397,14 @@ export interface ConfigurationItemInput { value: string; } +export interface CustomerFilterInput { + dateJoined?: DateRangeInput | null; + moneySpent?: PriceRangeInput | null; + numberOfOrders?: IntRangeInput | null; + placedOrders?: DateRangeInput | null; + search?: string | null; +} + export interface CustomerInput { defaultBillingAddress?: AddressInput | null; defaultShippingAddress?: AddressInput | null; @@ -359,6 +420,11 @@ export interface DateRangeInput { lte?: any | null; } +export interface DateTimeRangeInput { + gte?: any | null; + lte?: any | null; +} + export interface DraftOrderInput { billingAddress?: AddressInput | null; user?: string | null; @@ -389,6 +455,11 @@ export interface FulfillmentUpdateTrackingInput { notifyCustomer?: boolean | null; } +export interface IntRangeInput { + gte?: number | null; + lte?: number | null; +} + export interface MenuCreateInput { name: string; items?: (MenuItemInput | null)[] | null; @@ -426,11 +497,18 @@ export interface OrderAddNoteInput { message?: string | null; } +export interface OrderDraftFilterInput { + customer?: string | null; + created?: DateRangeInput | null; + search?: string | null; +} + export interface OrderFilterInput { paymentStatus?: (PaymentChargeStatusEnum | null)[] | null; status?: (OrderStatusFilter | null)[] | null; customer?: string | null; created?: DateRangeInput | null; + search?: string | null; } export interface OrderLineCreateInput { @@ -452,6 +530,10 @@ export interface OrderUpdateShippingInput { shippingMethod?: string | null; } +export interface PageFilterInput { + search?: string | null; +} + export interface PageInput { slug?: string | null; title?: string | null; @@ -492,6 +574,12 @@ export interface ProductFilterInput { minimalPrice?: PriceRangeInput | null; } +export interface ProductTypeFilterInput { + search?: string | null; + configurable?: ProductTypeConfigurable | null; + productType?: ProductTypeEnum | null; +} + export interface ProductTypeInput { name?: string | null; hasVariants?: boolean | null; @@ -518,6 +606,13 @@ export interface ReorderInput { sortOrder?: number | null; } +export interface SaleFilterInput { + status?: (DiscountStatusEnum | null)[] | null; + saleType?: DiscountValueTypeEnum | null; + started?: DateTimeRangeInput | null; + search?: string | null; +} + export interface SaleInput { name?: string | null; type?: DiscountValueTypeEnum | null; @@ -577,6 +672,7 @@ export interface StaffCreateInput { note?: string | null; permissions?: (PermissionEnum | null)[] | null; sendPasswordEmail?: boolean | null; + redirectUrl?: string | null; } export interface StaffInput { @@ -588,6 +684,11 @@ export interface StaffInput { permissions?: (PermissionEnum | null)[] | null; } +export interface StaffUserInput { + status?: StaffMemberStatus | null; + search?: string | null; +} + export interface TranslationInput { seoTitle?: string | null; seoDescription?: string | null; @@ -605,6 +706,15 @@ export interface UserCreateInput { isActive?: boolean | null; note?: string | null; sendPasswordEmail?: boolean | null; + redirectUrl?: string | null; +} + +export interface VoucherFilterInput { + status?: (DiscountStatusEnum | null)[] | null; + timesUsed?: IntRangeInput | null; + discountType?: (VoucherDiscountType | null)[] | null; + started?: DateTimeRangeInput | null; + search?: string | null; } export interface VoucherInput {