diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a4805e85..af266c1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ All notable, unreleased changes to this project will be documented in this file. - Enforce using "name" property in style hooks - #288 by @dominik-zeglen - Add ability to reset own password - #289 by @dominik-zeglen - Move mutation state to mutation - #297 by @dominik-zeglen +- Add table sorting - #292 by @dominik-zeglen ## 2.0.0 diff --git a/schema.graphql b/schema.graphql index 26f6aa664..e3c2d53e7 100644 --- a/schema.graphql +++ b/schema.graphql @@ -318,8 +318,8 @@ enum AttributeSortField { } input AttributeSortingInput { - field: AttributeSortField! direction: OrderDirection! + field: AttributeSortField } type AttributeTranslatableContent implements Node { @@ -570,6 +570,17 @@ input CategoryInput { backgroundImageAlt: String } +enum CategorySortField { + NAME + PRODUCT_COUNT + SUBCATEGORY_COUNT +} + +input CategorySortingInput { + direction: OrderDirection! + field: CategorySortField +} + type CategoryTranslatableContent implements Node { seoTitle: String seoDescription: String @@ -950,6 +961,17 @@ type CollectionReorderProducts { productErrors: [ProductError!] } +enum CollectionSortField { + NAME + AVAILABILITY + PRODUCT_COUNT +} + +input CollectionSortingInput { + direction: OrderDirection! + field: CollectionSortField +} + type CollectionTranslatableContent implements Node { seoTitle: String seoDescription: String @@ -1398,6 +1420,8 @@ input DateTimeRangeInput { scalar Decimal +union DefaultTranslationItem = ProductTranslatableContent | CollectionTranslatableContent | CategoryTranslatableContent | AttributeTranslatableContent | AttributeValueTranslatableContent | ProductVariantTranslatableContent | PageTranslatableContent | ShippingMethodTranslatableContent | SaleTranslatableContent | VoucherTranslatableContent | MenuItemTranslatableContent + type DigitalContent implements Node { useDefaultSettings: Boolean! automaticFulfillment: Boolean! @@ -1990,6 +2014,11 @@ input MenuItemMoveInput { sortOrder: Int } +input MenuItemSortingInput { + direction: OrderDirection! + field: MenuItemsSortField +} + type MenuItemTranslatableContent implements Node { id: ID! name: String! @@ -2014,6 +2043,20 @@ type MenuItemUpdate { menuItem: MenuItem } +enum MenuItemsSortField { + NAME +} + +enum MenuSortField { + NAME + ITEMS_COUNT +} + +input MenuSortingInput { + direction: OrderDirection! + field: MenuSortField +} + type MenuUpdate { errors: [Error!] menuErrors: [MenuError!] @@ -2575,6 +2618,20 @@ type OrderRefund { orderErrors: [OrderError!] } +enum OrderSortField { + NUMBER + CREATION_DATE + CUSTOMER + PAYMENT + FULFILLMENT_STATUS + TOTAL +} + +input OrderSortingInput { + direction: OrderDirection! + field: OrderSortField +} + enum OrderStatus { DRAFT UNFULFILLED @@ -2696,6 +2753,19 @@ input PageInput { seo: SeoInput } +enum PageSortField { + TITLE + SLUG + VISIBILITY + CREATION_DATE + PUBLICATION_DATE +} + +input PageSortingInput { + direction: OrderDirection! + field: PageSortField +} + type PageTranslatableContent implements Node { seoTitle: String seoDescription: String @@ -2892,6 +2962,16 @@ input PluginFilterInput { search: String } +enum PluginSortField { + NAME + IS_ACTIVE +} + +input PluginSortingInput { + direction: OrderDirection! + field: PluginSortField +} + type PluginUpdate { errors: [Error!] plugin: Plugin @@ -3115,9 +3195,9 @@ input ProductInput { } input ProductOrder { - field: ProductOrderField - attributeId: ID direction: OrderDirection! + attributeId: ID + field: ProductOrderField } enum ProductOrderField { @@ -3256,6 +3336,17 @@ type ProductTypeReorderAttributes { productErrors: [ProductError!] } +enum ProductTypeSortField { + NAME + DIGITAL + SHIPPING_REQUIRED +} + +input ProductTypeSortingInput { + direction: OrderDirection! + field: ProductTypeSortField +} + type ProductTypeUpdate { errors: [Error!] productErrors: [ProductError!] @@ -3436,11 +3527,11 @@ type ProductVariantUpdatePrivateMeta { type Query { webhook(id: ID!): Webhook - webhooks(filter: WebhookFilterInput, before: String, after: String, first: Int, last: Int): WebhookCountableConnection + webhooks(sortBy: WebhookSortingInput, filter: WebhookFilterInput, before: String, after: String, first: Int, last: Int): WebhookCountableConnection webhookEvents: [WebhookEvent] webhookSamplePayload(eventType: WebhookEventTypeEnum!): JSONString translations(kind: TranslatableKinds!, before: String, after: String, first: Int, last: Int): TranslatableItemConnection - translation(id: ID!, kind: TranslatableKinds!): TranslatableItem + translation(id: ID!, kind: TranslatableKinds!): DefaultTranslationItem shop: Shop shippingZone(id: ID!): ShippingZone shippingZones(before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection @@ -3448,14 +3539,14 @@ type Query { digitalContents(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, filter: CategoryFilterInput, level: Int, before: String, after: String, first: Int, last: Int): CategoryCountableConnection + categories(query: String, filter: CategoryFilterInput, sortBy: CategorySortingInput, 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 + collections(filter: CollectionFilterInput, sortBy: CollectionSortingInput, query: String, before: String, after: String, first: Int, last: Int): CollectionCountableConnection product(id: ID!): Product products(filter: ProductFilterInput, attributes: [AttributeScalar], categories: [ID], collections: [ID], sortBy: ProductOrder, stockAvailability: StockAvailability, query: String, before: String, after: String, first: Int, last: Int): ProductCountableConnection productType(id: ID!): ProductType - productTypes(filter: ProductTypeFilterInput, query: String, before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection + productTypes(filter: ProductTypeFilterInput, query: String, sortBy: ProductTypeSortingInput, before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection productVariant(id: ID!): ProductVariant productVariants(ids: [ID], before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection reportProductSales(period: ReportingPeriod!, before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection @@ -3463,37 +3554,38 @@ type Query { payments(before: String, after: String, first: Int, last: Int): PaymentCountableConnection paymentClientToken(gateway: String!): String @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, use payment gateway config instead in availablePaymentGateways.") page(id: ID, slug: String): Page - pages(query: String, filter: PageFilterInput, before: String, after: String, first: Int, last: Int): PageCountableConnection + pages(query: String, sortBy: PageSortingInput, 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 - draftOrders(filter: OrderDraftFilterInput, query: String, created: ReportingPeriod, before: String, after: String, first: Int, last: Int): OrderCountableConnection + orders(sortBy: OrderSortingInput, filter: OrderFilterInput, query: String, created: ReportingPeriod, status: OrderStatusFilter, before: String, after: String, first: Int, last: Int): OrderCountableConnection + draftOrders(sortBy: OrderSortingInput, filter: OrderDraftFilterInput, query: String, created: ReportingPeriod, before: String, after: String, first: Int, last: Int): OrderCountableConnection ordersTotal(period: ReportingPeriod): TaxedMoney orderByToken(token: UUID!): Order menu(id: ID, name: String): Menu - menus(query: String, filter: MenuFilterInput, before: String, after: String, first: Int, last: Int): MenuCountableConnection + menus(query: String, sortBy: MenuSortingInput, filter: MenuFilterInput, before: String, after: String, first: Int, last: Int): MenuCountableConnection menuItem(id: ID!): MenuItem - menuItems(query: String, filter: MenuItemFilterInput, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection + menuItems(query: String, sortBy: MenuItemSortingInput, 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(filter: PluginFilterInput, before: String, after: String, first: Int, last: Int): PluginCountableConnection + plugins(filter: PluginFilterInput, sortBy: PluginSortingInput, 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 + sales(filter: SaleFilterInput, sortBy: SaleSortingInput, query: String, before: String, after: String, first: Int, last: Int): SaleCountableConnection voucher(id: ID!): Voucher - vouchers(filter: VoucherFilterInput, query: String, before: String, after: String, first: Int, last: Int): VoucherCountableConnection + vouchers(filter: VoucherFilterInput, sortBy: VoucherSortingInput, query: String, before: String, after: String, first: Int, last: Int): VoucherCountableConnection taxTypes: [TaxType] checkout(token: UUID): Checkout checkouts(before: String, after: String, first: Int, last: Int): CheckoutCountableConnection checkoutLine(id: ID): CheckoutLine checkoutLines(before: String, after: String, first: Int, last: Int): CheckoutLineCountableConnection addressValidationRules(countryCode: CountryCode!, countryArea: String, city: String, cityArea: String): AddressValidationData - customers(filter: CustomerFilterInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection + customers(filter: CustomerFilterInput, sortBy: UserSortingInput, 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 + staffUsers(filter: StaffUserInput, sortBy: UserSortingInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection + serviceAccounts(filter: ServiceAccountFilterInput, sortBy: ServiceAccountSortingInput, before: String, after: String, first: Int, last: Int): ServiceAccountCountableConnection serviceAccount(id: ID!): ServiceAccount user(id: ID!): User + node(id: ID!): Node _entities(representations: [_Any]): [_Entity] _service: _Service } @@ -3590,6 +3682,19 @@ type SaleRemoveCatalogues { sale: Sale } +enum SaleSortField { + NAME + START_DATE + END_DATE + VALUE + TYPE +} + +input SaleSortingInput { + direction: OrderDirection! + field: SaleSortField +} + type SaleTranslatableContent implements Node { id: ID! name: String! @@ -3681,6 +3786,16 @@ input ServiceAccountInput { permissions: [PermissionEnum] } +enum ServiceAccountSortField { + NAME + CREATION_DATE +} + +input ServiceAccountSortingInput { + direction: OrderDirection! + field: ServiceAccountSortField +} + type ServiceAccountToken implements Node { name: String authToken: String @@ -4109,7 +4224,7 @@ enum TransactionKind { CONFIRM } -union TranslatableItem = ProductTranslatableContent | CollectionTranslatableContent | CategoryTranslatableContent | AttributeTranslatableContent | AttributeValueTranslatableContent | ProductVariantTranslatableContent | PageTranslatableContent | ShippingMethodTranslatableContent | SaleTranslatableContent | VoucherTranslatableContent | MenuItemTranslatableContent +union TranslatableItem = Product | Category | Collection | Attribute | AttributeValue | ProductVariant | Page | ShippingMethod | Sale | Voucher | MenuItem type TranslatableItemConnection { pageInfo: PageInfo! @@ -4230,6 +4345,18 @@ input UserCreateInput { redirectUrl: String } +enum UserSortField { + FIRST_NAME + LAST_NAME + EMAIL + ORDER_COUNT +} + +input UserSortingInput { + direction: OrderDirection! + field: UserSortField +} + type UserUpdateMeta { errors: [Error!] accountErrors: [AccountError!] @@ -4369,6 +4496,21 @@ type VoucherRemoveCatalogues { voucher: Voucher } +enum VoucherSortField { + CODE + START_DATE + END_DATE + VALUE + TYPE + USAGE_LIMIT + MINIMUM_SPENT_AMOUNT +} + +input VoucherSortingInput { + direction: OrderDirection! + field: VoucherSortField +} + type VoucherTranslatableContent implements Node { id: ID! name: String @@ -4475,6 +4617,17 @@ input WebhookFilterInput { isActive: Boolean } +enum WebhookSortField { + NAME + SERVICE_ACCOUNT + TARGET_URL +} + +input WebhookSortingInput { + direction: OrderDirection! + field: WebhookSortField +} + type WebhookUpdate { errors: [Error!] webhook: Webhook diff --git a/src/attributes/components/AttributeList/AttributeList.tsx b/src/attributes/components/AttributeList/AttributeList.tsx index 5f17abb10..b3844066f 100644 --- a/src/attributes/components/AttributeList/AttributeList.tsx +++ b/src/attributes/components/AttributeList/AttributeList.tsx @@ -13,10 +13,16 @@ import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; import { translateBoolean } from "@saleor/intl"; import { maybe, renderCollection } from "@saleor/misc"; -import { ListActions, ListProps } from "@saleor/types"; +import { ListActions, ListProps, SortPage } from "@saleor/types"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { AttributeListUrlSortField } from "@saleor/attributes/urls"; +import { getArrowDirection } from "@saleor/utils/sort"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList"; -export interface AttributeListProps extends ListProps, ListActions { +export interface AttributeListProps + extends ListProps, + ListActions, + SortPage { attributes: AttributeList_attributes_edges_node[]; } @@ -24,19 +30,19 @@ const useStyles = makeStyles( theme => ({ [theme.breakpoints.up("lg")]: { colFaceted: { - width: 150 + width: 180 }, colName: { width: "auto" }, colSearchable: { - width: 150 + width: 180 }, colSlug: { width: 200 }, colVisible: { - width: 150 + width: 180 } }, colFaceted: { @@ -70,9 +76,11 @@ const AttributeList: React.FC = ({ onRowClick, pageInfo, selected, + sort, toggle, toggleAll, - toolbar + toolbar, + onSort }) => { const classes = useStyles({}); const intl = useIntl(); @@ -87,33 +95,77 @@ const AttributeList: React.FC = ({ toggleAll={toggleAll} toolbar={toolbar} > - + onSort(AttributeListUrlSortField.slug)} + > - - + + onSort(AttributeListUrlSortField.name)} + > - - + + onSort(AttributeListUrlSortField.visible)} + > - - + + onSort(AttributeListUrlSortField.searchable)} + > - - + + onSort(AttributeListUrlSortField.useInFacetedSearch)} + > - + diff --git a/src/attributes/components/AttributeListPage/AttributeListPage.tsx b/src/attributes/components/AttributeListPage/AttributeListPage.tsx index 477cfff96..aadb1d933 100644 --- a/src/attributes/components/AttributeListPage/AttributeListPage.tsx +++ b/src/attributes/components/AttributeListPage/AttributeListPage.tsx @@ -6,13 +6,15 @@ import { FormattedMessage, useIntl } from "react-intl"; import AppHeader from "@saleor/components/AppHeader"; import SearchBar from "@saleor/components/SearchBar"; import { sectionNames } from "@saleor/intl"; +import { AttributeListUrlSortField } from "@saleor/attributes/urls"; import Container from "../../../components/Container"; import PageHeader from "../../../components/PageHeader"; import { ListActions, PageListProps, SearchPageProps, - TabPageProps + TabPageProps, + SortPage } from "../../../types"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList"; import AttributeList from "../AttributeList/AttributeList"; @@ -21,6 +23,7 @@ export interface AttributeListPageProps extends PageListProps, ListActions, SearchPageProps, + SortPage, TabPageProps { attributes: AttributeList_attributes_edges_node[]; onBack: () => void; diff --git a/src/attributes/index.tsx b/src/attributes/index.tsx index a21d82435..a15e5fc40 100644 --- a/src/attributes/index.tsx +++ b/src/attributes/index.tsx @@ -4,6 +4,7 @@ import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; import { useIntl } from "react-intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { attributeAddPath, @@ -11,7 +12,8 @@ import { attributeListPath, AttributeListUrlQueryParams, attributePath, - AttributeUrlQueryParams + AttributeUrlQueryParams, + AttributeListUrlSortField } from "./urls"; import AttributeCreateComponent from "./views/AttributeCreate"; import AttributeDetailsComponent from "./views/AttributeDetails"; @@ -19,7 +21,11 @@ import AttributeListComponent from "./views/AttributeList"; const AttributeList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: AttributeListUrlQueryParams = qs; + const params: AttributeListUrlQueryParams = asSortParams( + qs, + AttributeListUrlSortField + ); + return ; }; diff --git a/src/attributes/queries.ts b/src/attributes/queries.ts index f8850d581..983760f26 100644 --- a/src/attributes/queries.ts +++ b/src/attributes/queries.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { pageInfoFragment, TypedQuery } from "../queries"; import { AttributeDetails, @@ -59,6 +60,7 @@ const attributeList = gql` $after: String $first: Int $last: Int + $sort: AttributeSortingInput ) { attributes( filter: $filter @@ -68,6 +70,7 @@ const attributeList = gql` after: $after first: $first last: $last + sortBy: $sort ) { edges { node { @@ -85,7 +88,7 @@ const attributeList = gql` } } `; -export const AttributeListQuery = TypedQuery< +export const useAttributeListQuery = makeQuery< AttributeList, AttributeListVariables >(attributeList); diff --git a/src/attributes/types/AttributeList.ts b/src/attributes/types/AttributeList.ts index ad5050ce5..3fc5c5ed5 100644 --- a/src/attributes/types/AttributeList.ts +++ b/src/attributes/types/AttributeList.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeFilterInput } from "./../../types/globalTypes"; +import { AttributeFilterInput, AttributeSortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: AttributeList @@ -57,4 +57,5 @@ export interface AttributeListVariables { after?: string | null; first?: number | null; last?: number | null; + sort?: AttributeSortingInput | null; } diff --git a/src/attributes/urls.ts b/src/attributes/urls.ts index 80bef3768..fc0312068 100644 --- a/src/attributes/urls.ts +++ b/src/attributes/urls.ts @@ -8,6 +8,7 @@ import { Filters, Pagination, SingleAction, + Sort, TabActionDialog } from "../types"; @@ -18,8 +19,17 @@ export enum AttributeListUrlFiltersEnum { } export type AttributeListUrlFilters = Filters; export type AttributeListUrlDialog = "remove" | TabActionDialog; +export enum AttributeListUrlSortField { + name = "name", + slug = "slug", + visible = "visible", + searchable = "searchable", + useInFacetedSearch = "use-in-faceted-search" +} +export type AttributeListUrlSort = Sort; export type AttributeListUrlQueryParams = ActiveTab & AttributeListUrlFilters & + AttributeListUrlSort & BulkAction & Dialog & Pagination; diff --git a/src/attributes/views/AttributeList/AttributeList.tsx b/src/attributes/views/AttributeList/AttributeList.tsx index e1b827045..4f9b1c745 100644 --- a/src/attributes/views/AttributeList/AttributeList.tsx +++ b/src/attributes/views/AttributeList/AttributeList.tsx @@ -21,13 +21,15 @@ import useNotifier from "@saleor/hooks/useNotifier"; import usePaginator, { createPaginationState } from "@saleor/hooks/usePaginator"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { PAGINATE_BY } from "../../../config"; import useBulkActions from "../../../hooks/useBulkActions"; import { maybe } from "../../../misc"; import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog"; import AttributeListPage from "../../components/AttributeListPage"; import { AttributeBulkDeleteMutation } from "../../mutations"; -import { AttributeListQuery } from "../../queries"; +import { useAttributeListQuery } from "../../queries"; import { AttributeBulkDelete } from "../../types/AttributeBulkDelete"; import { attributeAddUrl, @@ -37,6 +39,7 @@ import { AttributeListUrlQueryParams, attributeUrl } from "../../urls"; +import { getSortQueryVariables } from "./sort"; interface AttributeListProps { params: AttributeListUrlQueryParams; @@ -51,6 +54,19 @@ const AttributeList: React.FC = ({ params }) => { ); const intl = useIntl(); + const paginationState = createPaginationState(PAGINATE_BY, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading, refetch } = useAttributeListQuery({ + variables: queryVariables + }); + const tabs = getFilterTabs(); const currentTab = @@ -111,105 +127,93 @@ const AttributeList: React.FC = ({ params }) => { handleTabChange(tabs.length + 1); }; - const paginationState = createPaginationState(PAGINATE_BY, params); - const queryVariables = React.useMemo( - () => ({ - ...paginationState, - filter: getFilterVariables(params) - }), - [params] + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.attributes.pageInfo), + paginationState, + params ); + const handleBulkDelete = (data: AttributeBulkDelete) => { + if (data.attributeBulkDelete.errors.length === 0) { + closeModal(); + notify({ + text: intl.formatMessage({ + defaultMessage: "Attributes successfully delete", + description: "deleted multiple attributes" + }) + }); + reset(); + refetch(); + } + }; + + const handleSort = createSortHandler(navigate, attributeListUrl, params); + return ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.attributes.pageInfo), - paginationState, - params - ); - - const handleBulkDelete = (data: AttributeBulkDelete) => { - if (data.attributeBulkDelete.errors.length === 0) { - closeModal(); - notify({ - text: intl.formatMessage({ - defaultMessage: "Attributes successfully delete", - description: "deleted multiple attributes" - }) - }); - reset(); - refetch(); - } - }; - - return ( - - {(attributeBulkDelete, attributeBulkDeleteOpts) => ( - <> - - data.attributes.edges.map(edge => edge.node) - )} - currentTab={currentTab} - disabled={loading || attributeBulkDeleteOpts.loading} - initialSearch={params.query || ""} - isChecked={isSelected} - onAdd={() => navigate(attributeAddUrl())} - onAll={() => navigate(attributeListUrl())} - onBack={() => navigate(configurationMenuUrl)} - 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={ - openModal("remove", listElements)} - > - - - } - /> - params.ids.length > 0) - } - onConfirm={() => - attributeBulkDelete({ variables: { ids: params.ids } }) - } - onClose={closeModal} - quantity={maybe(() => params.ids.length)} - /> - - tabs[currentTab - 1].name, "...")} - /> - + + {(attributeBulkDelete, attributeBulkDeleteOpts) => ( + <> + + data.attributes.edges.map(edge => edge.node) )} - - ); - }} - + currentTab={currentTab} + disabled={loading || attributeBulkDeleteOpts.loading} + initialSearch={params.query || ""} + isChecked={isSelected} + onAdd={() => navigate(attributeAddUrl())} + onAll={() => navigate(attributeListUrl())} + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onRowClick={id => () => navigate(attributeUrl(id))} + onSearchChange={query => changeFilterField({ query })} + onSort={handleSort} + onTabChange={handleTabChange} + onTabDelete={() => openModal("delete-search")} + onTabSave={() => openModal("save-search")} + pageInfo={pageInfo} + selected={listElements.length} + sort={getSortParams(params)} + tabs={tabs.map(tab => tab.name)} + toggle={toggle} + toggleAll={toggleAll} + toolbar={ + openModal("remove", listElements)} + > + + + } + /> + params.ids.length > 0) + } + onConfirm={() => + attributeBulkDelete({ variables: { ids: params.ids } }) + } + onClose={closeModal} + quantity={maybe(() => params.ids.length)} + /> + + tabs[currentTab - 1].name, "...")} + /> + + )} + ); }; AttributeList.displayName = "AttributeList"; diff --git a/src/attributes/views/AttributeList/sort.ts b/src/attributes/views/AttributeList/sort.ts new file mode 100644 index 000000000..41f3dfbd0 --- /dev/null +++ b/src/attributes/views/AttributeList/sort.ts @@ -0,0 +1,26 @@ +import { AttributeListUrlSortField } from "@saleor/attributes/urls"; +import { AttributeSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: AttributeListUrlSortField +): AttributeSortField { + switch (sort) { + case AttributeListUrlSortField.name: + return AttributeSortField.NAME; + case AttributeListUrlSortField.slug: + return AttributeSortField.SLUG; + case AttributeListUrlSortField.searchable: + return AttributeSortField.FILTERABLE_IN_DASHBOARD; + case AttributeListUrlSortField.useInFacetedSearch: + return AttributeSortField.FILTERABLE_IN_STOREFRONT; + case AttributeListUrlSortField.visible: + return AttributeSortField.VISIBLE_IN_STOREFRONT; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/categories/components/CategoryList/CategoryList.tsx b/src/categories/components/CategoryList/CategoryList.tsx index 216d092e6..09ae331b4 100644 --- a/src/categories/components/CategoryList/CategoryList.tsx +++ b/src/categories/components/CategoryList/CategoryList.tsx @@ -13,7 +13,10 @@ import Skeleton from "@saleor/components/Skeleton"; import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; import { maybe, renderCollection } from "@saleor/misc"; -import { ListActions, ListProps } from "@saleor/types"; +import { ListActions, ListProps, SortPage } from "@saleor/types"; +import { CategoryListUrlSortField } from "@saleor/categories/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; const useStyles = makeStyles( theme => ({ @@ -44,7 +47,10 @@ const useStyles = makeStyles( { name: "CategoryList" } ); -interface CategoryListProps extends ListProps, ListActions { +interface CategoryListProps + extends ListProps, + ListActions, + SortPage { categories?: CategoryFragment[]; isRoot: boolean; onAdd?(); @@ -57,6 +63,7 @@ const CategoryList: React.FC = props => { categories, disabled, settings, + sort, pageInfo, isChecked, isRoot, @@ -67,7 +74,8 @@ const CategoryList: React.FC = props => { onNextPage, onPreviousPage, onUpdateListSettings, - onRowClick + onRowClick, + onSort } = props; const classes = useStyles(props); @@ -82,21 +90,53 @@ const CategoryList: React.FC = props => { toggleAll={toggleAll} toolbar={toolbar} > - + isRoot && onSort(CategoryListUrlSortField.name)} + > - - + + + isRoot && onSort(CategoryListUrlSortField.subcategoryCount) + } + > - - + + + isRoot && onSort(CategoryListUrlSortField.productCount) + } + > - + diff --git a/src/categories/components/CategoryListPage/CategoryListPage.tsx b/src/categories/components/CategoryListPage/CategoryListPage.tsx index 01cf9df33..11c22bcde 100644 --- a/src/categories/components/CategoryListPage/CategoryListPage.tsx +++ b/src/categories/components/CategoryListPage/CategoryListPage.tsx @@ -12,14 +12,17 @@ import { ListActions, PageListProps, SearchPageProps, - TabPageProps + TabPageProps, + SortPage } from "@saleor/types"; +import { CategoryListUrlSortField } from "@saleor/categories/urls"; import CategoryList from "../CategoryList"; export interface CategoryTableProps extends PageListProps, ListActions, SearchPageProps, + SortPage, TabPageProps { categories: CategoryFragment[]; } @@ -46,7 +49,8 @@ export const CategoryListPage: React.FC = ({ onTabChange, onTabDelete, onTabSave, - onUpdateListSettings + onUpdateListSettings, + ...listProps }) => { const intl = useIntl(); @@ -94,6 +98,7 @@ export const CategoryListPage: React.FC = ({ onPreviousPage={onPreviousPage} onRowClick={onRowClick} onUpdateListSettings={onUpdateListSettings} + {...listProps} /> diff --git a/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx b/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx index 81130dd32..eac85851e 100644 --- a/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx +++ b/src/categories/components/CategoryUpdatePage/CategoryUpdatePage.tsx @@ -205,12 +205,14 @@ export const CategoryUpdatePage: React.FC = ({ isRoot={false} pageInfo={pageInfo} selected={selected} + sort={undefined} toggle={toggle} toggleAll={toggleAll} toolbar={subcategoryListToolbar} onNextPage={onNextPage} onPreviousPage={onPreviousPage} onRowClick={onCategoryClick} + onSort={() => undefined} /> )} diff --git a/src/categories/index.tsx b/src/categories/index.tsx index 2bccd2f8d..2ae9db35f 100644 --- a/src/categories/index.tsx +++ b/src/categories/index.tsx @@ -4,13 +4,15 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { categoryAddPath, categoryListPath, CategoryListUrlQueryParams, categoryPath, - CategoryUrlQueryParams + CategoryUrlQueryParams, + CategoryListUrlSortField } from "./urls"; import { CategoryCreateView } from "./views/CategoryCreate"; import CategoryDetailsView, { getActiveTab } from "./views/CategoryDetails"; @@ -19,14 +21,15 @@ import CategoryListComponent from "./views/CategoryList"; interface CategoryDetailsRouteParams { id: string; } -const CategoryDetails: React.FC< - RouteComponentProps -> = ({ location, match }) => { +const CategoryDetails: React.FC> = ({ location, match }) => { const qs = parseQs(location.search.substr(1)); const params: CategoryUrlQueryParams = { ...qs, activeTab: getActiveTab(qs.activeTab) }; + return ( -> = ({ match }) => ( +const CategoryCreate: React.FC> = ({ match }) => ( @@ -48,7 +51,10 @@ const CategoryCreate: React.FC< const CategoryList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: CategoryListUrlQueryParams = qs; + const params: CategoryListUrlQueryParams = { + ...asSortParams(qs, CategoryListUrlSortField) + }; + return ; }; diff --git a/src/categories/queries.ts b/src/categories/queries.ts index d8308b92d..15ddc9247 100644 --- a/src/categories/queries.ts +++ b/src/categories/queries.ts @@ -46,6 +46,7 @@ export const rootCategories = gql` $last: Int $before: String $filter: CategoryFilterInput + $sort: CategorySortingInput ) { categories( level: 0 @@ -54,6 +55,7 @@ export const rootCategories = gql` last: $last before: $before filter: $filter + sortBy: $sort ) { edges { node { diff --git a/src/categories/types/RootCategories.ts b/src/categories/types/RootCategories.ts index 2517b129f..5811b4f71 100644 --- a/src/categories/types/RootCategories.ts +++ b/src/categories/types/RootCategories.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { CategoryFilterInput } from "./../../types/globalTypes"; +import { CategoryFilterInput, CategorySortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: RootCategories @@ -55,4 +55,5 @@ export interface RootCategoriesVariables { last?: number | null; before?: string | null; filter?: CategoryFilterInput | null; + sort?: CategorySortingInput | null; } diff --git a/src/categories/urls.ts b/src/categories/urls.ts index 1c06252e7..13c88a5d5 100644 --- a/src/categories/urls.ts +++ b/src/categories/urls.ts @@ -7,7 +7,8 @@ import { Dialog, Filters, Pagination, - TabActionDialog + TabActionDialog, + Sort } from "../types"; import { CategoryPageTab } from "./components/CategoryUpdatePage"; @@ -19,9 +20,16 @@ export enum CategoryListUrlFiltersEnum { } export type CategoryListUrlFilters = Filters; export type CategoryListUrlDialog = "delete" | TabActionDialog; +export enum CategoryListUrlSortField { + name = "name", + productCount = "products", + subcategoryCount = "subcategories" +} +export type CategoryListUrlSort = Sort; export type CategoryListUrlQueryParams = ActiveTab & BulkAction & CategoryListUrlFilters & + CategoryListUrlSort & Dialog & Pagination; export const categoryListUrl = (params?: CategoryListUrlQueryParams) => diff --git a/src/categories/views/CategoryList/CategoryList.tsx b/src/categories/views/CategoryList/CategoryList.tsx index d8bdcdac8..97f99fa78 100644 --- a/src/categories/views/CategoryList/CategoryList.tsx +++ b/src/categories/views/CategoryList/CategoryList.tsx @@ -17,6 +17,8 @@ import usePaginator, { } from "@saleor/hooks/usePaginator"; import { maybe } from "@saleor/misc"; import { ListViews } from "@saleor/types"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage"; import { useCategoryBulkDeleteMutation } from "../../mutations"; import { useRootCategoriesQuery } from "../../queries"; @@ -37,6 +39,7 @@ import { getFilterVariables, saveFilterTab } from "./filter"; +import { getSortQueryVariables } from "./sort"; interface CategoryListProps { params: CategoryListUrlQueryParams; @@ -57,7 +60,8 @@ export const CategoryList: React.FC = ({ params }) => { const queryVariables = React.useMemo( () => ({ ...paginationState, - filter: getFilterVariables(params) + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) }), [params] ); @@ -148,6 +152,8 @@ export const CategoryList: React.FC = ({ params }) => { onCompleted: handleCategoryBulkDelete }); + const handleSort = createSortHandler(navigate, categoryListUrl, params); + return ( <> = ({ params }) => { onTabSave={() => openModal("save-search")} tabs={tabs.map(tab => tab.name)} settings={settings} + sort={getSortParams(params)} onAdd={() => navigate(categoryAddUrl())} onRowClick={id => () => navigate(categoryUrl(id))} + onSort={handleSort} disabled={loading} onNextPage={loadNextPage} onPreviousPage={loadPreviousPage} diff --git a/src/categories/views/CategoryList/sort.ts b/src/categories/views/CategoryList/sort.ts new file mode 100644 index 000000000..d7462b009 --- /dev/null +++ b/src/categories/views/CategoryList/sort.ts @@ -0,0 +1,22 @@ +import { CategoryListUrlSortField } from "@saleor/categories/urls"; +import { CategorySortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: CategoryListUrlSortField +): CategorySortField { + switch (sort) { + case CategoryListUrlSortField.name: + return CategorySortField.NAME; + case CategoryListUrlSortField.productCount: + return CategorySortField.PRODUCT_COUNT; + case CategoryListUrlSortField.subcategoryCount: + return CategorySortField.SUBCATEGORY_COUNT; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/collections/components/CollectionList/CollectionList.tsx b/src/collections/components/CollectionList/CollectionList.tsx index 46953f107..8657460a6 100644 --- a/src/collections/components/CollectionList/CollectionList.tsx +++ b/src/collections/components/CollectionList/CollectionList.tsx @@ -13,7 +13,10 @@ import StatusLabel from "@saleor/components/StatusLabel"; import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; import { maybe, renderCollection } from "@saleor/misc"; -import { ListActions, ListProps } from "@saleor/types"; +import { ListActions, ListProps, SortPage } from "@saleor/types"; +import { CollectionListUrlSortField } from "@saleor/collections/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; import { CollectionList_collections_edges_node } from "../../types/CollectionList"; const useStyles = makeStyles( @@ -41,7 +44,10 @@ const useStyles = makeStyles( { name: "CollectionList" } ); -interface CollectionListProps extends ListProps, ListActions { +interface CollectionListProps + extends ListProps, + ListActions, + SortPage { collections: CollectionList_collections_edges_node[]; } @@ -52,10 +58,12 @@ const CollectionList: React.FC = props => { collections, disabled, settings, + sort, onNextPage, onPreviousPage, onUpdateListSettings, onRowClick, + onSort, pageInfo, isChecked, selected, @@ -77,18 +85,43 @@ const CollectionList: React.FC = props => { toggleAll={toggleAll} toolbar={toolbar} > - - - - + onSort(CollectionListUrlSortField.name)} + className={classes.colName} + > + + + onSort(CollectionListUrlSortField.productCount)} + className={classes.colProducts} + > - - + + onSort(CollectionListUrlSortField.available)} + className={classes.colAvailability} + > - + diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index 683fec082..df72134ba 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -11,8 +11,10 @@ import { ListActions, PageListProps, SearchPageProps, - TabPageProps + TabPageProps, + SortPage } from "@saleor/types"; +import { CollectionListUrlSortField } from "@saleor/collections/urls"; import { CollectionList_collections_edges_node } from "../../types/CollectionList"; import CollectionList from "../CollectionList/CollectionList"; @@ -20,6 +22,7 @@ export interface CollectionListPageProps extends PageListProps, ListActions, SearchPageProps, + SortPage, TabPageProps { collections: CollectionList_collections_edges_node[]; } diff --git a/src/collections/index.tsx b/src/collections/index.tsx index 1c799eab4..4c76b7bd8 100644 --- a/src/collections/index.tsx +++ b/src/collections/index.tsx @@ -4,13 +4,15 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { collectionAddPath, collectionListPath, CollectionListUrlQueryParams, collectionPath, - CollectionUrlQueryParams + CollectionUrlQueryParams, + CollectionListUrlSortField } from "./urls"; import CollectionCreate from "./views/CollectionCreate"; import CollectionDetailsView from "./views/CollectionDetails"; @@ -18,16 +20,19 @@ import CollectionListView from "./views/CollectionList"; const CollectionList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: CollectionListUrlQueryParams = qs; + const params: CollectionListUrlQueryParams = asSortParams( + qs, + CollectionListUrlSortField + ); return ; }; interface CollectionDetailsRouteProps { id: string; } -const CollectionDetails: React.FC< - RouteComponentProps -> = ({ location, match }) => { +const CollectionDetails: React.FC> = ({ location, match }) => { const qs = parseQs(location.search.substr(1)); const params: CollectionUrlQueryParams = qs; return ( diff --git a/src/collections/queries.ts b/src/collections/queries.ts index bc2a67d1a..b8230d0f9 100644 --- a/src/collections/queries.ts +++ b/src/collections/queries.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { TypedQuery } from "../queries"; import { CollectionDetails, @@ -61,6 +62,7 @@ export const collectionList = gql` $last: Int $before: String $filter: CollectionFilterInput + $sort: CollectionSortingInput ) { collections( first: $first @@ -68,6 +70,7 @@ export const collectionList = gql` before: $before last: $last filter: $filter + sortBy: $sort ) { edges { node { @@ -86,7 +89,7 @@ export const collectionList = gql` } } `; -export const TypedCollectionListQuery = TypedQuery< +export const useCollectionListQuery = makeQuery< CollectionList, CollectionListVariables >(collectionList); diff --git a/src/collections/types/CollectionList.ts b/src/collections/types/CollectionList.ts index 93c2af1c7..770a27347 100644 --- a/src/collections/types/CollectionList.ts +++ b/src/collections/types/CollectionList.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { CollectionFilterInput } from "./../../types/globalTypes"; +import { CollectionFilterInput, CollectionSortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: CollectionList @@ -50,4 +50,5 @@ export interface CollectionListVariables { last?: number | null; before?: string | null; filter?: CollectionFilterInput | null; + sort?: CollectionSortingInput | null; } diff --git a/src/collections/urls.ts b/src/collections/urls.ts index e17cfca21..908fc998a 100644 --- a/src/collections/urls.ts +++ b/src/collections/urls.ts @@ -7,7 +7,8 @@ import { Dialog, Filters, Pagination, - TabActionDialog + TabActionDialog, + Sort } from "../types"; const collectionSectionUrl = "/collections/"; @@ -22,9 +23,16 @@ export type CollectionListUrlDialog = | "unpublish" | "remove" | TabActionDialog; +export enum CollectionListUrlSortField { + name = "name", + available = "available", + productCount = "products" +} +export type CollectionListUrlSort = Sort; export type CollectionListUrlQueryParams = ActiveTab & BulkAction & CollectionListUrlFilters & + CollectionListUrlSort & Dialog & Pagination; export const collectionListUrl = (params?: CollectionListUrlQueryParams) => diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 8423d1041..346ce0bbe 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -20,12 +20,14 @@ import usePaginator, { import { commonMessages } from "@saleor/intl"; import { maybe } from "@saleor/misc"; import { ListViews } from "@saleor/types"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; import CollectionListPage from "../../components/CollectionListPage/CollectionListPage"; import { TypedCollectionBulkDelete, TypedCollectionBulkPublish } from "../../mutations"; -import { TypedCollectionListQuery } from "../../queries"; +import { useCollectionListQuery } from "../../queries"; import { CollectionBulkDelete } from "../../types/CollectionBulkDelete"; import { CollectionBulkPublish } from "../../types/CollectionBulkPublish"; import { @@ -44,6 +46,7 @@ import { getFilterVariables, saveFilterTab } from "./filter"; +import { getSortQueryVariables } from "./sort"; interface CollectionListProps { params: CollectionListUrlQueryParams; @@ -61,6 +64,20 @@ export const CollectionList: React.FC = ({ params }) => { ); const intl = useIntl(); + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading, refetch } = useCollectionListQuery({ + displayLoader: true, + variables: queryVariables + }); + const tabs = getFilterTabs(); const currentTab = @@ -121,226 +138,213 @@ export const CollectionList: React.FC = ({ params }) => { handleTabChange(tabs.length + 1); }; - const paginationState = createPaginationState(settings.rowNumber, params); - const queryVariables = React.useMemo( - () => ({ - ...paginationState, - filter: getFilterVariables(params) - }), - [params] + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.collections.pageInfo), + paginationState, + params ); + const handleCollectionBulkDelete = (data: CollectionBulkDelete) => { + if (data.collectionBulkDelete.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + refetch(); + reset(); + closeModal(); + } + }; + + const handleCollectionBulkPublish = (data: CollectionBulkPublish) => { + if (data.collectionBulkPublish.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + refetch(); + reset(); + closeModal(); + } + }; + + const handleSort = createSortHandler(navigate, collectionListUrl, params); + return ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.collections.pageInfo), - paginationState, - params - ); - - const handleCollectionBulkDelete = (data: CollectionBulkDelete) => { - if (data.collectionBulkDelete.errors.length === 0) { - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - refetch(); - reset(); - closeModal(); - } - }; - - const handleCollectionBulkPublish = (data: CollectionBulkPublish) => { - if (data.collectionBulkPublish.errors.length === 0) { - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - refetch(); - reset(); - closeModal(); - } - }; - - return ( - - {(collectionBulkDelete, collectionBulkDeleteOpts) => ( - - {(collectionBulkPublish, collectionBulkPublishOpts) => ( - <> - 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) - )} - settings={settings} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - pageInfo={pageInfo} - onRowClick={id => () => navigate(collectionUrl(id))} - toolbar={ - <> - - - openModal("remove", listElements)} - > - - - - } - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - /> - params.ids.length > 0) - } - onClose={closeModal} - confirmButtonState={collectionBulkPublishOpts.status} - onConfirm={() => - collectionBulkPublish({ - variables: { - ids: params.ids, - isPublished: true - } - }) - } - variant="default" - title={intl.formatMessage({ - defaultMessage: "Publish collections", - description: "dialog title" - })} - > - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - params.ids.length > 0) - } - onClose={closeModal} - confirmButtonState={collectionBulkPublishOpts.status} - onConfirm={() => - collectionBulkPublish({ - variables: { - ids: params.ids, - isPublished: false - } - }) - } - variant="default" - title={intl.formatMessage({ - defaultMessage: "Unpublish collections", - description: "dialog title" - })} - > - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - params.ids.length > 0) - } - onClose={closeModal} - confirmButtonState={collectionBulkDeleteOpts.status} - onConfirm={() => - collectionBulkDelete({ - variables: { - ids: params.ids - } - }) - } - variant="delete" - title={intl.formatMessage({ - defaultMessage: "Delete collections", - description: "dialog title" - })} - > - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - - tabs[currentTab - 1].name, "...")} - /> - + + {(collectionBulkDelete, collectionBulkDeleteOpts) => ( + + {(collectionBulkPublish, collectionBulkPublishOpts) => ( + <> + 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) )} - - )} - - ); - }} - + settings={settings} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onSort={handleSort} + onUpdateListSettings={updateListSettings} + pageInfo={pageInfo} + sort={getSortParams(params)} + onRowClick={id => () => navigate(collectionUrl(id))} + toolbar={ + <> + + + openModal("remove", listElements)} + > + + + + } + isChecked={isSelected} + selected={listElements.length} + toggle={toggle} + toggleAll={toggleAll} + /> + params.ids.length > 0) + } + onClose={closeModal} + confirmButtonState={collectionBulkPublishOpts.status} + onConfirm={() => + collectionBulkPublish({ + variables: { + ids: params.ids, + isPublished: true + } + }) + } + variant="default" + title={intl.formatMessage({ + defaultMessage: "Publish collections", + description: "dialog title" + })} + > + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + params.ids.length > 0) + } + onClose={closeModal} + confirmButtonState={collectionBulkPublishOpts.status} + onConfirm={() => + collectionBulkPublish({ + variables: { + ids: params.ids, + isPublished: false + } + }) + } + variant="default" + title={intl.formatMessage({ + defaultMessage: "Unpublish collections", + description: "dialog title" + })} + > + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + params.ids.length > 0) + } + onClose={closeModal} + confirmButtonState={collectionBulkDeleteOpts.status} + onConfirm={() => + collectionBulkDelete({ + variables: { + ids: params.ids + } + }) + } + variant="delete" + title={intl.formatMessage({ + defaultMessage: "Delete collections", + description: "dialog title" + })} + > + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + + tabs[currentTab - 1].name, "...")} + /> + + )} + + )} + ); }; export default CollectionList; diff --git a/src/collections/views/CollectionList/sort.ts b/src/collections/views/CollectionList/sort.ts new file mode 100644 index 000000000..34cf3ed2c --- /dev/null +++ b/src/collections/views/CollectionList/sort.ts @@ -0,0 +1,22 @@ +import { CollectionListUrlSortField } from "@saleor/collections/urls"; +import { CollectionSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: CollectionListUrlSortField +): CollectionSortField { + switch (sort) { + case CollectionListUrlSortField.name: + return CollectionSortField.NAME; + case CollectionListUrlSortField.available: + return CollectionSortField.AVAILABILITY; + case CollectionListUrlSortField.productCount: + return CollectionSortField.PRODUCT_COUNT; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/components/Navigator/modes/default/views.ts b/src/components/Navigator/modes/default/views.ts index 639f1852e..afcdad69a 100644 --- a/src/components/Navigator/modes/default/views.ts +++ b/src/components/Navigator/modes/default/views.ts @@ -11,7 +11,7 @@ import { sectionNames } from "@saleor/intl"; import { menuListUrl } from "@saleor/navigation/urls"; import { orderDraftListUrl, orderListUrl } from "@saleor/orders/urls"; import { pageListUrl } from "@saleor/pages/urls"; -import { pluginsListUrl } from "@saleor/plugins/urls"; +import { pluginListUrl } from "@saleor/plugins/urls"; import { productListUrl } from "@saleor/products/urls"; import { productTypeListUrl } from "@saleor/productTypes/urls"; import { serviceListUrl } from "@saleor/services/urls"; @@ -71,7 +71,7 @@ function searchInViews( }, { label: intl.formatMessage(sectionNames.plugins), - url: pluginsListUrl() + url: pluginListUrl() }, { label: intl.formatMessage(sectionNames.productTypes), diff --git a/src/components/TableCellHeader/TableCellHeader.tsx b/src/components/TableCellHeader/TableCellHeader.tsx index f6242eb5f..609bb49b1 100644 --- a/src/components/TableCellHeader/TableCellHeader.tsx +++ b/src/components/TableCellHeader/TableCellHeader.tsx @@ -16,6 +16,11 @@ const useStyles = makeStyles( arrowUp: { transform: "rotate(180deg)" }, + disableClick: { + "&&": { + cursor: "unset" + } + }, label: { alignSelf: "center", display: "inline-block" @@ -48,6 +53,7 @@ export type TableCellHeaderArrowPosition = "left" | "right"; export interface TableCellHeaderProps extends TableCellProps { arrowPosition?: TableCellHeaderArrowPosition; direction?: TableCellHeaderArrowDirection; + disableClick?: boolean; textAlign?: "left" | "center" | "right"; } @@ -58,12 +64,18 @@ const TableCellHeader: React.FC = props => { children, className, direction, + disableClick, textAlign, ...rest } = props; return ( - +
{ customers: ListCustomers_customers_edges_node[]; } @@ -54,10 +60,12 @@ const CustomerList: React.FC = props => { onPreviousPage, onUpdateListSettings, onRowClick, + onSort, toolbar, toggle, toggleAll, selected, + sort, isChecked } = props; @@ -73,15 +81,41 @@ const CustomerList: React.FC = props => { toggleAll={toggleAll} toolbar={toolbar} > - + onSort(CustomerListUrlSortField.name)} + className={classes.colName} + > - - + + onSort(CustomerListUrlSortField.email)} + className={classes.colEmail} + > - - + + onSort(CustomerListUrlSortField.orders)} + className={classes.colOrders} + > - + diff --git a/src/customers/components/CustomerListPage/CustomerListPage.tsx b/src/customers/components/CustomerListPage/CustomerListPage.tsx index 5dd5715f7..772fa7e33 100644 --- a/src/customers/components/CustomerListPage/CustomerListPage.tsx +++ b/src/customers/components/CustomerListPage/CustomerListPage.tsx @@ -11,8 +11,10 @@ import { ListActions, PageListProps, SearchPageProps, - TabPageProps + TabPageProps, + SortPage } from "@saleor/types"; +import { CustomerListUrlSortField } from "@saleor/customers/urls"; import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; import CustomerList from "../CustomerList/CustomerList"; @@ -20,14 +22,13 @@ export interface CustomerListPageProps extends PageListProps, ListActions, SearchPageProps, + SortPage, TabPageProps { customers: ListCustomers_customers_edges_node[]; } const CustomerListPage: React.FC = ({ currentTab, - customers, - disabled, initialSearch, onAdd, onAll, @@ -43,12 +44,7 @@ const CustomerListPage: React.FC = ({ return ( - - + ); }; diff --git a/src/navigation/index.tsx b/src/navigation/index.tsx index 70e3663fc..39d1559bb 100644 --- a/src/navigation/index.tsx +++ b/src/navigation/index.tsx @@ -2,13 +2,21 @@ import { parse as parseQs } from "qs"; import React from "react"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; -import { menuListPath, menuPath } from "./urls"; +import { asSortParams } from "@saleor/utils/sort"; +import { + menuListPath, + menuPath, + MenuListUrlQueryParams, + MenuListUrlSortField +} from "./urls"; import MenuDetailsComponent from "./views/MenuDetails"; import MenuListComponent from "./views/MenuList"; const MenuList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - return ; + const params: MenuListUrlQueryParams = asSortParams(qs, MenuListUrlSortField); + + return ; }; const MenuDetails: React.FC> = ({ @@ -16,6 +24,7 @@ const MenuDetails: React.FC> = ({ match }) => { const qs = parseQs(location.search.substr(1)); + return ( (menuList); +export const useMenuListQuery = makeQuery( + menuList +); const menuDetails = gql` ${menuDetailsFragment} diff --git a/src/navigation/types/MenuList.ts b/src/navigation/types/MenuList.ts index 8e95e1110..f9adf04ac 100644 --- a/src/navigation/types/MenuList.ts +++ b/src/navigation/types/MenuList.ts @@ -2,6 +2,8 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. +import { MenuSortingInput } from "./../../types/globalTypes"; + // ==================================================== // GraphQL query operation: MenuList // ==================================================== @@ -46,4 +48,5 @@ export interface MenuListVariables { after?: string | null; last?: number | null; before?: string | null; + sort?: MenuSortingInput | null; } diff --git a/src/navigation/urls.ts b/src/navigation/urls.ts index 0d70cf491..53bc69f17 100644 --- a/src/navigation/urls.ts +++ b/src/navigation/urls.ts @@ -1,14 +1,20 @@ import { stringify as stringifyQs } from "qs"; import urlJoin from "url-join"; -import { BulkAction, Dialog, Pagination, SingleAction } from "../types"; +import { BulkAction, Dialog, Pagination, SingleAction, Sort } from "../types"; export const navigationSection = "/navigation"; export const menuListPath = navigationSection; export type MenuListUrlDialog = "add" | "remove" | "remove-many"; +export enum MenuListUrlSortField { + name = "name", + items = "items" +} +export type MenuListUrlSort = Sort; export type MenuListUrlQueryParams = BulkAction & Dialog & + MenuListUrlSort & Pagination & SingleAction; export const menuListUrl = (params?: MenuListUrlQueryParams) => diff --git a/src/navigation/views/MenuList.tsx b/src/navigation/views/MenuList.tsx deleted file mode 100644 index c320bdbbf..000000000 --- a/src/navigation/views/MenuList.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import Button from "@material-ui/core/Button"; -import DialogContentText from "@material-ui/core/DialogContentText"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -import ActionDialog from "@saleor/components/ActionDialog"; -import { configurationMenuUrl } from "@saleor/configuration"; -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 { buttonMessages, commonMessages } from "@saleor/intl"; -import { maybe } from "@saleor/misc"; -import { ListViews } from "@saleor/types"; -import MenuCreateDialog from "../components/MenuCreateDialog"; -import MenuListPage from "../components/MenuListPage"; -import { - MenuBulkDeleteMutation, - MenuCreateMutation, - MenuDeleteMutation -} from "../mutations"; -import { MenuListQuery } from "../queries"; -import { MenuBulkDelete } from "../types/MenuBulkDelete"; -import { MenuCreate } from "../types/MenuCreate"; -import { MenuDelete } from "../types/MenuDelete"; -import { menuListUrl, MenuListUrlQueryParams, menuUrl } from "../urls"; - -interface MenuListProps { - params: MenuListUrlQueryParams; -} -const MenuList: React.FC = ({ params }) => { - const navigate = useNavigator(); - const notify = useNotifier(); - const paginate = usePaginator(); - const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( - params.ids - ); - const { updateListSettings, settings } = useListSettings( - ListViews.NAVIGATION_LIST - ); - const intl = useIntl(); - - const closeModal = () => - navigate( - menuListUrl({ - ...params, - action: undefined, - id: undefined, - ids: undefined - }), - true - ); - - const paginationState = createPaginationState(settings.rowNumber, params); - - return ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.menus.pageInfo), - paginationState, - params - ); - - const handleCreate = (data: MenuCreate) => { - if (data.menuCreate.errors.length === 0) { - notify({ - text: intl.formatMessage({ - defaultMessage: "Created menu", - id: "menuListCreatedMenu" - }) - }); - navigate(menuUrl(data.menuCreate.menu.id)); - } - }; - - const handleBulkDelete = (data: MenuBulkDelete) => { - if (data.menuBulkDelete.errors.length === 0) { - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - closeModal(); - reset(); - refetch(); - } - }; - - const handleDelete = (data: MenuDelete) => { - if (data.menuDelete.errors.length === 0) { - notify({ - text: intl.formatMessage({ - defaultMessage: "Deleted menu", - id: "menuListDeletedMenu" - }) - }); - closeModal(); - refetch(); - } - }; - - return ( - - {(menuCreate, menuCreateOpts) => ( - - {(menuDelete, menuDeleteOpts) => ( - - {(menuBulkDelete, menuBulkDeleteOpts) => ( - <> - - data.menus.edges.map(edge => edge.node) - )} - settings={settings} - onAdd={() => - navigate( - menuListUrl({ - action: "add" - }) - ) - } - onBack={() => navigate(configurationMenuUrl)} - onDelete={id => - navigate( - menuListUrl({ - action: "remove", - id - }) - ) - } - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - onRowClick={id => () => navigate(menuUrl(id))} - pageInfo={pageInfo} - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - toolbar={ - - } - /> - - menuCreate({ - variables: { input: formData } - }) - } - /> - - menuDelete({ - variables: { - id: params.id - } - }) - } - variant="delete" - title={intl.formatMessage({ - defaultMessage: "Delete Menu", - description: "dialog header", - id: "menuListDeleteMenuHeader" - })} - > - - - data.menus.edges.find( - edge => edge.node.id === params.id - ).node.name, - "..." - ) - }} - /> - - - params.ids.length > 0) - } - onClose={closeModal} - confirmButtonState={menuBulkDeleteOpts.status} - onConfirm={() => - menuBulkDelete({ - variables: { - ids: params.ids - } - }) - } - variant="delete" - title={intl.formatMessage({ - defaultMessage: "Delete Menus", - description: "dialog header", - id: "menuListDeleteMenusHeader" - })} - > - - params.ids.length.toString(), - "..." - ), - displayQuantity: ( - - {maybe( - () => params.ids.length.toString(), - "..." - )} - - ) - }} - /> - - - - )} - - )} - - )} - - ); - }} - - ); -}; -export default MenuList; diff --git a/src/navigation/views/MenuList/MenuList.tsx b/src/navigation/views/MenuList/MenuList.tsx new file mode 100644 index 000000000..55d88c2f5 --- /dev/null +++ b/src/navigation/views/MenuList/MenuList.tsx @@ -0,0 +1,266 @@ +import Button from "@material-ui/core/Button"; +import DialogContentText from "@material-ui/core/DialogContentText"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import ActionDialog from "@saleor/components/ActionDialog"; +import { configurationMenuUrl } from "@saleor/configuration"; +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 { buttonMessages, commonMessages } from "@saleor/intl"; +import { maybe } from "@saleor/misc"; +import { ListViews } from "@saleor/types"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import MenuCreateDialog from "../../components/MenuCreateDialog"; +import MenuListPage from "../../components/MenuListPage"; +import { + MenuBulkDeleteMutation, + MenuCreateMutation, + MenuDeleteMutation +} from "../../mutations"; +import { useMenuListQuery } from "../../queries"; +import { MenuBulkDelete } from "../../types/MenuBulkDelete"; +import { MenuCreate } from "../../types/MenuCreate"; +import { MenuDelete } from "../../types/MenuDelete"; +import { menuListUrl, MenuListUrlQueryParams, menuUrl } from "../../urls"; +import { getSortQueryVariables } from "./sort"; + +interface MenuListProps { + params: MenuListUrlQueryParams; +} +const MenuList: React.FC = ({ params }) => { + const navigate = useNavigator(); + const notify = useNotifier(); + const paginate = usePaginator(); + const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( + params.ids + ); + const { updateListSettings, settings } = useListSettings( + ListViews.NAVIGATION_LIST + ); + const intl = useIntl(); + + const closeModal = () => + navigate( + menuListUrl({ + ...params, + action: undefined, + id: undefined, + ids: undefined + }), + true + ); + + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading, refetch } = useMenuListQuery({ + displayLoader: true, + variables: queryVariables + }); + + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.menus.pageInfo), + paginationState, + params + ); + + const handleCreate = (data: MenuCreate) => { + if (data.menuCreate.errors.length === 0) { + notify({ + text: intl.formatMessage({ + defaultMessage: "Created menu", + id: "menuListCreatedMenu" + }) + }); + navigate(menuUrl(data.menuCreate.menu.id)); + } + }; + + const handleBulkDelete = (data: MenuBulkDelete) => { + if (data.menuBulkDelete.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + closeModal(); + reset(); + refetch(); + } + }; + + const handleDelete = (data: MenuDelete) => { + if (data.menuDelete.errors.length === 0) { + notify({ + text: intl.formatMessage({ + defaultMessage: "Deleted menu", + id: "menuListDeletedMenu" + }) + }); + closeModal(); + refetch(); + } + }; + + const handleSort = createSortHandler(navigate, menuListUrl, params); + + return ( + + {(menuCreate, menuCreateOpts) => ( + + {(menuDelete, menuDeleteOpts) => ( + + {(menuBulkDelete, menuBulkDeleteOpts) => ( + <> + data.menus.edges.map(edge => edge.node))} + settings={settings} + onAdd={() => + navigate( + menuListUrl({ + action: "add" + }) + ) + } + onBack={() => navigate(configurationMenuUrl)} + onDelete={id => + navigate( + menuListUrl({ + action: "remove", + id + }) + ) + } + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(menuUrl(id))} + onSort={handleSort} + pageInfo={pageInfo} + isChecked={isSelected} + selected={listElements.length} + sort={getSortParams(params)} + toggle={toggle} + toggleAll={toggleAll} + toolbar={ + + } + /> + + menuCreate({ + variables: { input: formData } + }) + } + /> + + menuDelete({ + variables: { + id: params.id + } + }) + } + variant="delete" + title={intl.formatMessage({ + defaultMessage: "Delete Menu", + description: "dialog header", + id: "menuListDeleteMenuHeader" + })} + > + + + data.menus.edges.find( + edge => edge.node.id === params.id + ).node.name, + "..." + ) + }} + /> + + + params.ids.length > 0) + } + onClose={closeModal} + confirmButtonState={menuBulkDeleteOpts.status} + onConfirm={() => + menuBulkDelete({ + variables: { + ids: params.ids + } + }) + } + variant="delete" + title={intl.formatMessage({ + defaultMessage: "Delete Menus", + description: "dialog header", + id: "menuListDeleteMenusHeader" + })} + > + + params.ids.length.toString(), + "..." + ), + displayQuantity: ( + + {maybe(() => params.ids.length.toString(), "...")} + + ) + }} + /> + + + + )} + + )} + + )} + + ); +}; +export default MenuList; diff --git a/src/navigation/views/MenuList/index.ts b/src/navigation/views/MenuList/index.ts new file mode 100644 index 000000000..aea30ef3a --- /dev/null +++ b/src/navigation/views/MenuList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./MenuList"; +export * from "./MenuList"; diff --git a/src/navigation/views/MenuList/sort.ts b/src/navigation/views/MenuList/sort.ts new file mode 100644 index 000000000..4a9f3260a --- /dev/null +++ b/src/navigation/views/MenuList/sort.ts @@ -0,0 +1,18 @@ +import { MenuListUrlSortField } from "@saleor/navigation/urls"; +import { MenuSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField(sort: MenuListUrlSortField): MenuSortField { + switch (sort) { + case MenuListUrlSortField.name: + return MenuSortField.NAME; + case MenuListUrlSortField.items: + return MenuSortField.ITEMS_COUNT; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/orders/components/OrderDraftList/OrderDraftList.tsx b/src/orders/components/OrderDraftList/OrderDraftList.tsx index 3e50d964d..1b572d3a6 100644 --- a/src/orders/components/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/components/OrderDraftList/OrderDraftList.tsx @@ -19,7 +19,10 @@ import { transformOrderStatus, transformPaymentStatus } from "@saleor/misc"; -import { ListActions, ListProps } from "@saleor/types"; +import { ListActions, ListProps, SortPage } from "@saleor/types"; +import { OrderDraftListUrlSortField } from "@saleor/orders/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList"; const useStyles = makeStyles( @@ -32,7 +35,7 @@ const useStyles = makeStyles( width: 300 }, colNumber: { - width: 120 + width: 160 }, colTotal: {} }, @@ -51,7 +54,10 @@ const useStyles = makeStyles( { name: "OrderDraftList" } ); -interface OrderDraftListProps extends ListProps, ListActions { +interface OrderDraftListProps + extends ListProps, + ListActions, + SortPage { orders: OrderDraftList_draftOrders_edges_node[]; } @@ -67,8 +73,10 @@ export const OrderDraftList: React.FC = props => { onNextPage, onUpdateListSettings, onRowClick, + onSort, isChecked, selected, + sort, toggle, toggleAll, toolbar @@ -95,24 +103,58 @@ export const OrderDraftList: React.FC = props => { toggleAll={toggleAll} toolbar={toolbar} > - + onSort(OrderDraftListUrlSortField.number)} + className={classes.colNumber} + > - - + + onSort(OrderDraftListUrlSortField.date)} + className={classes.colDate} + > - - + + onSort(OrderDraftListUrlSortField.customer)} + className={classes.colCustomer} + > - - + + onSort(OrderDraftListUrlSortField.total)} + className={classes.colTotal} + > - + diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index 85c9719ce..c42bbc0a8 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -12,8 +12,10 @@ import { ListActions, PageListProps, SearchPageProps, - TabPageProps + TabPageProps, + SortPage } from "@saleor/types"; +import { OrderDraftListUrlSortField } from "@saleor/orders/urls"; import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList"; import OrderDraftList from "../OrderDraftList"; @@ -21,6 +23,7 @@ export interface OrderDraftListPageProps extends PageListProps, ListActions, SearchPageProps, + SortPage, TabPageProps { orders: OrderDraftList_draftOrders_edges_node[]; } diff --git a/src/orders/components/OrderList/OrderList.tsx b/src/orders/components/OrderList/OrderList.tsx index 4f978352d..45a9f7cc9 100644 --- a/src/orders/components/OrderList/OrderList.tsx +++ b/src/orders/components/OrderList/OrderList.tsx @@ -20,7 +20,10 @@ import { transformOrderStatus, transformPaymentStatus } from "@saleor/misc"; -import { ListActions, ListProps } from "@saleor/types"; +import { ListActions, ListProps, SortPage } from "@saleor/types"; +import { OrderListUrlSortField } from "@saleor/orders/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; import { OrderList_orders_edges_node } from "../../types/OrderList"; const useStyles = makeStyles( @@ -56,7 +59,10 @@ const useStyles = makeStyles( { name: "OrderList" } ); -interface OrderListProps extends ListProps, ListActions { +interface OrderListProps + extends ListProps, + ListActions, + SortPage { orders: OrderList_orders_edges_node[]; } @@ -72,8 +78,10 @@ export const OrderList: React.FC = props => { onNextPage, onUpdateListSettings, onRowClick, + onSort, isChecked, selected, + sort, toggle, toggleAll, toolbar @@ -99,36 +107,86 @@ export const OrderList: React.FC = props => { toggleAll={toggleAll} toolbar={toolbar} > - + onSort(OrderListUrlSortField.number)} + className={classes.colNumber} + > - - + + onSort(OrderListUrlSortField.date)} + className={classes.colDate} + > - - + + onSort(OrderListUrlSortField.customer)} + className={classes.colCustomer} + > - - + + onSort(OrderListUrlSortField.payment)} + className={classes.colPayment} + > - - + + onSort(OrderListUrlSortField.fulfillment)} + className={classes.colFulfillment} + > - - + + onSort(OrderListUrlSortField.total)} + className={classes.colTotal} + > - + diff --git a/src/orders/components/OrderListPage/OrderListPage.tsx b/src/orders/components/OrderListPage/OrderListPage.tsx index 0e89cabd2..7a72aedbb 100644 --- a/src/orders/components/OrderListPage/OrderListPage.tsx +++ b/src/orders/components/OrderListPage/OrderListPage.tsx @@ -7,7 +7,13 @@ import { FormattedMessage, useIntl } from "react-intl"; import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import { sectionNames } from "@saleor/intl"; -import { FilterPageProps, ListActions, PageListProps } from "@saleor/types"; +import { + FilterPageProps, + ListActions, + PageListProps, + SortPage +} from "@saleor/types"; +import { OrderListUrlSortField } from "@saleor/orders/urls"; import { OrderList_orders_edges_node } from "../../types/OrderList"; import OrderList from "../OrderList"; import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter"; @@ -15,7 +21,8 @@ import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter"; export interface OrderListPageProps extends PageListProps, ListActions, - FilterPageProps { + FilterPageProps, + SortPage { orders: OrderList_orders_edges_node[]; } diff --git a/src/orders/index.tsx b/src/orders/index.tsx index b78d1fd82..608cba793 100644 --- a/src/orders/index.tsx +++ b/src/orders/index.tsx @@ -4,6 +4,7 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { orderDraftListPath, @@ -11,7 +12,9 @@ import { orderListPath, OrderListUrlQueryParams, orderPath, - OrderUrlQueryParams + OrderUrlQueryParams, + OrderDraftListUrlSortField, + OrderListUrlSortField } from "./urls"; import OrderDetailsComponent from "./views/OrderDetails"; import OrderDraftListComponent from "./views/OrderDraftList"; @@ -19,12 +22,23 @@ import OrderListComponent from "./views/OrderList"; const OrderList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: OrderListUrlQueryParams = qs; + const params: OrderListUrlQueryParams = asSortParams( + qs, + OrderListUrlSortField, + OrderListUrlSortField.number, + false + ); return ; }; const OrderDraftList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: OrderDraftListUrlQueryParams = qs; + const params: OrderDraftListUrlQueryParams = asSortParams( + qs, + OrderDraftListUrlSortField, + OrderDraftListUrlSortField.number, + false + ); + return ; }; diff --git a/src/orders/queries.ts b/src/orders/queries.ts index a18c65894..b7245939f 100644 --- a/src/orders/queries.ts +++ b/src/orders/queries.ts @@ -1,6 +1,7 @@ import gql from "graphql-tag"; import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch"; +import makeQuery from "@saleor/hooks/makeQuery"; import { TypedQuery } from "../queries"; import { OrderDetails, OrderDetailsVariables } from "./types/OrderDetails"; import { @@ -169,6 +170,7 @@ export const orderListQuery = gql` $last: Int $before: String $filter: OrderFilterInput + $sort: OrderSortingInput ) { orders( before: $before @@ -176,6 +178,7 @@ export const orderListQuery = gql` first: $first last: $last filter: $filter + sortBy: $sort ) { edges { node { @@ -208,7 +211,7 @@ export const orderListQuery = gql` } } `; -export const TypedOrderListQuery = TypedQuery( +export const useOrderListQuery = makeQuery( orderListQuery ); @@ -220,6 +223,7 @@ export const orderDraftListQuery = gql` $last: Int $before: String $filter: OrderDraftFilterInput + $sort: OrderSortingInput ) { draftOrders( before: $before @@ -227,6 +231,7 @@ export const orderDraftListQuery = gql` first: $first last: $last filter: $filter + sortBy: $sort ) { edges { node { @@ -259,7 +264,7 @@ export const orderDraftListQuery = gql` } } `; -export const TypedOrderDraftListQuery = TypedQuery< +export const useOrderDraftListQuery = makeQuery< OrderDraftList, OrderDraftListVariables >(orderDraftListQuery); diff --git a/src/orders/types/OrderDraftList.ts b/src/orders/types/OrderDraftList.ts index ee2f0ff82..d8ddd8ab5 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 { OrderDraftFilterInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes"; +import { OrderDraftFilterInput, OrderSortingInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: OrderDraftList @@ -82,4 +82,5 @@ export interface OrderDraftListVariables { last?: number | null; before?: string | null; filter?: OrderDraftFilterInput | null; + sort?: OrderSortingInput | null; } diff --git a/src/orders/types/OrderList.ts b/src/orders/types/OrderList.ts index 95c4e4a4f..e1fcdb607 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 { OrderFilterInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes"; +import { OrderFilterInput, OrderSortingInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: OrderList @@ -82,4 +82,5 @@ export interface OrderListVariables { last?: number | null; before?: string | null; filter?: OrderFilterInput | null; + sort?: OrderSortingInput | null; } diff --git a/src/orders/urls.ts b/src/orders/urls.ts index ce0ce9a0e..ffeed351c 100644 --- a/src/orders/urls.ts +++ b/src/orders/urls.ts @@ -9,7 +9,8 @@ import { FiltersWithMultipleValues, Pagination, SingleAction, - TabActionDialog + TabActionDialog, + Sort } from "../types"; const orderSectionUrl = "/orders"; @@ -28,9 +29,19 @@ export enum OrderListUrlFiltersWithMultipleValuesEnum { export type OrderListUrlFilters = Filters & FiltersWithMultipleValues; export type OrderListUrlDialog = "cancel" | TabActionDialog; +export enum OrderListUrlSortField { + number = "number", + customer = "customer", + date = "date", + fulfillment = "status", + payment = "payment", + total = "total" +} +export type OrderListUrlSort = Sort; export type OrderListUrlQueryParams = BulkAction & Dialog & OrderListUrlFilters & + OrderListUrlSort & Pagination & ActiveTab; export const orderListUrl = (params?: OrderListUrlQueryParams): string => { @@ -48,10 +59,18 @@ export enum OrderDraftListUrlFiltersEnum { } export type OrderDraftListUrlFilters = Filters; export type OrderDraftListUrlDialog = "remove" | TabActionDialog; +export enum OrderDraftListUrlSortField { + number = "number", + customer = "customer", + date = "date", + total = "total" +} +export type OrderDraftListUrlSort = Sort; export type OrderDraftListUrlQueryParams = ActiveTab & BulkAction & Dialog & OrderDraftListUrlFilters & + OrderDraftListUrlSort & Pagination; export const orderDraftListUrl = ( params?: OrderDraftListUrlQueryParams diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 319ced5b6..bfa2031ca 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -18,12 +18,14 @@ import usePaginator, { } from "@saleor/hooks/usePaginator"; import { maybe } from "@saleor/misc"; import { ListViews } from "@saleor/types"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import { getSortParams } from "@saleor/utils/sort"; import OrderDraftListPage from "../../components/OrderDraftListPage"; import { TypedOrderDraftBulkCancelMutation, useOrderDraftCreateMutation } from "../../mutations"; -import { TypedOrderDraftListQuery } from "../../queries"; +import { useOrderDraftListQuery } from "../../queries"; import { OrderDraftBulkCancel } from "../../types/OrderDraftBulkCancel"; import { OrderDraftCreate } from "../../types/OrderDraftCreate"; import { @@ -41,6 +43,7 @@ import { getFilterVariables, saveFilterTab } from "./filter"; +import { getSortQueryVariables } from "./sort"; interface OrderDraftListProps { params: OrderDraftListUrlQueryParams; @@ -135,131 +138,132 @@ export const OrderDraftList: React.FC = ({ params }) => { const queryVariables = React.useMemo( () => ({ ...paginationState, - filter: getFilterVariables(params) + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) }), [params] ); + const { data, loading, refetch } = useOrderDraftListQuery({ + displayLoader: true, + variables: queryVariables + }); + + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.draftOrders.pageInfo), + paginationState, + params + ); + + const handleOrderDraftBulkCancel = (data: OrderDraftBulkCancel) => { + if (data.draftOrderBulkDelete.errors.length === 0) { + notify({ + text: intl.formatMessage({ + defaultMessage: "Deleted draft orders" + }) + }); + refetch(); + reset(); + closeModal(); + } + }; + + const handleSort = createSortHandler(navigate, orderDraftListUrl, params); return ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.draftOrders.pageInfo), - paginationState, - params - ); - - const handleOrderDraftBulkCancel = (data: OrderDraftBulkCancel) => { - if (data.draftOrderBulkDelete.errors.length === 0) { - notify({ - text: intl.formatMessage({ - defaultMessage: "Deleted draft orders" - }) - }); - refetch(); - reset(); - closeModal(); - } - }; + + {(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => { + const onOrderDraftBulkDelete = () => + orderDraftBulkDelete({ + variables: { + ids: params.ids + } + }); return ( - - {(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => { - const onOrderDraftBulkDelete = () => - orderDraftBulkDelete({ - variables: { - ids: params.ids + <> + 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(() => + data.draftOrders.edges.map(edge => edge.node) + )} + pageInfo={pageInfo} + onAdd={createOrder} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onRowClick={id => () => navigate(orderUrl(id))} + onSort={handleSort} + onUpdateListSettings={updateListSettings} + isChecked={isSelected} + selected={listElements.length} + sort={getSortParams(params)} + toggle={toggle} + toggleAll={toggleAll} + toolbar={ + + navigate( + orderDraftListUrl({ + action: "remove", + ids: listElements + }) + ) } - }); - - 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(() => - data.draftOrders.edges.map(edge => edge.node) - )} - pageInfo={pageInfo} - onAdd={createOrder} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - onRowClick={id => () => navigate(orderUrl(id))} - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - toolbar={ - - navigate( - orderDraftListUrl({ - action: "remove", - ids: listElements - }) - ) - } - > - - - } - /> - - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - - tabs[currentTab - 1].name, "...")} - /> - - ); - }} - + > + + + } + /> + + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + + tabs[currentTab - 1].name, "...")} + /> + ); }} - + ); }; diff --git a/src/orders/views/OrderDraftList/sort.ts b/src/orders/views/OrderDraftList/sort.ts new file mode 100644 index 000000000..961bd6b87 --- /dev/null +++ b/src/orders/views/OrderDraftList/sort.ts @@ -0,0 +1,24 @@ +import { OrderDraftListUrlSortField } from "@saleor/orders/urls"; +import { OrderSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: OrderDraftListUrlSortField +): OrderSortField { + switch (sort) { + case OrderDraftListUrlSortField.number: + return OrderSortField.NUMBER; + case OrderDraftListUrlSortField.date: + return OrderSortField.CREATION_DATE; + case OrderDraftListUrlSortField.customer: + return OrderSortField.CUSTOMER; + case OrderDraftListUrlSortField.total: + return OrderSortField.TOTAL; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index ce70a669b..d1ae598dc 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -17,13 +17,15 @@ import usePaginator, { import useShop from "@saleor/hooks/useShop"; import { maybe } from "@saleor/misc"; import { ListViews } from "@saleor/types"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import { getSortParams } from "@saleor/utils/sort"; import OrderBulkCancelDialog from "../../components/OrderBulkCancelDialog"; import OrderListPage from "../../components/OrderListPage/OrderListPage"; import { TypedOrderBulkCancelMutation, useOrderDraftCreateMutation } from "../../mutations"; -import { TypedOrderListQuery } from "../../queries"; +import { useOrderListQuery } from "../../queries"; import { OrderBulkCancel } from "../../types/OrderBulkCancel"; import { OrderDraftCreate } from "../../types/OrderDraftCreate"; import { @@ -43,6 +45,7 @@ import { getFilterVariables, saveFilterTab } from "./filters"; +import { getSortQueryVariables } from "./sort"; interface OrderListProps { params: OrderListUrlQueryParams; @@ -146,128 +149,126 @@ export const OrderList: React.FC = ({ params }) => { const queryVariables = React.useMemo( () => ({ ...paginationState, - filter: getFilterVariables(params) + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) }), [params, settings.rowNumber] ); + const { data, loading, refetch } = useOrderListQuery({ + displayLoader: true, + variables: queryVariables + }); + + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.orders.pageInfo), + paginationState, + params + ); + + const handleOrderBulkCancel = (data: OrderBulkCancel) => { + if (data.orderBulkCancel.errors.length === 0) { + notify({ + text: intl.formatMessage({ + defaultMessage: "Orders cancelled" + }) + }); + reset(); + refetch(); + closeModal(); + } + }; + + const handleSort = createSortHandler(navigate, orderListUrl, params); return ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.orders.pageInfo), - paginationState, - params - ); - - const handleOrderBulkCancel = (data: OrderBulkCancel) => { - if (data.orderBulkCancel.errors.length === 0) { - notify({ - text: intl.formatMessage({ - defaultMessage: "Orders cancelled" - }) - }); - reset(); - refetch(); - closeModal(); - } - }; + + {(orderBulkCancel, orderBulkCancelOpts) => { + const onOrderBulkCancel = (restock: boolean) => + orderBulkCancel({ + variables: { + ids: params.ids, + restock + } + }); return ( - - {(orderBulkCancel, orderBulkCancelOpts) => { - const onOrderBulkCancel = (restock: boolean) => - orderBulkCancel({ - variables: { - ids: params.ids, - restock - } - }); - - return ( - <> - - data.orders.edges.map(edge => edge.node) - )} - pageInfo={pageInfo} - onAdd={createOrder} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - onRowClick={id => () => navigate(orderUrl(id))} - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - toolbar={ - - } - onSearchChange={query => changeFilterField({ query })} - onFilterAdd={data => - changeFilterField(createFilter(params, data)) - } - onTabSave={() => openModal("save-search")} - onTabDelete={() => openModal("delete-search")} - onTabChange={handleTabChange} - initialSearch={params.query || ""} - tabs={getFilterTabs().map(tab => tab.name)} - onAll={() => - changeFilters({ - status: undefined - }) - } + <> + data.orders.edges.map(edge => edge.node))} + pageInfo={pageInfo} + sort={getSortParams(params)} + onAdd={createOrder} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(orderUrl(id))} + onSort={handleSort} + isChecked={isSelected} + selected={listElements.length} + toggle={toggle} + toggleAll={toggleAll} + toolbar={ + + } + onSearchChange={query => changeFilterField({ query })} + onFilterAdd={data => + changeFilterField(createFilter(params, data)) + } + onTabSave={() => openModal("save-search")} + onTabDelete={() => openModal("delete-search")} + onTabChange={handleTabChange} + initialSearch={params.query || ""} + tabs={getFilterTabs().map(tab => tab.name)} + onAll={() => + changeFilters({ + status: undefined + }) + } + /> + params.ids.length.toString(), "...")} + onClose={closeModal} + onConfirm={onOrderBulkCancel} + open={params.action === "cancel"} + /> + + tabs[currentTab - 1].name, "...")} + /> + ); }} - + ); }; diff --git a/src/orders/views/OrderList/sort.ts b/src/orders/views/OrderList/sort.ts new file mode 100644 index 000000000..2a2bf71ef --- /dev/null +++ b/src/orders/views/OrderList/sort.ts @@ -0,0 +1,26 @@ +import { OrderListUrlSortField } from "@saleor/orders/urls"; +import { OrderSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField(sort: OrderListUrlSortField): OrderSortField { + switch (sort) { + case OrderListUrlSortField.number: + return OrderSortField.NUMBER; + case OrderListUrlSortField.date: + return OrderSortField.CREATION_DATE; + case OrderListUrlSortField.customer: + return OrderSortField.CUSTOMER; + case OrderListUrlSortField.total: + return OrderSortField.TOTAL; + case OrderListUrlSortField.fulfillment: + return OrderSortField.FULFILLMENT_STATUS; + case OrderListUrlSortField.payment: + return OrderSortField.PAYMENT; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/pages/components/PageList/PageList.tsx b/src/pages/components/PageList/PageList.tsx index c8a245bcc..2ab9e07da 100644 --- a/src/pages/components/PageList/PageList.tsx +++ b/src/pages/components/PageList/PageList.tsx @@ -14,10 +14,16 @@ import StatusLabel from "@saleor/components/StatusLabel"; import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; import { maybe, renderCollection } from "@saleor/misc"; -import { ListActions, ListProps } from "@saleor/types"; +import { ListActions, ListProps, SortPage } from "@saleor/types"; +import { PageListUrlSortField } from "@saleor/pages/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; import { PageList_pages_edges_node } from "../../types/PageList"; -export interface PageListProps extends ListProps, ListActions { +export interface PageListProps + extends ListProps, + ListActions, + SortPage { pages: PageList_pages_edges_node[]; } @@ -54,10 +60,12 @@ const PageList: React.FC = props => { onNextPage, pageInfo, onRowClick, + onSort, onUpdateListSettings, onPreviousPage, isChecked, selected, + sort, toggle, toggleAll, toolbar @@ -77,24 +85,51 @@ const PageList: React.FC = props => { toggleAll={toggleAll} toolbar={toolbar} > - + onSort(PageListUrlSortField.title)} + className={classes.colTitle} + > - - + + onSort(PageListUrlSortField.slug)} + className={classes.colSlug} + > - - + + onSort(PageListUrlSortField.visible)} + className={classes.colVisibility} + > - + @@ -133,13 +168,13 @@ const PageList: React.FC = props => { onChange={() => toggle(page.id)} /> - + {maybe(() => page.title, )} - - + + {maybe(() => page.slug, )} - - + + {maybe( () => ( = props => { ), )} - + ); }, diff --git a/src/pages/components/PageListPage/PageListPage.tsx b/src/pages/components/PageListPage/PageListPage.tsx index 85a7dbdc6..1d7b49725 100644 --- a/src/pages/components/PageListPage/PageListPage.tsx +++ b/src/pages/components/PageListPage/PageListPage.tsx @@ -7,31 +7,23 @@ import AppHeader from "@saleor/components/AppHeader"; import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import { sectionNames } from "@saleor/intl"; -import { ListActions, PageListProps } from "@saleor/types"; +import { ListActions, PageListProps, SortPage } from "@saleor/types"; +import { PageListUrlSortField } from "@saleor/pages/urls"; import { PageList_pages_edges_node } from "../../types/PageList"; -import PageList from "../PageList/PageList"; +import PageList from "../PageList"; -export interface PageListPageProps extends PageListProps, ListActions { +export interface PageListPageProps + extends PageListProps, + ListActions, + SortPage { pages: PageList_pages_edges_node[]; onBack: () => void; } const PageListPage: React.FC = ({ - disabled, - settings, onAdd, onBack, - onNextPage, - onPreviousPage, - onRowClick, - onUpdateListSettings, - pageInfo, - pages, - isChecked, - selected, - toggle, - toggleAll, - toolbar + ...listProps }) => { const intl = useIntl(); @@ -41,30 +33,11 @@ const PageListPage: React.FC = ({ {intl.formatMessage(sectionNames.configuration)} - - + ); }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 5bc69c675..9929240cc 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,13 +4,15 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { pageCreatePath, pageListPath, PageListUrlQueryParams, pagePath, - PageUrlQueryParams + PageUrlQueryParams, + PageListUrlSortField } from "./urls"; import PageCreate from "./views/PageCreate"; import PageDetailsComponent from "./views/PageDetails"; @@ -18,7 +20,11 @@ import PageListComponent from "./views/PageList"; const PageList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: PageListUrlQueryParams = qs; + const params: PageListUrlQueryParams = asSortParams( + qs, + PageListUrlSortField, + PageListUrlSortField.title + ); return ; }; diff --git a/src/pages/queries.ts b/src/pages/queries.ts index 8990a6267..5623f1273 100644 --- a/src/pages/queries.ts +++ b/src/pages/queries.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { TypedQuery } from "../queries"; import { PageDetails, PageDetailsVariables } from "./types/PageDetails"; import { PageList, PageListVariables } from "./types/PageList"; @@ -26,8 +27,20 @@ export const pageDetailsFragment = gql` const pageList = gql` ${pageFragment} - query PageList($first: Int, $after: String, $last: Int, $before: String) { - pages(before: $before, after: $after, first: $first, last: $last) { + query PageList( + $first: Int + $after: String + $last: Int + $before: String + $sort: PageSortingInput + ) { + pages( + before: $before + after: $after + first: $first + last: $last + sortBy: $sort + ) { edges { node { ...PageFragment @@ -42,7 +55,7 @@ const pageList = gql` } } `; -export const TypedPageListQuery = TypedQuery( +export const usePageListQuery = makeQuery( pageList ); diff --git a/src/pages/types/PageList.ts b/src/pages/types/PageList.ts index df8718b7b..ab2165b26 100644 --- a/src/pages/types/PageList.ts +++ b/src/pages/types/PageList.ts @@ -2,6 +2,8 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. +import { PageSortingInput } from "./../../types/globalTypes"; + // ==================================================== // GraphQL query operation: PageList // ==================================================== @@ -42,4 +44,5 @@ export interface PageListVariables { after?: string | null; last?: number | null; before?: string | null; + sort?: PageSortingInput | null; } diff --git a/src/pages/urls.ts b/src/pages/urls.ts index dd4c6c88f..ff6e228f9 100644 --- a/src/pages/urls.ts +++ b/src/pages/urls.ts @@ -1,14 +1,21 @@ import { stringify as stringifyQs } from "qs"; import urlJoin from "url-join"; -import { BulkAction, Dialog, Pagination } from "../types"; +import { BulkAction, Dialog, Pagination, Sort } from "../types"; export const pagesSection = "/pages/"; export const pageListPath = pagesSection; export type PageListUrlDialog = "publish" | "unpublish" | "remove"; +export enum PageListUrlSortField { + title = "title", + slug = "slug", + visible = "visible" +} +export type PageListUrlSort = Sort; export type PageListUrlQueryParams = BulkAction & Dialog & + PageListUrlSort & Pagination; export const pageListUrl = (params?: PageListUrlQueryParams) => pageListPath + "?" + stringifyQs(params); diff --git a/src/pages/views/PageList.tsx b/src/pages/views/PageList.tsx deleted file mode 100644 index 7c713b835..000000000 --- a/src/pages/views/PageList.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import Button from "@material-ui/core/Button"; -import DialogContentText from "@material-ui/core/DialogContentText"; -import IconButton from "@material-ui/core/IconButton"; -import DeleteIcon from "@material-ui/icons/Delete"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -import ActionDialog from "@saleor/components/ActionDialog"; -import { configurationMenuUrl } from "@saleor/configuration"; -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 { maybe } from "@saleor/misc"; -import { ListViews } from "@saleor/types"; -import PageListPage from "../components/PageListPage/PageListPage"; -import { TypedPageBulkPublish, TypedPageBulkRemove } from "../mutations"; -import { TypedPageListQuery } from "../queries"; -import { PageBulkPublish } from "../types/PageBulkPublish"; -import { PageBulkRemove } from "../types/PageBulkRemove"; -import { - pageCreateUrl, - pageListUrl, - PageListUrlDialog, - PageListUrlQueryParams, - pageUrl -} from "../urls"; - -interface PageListProps { - params: PageListUrlQueryParams; -} - -export const PageList: React.FC = ({ params }) => { - const navigate = useNavigator(); - const notify = useNotifier(); - const paginate = usePaginator(); - const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( - params.ids - ); - const { updateListSettings, settings } = useListSettings( - ListViews.PAGES_LIST - ); - const intl = useIntl(); - - const paginationState = createPaginationState(settings.rowNumber, params); - - return ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.pages.pageInfo), - paginationState, - params - ); - - const closeModal = () => - navigate( - pageListUrl({ - ...params, - action: undefined, - ids: undefined - }), - true - ); - - const openModal = (action: PageListUrlDialog, ids: string[]) => - navigate( - pageListUrl({ - ...params, - action, - ids - }) - ); - - const handlePageBulkPublish = (data: PageBulkPublish) => { - if (data.pageBulkPublish.errors.length === 0) { - closeModal(); - notify({ - text: intl.formatMessage({ - defaultMessage: "Published pages", - description: "notification" - }) - }); - reset(); - refetch(); - } - }; - - const handlePageBulkRemove = (data: PageBulkRemove) => { - if (data.pageBulkDelete.errors.length === 0) { - closeModal(); - notify({ - text: intl.formatMessage({ - defaultMessage: "Removed pages", - description: "notification" - }) - }); - reset(); - refetch(); - } - }; - - return ( - - {(bulkPageRemove, bulkPageRemoveOpts) => ( - - {(bulkPagePublish, bulkPagePublishOpts) => ( - <> - - data.pages.edges.map(edge => edge.node) - )} - pageInfo={pageInfo} - onAdd={() => navigate(pageCreateUrl)} - onBack={() => navigate(configurationMenuUrl)} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - onRowClick={id => () => navigate(pageUrl(id))} - toolbar={ - <> - - - openModal("remove", listElements)} - > - - - - } - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - /> - - bulkPagePublish({ - variables: { - ids: params.ids, - isPublished: true - } - }) - } - title={intl.formatMessage({ - defaultMessage: "Publish Pages", - description: "dialog header" - })} - > - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - - bulkPagePublish({ - variables: { - ids: params.ids, - isPublished: false - } - }) - } - title={intl.formatMessage({ - defaultMessage: "Unpublish Pages", - description: "dialog header" - })} - > - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - bulkPageRemove({ - variables: { - ids: params.ids - } - }) - } - variant="delete" - title={intl.formatMessage({ - defaultMessage: "Delete Pages", - description: "dialog header" - })} - > - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - )} - - )} - - ); - }} - - ); -}; - -export default PageList; diff --git a/src/pages/views/PageList/PageList.tsx b/src/pages/views/PageList/PageList.tsx new file mode 100644 index 000000000..0735dc2e3 --- /dev/null +++ b/src/pages/views/PageList/PageList.tsx @@ -0,0 +1,265 @@ +import Button from "@material-ui/core/Button"; +import DialogContentText from "@material-ui/core/DialogContentText"; +import IconButton from "@material-ui/core/IconButton"; +import DeleteIcon from "@material-ui/icons/Delete"; +import React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import ActionDialog from "@saleor/components/ActionDialog"; +import { configurationMenuUrl } from "@saleor/configuration"; +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 { maybe } from "@saleor/misc"; +import { ListViews } from "@saleor/types"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import PageListPage from "../../components/PageListPage/PageListPage"; +import { TypedPageBulkPublish, TypedPageBulkRemove } from "../../mutations"; +import { usePageListQuery } from "../../queries"; +import { PageBulkPublish } from "../../types/PageBulkPublish"; +import { PageBulkRemove } from "../../types/PageBulkRemove"; +import { + pageCreateUrl, + pageListUrl, + PageListUrlDialog, + PageListUrlQueryParams, + pageUrl +} from "../../urls"; +import { getSortQueryVariables } from "./sort"; + +interface PageListProps { + params: PageListUrlQueryParams; +} + +export const PageList: React.FC = ({ params }) => { + const navigate = useNavigator(); + const notify = useNotifier(); + const paginate = usePaginator(); + const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( + params.ids + ); + const { updateListSettings, settings } = useListSettings( + ListViews.PAGES_LIST + ); + const intl = useIntl(); + + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading, refetch } = usePageListQuery({ + displayLoader: true, + variables: queryVariables + }); + + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.pages.pageInfo), + paginationState, + params + ); + + const closeModal = () => + navigate( + pageListUrl({ + ...params, + action: undefined, + ids: undefined + }), + true + ); + + const openModal = (action: PageListUrlDialog, ids: string[]) => + navigate( + pageListUrl({ + ...params, + action, + ids + }) + ); + + const handlePageBulkPublish = (data: PageBulkPublish) => { + if (data.pageBulkPublish.errors.length === 0) { + closeModal(); + notify({ + text: intl.formatMessage({ + defaultMessage: "Published pages", + description: "notification" + }) + }); + reset(); + refetch(); + } + }; + + const handlePageBulkRemove = (data: PageBulkRemove) => { + if (data.pageBulkDelete.errors.length === 0) { + closeModal(); + notify({ + text: intl.formatMessage({ + defaultMessage: "Removed pages", + description: "notification" + }) + }); + reset(); + refetch(); + } + }; + + const handleSort = createSortHandler(navigate, pageListUrl, params); + + return ( + + {(bulkPageRemove, bulkPageRemoveOpts) => ( + + {(bulkPagePublish, bulkPagePublishOpts) => ( + <> + data.pages.edges.map(edge => edge.node))} + pageInfo={pageInfo} + onAdd={() => navigate(pageCreateUrl)} + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(pageUrl(id))} + onSort={handleSort} + toolbar={ + <> + + + openModal("remove", listElements)} + > + + + + } + isChecked={isSelected} + selected={listElements.length} + sort={getSortParams(params)} + toggle={toggle} + toggleAll={toggleAll} + /> + + bulkPagePublish({ + variables: { + ids: params.ids, + isPublished: true + } + }) + } + title={intl.formatMessage({ + defaultMessage: "Publish Pages", + description: "dialog header" + })} + > + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + + bulkPagePublish({ + variables: { + ids: params.ids, + isPublished: false + } + }) + } + title={intl.formatMessage({ + defaultMessage: "Unpublish Pages", + description: "dialog header" + })} + > + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + bulkPageRemove({ + variables: { + ids: params.ids + } + }) + } + variant="delete" + title={intl.formatMessage({ + defaultMessage: "Delete Pages", + description: "dialog header" + })} + > + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + )} + + )} + + ); +}; + +export default PageList; diff --git a/src/pages/views/PageList/index.ts b/src/pages/views/PageList/index.ts new file mode 100644 index 000000000..ceef274c3 --- /dev/null +++ b/src/pages/views/PageList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PageList"; +export * from "./PageList"; diff --git a/src/pages/views/PageList/sort.ts b/src/pages/views/PageList/sort.ts new file mode 100644 index 000000000..cdb9d5e93 --- /dev/null +++ b/src/pages/views/PageList/sort.ts @@ -0,0 +1,20 @@ +import { PageListUrlSortField } from "@saleor/pages/urls"; +import { PageSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField(sort: PageListUrlSortField): PageSortField { + switch (sort) { + case PageListUrlSortField.title: + return PageSortField.TITLE; + case PageListUrlSortField.visible: + return PageSortField.VISIBILITY; + case PageListUrlSortField.slug: + return PageSortField.SLUG; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/plugins/components/PluginsList/PluginsList.tsx b/src/plugins/components/PluginsList/PluginsList.tsx index aa28941d9..9fa315136 100644 --- a/src/plugins/components/PluginsList/PluginsList.tsx +++ b/src/plugins/components/PluginsList/PluginsList.tsx @@ -15,10 +15,15 @@ import StatusLabel from "@saleor/components/StatusLabel"; import TablePagination from "@saleor/components/TablePagination"; import { translateBoolean } from "@saleor/intl"; import { maybe, renderCollection } from "@saleor/misc"; -import { ListProps } from "@saleor/types"; +import { ListProps, SortPage } from "@saleor/types"; +import { PluginListUrlSortField } from "@saleor/plugins/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; import { Plugins_plugins_edges_node } from "../../types/Plugins"; -export interface PluginListProps extends ListProps { +export interface PluginListProps + extends ListProps, + SortPage { plugins: Plugins_plugins_edges_node[]; } @@ -53,7 +58,9 @@ const PluginList: React.FC = props => { disabled, onNextPage, pageInfo, + sort, onRowClick, + onSort, onUpdateListSettings, onPreviousPage } = props; @@ -64,18 +71,35 @@ const PluginList: React.FC = props => { - + onSort(PluginListUrlSortField.name)} + className={classes.colName} + > {intl.formatMessage({ defaultMessage: "Name", description: "plugin name" })} - - + + onSort(PluginListUrlSortField.active)} + className={classes.colActive} + > {intl.formatMessage({ defaultMessage: "Active", description: "plugin status" })} - + {intl.formatMessage({ defaultMessage: "Action", diff --git a/src/plugins/components/PluginsListPage/PluginsListPage.tsx b/src/plugins/components/PluginsListPage/PluginsListPage.tsx index 8add7de75..efcb3372b 100644 --- a/src/plugins/components/PluginsListPage/PluginsListPage.tsx +++ b/src/plugins/components/PluginsListPage/PluginsListPage.tsx @@ -5,43 +5,31 @@ import AppHeader from "@saleor/components/AppHeader"; import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import { sectionNames } from "@saleor/intl"; -import { PageListProps } from "@saleor/types"; +import { PageListProps, SortPage } from "@saleor/types"; +import { PluginListUrlSortField } from "@saleor/plugins/urls"; import { Plugins_plugins_edges_node } from "../../types/Plugins"; import PluginsList from "../PluginsList/PluginsList"; -export interface PluginsListPageProps extends PageListProps { +export interface PluginsListPageProps + extends PageListProps, + SortPage { plugins: Plugins_plugins_edges_node[]; onBack: () => void; } const PluginsListPage: React.FC = ({ - disabled, - settings, onBack, - onNextPage, - onPreviousPage, - onRowClick, - onUpdateListSettings, - pageInfo, - plugins + ...listProps }) => { const intl = useIntl(); + return ( {intl.formatMessage(sectionNames.configuration)} - + ); }; diff --git a/src/plugins/index.tsx b/src/plugins/index.tsx index 53080b861..f88721f37 100644 --- a/src/plugins/index.tsx +++ b/src/plugins/index.tsx @@ -4,25 +4,30 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { - pluginsListPath, - PluginsListUrlQueryParams, - pluginsPath, - PluginsUrlQueryParams + pluginListPath, + PluginListUrlQueryParams, + pluginPath, + PluginUrlQueryParams, + PluginListUrlSortField } from "./urls"; import PluginsDetailsComponent from "./views/PluginsDetails"; -import PluginsListComponent from "./views/PluginsList"; +import PluginsListComponent from "./views/PluginList"; const PluginList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: PluginsListUrlQueryParams = qs; + const params: PluginListUrlQueryParams = asSortParams( + qs, + PluginListUrlSortField + ); return ; }; const PageDetails: React.FC> = ({ match }) => { const qs = parseQs(location.search.substr(1)); - const params: PluginsUrlQueryParams = qs; + const params: PluginUrlQueryParams = qs; return ( { <> - - + + ); diff --git a/src/plugins/queries.ts b/src/plugins/queries.ts index ec3ae773a..dc518f6a5 100644 --- a/src/plugins/queries.ts +++ b/src/plugins/queries.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { TypedQuery } from "../queries"; import { Plugin, PluginVariables } from "./types/Plugin"; import { Plugins, PluginsVariables } from "./types/Plugins"; @@ -29,8 +30,20 @@ export const pluginsDetailsFragment = gql` const pluginsList = gql` ${pluginsFragment} - query Plugins($first: Int, $after: String, $last: Int, $before: String) { - plugins(before: $before, after: $after, first: $first, last: $last) { + query Plugins( + $first: Int + $after: String + $last: Int + $before: String + $sort: PluginSortingInput + ) { + plugins( + before: $before + after: $after + first: $first + last: $last + sortBy: $sort + ) { edges { node { ...PluginFragment @@ -45,7 +58,7 @@ const pluginsList = gql` } } `; -export const TypedPluginsListQuery = TypedQuery( +export const usePluginsListQuery = makeQuery( pluginsList ); diff --git a/src/plugins/types/Plugins.ts b/src/plugins/types/Plugins.ts index c8861ad45..a8d888402 100644 --- a/src/plugins/types/Plugins.ts +++ b/src/plugins/types/Plugins.ts @@ -2,6 +2,8 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. +import { PluginSortingInput } from "./../../types/globalTypes"; + // ==================================================== // GraphQL query operation: Plugins // ==================================================== @@ -42,4 +44,5 @@ export interface PluginsVariables { after?: string | null; last?: number | null; before?: string | null; + sort?: PluginSortingInput | null; } diff --git a/src/plugins/urls.ts b/src/plugins/urls.ts index 29b7c5e95..f57e8a55b 100644 --- a/src/plugins/urls.ts +++ b/src/plugins/urls.ts @@ -1,19 +1,26 @@ import { stringify as stringifyQs } from "qs"; import urlJoin from "url-join"; -import { Dialog, Pagination, SingleAction } from "../types"; +import { Dialog, Pagination, SingleAction, Sort } from "../types"; -export const pluginsSection = "/plugins/"; +export const pluginSection = "/plugins/"; -export const pluginsListPath = pluginsSection; -export type PluginsListUrlQueryParams = Pagination & SingleAction; -export const pluginsListUrl = (params?: PluginsListUrlQueryParams) => - pluginsListPath + "?" + stringifyQs(params); +export const pluginListPath = pluginSection; +export enum PluginListUrlSortField { + name = "name", + active = "active" +} +export type PluginListUrlSort = Sort; +export type PluginListUrlQueryParams = Pagination & + PluginListUrlSort & + SingleAction; +export const pluginListUrl = (params?: PluginListUrlQueryParams) => + pluginListPath + "?" + stringifyQs(params); -export const pluginsPath = (id: string) => urlJoin(pluginsSection, id); +export const pluginPath = (id: string) => urlJoin(pluginSection, id); export type PluginUrlDialog = "clear" | "edit"; -export type PluginsUrlQueryParams = Dialog & { +export type PluginUrlQueryParams = Dialog & { field?: string; }; -export const pluginsUrl = (id: string, params?: PluginsUrlQueryParams) => - pluginsPath(encodeURIComponent(id)) + "?" + stringifyQs(params); +export const pluginsUrl = (id: string, params?: PluginUrlQueryParams) => + pluginPath(encodeURIComponent(id)) + "?" + stringifyQs(params); diff --git a/src/plugins/views/PluginList/PluginList.tsx b/src/plugins/views/PluginList/PluginList.tsx new file mode 100644 index 000000000..13eeaccd2 --- /dev/null +++ b/src/plugins/views/PluginList/PluginList.tsx @@ -0,0 +1,74 @@ +import { configurationMenuUrl } from "@saleor/configuration"; +import useListSettings from "@saleor/hooks/useListSettings"; +import useNavigator from "@saleor/hooks/useNavigator"; +import usePaginator, { + createPaginationState +} from "@saleor/hooks/usePaginator"; +import { maybe } from "@saleor/misc"; +import { ListViews } from "@saleor/types"; +import React from "react"; + +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import PluginsListPage from "../../components/PluginsListPage/PluginsListPage"; +import { usePluginsListQuery } from "../../queries"; +import { + PluginListUrlQueryParams, + pluginListUrl, + pluginsUrl +} from "../../urls"; +import { getSortQueryVariables } from "./sort"; + +interface PluginsListProps { + params: PluginListUrlQueryParams; +} + +export const PluginsList: React.FC = ({ params }) => { + const navigate = useNavigator(); + const paginate = usePaginator(); + const { updateListSettings, settings } = useListSettings( + ListViews.PLUGINS_LIST + ); + + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading } = usePluginsListQuery({ + displayLoader: true, + variables: queryVariables + }); + + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.plugins.pageInfo), + paginationState, + params + ); + + const handleSort = createSortHandler(navigate, pluginListUrl, params); + + return ( + <> + data.plugins.edges.map(edge => edge.node))} + pageInfo={pageInfo} + sort={getSortParams(params)} + onAdd={() => navigate(configurationMenuUrl)} + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onSort={handleSort} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(pluginsUrl(id))} + /> + + ); +}; + +export default PluginsList; diff --git a/src/plugins/views/PluginList/index.ts b/src/plugins/views/PluginList/index.ts new file mode 100644 index 000000000..0c7b21066 --- /dev/null +++ b/src/plugins/views/PluginList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./PluginList"; +export * from "./PluginList"; diff --git a/src/plugins/views/PluginList/sort.ts b/src/plugins/views/PluginList/sort.ts new file mode 100644 index 000000000..176ec8bde --- /dev/null +++ b/src/plugins/views/PluginList/sort.ts @@ -0,0 +1,20 @@ +import { PluginListUrlSortField } from "@saleor/plugins/urls"; +import { PluginSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: PluginListUrlSortField +): PluginSortField { + switch (sort) { + case PluginListUrlSortField.name: + return PluginSortField.NAME; + case PluginListUrlSortField.active: + return PluginSortField.IS_ACTIVE; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/plugins/views/PluginsDetails.tsx b/src/plugins/views/PluginsDetails.tsx index 538718ac8..ef6665ad5 100644 --- a/src/plugins/views/PluginsDetails.tsx +++ b/src/plugins/views/PluginsDetails.tsx @@ -16,16 +16,16 @@ import { TypedPluginsDetailsQuery } from "../queries"; import { Plugin_plugin_configuration } from "../types/Plugin"; import { PluginUpdate } from "../types/PluginUpdate"; import { - pluginsListUrl, + pluginListUrl, pluginsUrl, - PluginsUrlQueryParams, + PluginUrlQueryParams, PluginUrlDialog } from "../urls"; import { isSecretField } from "../utils"; export interface PluginsDetailsProps { id: string; - params: PluginsUrlQueryParams; + params: PluginUrlQueryParams; } export function getConfigurationInput( @@ -117,7 +117,7 @@ export const PluginsDetails: React.FC = ({ !params.action ? pluginUpdateOpts.status : "default" } plugin={maybe(() => pluginDetails.data.plugin)} - onBack={() => navigate(pluginsListUrl())} + onBack={() => navigate(pluginListUrl())} onClear={field => openModal("clear", field)} onEdit={field => openModal("edit", field)} onSubmit={formData => diff --git a/src/plugins/views/PluginsList.tsx b/src/plugins/views/PluginsList.tsx deleted file mode 100644 index 4f34e06be..000000000 --- a/src/plugins/views/PluginsList.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { configurationMenuUrl } from "@saleor/configuration"; -import useListSettings from "@saleor/hooks/useListSettings"; -import useNavigator from "@saleor/hooks/useNavigator"; -import usePaginator, { - createPaginationState -} from "@saleor/hooks/usePaginator"; -import { maybe } from "@saleor/misc"; -import { ListViews } from "@saleor/types"; -import React from "react"; - -import PluginsListPage from "../components/PluginsListPage/PluginsListPage"; -import { TypedPluginsListQuery } from "../queries"; -import { PluginsListUrlQueryParams, pluginsUrl } from "../urls"; - -interface PluginsListProps { - params: PluginsListUrlQueryParams; -} - -export const PluginsList: React.FC = ({ params }) => { - const navigate = useNavigator(); - const paginate = usePaginator(); - const { updateListSettings, settings } = useListSettings( - ListViews.PLUGINS_LIST - ); - const paginationState = createPaginationState(settings.rowNumber, params); - - return ( - - {({ data, loading }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.plugins.pageInfo), - paginationState, - params - ); - return ( - <> - data.plugins.edges.map(edge => edge.node))} - pageInfo={pageInfo} - onAdd={() => navigate(configurationMenuUrl)} - onBack={() => navigate(configurationMenuUrl)} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - onRowClick={id => () => navigate(pluginsUrl(id))} - /> - - ); - }} - - ); -}; - -export default PluginsList; diff --git a/src/productTypes/components/ProductTypeList/ProductTypeList.tsx b/src/productTypes/components/ProductTypeList/ProductTypeList.tsx index 4db1bc5ca..ef748da3f 100644 --- a/src/productTypes/components/ProductTypeList/ProductTypeList.tsx +++ b/src/productTypes/components/ProductTypeList/ProductTypeList.tsx @@ -12,8 +12,11 @@ import ResponsiveTable from "@saleor/components/ResponsiveTable"; import Skeleton from "@saleor/components/Skeleton"; import TableHead from "@saleor/components/TableHead"; import TablePagination from "@saleor/components/TablePagination"; +import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls"; +import { getArrowDirection } from "@saleor/utils/sort"; +import TableCellHeader from "@saleor/components/TableCellHeader"; import { maybe, renderCollection } from "../../../misc"; -import { ListActions, ListProps } from "../../../types"; +import { ListActions, ListProps, SortPage } from "../../../types"; import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList"; const useStyles = makeStyles( @@ -39,7 +42,10 @@ const useStyles = makeStyles( { name: "ProductTypeList" } ); -interface ProductTypeListProps extends ListProps, ListActions { +interface ProductTypeListProps + extends ListProps, + ListActions, + SortPage { productTypes: ProductTypeList_productTypes_edges_node[]; } @@ -53,8 +59,10 @@ const ProductTypeList: React.FC = props => { onNextPage, onPreviousPage, onRowClick, + onSort, isChecked, selected, + sort, toggle, toggleAll, toolbar @@ -73,18 +81,35 @@ const ProductTypeList: React.FC = props => { toggleAll={toggleAll} toolbar={toolbar} > - + onSort(ProductTypeListUrlSortField.name)} + className={classes.colName} + > - - + + onSort(ProductTypeListUrlSortField.digital)} + className={classes.colType} + > - + , TabPageProps { productTypes: ProductTypeList_productTypes_edges_node[]; onBack: () => void; diff --git a/src/productTypes/index.tsx b/src/productTypes/index.tsx index 6b4af2c1f..d97a8be5c 100644 --- a/src/productTypes/index.tsx +++ b/src/productTypes/index.tsx @@ -4,13 +4,15 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { productTypeAddPath, productTypeListPath, ProductTypeListUrlQueryParams, productTypePath, - ProductTypeUrlQueryParams + ProductTypeUrlQueryParams, + ProductTypeListUrlSortField } from "./urls"; import ProductTypeCreate from "./views/ProductTypeCreate"; import ProductTypeListComponent from "./views/ProductTypeList"; @@ -18,16 +20,19 @@ import ProductTypeUpdateComponent from "./views/ProductTypeUpdate"; const ProductTypeList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: ProductTypeListUrlQueryParams = qs; + const params: ProductTypeListUrlQueryParams = asSortParams( + qs, + ProductTypeListUrlSortField + ); return ; }; interface ProductTypeUpdateRouteParams { id: string; } -const ProductTypeUpdate: React.FC< - RouteComponentProps -> = ({ match }) => { +const ProductTypeUpdate: React.FC> = ({ match }) => { const qs = parseQs(location.search.substr(1)); const params: ProductTypeUrlQueryParams = qs; diff --git a/src/productTypes/queries.ts b/src/productTypes/queries.ts index d67b628d7..f6bdc47de 100644 --- a/src/productTypes/queries.ts +++ b/src/productTypes/queries.ts @@ -1,6 +1,7 @@ import gql from "graphql-tag"; import { attributeFragment } from "@saleor/attributes/queries"; +import makeQuery from "@saleor/hooks/makeQuery"; import { pageInfoFragment, TypedQuery } from "../queries"; import { ProductTypeCreateData } from "./types/ProductTypeCreateData"; import { @@ -52,6 +53,7 @@ export const productTypeListQuery = gql` $first: Int $last: Int $filter: ProductTypeFilterInput + $sort: ProductTypeSortingInput ) { productTypes( after: $after @@ -59,6 +61,7 @@ export const productTypeListQuery = gql` first: $first last: $last filter: $filter + sortBy: $sort ) { edges { node { @@ -71,7 +74,7 @@ export const productTypeListQuery = gql` } } `; -export const TypedProductTypeListQuery = TypedQuery< +export const useProductTypeListQuery = makeQuery< ProductTypeList, ProductTypeListVariables >(productTypeListQuery); diff --git a/src/productTypes/types/ProductTypeList.ts b/src/productTypes/types/ProductTypeList.ts index 6a83fa4de..0a8d69624 100644 --- a/src/productTypes/types/ProductTypeList.ts +++ b/src/productTypes/types/ProductTypeList.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { ProductTypeFilterInput } from "./../../types/globalTypes"; +import { ProductTypeFilterInput, ProductTypeSortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: ProductTypeList @@ -52,4 +52,5 @@ export interface ProductTypeListVariables { first?: number | null; last?: number | null; filter?: ProductTypeFilterInput | null; + sort?: ProductTypeSortingInput | null; } diff --git a/src/productTypes/urls.ts b/src/productTypes/urls.ts index 34733f1c4..bf295d6ae 100644 --- a/src/productTypes/urls.ts +++ b/src/productTypes/urls.ts @@ -8,7 +8,8 @@ import { Filters, Pagination, SingleAction, - TabActionDialog + TabActionDialog, + Sort } from "../types"; const productTypeSection = "/product-types/"; @@ -19,11 +20,17 @@ export enum ProductTypeListUrlFiltersEnum { } export type ProductTypeListUrlFilters = Filters; export type ProductTypeListUrlDialog = "remove" | TabActionDialog; +export enum ProductTypeListUrlSortField { + name = "name", + digital = "digital" +} +export type ProductTypeListUrlSort = Sort; export type ProductTypeListUrlQueryParams = ActiveTab & BulkAction & Dialog & Pagination & - ProductTypeListUrlFilters; + ProductTypeListUrlFilters & + ProductTypeListUrlSort; export const productTypeListUrl = (params?: ProductTypeListUrlQueryParams) => productTypeListPath + "?" + stringifyQs(params); diff --git a/src/productTypes/views/ProductTypeList/ProductTypeList.tsx b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx index 88b43aca9..93c2de910 100644 --- a/src/productTypes/views/ProductTypeList/ProductTypeList.tsx +++ b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx @@ -18,11 +18,13 @@ import usePaginator, { } from "@saleor/hooks/usePaginator"; import { commonMessages } from "@saleor/intl"; import { ListViews } from "@saleor/types"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; import { configurationMenuUrl } from "../../../configuration"; import { maybe } from "../../../misc"; import ProductTypeListPage from "../../components/ProductTypeListPage"; import { TypedProductTypeBulkDeleteMutation } from "../../mutations"; -import { TypedProductTypeListQuery } from "../../queries"; +import { useProductTypeListQuery } from "../../queries"; import { ProductTypeBulkDelete } from "../../types/ProductTypeBulkDelete"; import { productTypeAddUrl, @@ -40,6 +42,7 @@ import { getFilterVariables, saveFilterTab } from "./filter"; +import { getSortQueryVariables } from "./sort"; interface ProductTypeListProps { params: ProductTypeListUrlQueryParams; @@ -55,6 +58,20 @@ export const ProductTypeList: React.FC = ({ params }) => { const { settings } = useListSettings(ListViews.PRODUCT_LIST); const intl = useIntl(); + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading, refetch } = useProductTypeListQuery({ + displayLoader: true, + variables: queryVariables + }); + const tabs = getFilterTabs(); const currentTab = @@ -115,137 +132,127 @@ export const ProductTypeList: React.FC = ({ params }) => { handleTabChange(tabs.length + 1); }; - const paginationState = createPaginationState(settings.rowNumber, params); - const queryVariables = React.useMemo( - () => ({ - ...paginationState, - filter: getFilterVariables(params) - }), - [params] + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.productTypes.pageInfo), + paginationState, + params ); - return ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.productTypes.pageInfo), - paginationState, - params - ); + const handleProductTypeBulkDelete = (data: ProductTypeBulkDelete) => { + if (data.productTypeBulkDelete.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + reset(); + refetch(); + navigate( + productTypeListUrl({ + ...params, + action: undefined, + ids: undefined + }) + ); + } + }; - const handleProductTypeBulkDelete = (data: ProductTypeBulkDelete) => { - if (data.productTypeBulkDelete.errors.length === 0) { - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - reset(); - refetch(); - navigate( - productTypeListUrl({ - ...params, - action: undefined, - ids: undefined - }) - ); - } - }; + const handleSort = createSortHandler(navigate, productTypeListUrl, params); + + return ( + + {(productTypeBulkDelete, productTypeBulkDeleteOpts) => { + const onProductTypeBulkDelete = () => + productTypeBulkDelete({ + variables: { + ids: params.ids + } + }); return ( - - {(productTypeBulkDelete, productTypeBulkDeleteOpts) => { - const onProductTypeBulkDelete = () => - productTypeBulkDelete({ - variables: { - ids: params.ids + <> + 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) + )} + pageInfo={pageInfo} + onAdd={() => navigate(productTypeAddUrl)} + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onRowClick={id => () => navigate(productTypeUrl(id))} + onSort={handleSort} + isChecked={isSelected} + selected={listElements.length} + sort={getSortParams(params)} + toggle={toggle} + toggleAll={toggleAll} + toolbar={ + + navigate( + productTypeListUrl({ + action: "remove", + ids: listElements + }) + ) } - }); - 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) - )} - pageInfo={pageInfo} - onAdd={() => navigate(productTypeAddUrl)} - onBack={() => navigate(configurationMenuUrl)} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onRowClick={id => () => navigate(productTypeUrl(id))} - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - toolbar={ - - navigate( - productTypeListUrl({ - action: "remove", - ids: listElements - }) - ) - } - > - - - } - /> - - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - - tabs[currentTab - 1].name, "...")} - /> - - ); - }} - + > + + + } + /> + + + params.ids.length), + displayQuantity: ( + {maybe(() => params.ids.length)} + ) + }} + /> + + + + tabs[currentTab - 1].name, "...")} + /> + ); }} - + ); }; ProductTypeList.displayName = "ProductTypeList"; diff --git a/src/productTypes/views/ProductTypeList/sort.ts b/src/productTypes/views/ProductTypeList/sort.ts new file mode 100644 index 000000000..daabbce42 --- /dev/null +++ b/src/productTypes/views/ProductTypeList/sort.ts @@ -0,0 +1,20 @@ +import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls"; +import { ProductTypeSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: ProductTypeListUrlSortField +): ProductTypeSortField { + switch (sort) { + case ProductTypeListUrlSortField.name: + return ProductTypeSortField.NAME; + case ProductTypeListUrlSortField.digital: + return ProductTypeSortField.DIGITAL; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/products/index.tsx b/src/products/index.tsx index 179b094b9..fed57ca5a 100644 --- a/src/products/index.tsx +++ b/src/products/index.tsx @@ -4,7 +4,7 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; -import { findInEnum, parseBoolean } from "@saleor/misc"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { productAddPath, @@ -28,13 +28,10 @@ import ProductVariantCreateComponent from "./views/ProductVariantCreate"; const ProductList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: ProductListUrlQueryParams = { - ...qs, - asc: parseBoolean(qs.asc), - sort: qs.sort - ? findInEnum(qs.sort, ProductListUrlSortField) - : ProductListUrlSortField.name - }; + const params: ProductListUrlQueryParams = asSortParams( + qs, + ProductListUrlSortField + ); return ; }; diff --git a/src/services/components/ServiceList/ServiceList.tsx b/src/services/components/ServiceList/ServiceList.tsx index 2b4352d40..0d1ec61ae 100644 --- a/src/services/components/ServiceList/ServiceList.tsx +++ b/src/services/components/ServiceList/ServiceList.tsx @@ -15,10 +15,15 @@ import ResponsiveTable from "@saleor/components/ResponsiveTable"; import Skeleton from "@saleor/components/Skeleton"; import TablePagination from "@saleor/components/TablePagination"; import { maybe, renderCollection, stopPropagation } from "@saleor/misc"; -import { ListProps } from "@saleor/types"; +import { ListProps, SortPage } from "@saleor/types"; +import { ServiceListUrlSortField } from "@saleor/services/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; import { ServiceList_serviceAccounts_edges_node } from "../../types/ServiceList"; -export interface ServiceListProps extends ListProps { +export interface ServiceListProps + extends ListProps, + SortPage { services: ServiceList_serviceAccounts_edges_node[]; onRemove: (id: string) => void; } @@ -37,7 +42,7 @@ const useStyles = makeStyles( paddingRight: theme.spacing(1) }, textAlign: "right", - width: 100 + width: 140 }, colName: { paddingLeft: 0, @@ -64,8 +69,10 @@ const ServiceList: React.FC = props => { onUpdateListSettings, onRemove, onRowClick, + onSort, pageInfo, - services + services, + sort } = props; const classes = useStyles(props); @@ -74,12 +81,21 @@ const ServiceList: React.FC = props => { - + onSort(ServiceListUrlSortField.name)} + className={classes.colName} + > - + diff --git a/src/services/components/ServiceListPage/ServiceListPage.stories.tsx b/src/services/components/ServiceListPage/ServiceListPage.stories.tsx index cf670a2c7..cfa666b1e 100644 --- a/src/services/components/ServiceListPage/ServiceListPage.stories.tsx +++ b/src/services/components/ServiceListPage/ServiceListPage.stories.tsx @@ -5,22 +5,29 @@ import { listActionsProps, pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "@saleor/fixtures"; import ServiceListPage, { ServiceListPageProps } from "@saleor/services/components/ServiceListPage"; import Decorator from "@saleor/storybook/Decorator"; +import { ServiceListUrlSortField } from "@saleor/services/urls"; import { serviceList } from "../../fixtures"; const props: ServiceListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...sortPageProps, ...tabPageProps, onBack: () => undefined, onRemove: () => undefined, - services: serviceList + services: serviceList, + sort: { + ...sortPageProps.sort, + sort: ServiceListUrlSortField.name + } }; storiesOf("Views / Services / Service list", module) diff --git a/src/services/components/ServiceListPage/ServiceListPage.tsx b/src/services/components/ServiceListPage/ServiceListPage.tsx index 4f6fd2326..93ee114bf 100644 --- a/src/services/components/ServiceListPage/ServiceListPage.tsx +++ b/src/services/components/ServiceListPage/ServiceListPage.tsx @@ -8,13 +8,20 @@ import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import SearchBar from "@saleor/components/SearchBar"; import { sectionNames } from "@saleor/intl"; -import { PageListProps, SearchPageProps, TabPageProps } from "@saleor/types"; +import { + PageListProps, + SearchPageProps, + TabPageProps, + SortPage +} from "@saleor/types"; +import { ServiceListUrlSortField } from "@saleor/services/urls"; import { ServiceList_serviceAccounts_edges_node } from "../../types/ServiceList"; import ServiceList from "../ServiceList"; export interface ServiceListPageProps extends PageListProps, SearchPageProps, + SortPage, TabPageProps { services: ServiceList_serviceAccounts_edges_node[]; onBack: () => void; diff --git a/src/services/index.tsx b/src/services/index.tsx index 630aa5e0e..7c97a2577 100644 --- a/src/services/index.tsx +++ b/src/services/index.tsx @@ -4,13 +4,15 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { serviceAddPath, serviceListPath, ServiceListUrlQueryParams, servicePath, - ServiceUrlQueryParams + ServiceUrlQueryParams, + ServiceListUrlSortField } from "./urls"; import ServiceCreate from "./views/ServiceCreate"; import ServiceDetailsComponent from "./views/ServiceDetails"; @@ -18,7 +20,10 @@ import ServiceListComponent from "./views/ServiceList"; const ServiceList: React.FC = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: ServiceListUrlQueryParams = qs; + const params: ServiceListUrlQueryParams = asSortParams( + qs, + ServiceListUrlSortField + ); return ; }; diff --git a/src/services/queries.ts b/src/services/queries.ts index 7263670d3..c6e224820 100644 --- a/src/services/queries.ts +++ b/src/services/queries.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { pageInfoFragment, TypedQuery } from "../queries"; import { ServiceDetails, @@ -24,6 +25,7 @@ const serviceList = gql` $last: Int $before: String $filter: ServiceAccountFilterInput + $sort: ServiceAccountSortingInput ) { serviceAccounts( first: $first @@ -31,6 +33,7 @@ const serviceList = gql` before: $before last: $last filter: $filter + sortBy: $sort ) { edges { node { @@ -43,7 +46,7 @@ const serviceList = gql` } } `; -export const ServiceListQuery = TypedQuery( +export const useServiceListQuery = makeQuery( serviceList ); diff --git a/src/services/types/ServiceList.ts b/src/services/types/ServiceList.ts index 4e5c413c3..c03bf7bea 100644 --- a/src/services/types/ServiceList.ts +++ b/src/services/types/ServiceList.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { ServiceAccountFilterInput } from "./../../types/globalTypes"; +import { ServiceAccountFilterInput, ServiceAccountSortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: ServiceList @@ -44,4 +44,5 @@ export interface ServiceListVariables { last?: number | null; before?: string | null; filter?: ServiceAccountFilterInput | null; + sort?: ServiceAccountSortingInput | null; } diff --git a/src/services/urls.ts b/src/services/urls.ts index 3fb758a6d..d14e8e2f0 100644 --- a/src/services/urls.ts +++ b/src/services/urls.ts @@ -7,7 +7,8 @@ import { Filters, Pagination, SingleAction, - TabActionDialog + TabActionDialog, + Sort } from "../types"; export const serviceSection = "/services/"; @@ -18,10 +19,16 @@ export enum ServiceListUrlFiltersEnum { } export type ServiceListUrlFilters = Filters; export type ServiceListUrlDialog = "remove" | TabActionDialog; +export enum ServiceListUrlSortField { + name = "name", + active = "active" +} +export type ServiceListUrlSort = Sort; export type ServiceListUrlQueryParams = ActiveTab & - ServiceListUrlFilters & Dialog & Pagination & + ServiceListUrlFilters & + ServiceListUrlSort & SingleAction; export const serviceListUrl = (params?: ServiceListUrlQueryParams) => serviceListPath + "?" + stringifyQs(params); diff --git a/src/services/views/ServiceList/ServiceList.tsx b/src/services/views/ServiceList/ServiceList.tsx index 4a8dd2c85..26c3115b9 100644 --- a/src/services/views/ServiceList/ServiceList.tsx +++ b/src/services/views/ServiceList/ServiceList.tsx @@ -18,9 +18,11 @@ import { maybe } from "@saleor/misc"; import { ServiceDeleteMutation } from "@saleor/services/mutations"; import { ServiceDelete } from "@saleor/services/types/ServiceDelete"; import { ListViews } from "@saleor/types"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; import ServiceDeleteDialog from "../../components/ServiceDeleteDialog"; import ServiceListPage from "../../components/ServiceListPage"; -import { ServiceListQuery } from "../../queries"; +import { useServiceListQuery } from "../../queries"; import { serviceAddUrl, serviceListUrl, @@ -37,6 +39,7 @@ import { getFilterVariables, saveFilterTab } from "./filter"; +import { getSortQueryVariables } from "./sort"; interface ServiceListProps { params: ServiceListUrlQueryParams; @@ -51,6 +54,20 @@ export const ServiceList: React.FC = ({ params }) => { ); const intl = useIntl(); + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading, refetch } = useServiceListQuery({ + displayLoader: true, + variables: queryVariables + }); + const tabs = getFilterTabs(); const currentTab = @@ -107,111 +124,100 @@ export const ServiceList: React.FC = ({ params }) => { handleTabChange(tabs.length + 1); }; - const paginationState = createPaginationState(settings.rowNumber, params); - const queryVariables = React.useMemo( - () => ({ - ...paginationState, - filter: getFilterVariables(params) - }), - [params] + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.serviceAccounts.pageInfo), + paginationState, + params ); - return ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.serviceAccounts.pageInfo), - paginationState, - params - ); + const handleCreate = () => navigate(serviceAddUrl); + const handleRemove = (id: string) => + navigate( + serviceListUrl({ + ...params, + action: "remove", + id + }) + ); + const onRemove = (data: ServiceDelete) => { + if (data.serviceAccountDelete.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + closeModal(); + refetch(); + } + }; - const handleCreate = () => navigate(serviceAddUrl); - const handleRemove = (id: string) => - navigate( - serviceListUrl({ - ...params, - action: "remove", - id - }) - ); - const onRemove = (data: ServiceDelete) => { - if (data.serviceAccountDelete.errors.length === 0) { - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - closeModal(); - refetch(); - } - }; + const handleSort = createSortHandler(navigate, serviceListUrl, params); + + return ( + + {(deleteService, deleteServiceOpts) => { + const handleRemoveConfirm = () => + deleteService({ + variables: { + id: params.id + } + }); return ( - - {(deleteService, deleteServiceOpts) => { - const handleRemoveConfirm = () => - deleteService({ - variables: { - id: params.id - } - }); - - return ( - <> - changeFilterField({ query })} - onAll={() => navigate(serviceListUrl())} - onTabChange={handleTabChange} - onTabDelete={() => openModal("delete-search")} - onTabSave={() => openModal("save-search")} - tabs={tabs.map(tab => tab.name)} - disabled={loading} - settings={settings} - pageInfo={pageInfo} - services={maybe(() => - data.serviceAccounts.edges.map(edge => edge.node) - )} - onAdd={handleCreate} - onBack={() => navigate(configurationMenuUrl)} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - onRowClick={id => () => navigate(serviceUrl(id))} - onRemove={handleRemove} - /> - - data.serviceAccounts.edges.find( - edge => edge.node.id === params.id - ).node.name, - "..." - )} - onClose={closeModal} - onConfirm={handleRemoveConfirm} - open={params.action === "remove"} - /> - - tabs[currentTab - 1].name, "...")} - /> - - ); - }} - + <> + changeFilterField({ query })} + onAll={() => navigate(serviceListUrl())} + onTabChange={handleTabChange} + onTabDelete={() => openModal("delete-search")} + onTabSave={() => openModal("save-search")} + tabs={tabs.map(tab => tab.name)} + disabled={loading} + settings={settings} + pageInfo={pageInfo} + services={maybe(() => + data.serviceAccounts.edges.map(edge => edge.node) + )} + sort={getSortParams(params)} + onAdd={handleCreate} + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(serviceUrl(id))} + onRemove={handleRemove} + onSort={handleSort} + /> + + data.serviceAccounts.edges.find( + edge => edge.node.id === params.id + ).node.name, + "..." + )} + onClose={closeModal} + onConfirm={handleRemoveConfirm} + open={params.action === "remove"} + /> + + tabs[currentTab - 1].name, "...")} + /> + ); }} - + ); }; diff --git a/src/services/views/ServiceList/sort.ts b/src/services/views/ServiceList/sort.ts new file mode 100644 index 000000000..a4fc01c77 --- /dev/null +++ b/src/services/views/ServiceList/sort.ts @@ -0,0 +1,18 @@ +import { ServiceListUrlSortField } from "@saleor/services/urls"; +import { ServiceAccountSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: ServiceListUrlSortField +): ServiceAccountSortField { + switch (sort) { + case ServiceListUrlSortField.name: + return ServiceAccountSortField.NAME; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/staff/components/StaffList/StaffList.tsx b/src/staff/components/StaffList/StaffList.tsx index af1df7725..de2232f7d 100644 --- a/src/staff/components/StaffList/StaffList.tsx +++ b/src/staff/components/StaffList/StaffList.tsx @@ -18,7 +18,10 @@ import { maybe, renderCollection } from "@saleor/misc"; -import { ListProps } from "@saleor/types"; +import { ListProps, SortPage } from "@saleor/types"; +import { StaffListUrlSortField } from "@saleor/staff/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; import { StaffList_staffUsers_edges_node } from "../../types/StaffList"; const useStyles = makeStyles( @@ -61,7 +64,7 @@ const useStyles = makeStyles( { name: "StaffList" } ); -interface StaffListProps extends ListProps { +interface StaffListProps extends ListProps, SortPage { staffMembers: StaffList_staffUsers_edges_node[]; } @@ -73,7 +76,9 @@ const StaffList: React.FC = props => { onPreviousPage, onUpdateListSettings, onRowClick, + onSort, pageInfo, + sort, staffMembers } = props; @@ -84,15 +89,31 @@ const StaffList: React.FC = props => { - + onSort(StaffListUrlSortField.name)} + className={classes.wideColumn} + > - - + + onSort(StaffListUrlSortField.email)} + > - + diff --git a/src/staff/components/StaffListPage/StaffListPage.tsx b/src/staff/components/StaffListPage/StaffListPage.tsx index 7be57c1d1..6cd2d1211 100644 --- a/src/staff/components/StaffListPage/StaffListPage.tsx +++ b/src/staff/components/StaffListPage/StaffListPage.tsx @@ -8,13 +8,20 @@ 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, SearchPageProps, TabPageProps } from "@saleor/types"; +import { + ListProps, + SearchPageProps, + TabPageProps, + SortPage +} from "@saleor/types"; +import { StaffListUrlSortField } from "@saleor/staff/urls"; import { StaffList_staffUsers_edges_node } from "../../types/StaffList"; import StaffList from "../StaffList/StaffList"; export interface StaffListPageProps extends ListProps, SearchPageProps, + SortPage, TabPageProps { staffMembers: StaffList_staffUsers_edges_node[]; onAdd: () => void; diff --git a/src/staff/index.tsx b/src/staff/index.tsx index 1592929b5..71541c889 100644 --- a/src/staff/index.tsx +++ b/src/staff/index.tsx @@ -4,19 +4,25 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { staffListPath, StaffListUrlQueryParams, staffMemberDetailsPath, - StaffMemberDetailsUrlQueryParams + StaffMemberDetailsUrlQueryParams, + StaffListUrlSortField } from "./urls"; import StaffDetailsComponent from "./views/StaffDetails"; import StaffListComponent from "./views/StaffList"; const StaffList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: StaffListUrlQueryParams = qs; + const params: StaffListUrlQueryParams = asSortParams( + qs, + StaffListUrlSortField + ); + return ; }; diff --git a/src/staff/queries.ts b/src/staff/queries.ts index fc33936c2..382b12c82 100644 --- a/src/staff/queries.ts +++ b/src/staff/queries.ts @@ -1,4 +1,5 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { TypedQuery } from "../queries"; import { StaffList, StaffListVariables } from "./types/StaffList"; import { @@ -36,6 +37,7 @@ const staffList = gql` $last: Int $before: String $filter: StaffUserInput + $sort: UserSortingInput ) { staffUsers( before: $before @@ -43,6 +45,7 @@ const staffList = gql` first: $first last: $last filter: $filter + sortBy: $sort ) { edges { cursor @@ -59,7 +62,7 @@ const staffList = gql` } } `; -export const TypedStaffListQuery = TypedQuery( +export const useStaffListQuery = makeQuery( staffList ); diff --git a/src/staff/types/StaffList.ts b/src/staff/types/StaffList.ts index 67d8117ef..14e4df092 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 { StaffUserInput } from "./../../types/globalTypes"; +import { StaffUserInput, UserSortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: StaffList @@ -53,4 +53,5 @@ export interface StaffListVariables { last?: number | null; before?: string | null; filter?: StaffUserInput | null; + sort?: UserSortingInput | null; } diff --git a/src/staff/urls.ts b/src/staff/urls.ts index b99a09235..970299df9 100644 --- a/src/staff/urls.ts +++ b/src/staff/urls.ts @@ -7,7 +7,8 @@ import { Dialog, Filters, Pagination, - TabActionDialog + TabActionDialog, + Sort } from "../types"; const staffSection = "/staff/"; @@ -18,11 +19,17 @@ export enum StaffListUrlFiltersEnum { } export type StaffListUrlFilters = Filters; export type StaffListUrlDialog = "add" | "remove" | TabActionDialog; +export enum StaffListUrlSortField { + name = "name", + email = "email" +} +export type StaffListUrlSort = Sort; export type StaffListUrlQueryParams = ActiveTab & BulkAction & Dialog & Pagination & - StaffListUrlFilters; + StaffListUrlFilters & + StaffListUrlSort; export const staffListUrl = (params?: StaffListUrlQueryParams) => staffListPath + "?" + stringifyQs(params); diff --git a/src/staff/views/StaffList/StaffList.tsx b/src/staff/views/StaffList/StaffList.tsx index ca8e1d03f..1ec2e7483 100644 --- a/src/staff/views/StaffList/StaffList.tsx +++ b/src/staff/views/StaffList/StaffList.tsx @@ -20,12 +20,14 @@ import useShop from "@saleor/hooks/useShop"; import { commonMessages } from "@saleor/intl"; import { maybe } from "@saleor/misc"; import { ListViews } from "@saleor/types"; +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; import StaffAddMemberDialog, { FormData as AddStaffMemberForm } from "../../components/StaffAddMemberDialog"; import StaffListPage from "../../components/StaffListPage"; import { TypedStaffMemberAddMutation } from "../../mutations"; -import { TypedStaffListQuery } from "../../queries"; +import { useStaffListQuery } from "../../queries"; import { StaffMemberAdd } from "../../types/StaffMemberAdd"; import { staffListUrl, @@ -42,6 +44,7 @@ import { getFilterVariables, saveFilterTab } from "./filter"; +import { getSortQueryVariables } from "./sort"; interface StaffListProps { params: StaffListUrlQueryParams; @@ -57,6 +60,20 @@ export const StaffList: React.FC = ({ params }) => { const intl = useIntl(); const shop = useShop(); + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading } = useStaffListQuery({ + displayLoader: true, + variables: queryVariables + }); + const tabs = getFilterTabs(); const currentTab = @@ -113,118 +130,105 @@ export const StaffList: React.FC = ({ params }) => { handleTabChange(tabs.length + 1); }; - const paginationState = createPaginationState(settings.rowNumber, params); - const queryVariables = React.useMemo( - () => ({ - ...paginationState, - filter: getFilterVariables(params) - }), - [params] - ); + const handleStaffMemberAddSuccess = (data: StaffMemberAdd) => { + if (data.staffCreate.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + navigate(staffMemberDetailsUrl(data.staffCreate.user.id)); + } + }; return ( - - {({ data, loading }) => { - const handleStaffMemberAddSuccess = (data: StaffMemberAdd) => { - if (data.staffCreate.errors.length === 0) { - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - navigate(staffMemberDetailsUrl(data.staffCreate.user.id)); - } - }; + + {(addStaffMember, addStaffMemberData) => { + const handleStaffMemberAdd = (variables: AddStaffMemberForm) => + addStaffMember({ + variables: { + input: { + email: variables.email, + firstName: variables.firstName, + lastName: variables.lastName, + permissions: variables.fullAccess + ? maybe(() => shop.permissions.map(perm => perm.code)) + : undefined, + redirectUrl: urlJoin( + window.location.origin, + APP_MOUNT_URI === "/" ? "" : APP_MOUNT_URI, + newPasswordUrl().replace(/\?/, "") + ), + sendPasswordEmail: true + } + } + }); + + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.staffUsers.pageInfo), + paginationState, + params + ); + + const handleSort = createSortHandler(navigate, staffListUrl, params); return ( - - {(addStaffMember, addStaffMemberData) => { - const handleStaffMemberAdd = (variables: AddStaffMemberForm) => - addStaffMember({ - variables: { - input: { - email: variables.email, - firstName: variables.firstName, - lastName: variables.lastName, - permissions: variables.fullAccess - ? maybe(() => shop.permissions.map(perm => perm.code)) - : undefined, - redirectUrl: urlJoin( - window.location.origin, - APP_MOUNT_URI === "/" ? "" : APP_MOUNT_URI, - newPasswordUrl().replace(/\?/, "") - ), - sendPasswordEmail: true - } - } - }); - - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.staffUsers.pageInfo), - paginationState, - params - ); - - 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} - staffMembers={maybe(() => - data.staffUsers.edges.map(edge => edge.node) - )} - onAdd={() => - navigate( - staffListUrl({ - action: "add" - }) - ) - } - onBack={() => navigate(configurationMenuUrl)} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onUpdateListSettings={updateListSettings} - onRowClick={id => () => navigate(staffMemberDetailsUrl(id))} - /> - addStaffMemberData.data.staffCreate.errors, - [] - )} - open={params.action === "add"} - onClose={closeModal} - onConfirm={handleStaffMemberAdd} - /> - - tabs[currentTab - 1].name, "...")} - /> - - ); - }} - + <> + 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} + sort={getSortParams(params)} + staffMembers={maybe(() => + data.staffUsers.edges.map(edge => edge.node) + )} + onAdd={() => + navigate( + staffListUrl({ + action: "add" + }) + ) + } + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(staffMemberDetailsUrl(id))} + onSort={handleSort} + /> + addStaffMemberData.data.staffCreate.errors, + [] + )} + open={params.action === "add"} + onClose={closeModal} + onConfirm={handleStaffMemberAdd} + /> + + tabs[currentTab - 1].name, "...")} + /> + ); }} - + ); }; diff --git a/src/staff/views/StaffList/sort.ts b/src/staff/views/StaffList/sort.ts new file mode 100644 index 000000000..cdda48321 --- /dev/null +++ b/src/staff/views/StaffList/sort.ts @@ -0,0 +1,18 @@ +import { StaffListUrlSortField } from "@saleor/staff/urls"; +import { UserSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField(sort: StaffListUrlSortField): UserSortField { + switch (sort) { + case StaffListUrlSortField.name: + return UserSortField.LAST_NAME; + case StaffListUrlSortField.email: + return UserSortField.EMAIL; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index a03cbb98b..ea6f6d9ee 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -11651,34 +11651,93 @@ exports[`Storyshots Views / Attributes / Attribute list default 1`] = ` - Attribute Code +
+
+ Attribute Code +
+
- Default Label +
+ +
+ Default Label +
+
- Visible +
+
+ Visible +
+
- Searchable +
+
+ Searchable +
+
- Use in faceted search +
+
+ Use in faceted search +
+
@@ -12573,34 +12632,93 @@ exports[`Storyshots Views / Attributes / Attribute list loading 1`] = ` - Attribute Code +
+
+ Attribute Code +
+
- Default Label +
+ +
+ Default Label +
+
- Visible +
+
+ Visible +
+
- Searchable +
+
+ Searchable +
+
- Use in faceted search +
+
+ Use in faceted search +
+
@@ -12891,34 +13009,93 @@ exports[`Storyshots Views / Attributes / Attribute list no data 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - Attribute Code +
+
+ Attribute Code +
+
- Default Label +
+ +
+ Default Label +
+
- Visible +
+
+ Visible +
+
- Searchable +
+
+ Searchable +
+
- Use in faceted search +
+
+ Use in faceted search +
+
@@ -14091,22 +14268,65 @@ exports[`Storyshots Views / Categories / Category list default 1`] = ` - Category Name +
+
+ Category Name +
+ +
- Subcategories +
+
+ Subcategories +
+
- No. of Products +
+
+ No. of Products +
+
@@ -14581,22 +14801,65 @@ exports[`Storyshots Views / Categories / Category list empty 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - Category Name +
+
+ Category Name +
+ +
- Subcategories +
+
+ Subcategories +
+
- No. of Products +
+
+ No. of Products +
+
@@ -14875,22 +15138,65 @@ exports[`Storyshots Views / Categories / Category list loading 1`] = ` - Category Name +
+
+ Category Name +
+ +
- Subcategories +
+
+ Subcategories +
+
- No. of Products +
+
+ No. of Products +
+
@@ -16836,22 +17142,46 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > - Category Name +
+
+ Category Name +
+
- Subcategories +
+
+ Subcategories +
+
- No. of Products +
+
+ No. of Products +
+
@@ -17549,22 +17879,46 @@ Ctrl + K" - Category Name +
+
+ Category Name +
+
- Subcategories +
+
+ Subcategories +
+
- No. of Products +
+
+ No. of Products +
+
@@ -18522,22 +18876,46 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > - Category Name +
+
+ Category Name +
+
- Subcategories +
+
+ Subcategories +
+
- No. of Products +
+
+ No. of Products +
+
@@ -20503,22 +20881,46 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > - Category Name +
+
+ Category Name +
+
- Subcategories +
+
+ Subcategories +
+
- No. of Products +
+
+ No. of Products +
+
@@ -26013,22 +26415,65 @@ exports[`Storyshots Views / Collections / Collection list default 1`] = ` - Category Name +
+
+ Collection Name +
+ +
- No. of Products +
+
+ No. of Products +
+
- Availability +
+
+ Availability +
+
@@ -26558,22 +27003,65 @@ exports[`Storyshots Views / Collections / Collection list loading 1`] = ` - Category Name +
+
+ Collection Name +
+ +
- No. of Products +
+
+ No. of Products +
+
- Availability +
+
+ Availability +
+
@@ -26878,22 +27366,65 @@ exports[`Storyshots Views / Collections / Collection list no data 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - Category Name +
+
+ Collection Name +
+ +
- No. of Products +
+
+ No. of Products +
+
- Availability +
+
+ Availability +
+
@@ -36805,22 +37336,65 @@ exports[`Storyshots Views / Customers / Customer list default 1`] = ` - Customer Name +
+
+ Customer Name +
+ +
- Customer Email +
+
+ Customer Email +
+
- No. of Orders +
+
+ No. of Orders +
+
@@ -37786,9 +38360,8 @@ exports[`Storyshots Views / Customers / Customer list loading 1`] = ` class="PageHeader-root-id" > - Name +
+
+ Name +
+ +
- Starts +
+
+ Starts +
+
- Ends +
+
+ Ends +
+
- Value +
+
+ Value +
+
@@ -44215,28 +44925,79 @@ exports[`Storyshots Views / Discounts / Sale list loading 1`] = ` - Name +
+
+ Name +
+ +
- Starts +
+
+ Starts +
+
- Ends +
+
+ Ends +
+
- Value +
+
+ Value +
+
@@ -44544,28 +45305,79 @@ exports[`Storyshots Views / Discounts / Sale list no data 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - Name +
+
+ Name +
+ +
- Starts +
+
+ Starts +
+
- Ends +
+
+ Ends +
+
- Value +
+
+ Value +
+
@@ -49729,40 +50541,107 @@ exports[`Storyshots Views / Discounts / Voucher list default 1`] = ` - Code +
+
+ Code +
+ +
- Min. Spent +
+
+ Min. Spent +
+
- Starts +
+
+ Starts +
+
- Ends +
+
+ Ends +
+
- Value +
+
+ Value +
+
- Uses +
+
+ Uses +
+
@@ -50141,40 +51020,107 @@ exports[`Storyshots Views / Discounts / Voucher list loading 1`] = ` - Code +
+
+ Code +
+ +
- Min. Spent +
+
+ Min. Spent +
+
- Starts +
+
+ Starts +
+
- Ends +
+
+ Ends +
+
- Value +
+
+ Value +
+
- Uses +
+
+ Uses +
+
@@ -50500,40 +51446,107 @@ exports[`Storyshots Views / Discounts / Voucher list no data 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - Code +
+
+ Code +
+ +
- Min. Spent +
+
+ Min. Spent +
+
- Starts +
+
+ Starts +
+
- Ends +
+
+ Ends +
+
- Value +
+
+ Value +
+
- Uses +
+
+ Uses +
+
@@ -53918,16 +54931,51 @@ exports[`Storyshots Views / Navigation / Menu list default 1`] = ` - Menu Title +
+
+ Menu Title +
+ +
- Items +
+
+ Items +
+
- No. of Order +
+
+ No. of Order +
+ +
- Date +
+
+ Date +
+
- Customer +
+
+ Customer +
+
- Total +
+
+ Total +
+
@@ -56366,28 +57585,79 @@ exports[`Storyshots Views / Orders / Draft order list when no data 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - No. of Order +
+
+ No. of Order +
+ +
- Date +
+
+ Date +
+
- Customer +
+
+ Customer +
+
- Total +
+
+ Total +
+
@@ -71864,40 +73134,107 @@ exports[`Storyshots Views / Orders / Order list default 1`] = ` - No. of Order +
+
+ No. of Order +
+ +
- Date +
+
+ Date +
+
- Customer +
+
+ Customer +
+
- Payment +
+
+ Payment +
+
- Fulfillment status +
+
+ Fulfillment status +
+
- Total +
+
+ Total +
+
@@ -73452,40 +74789,107 @@ exports[`Storyshots Views / Orders / Order list loading 1`] = ` - No. of Order +
+
+ No. of Order +
+ +
- Date +
+
+ Date +
+
- Customer +
+
+ Customer +
+
- Payment +
+
+ Payment +
+
- Fulfillment status +
+
+ Fulfillment status +
+
- Total +
+
+ Total +
+
@@ -73838,40 +75242,107 @@ exports[`Storyshots Views / Orders / Order list when no data 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - No. of Order +
+
+ No. of Order +
+ +
- Date +
+
+ Date +
+
- Customer +
+
+ Customer +
+
- Payment +
+
+ Payment +
+
- Fulfillment status +
+
+ Fulfillment status +
+
- Total +
+
+ Total +
+
@@ -74569,40 +76040,107 @@ exports[`Storyshots Views / Orders / Order list with custom filters 1`] = ` - No. of Order +
+
+ No. of Order +
+ +
- Date +
+
+ Date +
+
- Customer +
+
+ Customer +
+
- Payment +
+
+ Payment +
+
- Fulfillment status +
+
+ Fulfillment status +
+
- Total +
+
+ Total +
+
@@ -78545,22 +80083,65 @@ exports[`Storyshots Views / Pages / Page list default 1`] = ` - Title +
+
+ Title +
+ +
- Slug +
+
+ Slug +
+
- Visibility +
+
+ Visibility +
+
@@ -78699,22 +80280,46 @@ exports[`Storyshots Views / Pages / Page list default 1`] = ` - About - - - about - -
- Published +
+ About +
+
+ + +
+
+ about +
+
+ + +
+
+
+ Published +
+
@@ -78737,22 +80342,46 @@ exports[`Storyshots Views / Pages / Page list default 1`] = ` - About - - - about - -
- Not Published +
+ About +
+
+ + +
+
+ about +
+
+ + +
+
+
+ Not Published +
+
@@ -78775,22 +80404,46 @@ exports[`Storyshots Views / Pages / Page list default 1`] = ` - About - - - about - -
- Published +
+ About +
+
+ + +
+
+ about +
+
+ + +
+
+
+ Published +
+
@@ -78813,22 +80466,46 @@ exports[`Storyshots Views / Pages / Page list default 1`] = ` - About - - - about - -
- Published +
+ About +
+
+ + +
+
+ about +
+
+ + +
+
+
+ Published +
+
@@ -78862,9 +80539,8 @@ exports[`Storyshots Views / Pages / Page list loading 1`] = ` class="PageHeader-root-id" > - - ‌ - +
+ + ‌ + +
+
- - ‌ - +
+ + ‌ + +
+ - - ‌ - +
+ + ‌ + +
+ @@ -79153,22 +80896,65 @@ exports[`Storyshots Views / Pages / Page list no data 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - Title +
+
+ Title +
+ +
- Slug +
+
+ Slug +
+
- Visibility +
+
+ Visibility +
+
@@ -80413,16 +82199,51 @@ exports[`Storyshots Views / Plugins / Plugin list default 1`] = ` > - Name +
+
+ Name +
+ +
- Active +
+
+ Active +
+
- Name +
+
+ Name +
+ +
- Active +
+
+ Active +
+
- Name +
+
+ Name +
+ +
- Active +
+
+ Active +
+
- Type Name +
+
+ Type Name +
+ +
- Type +
+
+ Type +
+
- Type Name +
+
+ Type Name +
+ +
- Type +
+
+ Type +
+
- Type Name +
+
+ Type Name +
+ +
- Type +
+
+ Type +
+
- Name +
+
+ Name +
+ +
- Name +
+
+ Name +
+ +
- Name +
+
+ Name +
+ +
- Name +
+
+ Name +
+ +
- Email Address +
+
+ Email Address +
+
@@ -123533,16 +125645,51 @@ exports[`Storyshots Views / Staff / Staff members when loading 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - Name +
+
+ Name +
+ +
- Email Address +
+
+ Email Address +
+
@@ -128869,16 +131016,51 @@ exports[`Storyshots Views / Webhooks / Webhook list default 1`] = ` class="MuiTableRow-root-id MuiTableRow-head-id" > - Name +
+
+ Name +
+ +
- Service Account +
+
+ Service Account +
+
- Name +
+
+ Name +
+ +
- Service Account +
+
+ Service Account +
+
- Name +
+
+ Name +
+ +
- Service Account +
+
+ Service Account +
+
undefined + onBack: () => undefined, + onSort: () => undefined, + sort: { + ...sortPageProps.sort, + sort: AttributeListUrlSortField.name + } }; storiesOf("Views / Attributes / Attribute list", module) diff --git a/src/storybook/stories/categories/CategoryListPage.tsx b/src/storybook/stories/categories/CategoryListPage.tsx index d488c2175..cc0536b27 100644 --- a/src/storybook/stories/categories/CategoryListPage.tsx +++ b/src/storybook/stories/categories/CategoryListPage.tsx @@ -1,24 +1,33 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import CategoryListPage from "../../../categories/components/CategoryListPage"; -import { categories } from "../../../categories/fixtures"; +import CategoryListPage, { + CategoryTableProps +} from "@saleor/categories/components/CategoryListPage"; +import { categories } from "@saleor/categories/fixtures"; import { listActionsProps, pageListProps, searchPageProps, - tabPageProps -} from "../../../fixtures"; + tabPageProps, + sortPageProps +} from "@saleor/fixtures"; +import { CategoryListUrlSortField } from "@saleor/categories/urls"; import Decorator from "../../Decorator"; -const categoryTableProps = { +const categoryTableProps: CategoryTableProps = { categories, - onAddCategory: undefined, - onCategoryClick: () => undefined, + onAdd: undefined, + onRowClick: () => undefined, ...listActionsProps, ...tabPageProps, + ...pageListProps.default, ...searchPageProps, - ...pageListProps.default + ...sortPageProps, + sort: { + ...sortPageProps.sort, + sort: CategoryListUrlSortField.name + } }; storiesOf("Views / Categories / Category list", module) diff --git a/src/storybook/stories/collections/CollectionListPage.tsx b/src/storybook/stories/collections/CollectionListPage.tsx index c3847a220..80b741a6f 100644 --- a/src/storybook/stories/collections/CollectionListPage.tsx +++ b/src/storybook/stories/collections/CollectionListPage.tsx @@ -1,6 +1,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; +import { CollectionListUrlSortField } from "@saleor/collections/urls"; import CollectionListPage, { CollectionListPageProps } from "../../../collections/components/CollectionListPage"; @@ -9,7 +10,8 @@ import { listActionsProps, pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; @@ -17,6 +19,11 @@ const props: CollectionListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...sortPageProps, + sort: { + ...sortPageProps.sort, + sort: CollectionListUrlSortField.name + }, ...tabPageProps, collections }; diff --git a/src/storybook/stories/customers/CustomerListPage.tsx b/src/storybook/stories/customers/CustomerListPage.tsx index ec00a8bc9..2ec0d992a 100644 --- a/src/storybook/stories/customers/CustomerListPage.tsx +++ b/src/storybook/stories/customers/CustomerListPage.tsx @@ -1,6 +1,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; +import { CustomerListUrlSortField } from "@saleor/customers/urls"; import CustomerListPage, { CustomerListPageProps } from "../../../customers/components/CustomerListPage"; @@ -9,7 +10,8 @@ import { listActionsProps, pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; @@ -17,8 +19,13 @@ const props: CustomerListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...sortPageProps, ...tabPageProps, - customers: customerList + customers: customerList, + sort: { + ...sortPageProps.sort, + sort: CustomerListUrlSortField.name + } }; storiesOf("Views / Customers / Customer list", module) diff --git a/src/storybook/stories/discounts/SaleListPage.tsx b/src/storybook/stories/discounts/SaleListPage.tsx index 3494bb9ef..07eb24509 100644 --- a/src/storybook/stories/discounts/SaleListPage.tsx +++ b/src/storybook/stories/discounts/SaleListPage.tsx @@ -1,6 +1,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; +import { SaleListUrlSortField } from "@saleor/discounts/urls"; import SaleListPage, { SaleListPageProps } from "../../../discounts/components/SaleListPage"; @@ -9,7 +10,8 @@ import { listActionsProps, pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; @@ -17,9 +19,14 @@ const props: SaleListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...sortPageProps, ...tabPageProps, defaultCurrency: "USD", - sales: saleList + sales: saleList, + sort: { + ...sortPageProps.sort, + sort: SaleListUrlSortField.name + } }; storiesOf("Views / Discounts / Sale list", module) diff --git a/src/storybook/stories/discounts/VoucherListPage.tsx b/src/storybook/stories/discounts/VoucherListPage.tsx index 7e53c7211..ffa4dff49 100644 --- a/src/storybook/stories/discounts/VoucherListPage.tsx +++ b/src/storybook/stories/discounts/VoucherListPage.tsx @@ -1,6 +1,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; +import { VoucherListUrlSortField } from "@saleor/discounts/urls"; import VoucherListPage, { VoucherListPageProps } from "../../../discounts/components/VoucherListPage"; @@ -9,7 +10,8 @@ import { listActionsProps, pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; @@ -17,8 +19,13 @@ const props: VoucherListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...sortPageProps, ...tabPageProps, defaultCurrency: "USD", + sort: { + ...sortPageProps.sort, + sort: VoucherListUrlSortField.code + }, vouchers: voucherList }; diff --git a/src/storybook/stories/navigation/MenuListPage.tsx b/src/storybook/stories/navigation/MenuListPage.tsx index 712476be5..34b4760db 100644 --- a/src/storybook/stories/navigation/MenuListPage.tsx +++ b/src/storybook/stories/navigation/MenuListPage.tsx @@ -1,7 +1,12 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import { listActionsProps, pageListProps } from "../../../fixtures"; +import { MenuListUrlSortField } from "@saleor/navigation/urls"; +import { + listActionsProps, + pageListProps, + sortPageProps +} from "../../../fixtures"; import MenuListPage, { MenuListPageProps } from "../../../navigation/components/MenuListPage"; @@ -11,9 +16,14 @@ import Decorator from "../../Decorator"; const props: MenuListPageProps = { ...pageListProps.default, ...listActionsProps, + ...sortPageProps, menus: menuList, onBack: () => undefined, - onDelete: () => undefined + onDelete: () => undefined, + sort: { + ...sortPageProps.sort, + sort: MenuListUrlSortField.name + } }; storiesOf("Views / Navigation / Menu list", module) diff --git a/src/storybook/stories/orders/OrderDraftListPage.tsx b/src/storybook/stories/orders/OrderDraftListPage.tsx index 5837e1f34..429a40b36 100644 --- a/src/storybook/stories/orders/OrderDraftListPage.tsx +++ b/src/storybook/stories/orders/OrderDraftListPage.tsx @@ -1,11 +1,13 @@ import { storiesOf } from "@storybook/react"; import React from "react"; +import { OrderDraftListUrlSortField } from "@saleor/orders/urls"; import { listActionsProps, pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "../../../fixtures"; import OrderDraftListPage, { OrderDraftListPageProps @@ -17,9 +19,14 @@ const props: OrderDraftListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...sortPageProps, ...tabPageProps, onAdd: () => undefined, - orders + orders, + sort: { + ...sortPageProps.sort, + sort: OrderDraftListUrlSortField.number + } }; storiesOf("Views / Orders / Draft order list", module) diff --git a/src/storybook/stories/orders/OrderListPage.tsx b/src/storybook/stories/orders/OrderListPage.tsx index bdd69d727..c3095aee4 100644 --- a/src/storybook/stories/orders/OrderListPage.tsx +++ b/src/storybook/stories/orders/OrderListPage.tsx @@ -4,11 +4,13 @@ import React from "react"; import OrderListPage, { OrderListPageProps } from "@saleor/orders/components/OrderListPage"; +import { OrderListUrlSortField } from "@saleor/orders/urls"; import { filterPageProps, filters, listActionsProps, - pageListProps + pageListProps, + sortPageProps } from "../../../fixtures"; import { orders } from "../../../orders/fixtures"; import Decorator from "../../Decorator"; @@ -17,7 +19,12 @@ const props: OrderListPageProps = { ...listActionsProps, ...pageListProps.default, ...filterPageProps, - orders + ...sortPageProps, + orders, + sort: { + ...sortPageProps.sort, + sort: OrderListUrlSortField.number + } }; storiesOf("Views / Orders / Order list", module) diff --git a/src/storybook/stories/pages/PageListPage.tsx b/src/storybook/stories/pages/PageListPage.tsx index 664eef121..8451be720 100644 --- a/src/storybook/stories/pages/PageListPage.tsx +++ b/src/storybook/stories/pages/PageListPage.tsx @@ -1,7 +1,12 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import { listActionsProps, pageListProps } from "../../../fixtures"; +import { PageListUrlSortField } from "@saleor/pages/urls"; +import { + listActionsProps, + pageListProps, + sortPageProps +} from "../../../fixtures"; import PageListPage, { PageListPageProps } from "../../../pages/components/PageListPage"; @@ -11,8 +16,13 @@ import Decorator from "../../Decorator"; const props: PageListPageProps = { ...listActionsProps, ...pageListProps.default, + ...sortPageProps, onBack: () => undefined, - pages: pageList + pages: pageList, + sort: { + ...sortPageProps.sort, + sort: PageListUrlSortField.title + } }; storiesOf("Views / Pages / Page list", module) diff --git a/src/storybook/stories/plugins/PluginsListPage.tsx b/src/storybook/stories/plugins/PluginsListPage.tsx index fd82f58c9..cf874005f 100644 --- a/src/storybook/stories/plugins/PluginsListPage.tsx +++ b/src/storybook/stories/plugins/PluginsListPage.tsx @@ -1,7 +1,8 @@ import { storiesOf } from "@storybook/react"; import React from "react"; -import { pageListProps } from "../../../fixtures"; +import { PluginListUrlSortField } from "@saleor/plugins/urls"; +import { pageListProps, sortPageProps } from "../../../fixtures"; import PluginsListPage, { PluginsListPageProps } from "../../../plugins/components/PluginsListPage"; @@ -10,8 +11,13 @@ import Decorator from "../../Decorator"; const props: PluginsListPageProps = { ...pageListProps.default, + ...sortPageProps, onBack: () => undefined, - plugins: pluginList + plugins: pluginList, + sort: { + ...sortPageProps.sort, + sort: PluginListUrlSortField.name + } }; storiesOf("Views / Plugins / Plugin list", module) diff --git a/src/storybook/stories/productTypes/ProductTypeListPage.tsx b/src/storybook/stories/productTypes/ProductTypeListPage.tsx index 5f1387a3a..3a339f83b 100644 --- a/src/storybook/stories/productTypes/ProductTypeListPage.tsx +++ b/src/storybook/stories/productTypes/ProductTypeListPage.tsx @@ -1,11 +1,13 @@ import { storiesOf } from "@storybook/react"; import React from "react"; +import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls"; import { listActionsProps, pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "../../../fixtures"; import ProductTypeListPage, { ProductTypeListPageProps @@ -17,6 +19,11 @@ const props: ProductTypeListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...sortPageProps, + sort: { + ...sortPageProps.sort, + sort: ProductTypeListUrlSortField.name + }, ...tabPageProps, onBack: () => undefined, productTypes diff --git a/src/storybook/stories/staff/StaffListPage.tsx b/src/storybook/stories/staff/StaffListPage.tsx index e18bb96f6..1c3d8c1cc 100644 --- a/src/storybook/stories/staff/StaffListPage.tsx +++ b/src/storybook/stories/staff/StaffListPage.tsx @@ -1,10 +1,12 @@ import { storiesOf } from "@storybook/react"; import React from "react"; +import { StaffListUrlSortField } from "@saleor/staff/urls"; import { pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "../../../fixtures"; import StaffListPage, { StaffListPageProps @@ -15,9 +17,14 @@ import Decorator from "../../Decorator"; const props: StaffListPageProps = { ...pageListProps.default, ...searchPageProps, + ...sortPageProps, ...tabPageProps, onAdd: undefined, onBack: () => undefined, + sort: { + ...sortPageProps.sort, + sort: StaffListUrlSortField.name + }, staffMembers }; diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 045577709..95347ef30 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -16,6 +16,20 @@ export enum AttributeInputTypeEnum { MULTISELECT = "MULTISELECT", } +export enum AttributeSortField { + AVAILABLE_IN_GRID = "AVAILABLE_IN_GRID", + DASHBOARD_PRODUCT_POSITION = "DASHBOARD_PRODUCT_POSITION", + DASHBOARD_VARIANT_POSITION = "DASHBOARD_VARIANT_POSITION", + FILTERABLE_IN_DASHBOARD = "FILTERABLE_IN_DASHBOARD", + FILTERABLE_IN_STOREFRONT = "FILTERABLE_IN_STOREFRONT", + IS_VARIANT_ONLY = "IS_VARIANT_ONLY", + NAME = "NAME", + SLUG = "SLUG", + STOREFRONT_SEARCH_POSITION = "STOREFRONT_SEARCH_POSITION", + VALUE_REQUIRED = "VALUE_REQUIRED", + VISIBLE_IN_STOREFRONT = "VISIBLE_IN_STOREFRONT", +} + export enum AttributeTypeEnum { PRODUCT = "PRODUCT", VARIANT = "VARIANT", @@ -33,11 +47,23 @@ export enum AuthorizationKeyType { GOOGLE_OAUTH2 = "GOOGLE_OAUTH2", } +export enum CategorySortField { + NAME = "NAME", + PRODUCT_COUNT = "PRODUCT_COUNT", + SUBCATEGORY_COUNT = "SUBCATEGORY_COUNT", +} + export enum CollectionPublished { HIDDEN = "HIDDEN", PUBLISHED = "PUBLISHED", } +export enum CollectionSortField { + AVAILABILITY = "AVAILABILITY", + NAME = "NAME", + PRODUCT_COUNT = "PRODUCT_COUNT", +} + export enum ConfigurationTypeFieldEnum { BOOLEAN = "BOOLEAN", PASSWORD = "PASSWORD", @@ -360,6 +386,11 @@ export enum LanguageCodeEnum { ZH_HANT = "ZH_HANT", } +export enum MenuSortField { + ITEMS_COUNT = "ITEMS_COUNT", + NAME = "NAME", +} + export enum OrderAction { CAPTURE = "CAPTURE", MARK_AS_PAID = "MARK_AS_PAID", @@ -405,6 +436,15 @@ export enum OrderEventsEnum { UPDATED_ADDRESS = "UPDATED_ADDRESS", } +export enum OrderSortField { + CREATION_DATE = "CREATION_DATE", + CUSTOMER = "CUSTOMER", + FULFILLMENT_STATUS = "FULFILLMENT_STATUS", + NUMBER = "NUMBER", + PAYMENT = "PAYMENT", + TOTAL = "TOTAL", +} + export enum OrderStatus { CANCELED = "CANCELED", DRAFT = "DRAFT", @@ -422,6 +462,14 @@ export enum OrderStatusFilter { UNFULFILLED = "UNFULFILLED", } +export enum PageSortField { + CREATION_DATE = "CREATION_DATE", + PUBLICATION_DATE = "PUBLICATION_DATE", + SLUG = "SLUG", + TITLE = "TITLE", + VISIBILITY = "VISIBILITY", +} + export enum PaymentChargeStatusEnum { FULLY_CHARGED = "FULLY_CHARGED", FULLY_REFUNDED = "FULLY_REFUNDED", @@ -448,6 +496,11 @@ export enum PermissionEnum { MANAGE_WEBHOOKS = "MANAGE_WEBHOOKS", } +export enum PluginSortField { + IS_ACTIVE = "IS_ACTIVE", + NAME = "NAME", +} + export enum ProductErrorCode { ALREADY_EXISTS = "ALREADY_EXISTS", ATTRIBUTE_ALREADY_ASSIGNED = "ATTRIBUTE_ALREADY_ASSIGNED", @@ -481,11 +534,30 @@ export enum ProductTypeEnum { SHIPPABLE = "SHIPPABLE", } +export enum ProductTypeSortField { + DIGITAL = "DIGITAL", + NAME = "NAME", + SHIPPING_REQUIRED = "SHIPPING_REQUIRED", +} + +export enum SaleSortField { + END_DATE = "END_DATE", + NAME = "NAME", + START_DATE = "START_DATE", + TYPE = "TYPE", + VALUE = "VALUE", +} + export enum SaleType { FIXED = "FIXED", PERCENTAGE = "PERCENTAGE", } +export enum ServiceAccountSortField { + CREATION_DATE = "CREATION_DATE", + NAME = "NAME", +} + export enum ShippingMethodTypeEnum { PRICE = "PRICE", WEIGHT = "WEIGHT", @@ -529,12 +601,29 @@ export enum TaxRateType { WINE = "WINE", } +export enum UserSortField { + EMAIL = "EMAIL", + FIRST_NAME = "FIRST_NAME", + LAST_NAME = "LAST_NAME", + ORDER_COUNT = "ORDER_COUNT", +} + export enum VoucherDiscountType { FIXED = "FIXED", PERCENTAGE = "PERCENTAGE", SHIPPING = "SHIPPING", } +export enum VoucherSortField { + CODE = "CODE", + END_DATE = "END_DATE", + MINIMUM_SPENT_AMOUNT = "MINIMUM_SPENT_AMOUNT", + START_DATE = "START_DATE", + TYPE = "TYPE", + USAGE_LIMIT = "USAGE_LIMIT", + VALUE = "VALUE", +} + export enum VoucherTypeEnum { ENTIRE_ORDER = "ENTIRE_ORDER", SHIPPING = "SHIPPING", @@ -560,6 +649,12 @@ export enum WebhookEventTypeEnum { PRODUCT_CREATED = "PRODUCT_CREATED", } +export enum WebhookSortField { + NAME = "NAME", + SERVICE_ACCOUNT = "SERVICE_ACCOUNT", + TARGET_URL = "TARGET_URL", +} + export enum WeightUnitsEnum { G = "G", KG = "KG", @@ -618,6 +713,11 @@ export interface AttributeInput { value: string; } +export interface AttributeSortingInput { + direction: OrderDirection; + field?: AttributeSortField | null; +} + export interface AttributeUpdateInput { name?: string | null; slug?: string | null; @@ -666,6 +766,11 @@ export interface CategoryInput { backgroundImageAlt?: string | null; } +export interface CategorySortingInput { + direction: OrderDirection; + field?: CategorySortField | null; +} + export interface CollectionCreateInput { isPublished?: boolean | null; name?: string | null; @@ -696,6 +801,11 @@ export interface CollectionInput { publicationDate?: any | null; } +export interface CollectionSortingInput { + direction: OrderDirection; + field?: CollectionSortField | null; +} + export interface ConfigurationItemInput { name: string; value?: string | null; @@ -794,6 +904,11 @@ export interface MenuItemMoveInput { sortOrder?: number | null; } +export interface MenuSortingInput { + direction: OrderDirection; + field?: MenuSortField | null; +} + export interface NameTranslationInput { name?: string | null; } @@ -825,6 +940,11 @@ export interface OrderLineInput { quantity: number; } +export interface OrderSortingInput { + direction: OrderDirection; + field?: OrderSortField | null; +} + export interface OrderUpdateInput { billingAddress?: AddressInput | null; userEmail?: string | null; @@ -849,6 +969,11 @@ export interface PageInput { seo?: SeoInput | null; } +export interface PageSortingInput { + direction: OrderDirection; + field?: PageSortField | null; +} + export interface PageTranslationInput { seoTitle?: string | null; seoDescription?: string | null; @@ -857,6 +982,11 @@ export interface PageTranslationInput { contentJson?: any | null; } +export interface PluginSortingInput { + direction: OrderDirection; + field?: PluginSortField | null; +} + export interface PluginUpdateInput { active?: boolean | null; configuration?: (ConfigurationItemInput | null)[] | null; @@ -881,9 +1011,9 @@ export interface ProductFilterInput { } export interface ProductOrder { - field?: ProductOrderField | null; - attributeId?: string | null; direction: OrderDirection; + attributeId?: string | null; + field?: ProductOrderField | null; } export interface ProductTypeFilterInput { @@ -903,6 +1033,11 @@ export interface ProductTypeInput { taxCode?: string | null; } +export interface ProductTypeSortingInput { + direction: OrderDirection; + field?: ProductTypeSortField | null; +} + export interface ProductVariantBulkCreateInput { attributes: (AttributeValueInput | null)[]; costPrice?: any | null; @@ -957,6 +1092,11 @@ export interface SaleInput { endDate?: any | null; } +export interface SaleSortingInput { + direction: OrderDirection; + field?: SaleSortField | null; +} + export interface SeoInput { title?: string | null; description?: string | null; @@ -973,6 +1113,11 @@ export interface ServiceAccountInput { permissions?: (PermissionEnum | null)[] | null; } +export interface ServiceAccountSortingInput { + direction: OrderDirection; + field?: ServiceAccountSortField | null; +} + export interface ServiceAccountTokenInput { name?: string | null; serviceAccount: string; @@ -1061,6 +1206,11 @@ export interface UserCreateInput { redirectUrl?: string | null; } +export interface UserSortingInput { + direction: OrderDirection; + field?: UserSortField | null; +} + export interface VoucherFilterInput { status?: (DiscountStatusEnum | null)[] | null; timesUsed?: IntRangeInput | null; @@ -1088,6 +1238,11 @@ export interface VoucherInput { usageLimit?: number | null; } +export interface VoucherSortingInput { + direction: OrderDirection; + field?: VoucherSortField | null; +} + export interface WebhookCreateInput { name?: string | null; targetUrl?: string | null; @@ -1102,6 +1257,11 @@ export interface WebhookFilterInput { isActive?: boolean | null; } +export interface WebhookSortingInput { + direction: OrderDirection; + field?: WebhookSortField | null; +} + export interface WebhookUpdateInput { name?: string | null; targetUrl?: string | null; diff --git a/src/utils/handlers/sortHandler.ts b/src/utils/handlers/sortHandler.ts new file mode 100644 index 000000000..c9656e960 --- /dev/null +++ b/src/utils/handlers/sortHandler.ts @@ -0,0 +1,22 @@ +import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; +import { Sort } from "@saleor/types"; +import { getSortUrlVariables } from "../sort"; + +type CreateUrl = (params: Sort) => string; + +function createSortHandler( + navigate: UseNavigatorResult, + createUrl: CreateUrl, + params: Sort +) { + return (field: T) => + navigate( + createUrl({ + ...params, + ...getSortUrlVariables(field, params) + }), + true + ); +} + +export default createSortHandler; diff --git a/src/utils/sort.ts b/src/utils/sort.ts index f5eabbd33..8f5e7fd77 100644 --- a/src/utils/sort.ts +++ b/src/utils/sort.ts @@ -1,3 +1,4 @@ +import { parseBoolean, findValueInEnum } from "@saleor/misc"; import { TableCellHeaderArrowDirection } from "../components/TableCellHeader"; import { Sort } from "../types"; import { OrderDirection } from "../types/globalTypes"; @@ -26,3 +27,60 @@ export function getOrderDirection(asc: boolean): OrderDirection { export function getArrowDirection(asc: boolean): TableCellHeaderArrowDirection { return asc ? "asc" : "desc"; } + +// Extracts Sort object from the querystring +export function getSortParams< + TParams extends Sort, + TFields extends string +>(params: TParams): Sort { + return { + asc: params.asc, + sort: params.sort + }; +} + +// Appends Sort object to the querystring params +export function asSortParams< + TParams extends Record, + TFields extends Record +>( + params: TParams, + fields: TFields, + defaultField?: keyof TFields, + defaultOrder?: boolean +): TParams & Sort { + return { + ...params, + asc: parseBoolean( + params.asc, + defaultOrder === undefined ? true : defaultOrder + ), + sort: params.sort + ? findValueInEnum(params.sort, fields) + : defaultField || "name" + }; +} + +interface SortingInput { + direction: OrderDirection; + field?: T | null; +} +type GetSortQueryField = ( + sort: TUrlField +) => TSortField; +type GetSortQueryVariables< + TSortField extends string, + TParams extends Record +> = (params: TParams) => SortingInput; +export function createGetSortQueryVariables< + TUrlField extends string, + TSortField extends string, + TParams extends Record +>( + getSortQueryField: GetSortQueryField +): GetSortQueryVariables { + return (params: TParams) => ({ + direction: getOrderDirection(params.asc), + field: getSortQueryField(params.sort) + }); +} diff --git a/src/webhooks/components/WebhooksList/WebhooksList.tsx b/src/webhooks/components/WebhooksList/WebhooksList.tsx index 5f53cd37e..14e49bd12 100644 --- a/src/webhooks/components/WebhooksList/WebhooksList.tsx +++ b/src/webhooks/components/WebhooksList/WebhooksList.tsx @@ -14,10 +14,15 @@ import ResponsiveTable from "@saleor/components/ResponsiveTable"; import Skeleton from "@saleor/components/Skeleton"; import TablePagination from "@saleor/components/TablePagination"; import { maybe, renderCollection, stopPropagation } from "@saleor/misc"; -import { ListProps } from "@saleor/types"; +import { ListProps, SortPage } from "@saleor/types"; +import { WebhookListUrlSortField } from "@saleor/webhooks/urls"; +import TableCellHeader from "@saleor/components/TableCellHeader"; +import { getArrowDirection } from "@saleor/utils/sort"; import { Webhooks_webhooks_edges_node } from "../../types/Webhooks"; -export interface WebhooksListProps extends ListProps { +export interface WebhooksListProps + extends ListProps, + SortPage { webhooks: Webhooks_webhooks_edges_node[]; onRemove: (id: string) => void; } @@ -62,8 +67,10 @@ const WebhooksList: React.FC = ({ disabled, onNextPage, pageInfo, + sort, onRowClick, onRemove, + onSort, onUpdateListSettings, onPreviousPage }) => { @@ -74,18 +81,35 @@ const WebhooksList: React.FC = ({ - + onSort(WebhookListUrlSortField.name)} + className={classes.colName} + > {intl.formatMessage({ defaultMessage: "Name", description: "webhook name" })} - - + + onSort(WebhookListUrlSortField.serviceAccount)} + className={classes.colActive} + > {intl.formatMessage({ defaultMessage: "Service Account", description: "webhook service account" })} - + {intl.formatMessage({ defaultMessage: "Action", diff --git a/src/webhooks/components/WebhooksListPage/WebhookListPage.stories.tsx b/src/webhooks/components/WebhooksListPage/WebhookListPage.stories.tsx index f772fdf9f..4c9314949 100644 --- a/src/webhooks/components/WebhooksListPage/WebhookListPage.stories.tsx +++ b/src/webhooks/components/WebhooksListPage/WebhookListPage.stories.tsx @@ -5,9 +5,11 @@ import { listActionsProps, pageListProps, searchPageProps, - tabPageProps + tabPageProps, + sortPageProps } from "@saleor/fixtures"; import Decorator from "@saleor/storybook/Decorator"; +import { WebhookListUrlSortField } from "@saleor/webhooks/urls"; import { webhookList } from "../../fixtures"; import WebhooksListPage, { WebhooksListPageProps } from "./WebhooksListPage"; @@ -15,9 +17,14 @@ const props: WebhooksListPageProps = { ...listActionsProps, ...pageListProps.default, ...searchPageProps, + ...sortPageProps, ...tabPageProps, onBack: () => undefined, onRemove: () => undefined, + sort: { + ...sortPageProps.sort, + sort: WebhookListUrlSortField.name + }, webhooks: webhookList }; diff --git a/src/webhooks/components/WebhooksListPage/WebhooksListPage.tsx b/src/webhooks/components/WebhooksListPage/WebhooksListPage.tsx index aa9d4c148..523b91b38 100644 --- a/src/webhooks/components/WebhooksListPage/WebhooksListPage.tsx +++ b/src/webhooks/components/WebhooksListPage/WebhooksListPage.tsx @@ -8,13 +8,20 @@ import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import SearchBar from "@saleor/components/SearchBar"; import { sectionNames } from "@saleor/intl"; -import { PageListProps, SearchPageProps, TabPageProps } from "@saleor/types"; +import { + PageListProps, + SearchPageProps, + TabPageProps, + SortPage +} from "@saleor/types"; +import { WebhookListUrlSortField } from "@saleor/webhooks/urls"; import { Webhooks_webhooks_edges_node } from "../../types/Webhooks"; import WebhooksList from "../WebhooksList/WebhooksList"; export interface WebhooksListPageProps extends PageListProps, SearchPageProps, + SortPage, TabPageProps { webhooks: Webhooks_webhooks_edges_node[]; onBack: () => void; diff --git a/src/webhooks/index.tsx b/src/webhooks/index.tsx index 0a84cea13..8d5113247 100644 --- a/src/webhooks/index.tsx +++ b/src/webhooks/index.tsx @@ -4,26 +4,32 @@ import { useIntl } from "react-intl"; import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { sectionNames } from "@saleor/intl"; +import { asSortParams } from "@saleor/utils/sort"; import { WindowTitle } from "../components/WindowTitle"; import { webhooksAddUrl, webhooksListPath, - WebhooksListUrlQueryParams, - webhooksPath + WebhookListUrlQueryParams, + webhooksPath, + WebhookListUrlSortField } from "./urls"; import WebhookCreate from "./views/WebhooksCreate"; import WebhooksDetails from "./views/WebhooksDetails"; -import WebhooksList from "./views/WebhooksList"; +import WebhooksList from "./views/WebhookList"; const WebhookList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); - const params: WebhooksListUrlQueryParams = qs; + const params: WebhookListUrlQueryParams = asSortParams( + qs, + WebhookListUrlSortField + ); + return ; }; const WebhookDetails: React.FC> = ({ match }) => { const qs = parseQs(location.search.substr(1)); - const params: WebhooksListUrlQueryParams = qs; + const params: WebhookListUrlQueryParams = qs; return ( diff --git a/src/webhooks/queries.ts b/src/webhooks/queries.ts index 27ee26fd4..e0e2ab190 100644 --- a/src/webhooks/queries.ts +++ b/src/webhooks/queries.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { TypedQuery } from "../queries"; import { WebhookDetails, @@ -34,6 +35,7 @@ const webhooksList = gql` $last: Int $before: String $filter: WebhookFilterInput + $sort: WebhookSortingInput ) { webhooks( first: $first @@ -41,6 +43,7 @@ const webhooksList = gql` before: $before last: $last filter: $filter + sortBy: $sort ) { edges { node { @@ -56,7 +59,7 @@ const webhooksList = gql` } } `; -export const TypedWebhooksListQuery = TypedQuery( +export const useWebhooksListQuery = makeQuery( webhooksList ); diff --git a/src/webhooks/types/Webhooks.ts b/src/webhooks/types/Webhooks.ts index d995aa7f5..0612d0247 100644 --- a/src/webhooks/types/Webhooks.ts +++ b/src/webhooks/types/Webhooks.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { WebhookFilterInput } from "./../../types/globalTypes"; +import { WebhookFilterInput, WebhookSortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: Webhooks @@ -51,4 +51,5 @@ export interface WebhooksVariables { last?: number | null; before?: string | null; filter?: WebhookFilterInput | null; + sort?: WebhookSortingInput | null; } diff --git a/src/webhooks/urls.ts b/src/webhooks/urls.ts index 7319aa36c..9d1b68037 100644 --- a/src/webhooks/urls.ts +++ b/src/webhooks/urls.ts @@ -7,7 +7,8 @@ import { Filters, Pagination, SingleAction, - TabActionDialog + TabActionDialog, + Sort } from "../types"; export const webhooksSection = "/webhooks/"; @@ -18,12 +19,18 @@ export enum WebhookListUrlFiltersEnum { } export type WebhookListUrlFilters = Filters; export type WebhookListUrlDialog = "remove" | TabActionDialog; -export type WebhooksListUrlQueryParams = ActiveTab & - WebhookListUrlFilters & +export enum WebhookListUrlSortField { + name = "name", + serviceAccount = "account" +} +export type WebhookListUrlSort = Sort; +export type WebhookListUrlQueryParams = ActiveTab & Dialog & Pagination & - SingleAction; -export const webhooksListUrl = (params?: WebhooksListUrlQueryParams) => + SingleAction & + WebhookListUrlFilters & + WebhookListUrlSort; +export const webhooksListUrl = (params?: WebhookListUrlQueryParams) => webhooksListPath + "?" + stringifyQs(params); export const webhooksPath = (id: string) => urlJoin(webhooksSection, id); diff --git a/src/webhooks/views/WebhookList/WebhookList.tsx b/src/webhooks/views/WebhookList/WebhookList.tsx new file mode 100644 index 000000000..bc26fba6d --- /dev/null +++ b/src/webhooks/views/WebhookList/WebhookList.tsx @@ -0,0 +1,219 @@ +import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; +import SaveFilterTabDialog, { + SaveFilterTabDialogFormData +} from "@saleor/components/SaveFilterTabDialog"; +import { configurationMenuUrl } from "@saleor/configuration"; +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 { maybe } from "@saleor/misc"; +import { ListViews } from "@saleor/types"; +import WebhookDeleteDialog from "@saleor/webhooks/components/WebhookDeleteDialog"; +import { WebhookDelete } from "@saleor/webhooks/types/WebhookDelete"; +import React from "react"; +import { useIntl } from "react-intl"; + +import { getSortParams } from "@saleor/utils/sort"; +import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import WebhooksListPage from "../../components/WebhooksListPage/WebhooksListPage"; +import { TypedWebhookDelete } from "../../mutations"; +import { useWebhooksListQuery } from "../../queries"; +import { + WebhookListUrlDialog, + WebhookListUrlFilters, + webhooksAddUrl, + webhooksListUrl, + WebhookListUrlQueryParams, + webhooksUrl +} from "../../urls"; +import { getSortQueryVariables } from "./sort"; +import { + areFiltersApplied, + deleteFilterTab, + getActiveFilters, + getFilterTabs, + getFilterVariables, + saveFilterTab +} from "./filter"; + +interface WebhooksListProps { + params: WebhookListUrlQueryParams; +} + +export const WebhooksList: React.FC = ({ params }) => { + const navigate = useNavigator(); + const paginate = usePaginator(); + const notify = useNotifier(); + const intl = useIntl(); + const { updateListSettings, settings } = useListSettings( + ListViews.WEBHOOK_LIST + ); + + const paginationState = createPaginationState(settings.rowNumber, params); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params) + }), + [params] + ); + const { data, loading, refetch } = useWebhooksListQuery({ + displayLoader: true, + variables: queryVariables + }); + + const tabs = getFilterTabs(); + + const currentTab = + params.activeTab === undefined + ? areFiltersApplied(params) + ? tabs.length + 1 + : 0 + : parseInt(params.activeTab, 0); + + const changeFilterField = (filter: WebhookListUrlFilters) => + navigate( + webhooksListUrl({ + ...getActiveFilters(params), + ...filter, + activeTab: undefined + }) + ); + const closeModal = () => + navigate( + webhooksListUrl({ + ...params, + action: undefined, + id: undefined + }), + true + ); + + const openModal = (action: WebhookListUrlDialog, id?: string) => + navigate( + webhooksListUrl({ + ...params, + action, + id + }) + ); + + const handleTabChange = (tab: number) => { + navigate( + webhooksListUrl({ + activeTab: tab.toString(), + ...getFilterTabs()[tab - 1].data + }) + ); + }; + + const handleTabDelete = () => { + deleteFilterTab(currentTab); + navigate(webhooksListUrl()); + }; + + const handleTabSave = (data: SaveFilterTabDialogFormData) => { + saveFilterTab(data.name, getActiveFilters(params)); + handleTabChange(tabs.length + 1); + }; + + const onWebhookDelete = (data: WebhookDelete) => { + if (data.webhookDelete.errors.length === 0) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + navigate(webhooksListUrl()); + refetch(); + } + }; + + const handleSort = createSortHandler(navigate, webhooksListUrl, params); + + return ( + + {(webhookDelete, webhookDeleteOpts) => { + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.webhooks.pageInfo), + paginationState, + params + ); + const handleRemove = (id: string) => { + navigate( + webhooksListUrl({ + ...params, + action: "remove", + id + }) + ); + }; + const handleRemoveConfirm = () => { + webhookDelete({ + variables: { + id: params.id + } + }); + }; + + return ( + <> + changeFilterField({ query })} + onAll={() => navigate(webhooksListUrl())} + onTabChange={handleTabChange} + onTabDelete={() => openModal("delete-search")} + onTabSave={() => openModal("save-search")} + tabs={tabs.map(tab => tab.name)} + disabled={loading} + settings={settings} + sort={getSortParams(params)} + webhooks={maybe(() => data.webhooks.edges.map(edge => edge.node))} + pageInfo={pageInfo} + onAdd={() => navigate(webhooksAddUrl)} + onBack={() => navigate(configurationMenuUrl)} + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onRemove={handleRemove} + onSort={handleSort} + onUpdateListSettings={updateListSettings} + onRowClick={id => () => navigate(webhooksUrl(id))} + /> + + data.webhooks.edges.find(edge => edge.node.id === params.id) + .node.name, + "..." + )} + onClose={closeModal} + onConfirm={handleRemoveConfirm} + open={params.action === "remove"} + /> + + tabs[currentTab - 1].name, "...")} + /> + + ); + }} + + ); +}; + +export default WebhooksList; diff --git a/src/webhooks/views/filter.ts b/src/webhooks/views/WebhookList/filter.ts similarity index 78% rename from src/webhooks/views/filter.ts rename to src/webhooks/views/WebhookList/filter.ts index 1f7365ebc..c5445c621 100644 --- a/src/webhooks/views/filter.ts +++ b/src/webhooks/views/WebhookList/filter.ts @@ -1,10 +1,10 @@ import { WebhookFilterInput } from "@saleor/types/globalTypes"; -import { createFilterTabUtils, createFilterUtils } from "../../utils/filters"; +import { createFilterTabUtils, createFilterUtils } from "@saleor/utils/filters"; import { WebhookListUrlFilters, WebhookListUrlFiltersEnum, - WebhooksListUrlQueryParams -} from "../urls"; + WebhookListUrlQueryParams +} from "../../urls"; export const WEBHOOK_FILTERS_KEY = "webhookFilters"; @@ -23,6 +23,6 @@ export const { } = createFilterTabUtils(WEBHOOK_FILTERS_KEY); export const { areFiltersApplied, getActiveFilters } = createFilterUtils< - WebhooksListUrlQueryParams, + WebhookListUrlQueryParams, WebhookListUrlFilters >(WebhookListUrlFiltersEnum); diff --git a/src/webhooks/views/WebhookList/index.ts b/src/webhooks/views/WebhookList/index.ts new file mode 100644 index 000000000..825d5172e --- /dev/null +++ b/src/webhooks/views/WebhookList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./WebhookList"; +export * from "./WebhookList"; diff --git a/src/webhooks/views/WebhookList/sort.ts b/src/webhooks/views/WebhookList/sort.ts new file mode 100644 index 000000000..c0ccfc064 --- /dev/null +++ b/src/webhooks/views/WebhookList/sort.ts @@ -0,0 +1,20 @@ +import { WebhookListUrlSortField } from "@saleor/webhooks/urls"; +import { WebhookSortField } from "@saleor/types/globalTypes"; +import { createGetSortQueryVariables } from "@saleor/utils/sort"; + +export function getSortQueryField( + sort: WebhookListUrlSortField +): WebhookSortField { + switch (sort) { + case WebhookListUrlSortField.name: + return WebhookSortField.NAME; + case WebhookListUrlSortField.serviceAccount: + return WebhookSortField.SERVICE_ACCOUNT; + default: + return undefined; + } +} + +export const getSortQueryVariables = createGetSortQueryVariables( + getSortQueryField +); diff --git a/src/webhooks/views/WebhooksCreate.tsx b/src/webhooks/views/WebhooksCreate.tsx index ef75ba5c9..81477c2f5 100644 --- a/src/webhooks/views/WebhooksCreate.tsx +++ b/src/webhooks/views/WebhooksCreate.tsx @@ -13,13 +13,13 @@ import WebhookCreatePage, { FormData } from "../components/WebhookCreatePage"; import { TypedWebhookCreate } from "../mutations"; import { webhooksListUrl, - WebhooksListUrlQueryParams, + WebhookListUrlQueryParams, webhooksUrl } from "../urls"; export interface WebhooksCreateProps { id: string; - params: WebhooksListUrlQueryParams; + params: WebhookListUrlQueryParams; } export const WebhooksCreate: React.FC = () => { diff --git a/src/webhooks/views/WebhooksDetails.tsx b/src/webhooks/views/WebhooksDetails.tsx index 0746bd930..4d0c0c76d 100644 --- a/src/webhooks/views/WebhooksDetails.tsx +++ b/src/webhooks/views/WebhooksDetails.tsx @@ -17,14 +17,14 @@ import { TypedWebhookDelete, TypedWebhookUpdate } from "../mutations"; import { TypedWebhooksDetailsQuery } from "../queries"; import { webhooksListUrl, - WebhooksListUrlQueryParams, + WebhookListUrlQueryParams, webhooksUrl, WebhookUrlDialog } from "../urls"; export interface WebhooksDetailsProps { id: string; - params: WebhooksListUrlQueryParams; + params: WebhookListUrlQueryParams; } export const WebhooksDetails: React.FC = ({ diff --git a/src/webhooks/views/WebhooksList.tsx b/src/webhooks/views/WebhooksList.tsx deleted file mode 100644 index ad0e5de49..000000000 --- a/src/webhooks/views/WebhooksList.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; -import SaveFilterTabDialog, { - SaveFilterTabDialogFormData -} from "@saleor/components/SaveFilterTabDialog"; -import { configurationMenuUrl } from "@saleor/configuration"; -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 { maybe } from "@saleor/misc"; -import { ListViews } from "@saleor/types"; -import WebhookDeleteDialog from "@saleor/webhooks/components/WebhookDeleteDialog"; -import { WebhookDelete } from "@saleor/webhooks/types/WebhookDelete"; -import React from "react"; -import { useIntl } from "react-intl"; - -import WebhooksListPage from "../components/WebhooksListPage/WebhooksListPage"; -import { TypedWebhookDelete } from "../mutations"; -import { TypedWebhooksListQuery } from "../queries"; -import { - WebhookListUrlDialog, - WebhookListUrlFilters, - webhooksAddUrl, - webhooksListUrl, - WebhooksListUrlQueryParams, - webhooksUrl -} from "../urls"; -import { - areFiltersApplied, - deleteFilterTab, - getActiveFilters, - getFilterTabs, - getFilterVariables, - saveFilterTab -} from "./filter"; - -interface WebhooksListProps { - params: WebhooksListUrlQueryParams; -} - -export const WebhooksList: React.FC = ({ params }) => { - const navigate = useNavigator(); - const paginate = usePaginator(); - const notify = useNotifier(); - const intl = useIntl(); - const { updateListSettings, settings } = useListSettings( - ListViews.WEBHOOK_LIST - ); - const tabs = getFilterTabs(); - - const currentTab = - params.activeTab === undefined - ? areFiltersApplied(params) - ? tabs.length + 1 - : 0 - : parseInt(params.activeTab, 0); - - const changeFilterField = (filter: WebhookListUrlFilters) => - navigate( - webhooksListUrl({ - ...getActiveFilters(params), - ...filter, - activeTab: undefined - }) - ); - const closeModal = () => - navigate( - webhooksListUrl({ - ...params, - action: undefined, - id: undefined - }), - true - ); - - const openModal = (action: WebhookListUrlDialog, id?: string) => - navigate( - webhooksListUrl({ - ...params, - action, - id - }) - ); - - const handleTabChange = (tab: number) => { - navigate( - webhooksListUrl({ - activeTab: tab.toString(), - ...getFilterTabs()[tab - 1].data - }) - ); - }; - - const handleTabDelete = () => { - deleteFilterTab(currentTab); - navigate(webhooksListUrl()); - }; - - 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 onWebhookDelete = (data: WebhookDelete) => { - if (data.webhookDelete.errors.length === 0) { - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - navigate(webhooksListUrl()); - refetch(); - } - }; - return ( - - {(webhookDelete, webhookDeleteOpts) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.webhooks.pageInfo), - paginationState, - params - ); - const handleRemove = (id: string) => { - navigate( - webhooksListUrl({ - ...params, - action: "remove", - id - }) - ); - }; - const handleRemoveConfirm = () => { - webhookDelete({ - variables: { - id: params.id - } - }); - }; - - return ( - <> - changeFilterField({ query })} - onAll={() => navigate(webhooksListUrl())} - onTabChange={handleTabChange} - onTabDelete={() => openModal("delete-search")} - onTabSave={() => openModal("save-search")} - tabs={tabs.map(tab => tab.name)} - disabled={loading} - settings={settings} - webhooks={maybe(() => - data.webhooks.edges.map(edge => edge.node) - )} - pageInfo={pageInfo} - onAdd={() => navigate(webhooksAddUrl)} - onBack={() => navigate(configurationMenuUrl)} - onNextPage={loadNextPage} - onPreviousPage={loadPreviousPage} - onRemove={handleRemove} - onUpdateListSettings={updateListSettings} - onRowClick={id => () => navigate(webhooksUrl(id))} - /> - - data.webhooks.edges.find( - edge => edge.node.id === params.id - ).node.name, - "..." - )} - onClose={closeModal} - onConfirm={handleRemoveConfirm} - open={params.action === "remove"} - /> - - tabs[currentTab - 1].name, "...")} - /> - - ); - }} - - ); - }} - - ); -}; - -export default WebhooksList;