Add sorting to lists

This commit is contained in:
dominik-zeglen 2019-12-17 18:13:56 +01:00
parent 97abf33fd2
commit 9c688d3aef
153 changed files with 6897 additions and 2853 deletions

View file

@ -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 - Enforce using "name" property in style hooks - #288 by @dominik-zeglen
- Add ability to reset own password - #289 by @dominik-zeglen - Add ability to reset own password - #289 by @dominik-zeglen
- Move mutation state to mutation - #297 by @dominik-zeglen - Move mutation state to mutation - #297 by @dominik-zeglen
- Add table sorting - #292 by @dominik-zeglen
## 2.0.0 ## 2.0.0

View file

@ -318,8 +318,8 @@ enum AttributeSortField {
} }
input AttributeSortingInput { input AttributeSortingInput {
field: AttributeSortField!
direction: OrderDirection! direction: OrderDirection!
field: AttributeSortField
} }
type AttributeTranslatableContent implements Node { type AttributeTranslatableContent implements Node {
@ -570,6 +570,17 @@ input CategoryInput {
backgroundImageAlt: String backgroundImageAlt: String
} }
enum CategorySortField {
NAME
PRODUCT_COUNT
SUBCATEGORY_COUNT
}
input CategorySortingInput {
direction: OrderDirection!
field: CategorySortField
}
type CategoryTranslatableContent implements Node { type CategoryTranslatableContent implements Node {
seoTitle: String seoTitle: String
seoDescription: String seoDescription: String
@ -950,6 +961,17 @@ type CollectionReorderProducts {
productErrors: [ProductError!] productErrors: [ProductError!]
} }
enum CollectionSortField {
NAME
AVAILABILITY
PRODUCT_COUNT
}
input CollectionSortingInput {
direction: OrderDirection!
field: CollectionSortField
}
type CollectionTranslatableContent implements Node { type CollectionTranslatableContent implements Node {
seoTitle: String seoTitle: String
seoDescription: String seoDescription: String
@ -1398,6 +1420,8 @@ input DateTimeRangeInput {
scalar Decimal scalar Decimal
union DefaultTranslationItem = ProductTranslatableContent | CollectionTranslatableContent | CategoryTranslatableContent | AttributeTranslatableContent | AttributeValueTranslatableContent | ProductVariantTranslatableContent | PageTranslatableContent | ShippingMethodTranslatableContent | SaleTranslatableContent | VoucherTranslatableContent | MenuItemTranslatableContent
type DigitalContent implements Node { type DigitalContent implements Node {
useDefaultSettings: Boolean! useDefaultSettings: Boolean!
automaticFulfillment: Boolean! automaticFulfillment: Boolean!
@ -1990,6 +2014,11 @@ input MenuItemMoveInput {
sortOrder: Int sortOrder: Int
} }
input MenuItemSortingInput {
direction: OrderDirection!
field: MenuItemsSortField
}
type MenuItemTranslatableContent implements Node { type MenuItemTranslatableContent implements Node {
id: ID! id: ID!
name: String! name: String!
@ -2014,6 +2043,20 @@ type MenuItemUpdate {
menuItem: MenuItem menuItem: MenuItem
} }
enum MenuItemsSortField {
NAME
}
enum MenuSortField {
NAME
ITEMS_COUNT
}
input MenuSortingInput {
direction: OrderDirection!
field: MenuSortField
}
type MenuUpdate { type MenuUpdate {
errors: [Error!] errors: [Error!]
menuErrors: [MenuError!] menuErrors: [MenuError!]
@ -2575,6 +2618,20 @@ type OrderRefund {
orderErrors: [OrderError!] orderErrors: [OrderError!]
} }
enum OrderSortField {
NUMBER
CREATION_DATE
CUSTOMER
PAYMENT
FULFILLMENT_STATUS
TOTAL
}
input OrderSortingInput {
direction: OrderDirection!
field: OrderSortField
}
enum OrderStatus { enum OrderStatus {
DRAFT DRAFT
UNFULFILLED UNFULFILLED
@ -2696,6 +2753,19 @@ input PageInput {
seo: SeoInput seo: SeoInput
} }
enum PageSortField {
TITLE
SLUG
VISIBILITY
CREATION_DATE
PUBLICATION_DATE
}
input PageSortingInput {
direction: OrderDirection!
field: PageSortField
}
type PageTranslatableContent implements Node { type PageTranslatableContent implements Node {
seoTitle: String seoTitle: String
seoDescription: String seoDescription: String
@ -2892,6 +2962,16 @@ input PluginFilterInput {
search: String search: String
} }
enum PluginSortField {
NAME
IS_ACTIVE
}
input PluginSortingInput {
direction: OrderDirection!
field: PluginSortField
}
type PluginUpdate { type PluginUpdate {
errors: [Error!] errors: [Error!]
plugin: Plugin plugin: Plugin
@ -3115,9 +3195,9 @@ input ProductInput {
} }
input ProductOrder { input ProductOrder {
field: ProductOrderField
attributeId: ID
direction: OrderDirection! direction: OrderDirection!
attributeId: ID
field: ProductOrderField
} }
enum ProductOrderField { enum ProductOrderField {
@ -3256,6 +3336,17 @@ type ProductTypeReorderAttributes {
productErrors: [ProductError!] productErrors: [ProductError!]
} }
enum ProductTypeSortField {
NAME
DIGITAL
SHIPPING_REQUIRED
}
input ProductTypeSortingInput {
direction: OrderDirection!
field: ProductTypeSortField
}
type ProductTypeUpdate { type ProductTypeUpdate {
errors: [Error!] errors: [Error!]
productErrors: [ProductError!] productErrors: [ProductError!]
@ -3436,11 +3527,11 @@ type ProductVariantUpdatePrivateMeta {
type Query { type Query {
webhook(id: ID!): Webhook 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] webhookEvents: [WebhookEvent]
webhookSamplePayload(eventType: WebhookEventTypeEnum!): JSONString webhookSamplePayload(eventType: WebhookEventTypeEnum!): JSONString
translations(kind: TranslatableKinds!, before: String, after: String, first: Int, last: Int): TranslatableItemConnection 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 shop: Shop
shippingZone(id: ID!): ShippingZone shippingZone(id: ID!): ShippingZone
shippingZones(before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection 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 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 attributes(query: String, inCategory: ID, inCollection: ID, filter: AttributeFilterInput, sortBy: AttributeSortingInput, before: String, after: String, first: Int, last: Int): AttributeCountableConnection
attribute(id: ID!): Attribute attribute(id: ID!): Attribute
categories(query: String, 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 category(id: ID!): Category
collection(id: ID!): Collection collection(id: ID!): Collection
collections(filter: CollectionFilterInput, query: String, before: String, after: String, first: Int, last: Int): CollectionCountableConnection collections(filter: CollectionFilterInput, sortBy: CollectionSortingInput, query: String, before: String, after: String, first: Int, last: Int): CollectionCountableConnection
product(id: ID!): Product 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 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 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 productVariant(id: ID!): ProductVariant
productVariants(ids: [ID], before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection productVariants(ids: [ID], before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection
reportProductSales(period: ReportingPeriod!, 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 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.") 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 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 homepageEvents(before: String, after: String, first: Int, last: Int): OrderEventCountableConnection
order(id: ID!): Order order(id: ID!): Order
orders(filter: OrderFilterInput, query: String, created: ReportingPeriod, status: OrderStatusFilter, before: String, after: String, first: Int, last: Int): OrderCountableConnection orders(sortBy: OrderSortingInput, 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 draftOrders(sortBy: OrderSortingInput, filter: OrderDraftFilterInput, query: String, created: ReportingPeriod, before: String, after: String, first: Int, last: Int): OrderCountableConnection
ordersTotal(period: ReportingPeriod): TaxedMoney ordersTotal(period: ReportingPeriod): TaxedMoney
orderByToken(token: UUID!): Order orderByToken(token: UUID!): Order
menu(id: ID, name: String): Menu 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 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 giftCard(id: ID!): GiftCard
giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection
plugin(id: ID!): Plugin plugin(id: ID!): Plugin
plugins(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 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 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] taxTypes: [TaxType]
checkout(token: UUID): Checkout checkout(token: UUID): Checkout
checkouts(before: String, after: String, first: Int, last: Int): CheckoutCountableConnection checkouts(before: String, after: String, first: Int, last: Int): CheckoutCountableConnection
checkoutLine(id: ID): CheckoutLine checkoutLine(id: ID): CheckoutLine
checkoutLines(before: String, after: String, first: Int, last: Int): CheckoutLineCountableConnection checkoutLines(before: String, after: String, first: Int, last: Int): CheckoutLineCountableConnection
addressValidationRules(countryCode: CountryCode!, countryArea: String, city: String, cityArea: String): AddressValidationData 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 me: User
staffUsers(filter: StaffUserInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection staffUsers(filter: StaffUserInput, sortBy: UserSortingInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection
serviceAccounts(filter: ServiceAccountFilterInput, before: String, after: String, first: Int, last: Int): ServiceAccountCountableConnection serviceAccounts(filter: ServiceAccountFilterInput, sortBy: ServiceAccountSortingInput, before: String, after: String, first: Int, last: Int): ServiceAccountCountableConnection
serviceAccount(id: ID!): ServiceAccount serviceAccount(id: ID!): ServiceAccount
user(id: ID!): User user(id: ID!): User
node(id: ID!): Node
_entities(representations: [_Any]): [_Entity] _entities(representations: [_Any]): [_Entity]
_service: _Service _service: _Service
} }
@ -3590,6 +3682,19 @@ type SaleRemoveCatalogues {
sale: Sale sale: Sale
} }
enum SaleSortField {
NAME
START_DATE
END_DATE
VALUE
TYPE
}
input SaleSortingInput {
direction: OrderDirection!
field: SaleSortField
}
type SaleTranslatableContent implements Node { type SaleTranslatableContent implements Node {
id: ID! id: ID!
name: String! name: String!
@ -3681,6 +3786,16 @@ input ServiceAccountInput {
permissions: [PermissionEnum] permissions: [PermissionEnum]
} }
enum ServiceAccountSortField {
NAME
CREATION_DATE
}
input ServiceAccountSortingInput {
direction: OrderDirection!
field: ServiceAccountSortField
}
type ServiceAccountToken implements Node { type ServiceAccountToken implements Node {
name: String name: String
authToken: String authToken: String
@ -4109,7 +4224,7 @@ enum TransactionKind {
CONFIRM 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 { type TranslatableItemConnection {
pageInfo: PageInfo! pageInfo: PageInfo!
@ -4230,6 +4345,18 @@ input UserCreateInput {
redirectUrl: String redirectUrl: String
} }
enum UserSortField {
FIRST_NAME
LAST_NAME
EMAIL
ORDER_COUNT
}
input UserSortingInput {
direction: OrderDirection!
field: UserSortField
}
type UserUpdateMeta { type UserUpdateMeta {
errors: [Error!] errors: [Error!]
accountErrors: [AccountError!] accountErrors: [AccountError!]
@ -4369,6 +4496,21 @@ type VoucherRemoveCatalogues {
voucher: Voucher 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 { type VoucherTranslatableContent implements Node {
id: ID! id: ID!
name: String name: String
@ -4475,6 +4617,17 @@ input WebhookFilterInput {
isActive: Boolean isActive: Boolean
} }
enum WebhookSortField {
NAME
SERVICE_ACCOUNT
TARGET_URL
}
input WebhookSortingInput {
direction: OrderDirection!
field: WebhookSortField
}
type WebhookUpdate { type WebhookUpdate {
errors: [Error!] errors: [Error!]
webhook: Webhook webhook: Webhook

View file

@ -13,10 +13,16 @@ import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { translateBoolean } from "@saleor/intl"; import { translateBoolean } from "@saleor/intl";
import { maybe, renderCollection } from "@saleor/misc"; 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"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
export interface AttributeListProps extends ListProps, ListActions { export interface AttributeListProps
extends ListProps,
ListActions,
SortPage<AttributeListUrlSortField> {
attributes: AttributeList_attributes_edges_node[]; attributes: AttributeList_attributes_edges_node[];
} }
@ -24,19 +30,19 @@ const useStyles = makeStyles(
theme => ({ theme => ({
[theme.breakpoints.up("lg")]: { [theme.breakpoints.up("lg")]: {
colFaceted: { colFaceted: {
width: 150 width: 180
}, },
colName: { colName: {
width: "auto" width: "auto"
}, },
colSearchable: { colSearchable: {
width: 150 width: 180
}, },
colSlug: { colSlug: {
width: 200 width: 200
}, },
colVisible: { colVisible: {
width: 150 width: 180
} }
}, },
colFaceted: { colFaceted: {
@ -70,9 +76,11 @@ const AttributeList: React.FC<AttributeListProps> = ({
onRowClick, onRowClick,
pageInfo, pageInfo,
selected, selected,
sort,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar,
onSort
}) => { }) => {
const classes = useStyles({}); const classes = useStyles({});
const intl = useIntl(); const intl = useIntl();
@ -87,33 +95,77 @@ const AttributeList: React.FC<AttributeListProps> = ({
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colSlug}> <TableCellHeader
className={classes.colSlug}
direction={
sort.sort === AttributeListUrlSortField.slug
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(AttributeListUrlSortField.slug)}
>
<FormattedMessage defaultMessage="Attribute Code" /> <FormattedMessage defaultMessage="Attribute Code" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colName}> <TableCellHeader
className={classes.colName}
direction={
sort.sort === AttributeListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(AttributeListUrlSortField.name)}
>
<FormattedMessage <FormattedMessage
defaultMessage="Default Label" defaultMessage="Default Label"
description="attribute's label'" description="attribute's label'"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colVisible}> <TableCellHeader
className={classes.colVisible}
direction={
sort.sort === AttributeListUrlSortField.visible
? getArrowDirection(sort.asc)
: undefined
}
textAlign="center"
onClick={() => onSort(AttributeListUrlSortField.visible)}
>
<FormattedMessage <FormattedMessage
defaultMessage="Visible" defaultMessage="Visible"
description="attribute is visible" description="attribute is visible"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colSearchable}> <TableCellHeader
className={classes.colSearchable}
direction={
sort.sort === AttributeListUrlSortField.searchable
? getArrowDirection(sort.asc)
: undefined
}
textAlign="center"
onClick={() => onSort(AttributeListUrlSortField.searchable)}
>
<FormattedMessage <FormattedMessage
defaultMessage="Searchable" defaultMessage="Searchable"
description="attribute can be searched in dashboard" description="attribute can be searched in dashboard"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colFaceted}> <TableCellHeader
className={classes.colFaceted}
direction={
sort.sort === AttributeListUrlSortField.useInFacetedSearch
? getArrowDirection(sort.asc)
: undefined
}
textAlign="center"
onClick={() => onSort(AttributeListUrlSortField.useInFacetedSearch)}
>
<FormattedMessage <FormattedMessage
defaultMessage="Use in faceted search" defaultMessage="Use in faceted search"
description="attribute can be searched in storefront" description="attribute can be searched in storefront"
/> />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>

View file

@ -6,13 +6,15 @@ import { FormattedMessage, useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import SearchBar from "@saleor/components/SearchBar"; import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { AttributeListUrlSortField } from "@saleor/attributes/urls";
import Container from "../../../components/Container"; import Container from "../../../components/Container";
import PageHeader from "../../../components/PageHeader"; import PageHeader from "../../../components/PageHeader";
import { import {
ListActions, ListActions,
PageListProps, PageListProps,
SearchPageProps, SearchPageProps,
TabPageProps TabPageProps,
SortPage
} from "../../../types"; } from "../../../types";
import { AttributeList_attributes_edges_node } from "../../types/AttributeList"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
import AttributeList from "../AttributeList/AttributeList"; import AttributeList from "../AttributeList/AttributeList";
@ -21,6 +23,7 @@ export interface AttributeListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
SearchPageProps, SearchPageProps,
SortPage<AttributeListUrlSortField>,
TabPageProps { TabPageProps {
attributes: AttributeList_attributes_edges_node[]; attributes: AttributeList_attributes_edges_node[];
onBack: () => void; onBack: () => void;

View file

@ -4,6 +4,7 @@ import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { import {
attributeAddPath, attributeAddPath,
@ -11,7 +12,8 @@ import {
attributeListPath, attributeListPath,
AttributeListUrlQueryParams, AttributeListUrlQueryParams,
attributePath, attributePath,
AttributeUrlQueryParams AttributeUrlQueryParams,
AttributeListUrlSortField
} from "./urls"; } from "./urls";
import AttributeCreateComponent from "./views/AttributeCreate"; import AttributeCreateComponent from "./views/AttributeCreate";
import AttributeDetailsComponent from "./views/AttributeDetails"; import AttributeDetailsComponent from "./views/AttributeDetails";
@ -19,7 +21,11 @@ import AttributeListComponent from "./views/AttributeList";
const AttributeList: React.FC<RouteComponentProps<{}>> = ({ location }) => { const AttributeList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: AttributeListUrlQueryParams = qs; const params: AttributeListUrlQueryParams = asSortParams(
qs,
AttributeListUrlSortField
);
return <AttributeListComponent params={params} />; return <AttributeListComponent params={params} />;
}; };

View file

@ -1,5 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import makeQuery from "@saleor/hooks/makeQuery";
import { pageInfoFragment, TypedQuery } from "../queries"; import { pageInfoFragment, TypedQuery } from "../queries";
import { import {
AttributeDetails, AttributeDetails,
@ -59,6 +60,7 @@ const attributeList = gql`
$after: String $after: String
$first: Int $first: Int
$last: Int $last: Int
$sort: AttributeSortingInput
) { ) {
attributes( attributes(
filter: $filter filter: $filter
@ -68,6 +70,7 @@ const attributeList = gql`
after: $after after: $after
first: $first first: $first
last: $last last: $last
sortBy: $sort
) { ) {
edges { edges {
node { node {
@ -85,7 +88,7 @@ const attributeList = gql`
} }
} }
`; `;
export const AttributeListQuery = TypedQuery< export const useAttributeListQuery = makeQuery<
AttributeList, AttributeList,
AttributeListVariables AttributeListVariables
>(attributeList); >(attributeList);

View file

@ -2,7 +2,7 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { AttributeFilterInput } from "./../../types/globalTypes"; import { AttributeFilterInput, AttributeSortingInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: AttributeList // GraphQL query operation: AttributeList
@ -57,4 +57,5 @@ export interface AttributeListVariables {
after?: string | null; after?: string | null;
first?: number | null; first?: number | null;
last?: number | null; last?: number | null;
sort?: AttributeSortingInput | null;
} }

View file

@ -8,6 +8,7 @@ import {
Filters, Filters,
Pagination, Pagination,
SingleAction, SingleAction,
Sort,
TabActionDialog TabActionDialog
} from "../types"; } from "../types";
@ -18,8 +19,17 @@ export enum AttributeListUrlFiltersEnum {
} }
export type AttributeListUrlFilters = Filters<AttributeListUrlFiltersEnum>; export type AttributeListUrlFilters = Filters<AttributeListUrlFiltersEnum>;
export type AttributeListUrlDialog = "remove" | TabActionDialog; 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<AttributeListUrlSortField>;
export type AttributeListUrlQueryParams = ActiveTab & export type AttributeListUrlQueryParams = ActiveTab &
AttributeListUrlFilters & AttributeListUrlFilters &
AttributeListUrlSort &
BulkAction & BulkAction &
Dialog<AttributeListUrlDialog> & Dialog<AttributeListUrlDialog> &
Pagination; Pagination;

View file

@ -21,13 +21,15 @@ import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, { import usePaginator, {
createPaginationState createPaginationState
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import { PAGINATE_BY } from "../../../config"; import { PAGINATE_BY } from "../../../config";
import useBulkActions from "../../../hooks/useBulkActions"; import useBulkActions from "../../../hooks/useBulkActions";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog"; import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
import AttributeListPage from "../../components/AttributeListPage"; import AttributeListPage from "../../components/AttributeListPage";
import { AttributeBulkDeleteMutation } from "../../mutations"; import { AttributeBulkDeleteMutation } from "../../mutations";
import { AttributeListQuery } from "../../queries"; import { useAttributeListQuery } from "../../queries";
import { AttributeBulkDelete } from "../../types/AttributeBulkDelete"; import { AttributeBulkDelete } from "../../types/AttributeBulkDelete";
import { import {
attributeAddUrl, attributeAddUrl,
@ -37,6 +39,7 @@ import {
AttributeListUrlQueryParams, AttributeListUrlQueryParams,
attributeUrl attributeUrl
} from "../../urls"; } from "../../urls";
import { getSortQueryVariables } from "./sort";
interface AttributeListProps { interface AttributeListProps {
params: AttributeListUrlQueryParams; params: AttributeListUrlQueryParams;
@ -51,6 +54,19 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
); );
const intl = useIntl(); 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 tabs = getFilterTabs();
const currentTab = const currentTab =
@ -111,105 +127,93 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
handleTabChange(tabs.length + 1); handleTabChange(tabs.length + 1);
}; };
const paginationState = createPaginationState(PAGINATE_BY, params); const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
const queryVariables = React.useMemo( maybe(() => data.attributes.pageInfo),
() => ({ paginationState,
...paginationState, params
filter: getFilterVariables(params)
}),
[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 ( return (
<AttributeListQuery variables={queryVariables}> <AttributeBulkDeleteMutation onCompleted={handleBulkDelete}>
{({ data, loading, refetch }) => { {(attributeBulkDelete, attributeBulkDeleteOpts) => (
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( <>
maybe(() => data.attributes.pageInfo), <AttributeListPage
paginationState, attributes={maybe(() =>
params data.attributes.edges.map(edge => edge.node)
);
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 (
<AttributeBulkDeleteMutation onCompleted={handleBulkDelete}>
{(attributeBulkDelete, attributeBulkDeleteOpts) => (
<>
<AttributeListPage
attributes={maybe(() =>
data.attributes.edges.map(edge => edge.node)
)}
currentTab={currentTab}
disabled={loading || attributeBulkDeleteOpts.loading}
initialSearch={params.query || ""}
isChecked={isSelected}
onAdd={() => navigate(attributeAddUrl())}
onAll={() => navigate(attributeListUrl())}
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={
<IconButton
color="primary"
onClick={() => openModal("remove", listElements)}
>
<DeleteIcon />
</IconButton>
}
/>
<AttributeBulkDeleteDialog
confirmButtonState={attributeBulkDeleteOpts.status}
open={
params.action === "remove" &&
maybe(() => params.ids.length > 0)
}
onConfirm={() =>
attributeBulkDelete({ variables: { ids: params.ids } })
}
onClose={closeModal}
quantity={maybe(() => params.ids.length)}
/>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
)} )}
</AttributeBulkDeleteMutation> currentTab={currentTab}
); disabled={loading || attributeBulkDeleteOpts.loading}
}} initialSearch={params.query || ""}
</AttributeListQuery> 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={
<IconButton
color="primary"
onClick={() => openModal("remove", listElements)}
>
<DeleteIcon />
</IconButton>
}
/>
<AttributeBulkDeleteDialog
confirmButtonState={attributeBulkDeleteOpts.status}
open={
params.action === "remove" && maybe(() => params.ids.length > 0)
}
onConfirm={() =>
attributeBulkDelete({ variables: { ids: params.ids } })
}
onClose={closeModal}
quantity={maybe(() => params.ids.length)}
/>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
)}
</AttributeBulkDeleteMutation>
); );
}; };
AttributeList.displayName = "AttributeList"; AttributeList.displayName = "AttributeList";

View file

@ -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
);

View file

@ -13,7 +13,10 @@ import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { maybe, renderCollection } from "@saleor/misc"; 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( const useStyles = makeStyles(
theme => ({ theme => ({
@ -44,7 +47,10 @@ const useStyles = makeStyles(
{ name: "CategoryList" } { name: "CategoryList" }
); );
interface CategoryListProps extends ListProps, ListActions { interface CategoryListProps
extends ListProps,
ListActions,
SortPage<CategoryListUrlSortField> {
categories?: CategoryFragment[]; categories?: CategoryFragment[];
isRoot: boolean; isRoot: boolean;
onAdd?(); onAdd?();
@ -57,6 +63,7 @@ const CategoryList: React.FC<CategoryListProps> = props => {
categories, categories,
disabled, disabled,
settings, settings,
sort,
pageInfo, pageInfo,
isChecked, isChecked,
isRoot, isRoot,
@ -67,7 +74,8 @@ const CategoryList: React.FC<CategoryListProps> = props => {
onNextPage, onNextPage,
onPreviousPage, onPreviousPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick onRowClick,
onSort
} = props; } = props;
const classes = useStyles(props); const classes = useStyles(props);
@ -82,21 +90,53 @@ const CategoryList: React.FC<CategoryListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCellHeader
direction={
isRoot && sort.sort === CategoryListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
className={classes.colName}
disableClick={!isRoot}
onClick={() => isRoot && onSort(CategoryListUrlSortField.name)}
>
<FormattedMessage defaultMessage="Category Name" /> <FormattedMessage defaultMessage="Category Name" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colSubcategories}> <TableCellHeader
direction={
isRoot && sort.sort === CategoryListUrlSortField.subcategoryCount
? getArrowDirection(sort.asc)
: undefined
}
className={classes.colSubcategories}
disableClick={!isRoot}
onClick={() =>
isRoot && onSort(CategoryListUrlSortField.subcategoryCount)
}
>
<FormattedMessage <FormattedMessage
defaultMessage="Subcategories" defaultMessage="Subcategories"
description="number of subcategories" description="number of subcategories"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colProducts}> <TableCellHeader
direction={
isRoot && sort.sort === CategoryListUrlSortField.productCount
? getArrowDirection(sort.asc)
: undefined
}
className={classes.colProducts}
disableClick={!isRoot}
onClick={() =>
isRoot && onSort(CategoryListUrlSortField.productCount)
}
>
<FormattedMessage <FormattedMessage
defaultMessage="No. of Products" defaultMessage="No. of Products"
description="number of products" description="number of products"
/> />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>

View file

@ -12,14 +12,17 @@ import {
ListActions, ListActions,
PageListProps, PageListProps,
SearchPageProps, SearchPageProps,
TabPageProps TabPageProps,
SortPage
} from "@saleor/types"; } from "@saleor/types";
import { CategoryListUrlSortField } from "@saleor/categories/urls";
import CategoryList from "../CategoryList"; import CategoryList from "../CategoryList";
export interface CategoryTableProps export interface CategoryTableProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
SearchPageProps, SearchPageProps,
SortPage<CategoryListUrlSortField>,
TabPageProps { TabPageProps {
categories: CategoryFragment[]; categories: CategoryFragment[];
} }
@ -46,7 +49,8 @@ export const CategoryListPage: React.FC<CategoryTableProps> = ({
onTabChange, onTabChange,
onTabDelete, onTabDelete,
onTabSave, onTabSave,
onUpdateListSettings onUpdateListSettings,
...listProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -94,6 +98,7 @@ export const CategoryListPage: React.FC<CategoryTableProps> = ({
onPreviousPage={onPreviousPage} onPreviousPage={onPreviousPage}
onRowClick={onRowClick} onRowClick={onRowClick}
onUpdateListSettings={onUpdateListSettings} onUpdateListSettings={onUpdateListSettings}
{...listProps}
/> />
</Card> </Card>
</Container> </Container>

View file

@ -205,12 +205,14 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
isRoot={false} isRoot={false}
pageInfo={pageInfo} pageInfo={pageInfo}
selected={selected} selected={selected}
sort={undefined}
toggle={toggle} toggle={toggle}
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={subcategoryListToolbar} toolbar={subcategoryListToolbar}
onNextPage={onNextPage} onNextPage={onNextPage}
onPreviousPage={onPreviousPage} onPreviousPage={onPreviousPage}
onRowClick={onCategoryClick} onRowClick={onCategoryClick}
onSort={() => undefined}
/> />
</Card> </Card>
)} )}

View file

@ -4,13 +4,15 @@ import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { import {
categoryAddPath, categoryAddPath,
categoryListPath, categoryListPath,
CategoryListUrlQueryParams, CategoryListUrlQueryParams,
categoryPath, categoryPath,
CategoryUrlQueryParams CategoryUrlQueryParams,
CategoryListUrlSortField
} from "./urls"; } from "./urls";
import { CategoryCreateView } from "./views/CategoryCreate"; import { CategoryCreateView } from "./views/CategoryCreate";
import CategoryDetailsView, { getActiveTab } from "./views/CategoryDetails"; import CategoryDetailsView, { getActiveTab } from "./views/CategoryDetails";
@ -19,14 +21,15 @@ import CategoryListComponent from "./views/CategoryList";
interface CategoryDetailsRouteParams { interface CategoryDetailsRouteParams {
id: string; id: string;
} }
const CategoryDetails: React.FC< const CategoryDetails: React.FC<RouteComponentProps<
RouteComponentProps<CategoryDetailsRouteParams> CategoryDetailsRouteParams
> = ({ location, match }) => { >> = ({ location, match }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: CategoryUrlQueryParams = { const params: CategoryUrlQueryParams = {
...qs, ...qs,
activeTab: getActiveTab(qs.activeTab) activeTab: getActiveTab(qs.activeTab)
}; };
return ( return (
<CategoryDetailsView <CategoryDetailsView
id={decodeURIComponent(match.params.id)} id={decodeURIComponent(match.params.id)}
@ -38,9 +41,9 @@ const CategoryDetails: React.FC<
interface CategoryCreateRouteParams { interface CategoryCreateRouteParams {
id: string; id: string;
} }
const CategoryCreate: React.FC< const CategoryCreate: React.FC<RouteComponentProps<
RouteComponentProps<CategoryCreateRouteParams> CategoryCreateRouteParams
> = ({ match }) => ( >> = ({ match }) => (
<CategoryCreateView <CategoryCreateView
parentId={match.params.id ? decodeURIComponent(match.params.id) : undefined} parentId={match.params.id ? decodeURIComponent(match.params.id) : undefined}
/> />
@ -48,7 +51,10 @@ const CategoryCreate: React.FC<
const CategoryList: React.FC<RouteComponentProps<{}>> = ({ location }) => { const CategoryList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: CategoryListUrlQueryParams = qs; const params: CategoryListUrlQueryParams = {
...asSortParams(qs, CategoryListUrlSortField)
};
return <CategoryListComponent params={params} />; return <CategoryListComponent params={params} />;
}; };

View file

@ -46,6 +46,7 @@ export const rootCategories = gql`
$last: Int $last: Int
$before: String $before: String
$filter: CategoryFilterInput $filter: CategoryFilterInput
$sort: CategorySortingInput
) { ) {
categories( categories(
level: 0 level: 0
@ -54,6 +55,7 @@ export const rootCategories = gql`
last: $last last: $last
before: $before before: $before
filter: $filter filter: $filter
sortBy: $sort
) { ) {
edges { edges {
node { node {

View file

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

View file

@ -7,7 +7,8 @@ import {
Dialog, Dialog,
Filters, Filters,
Pagination, Pagination,
TabActionDialog TabActionDialog,
Sort
} from "../types"; } from "../types";
import { CategoryPageTab } from "./components/CategoryUpdatePage"; import { CategoryPageTab } from "./components/CategoryUpdatePage";
@ -19,9 +20,16 @@ export enum CategoryListUrlFiltersEnum {
} }
export type CategoryListUrlFilters = Filters<CategoryListUrlFiltersEnum>; export type CategoryListUrlFilters = Filters<CategoryListUrlFiltersEnum>;
export type CategoryListUrlDialog = "delete" | TabActionDialog; export type CategoryListUrlDialog = "delete" | TabActionDialog;
export enum CategoryListUrlSortField {
name = "name",
productCount = "products",
subcategoryCount = "subcategories"
}
export type CategoryListUrlSort = Sort<CategoryListUrlSortField>;
export type CategoryListUrlQueryParams = ActiveTab & export type CategoryListUrlQueryParams = ActiveTab &
BulkAction & BulkAction &
CategoryListUrlFilters & CategoryListUrlFilters &
CategoryListUrlSort &
Dialog<CategoryListUrlDialog> & Dialog<CategoryListUrlDialog> &
Pagination; Pagination;
export const categoryListUrl = (params?: CategoryListUrlQueryParams) => export const categoryListUrl = (params?: CategoryListUrlQueryParams) =>

View file

@ -17,6 +17,8 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; 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 { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage";
import { useCategoryBulkDeleteMutation } from "../../mutations"; import { useCategoryBulkDeleteMutation } from "../../mutations";
import { useRootCategoriesQuery } from "../../queries"; import { useRootCategoriesQuery } from "../../queries";
@ -37,6 +39,7 @@ import {
getFilterVariables, getFilterVariables,
saveFilterTab saveFilterTab
} from "./filter"; } from "./filter";
import { getSortQueryVariables } from "./sort";
interface CategoryListProps { interface CategoryListProps {
params: CategoryListUrlQueryParams; params: CategoryListUrlQueryParams;
@ -57,7 +60,8 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
const queryVariables = React.useMemo( const queryVariables = React.useMemo(
() => ({ () => ({
...paginationState, ...paginationState,
filter: getFilterVariables(params) filter: getFilterVariables(params),
sort: getSortQueryVariables(params)
}), }),
[params] [params]
); );
@ -148,6 +152,8 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
onCompleted: handleCategoryBulkDelete onCompleted: handleCategoryBulkDelete
}); });
const handleSort = createSortHandler(navigate, categoryListUrl, params);
return ( return (
<> <>
<CategoryListPage <CategoryListPage
@ -164,8 +170,10 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
onTabSave={() => openModal("save-search")} onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)} tabs={tabs.map(tab => tab.name)}
settings={settings} settings={settings}
sort={getSortParams(params)}
onAdd={() => navigate(categoryAddUrl())} onAdd={() => navigate(categoryAddUrl())}
onRowClick={id => () => navigate(categoryUrl(id))} onRowClick={id => () => navigate(categoryUrl(id))}
onSort={handleSort}
disabled={loading} disabled={loading}
onNextPage={loadNextPage} onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage} onPreviousPage={loadPreviousPage}

View file

@ -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
);

View file

@ -13,7 +13,10 @@ import StatusLabel from "@saleor/components/StatusLabel";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { maybe, renderCollection } from "@saleor/misc"; 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"; import { CollectionList_collections_edges_node } from "../../types/CollectionList";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -41,7 +44,10 @@ const useStyles = makeStyles(
{ name: "CollectionList" } { name: "CollectionList" }
); );
interface CollectionListProps extends ListProps, ListActions { interface CollectionListProps
extends ListProps,
ListActions,
SortPage<CollectionListUrlSortField> {
collections: CollectionList_collections_edges_node[]; collections: CollectionList_collections_edges_node[];
} }
@ -52,10 +58,12 @@ const CollectionList: React.FC<CollectionListProps> = props => {
collections, collections,
disabled, disabled,
settings, settings,
sort,
onNextPage, onNextPage,
onPreviousPage, onPreviousPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick, onRowClick,
onSort,
pageInfo, pageInfo,
isChecked, isChecked,
selected, selected,
@ -77,18 +85,43 @@ const CollectionList: React.FC<CollectionListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCellHeader
<FormattedMessage defaultMessage="Category Name" /> direction={
</TableCell> sort.sort === CollectionListUrlSortField.name
<TableCell className={classes.colProducts}> ? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(CollectionListUrlSortField.name)}
className={classes.colName}
>
<FormattedMessage defaultMessage="Collection Name" />
</TableCellHeader>
<TableCellHeader
direction={
sort.sort === CollectionListUrlSortField.productCount
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(CollectionListUrlSortField.productCount)}
className={classes.colProducts}
>
<FormattedMessage defaultMessage="No. of Products" /> <FormattedMessage defaultMessage="No. of Products" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colAvailability}> <TableCellHeader
direction={
sort.sort === CollectionListUrlSortField.available
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(CollectionListUrlSortField.available)}
className={classes.colAvailability}
>
<FormattedMessage <FormattedMessage
defaultMessage="Availability" defaultMessage="Availability"
description="collection availability" description="collection availability"
/> />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>

View file

@ -11,8 +11,10 @@ import {
ListActions, ListActions,
PageListProps, PageListProps,
SearchPageProps, SearchPageProps,
TabPageProps TabPageProps,
SortPage
} from "@saleor/types"; } from "@saleor/types";
import { CollectionListUrlSortField } from "@saleor/collections/urls";
import { CollectionList_collections_edges_node } from "../../types/CollectionList"; import { CollectionList_collections_edges_node } from "../../types/CollectionList";
import CollectionList from "../CollectionList/CollectionList"; import CollectionList from "../CollectionList/CollectionList";
@ -20,6 +22,7 @@ export interface CollectionListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
SearchPageProps, SearchPageProps,
SortPage<CollectionListUrlSortField>,
TabPageProps { TabPageProps {
collections: CollectionList_collections_edges_node[]; collections: CollectionList_collections_edges_node[];
} }

View file

@ -4,13 +4,15 @@ import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { import {
collectionAddPath, collectionAddPath,
collectionListPath, collectionListPath,
CollectionListUrlQueryParams, CollectionListUrlQueryParams,
collectionPath, collectionPath,
CollectionUrlQueryParams CollectionUrlQueryParams,
CollectionListUrlSortField
} from "./urls"; } from "./urls";
import CollectionCreate from "./views/CollectionCreate"; import CollectionCreate from "./views/CollectionCreate";
import CollectionDetailsView from "./views/CollectionDetails"; import CollectionDetailsView from "./views/CollectionDetails";
@ -18,16 +20,19 @@ import CollectionListView from "./views/CollectionList";
const CollectionList: React.FC<RouteComponentProps<{}>> = ({ location }) => { const CollectionList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: CollectionListUrlQueryParams = qs; const params: CollectionListUrlQueryParams = asSortParams(
qs,
CollectionListUrlSortField
);
return <CollectionListView params={params} />; return <CollectionListView params={params} />;
}; };
interface CollectionDetailsRouteProps { interface CollectionDetailsRouteProps {
id: string; id: string;
} }
const CollectionDetails: React.FC< const CollectionDetails: React.FC<RouteComponentProps<
RouteComponentProps<CollectionDetailsRouteProps> CollectionDetailsRouteProps
> = ({ location, match }) => { >> = ({ location, match }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: CollectionUrlQueryParams = qs; const params: CollectionUrlQueryParams = qs;
return ( return (

View file

@ -1,5 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import makeQuery from "@saleor/hooks/makeQuery";
import { TypedQuery } from "../queries"; import { TypedQuery } from "../queries";
import { import {
CollectionDetails, CollectionDetails,
@ -61,6 +62,7 @@ export const collectionList = gql`
$last: Int $last: Int
$before: String $before: String
$filter: CollectionFilterInput $filter: CollectionFilterInput
$sort: CollectionSortingInput
) { ) {
collections( collections(
first: $first first: $first
@ -68,6 +70,7 @@ export const collectionList = gql`
before: $before before: $before
last: $last last: $last
filter: $filter filter: $filter
sortBy: $sort
) { ) {
edges { edges {
node { node {
@ -86,7 +89,7 @@ export const collectionList = gql`
} }
} }
`; `;
export const TypedCollectionListQuery = TypedQuery< export const useCollectionListQuery = makeQuery<
CollectionList, CollectionList,
CollectionListVariables CollectionListVariables
>(collectionList); >(collectionList);

View file

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

View file

@ -7,7 +7,8 @@ import {
Dialog, Dialog,
Filters, Filters,
Pagination, Pagination,
TabActionDialog TabActionDialog,
Sort
} from "../types"; } from "../types";
const collectionSectionUrl = "/collections/"; const collectionSectionUrl = "/collections/";
@ -22,9 +23,16 @@ export type CollectionListUrlDialog =
| "unpublish" | "unpublish"
| "remove" | "remove"
| TabActionDialog; | TabActionDialog;
export enum CollectionListUrlSortField {
name = "name",
available = "available",
productCount = "products"
}
export type CollectionListUrlSort = Sort<CollectionListUrlSortField>;
export type CollectionListUrlQueryParams = ActiveTab & export type CollectionListUrlQueryParams = ActiveTab &
BulkAction & BulkAction &
CollectionListUrlFilters & CollectionListUrlFilters &
CollectionListUrlSort &
Dialog<CollectionListUrlDialog> & Dialog<CollectionListUrlDialog> &
Pagination; Pagination;
export const collectionListUrl = (params?: CollectionListUrlQueryParams) => export const collectionListUrl = (params?: CollectionListUrlQueryParams) =>

View file

@ -20,12 +20,14 @@ import usePaginator, {
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; 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 CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
import { import {
TypedCollectionBulkDelete, TypedCollectionBulkDelete,
TypedCollectionBulkPublish TypedCollectionBulkPublish
} from "../../mutations"; } from "../../mutations";
import { TypedCollectionListQuery } from "../../queries"; import { useCollectionListQuery } from "../../queries";
import { CollectionBulkDelete } from "../../types/CollectionBulkDelete"; import { CollectionBulkDelete } from "../../types/CollectionBulkDelete";
import { CollectionBulkPublish } from "../../types/CollectionBulkPublish"; import { CollectionBulkPublish } from "../../types/CollectionBulkPublish";
import { import {
@ -44,6 +46,7 @@ import {
getFilterVariables, getFilterVariables,
saveFilterTab saveFilterTab
} from "./filter"; } from "./filter";
import { getSortQueryVariables } from "./sort";
interface CollectionListProps { interface CollectionListProps {
params: CollectionListUrlQueryParams; params: CollectionListUrlQueryParams;
@ -61,6 +64,20 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
); );
const intl = useIntl(); 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 tabs = getFilterTabs();
const currentTab = const currentTab =
@ -121,226 +138,213 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
handleTabChange(tabs.length + 1); handleTabChange(tabs.length + 1);
}; };
const paginationState = createPaginationState(settings.rowNumber, params); const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
const queryVariables = React.useMemo( maybe(() => data.collections.pageInfo),
() => ({ paginationState,
...paginationState, params
filter: getFilterVariables(params)
}),
[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 ( return (
<TypedCollectionListQuery displayLoader variables={queryVariables}> <TypedCollectionBulkDelete onCompleted={handleCollectionBulkDelete}>
{({ data, loading, refetch }) => { {(collectionBulkDelete, collectionBulkDeleteOpts) => (
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( <TypedCollectionBulkPublish onCompleted={handleCollectionBulkPublish}>
maybe(() => data.collections.pageInfo), {(collectionBulkPublish, collectionBulkPublishOpts) => (
paginationState, <>
params <CollectionListPage
); currentTab={currentTab}
initialSearch={params.query || ""}
const handleCollectionBulkDelete = (data: CollectionBulkDelete) => { onSearchChange={query => changeFilterField({ query })}
if (data.collectionBulkDelete.errors.length === 0) { onAdd={() => navigate(collectionAddUrl)}
notify({ onAll={() => navigate(collectionListUrl())}
text: intl.formatMessage(commonMessages.savedChanges) onTabChange={handleTabChange}
}); onTabDelete={() => openModal("delete-search")}
refetch(); onTabSave={() => openModal("save-search")}
reset(); tabs={tabs.map(tab => tab.name)}
closeModal(); disabled={loading}
} collections={maybe(() =>
}; data.collections.edges.map(edge => edge.node)
const handleCollectionBulkPublish = (data: CollectionBulkPublish) => {
if (data.collectionBulkPublish.errors.length === 0) {
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
refetch();
reset();
closeModal();
}
};
return (
<TypedCollectionBulkDelete onCompleted={handleCollectionBulkDelete}>
{(collectionBulkDelete, collectionBulkDeleteOpts) => (
<TypedCollectionBulkPublish
onCompleted={handleCollectionBulkPublish}
>
{(collectionBulkPublish, collectionBulkPublishOpts) => (
<>
<CollectionListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAdd={() => navigate(collectionAddUrl)}
onAll={() => navigate(collectionListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
disabled={loading}
collections={maybe(() =>
data.collections.edges.map(edge => edge.node)
)}
settings={settings}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
pageInfo={pageInfo}
onRowClick={id => () => navigate(collectionUrl(id))}
toolbar={
<>
<Button
color="primary"
onClick={() => openModal("unpublish", listElements)}
>
<FormattedMessage
defaultMessage="Unpublish"
description="unpublish collections"
/>
</Button>
<Button
color="primary"
onClick={() => openModal("publish", listElements)}
>
<FormattedMessage
defaultMessage="Publish"
description="publish collections"
/>
</Button>
<IconButton
color="primary"
onClick={() => openModal("remove", listElements)}
>
<DeleteIcon />
</IconButton>
</>
}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
/>
<ActionDialog
open={
params.action === "publish" &&
maybe(() => 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"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to publish {counter,plural,one{this collection} other{{displayQuantity} collections}}?"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={
params.action === "unpublish" &&
maybe(() => 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"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to unpublish {counter,plural,one{this collection} other{{displayQuantity} collections}}?"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={
params.action === "remove" &&
maybe(() => 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"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this collection} other{{displayQuantity} collections}}?"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
)} )}
</TypedCollectionBulkPublish> settings={settings}
)} onNextPage={loadNextPage}
</TypedCollectionBulkDelete> onPreviousPage={loadPreviousPage}
); onSort={handleSort}
}} onUpdateListSettings={updateListSettings}
</TypedCollectionListQuery> pageInfo={pageInfo}
sort={getSortParams(params)}
onRowClick={id => () => navigate(collectionUrl(id))}
toolbar={
<>
<Button
color="primary"
onClick={() => openModal("unpublish", listElements)}
>
<FormattedMessage
defaultMessage="Unpublish"
description="unpublish collections"
/>
</Button>
<Button
color="primary"
onClick={() => openModal("publish", listElements)}
>
<FormattedMessage
defaultMessage="Publish"
description="publish collections"
/>
</Button>
<IconButton
color="primary"
onClick={() => openModal("remove", listElements)}
>
<DeleteIcon />
</IconButton>
</>
}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
/>
<ActionDialog
open={
params.action === "publish" &&
maybe(() => 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"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to publish {counter,plural,one{this collection} other{{displayQuantity} collections}}?"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={
params.action === "unpublish" &&
maybe(() => 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"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to unpublish {counter,plural,one{this collection} other{{displayQuantity} collections}}?"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={
params.action === "remove" &&
maybe(() => 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"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this collection} other{{displayQuantity} collections}}?"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
)}
</TypedCollectionBulkPublish>
)}
</TypedCollectionBulkDelete>
); );
}; };
export default CollectionList; export default CollectionList;

View file

@ -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
);

View file

@ -11,7 +11,7 @@ import { sectionNames } from "@saleor/intl";
import { menuListUrl } from "@saleor/navigation/urls"; import { menuListUrl } from "@saleor/navigation/urls";
import { orderDraftListUrl, orderListUrl } from "@saleor/orders/urls"; import { orderDraftListUrl, orderListUrl } from "@saleor/orders/urls";
import { pageListUrl } from "@saleor/pages/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 { productListUrl } from "@saleor/products/urls";
import { productTypeListUrl } from "@saleor/productTypes/urls"; import { productTypeListUrl } from "@saleor/productTypes/urls";
import { serviceListUrl } from "@saleor/services/urls"; import { serviceListUrl } from "@saleor/services/urls";
@ -71,7 +71,7 @@ function searchInViews(
}, },
{ {
label: intl.formatMessage(sectionNames.plugins), label: intl.formatMessage(sectionNames.plugins),
url: pluginsListUrl() url: pluginListUrl()
}, },
{ {
label: intl.formatMessage(sectionNames.productTypes), label: intl.formatMessage(sectionNames.productTypes),

View file

@ -16,6 +16,11 @@ const useStyles = makeStyles(
arrowUp: { arrowUp: {
transform: "rotate(180deg)" transform: "rotate(180deg)"
}, },
disableClick: {
"&&": {
cursor: "unset"
}
},
label: { label: {
alignSelf: "center", alignSelf: "center",
display: "inline-block" display: "inline-block"
@ -48,6 +53,7 @@ export type TableCellHeaderArrowPosition = "left" | "right";
export interface TableCellHeaderProps extends TableCellProps { export interface TableCellHeaderProps extends TableCellProps {
arrowPosition?: TableCellHeaderArrowPosition; arrowPosition?: TableCellHeaderArrowPosition;
direction?: TableCellHeaderArrowDirection; direction?: TableCellHeaderArrowDirection;
disableClick?: boolean;
textAlign?: "left" | "center" | "right"; textAlign?: "left" | "center" | "right";
} }
@ -58,12 +64,18 @@ const TableCellHeader: React.FC<TableCellHeaderProps> = props => {
children, children,
className, className,
direction, direction,
disableClick,
textAlign, textAlign,
...rest ...rest
} = props; } = props;
return ( return (
<TableCell {...rest} className={classNames(classes.root, className)}> <TableCell
{...rest}
className={classNames(classes.root, className, {
[classes.disableClick]: disableClick
})}
>
<div <div
className={classNames(classes.labelContainer, { className={classNames(classes.labelContainer, {
[classes.labelContainerActive]: !!direction && !!arrowPosition, [classes.labelContainerActive]: !!direction && !!arrowPosition,

View file

@ -20,7 +20,7 @@ import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { menuListUrl } from "@saleor/navigation/urls"; import { menuListUrl } from "@saleor/navigation/urls";
import { pageListUrl } from "@saleor/pages/urls"; import { pageListUrl } from "@saleor/pages/urls";
import { pluginsListUrl } from "@saleor/plugins/urls"; import { pluginListUrl } from "@saleor/plugins/urls";
import { productTypeListUrl } from "@saleor/productTypes/urls"; import { productTypeListUrl } from "@saleor/productTypes/urls";
import { serviceListUrl } from "@saleor/services/urls"; import { serviceListUrl } from "@saleor/services/urls";
import { shippingZonesListUrl } from "@saleor/shipping/urls"; import { shippingZonesListUrl } from "@saleor/shipping/urls";
@ -153,7 +153,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
), ),
permission: PermissionEnum.MANAGE_PLUGINS, permission: PermissionEnum.MANAGE_PLUGINS,
title: intl.formatMessage(sectionNames.plugins), title: intl.formatMessage(sectionNames.plugins),
url: pluginsListUrl() url: pluginListUrl()
}, },
{ {
description: intl.formatMessage({ description: intl.formatMessage({

View file

@ -12,7 +12,10 @@ import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { getUserName, maybe, renderCollection } from "@saleor/misc"; import { getUserName, maybe, renderCollection } from "@saleor/misc";
import { ListActions, ListProps } from "@saleor/types"; import { ListActions, ListProps, SortPage } from "@saleor/types";
import { CustomerListUrlSortField } from "@saleor/customers/urls";
import TableCellHeader from "@saleor/components/TableCellHeader";
import { getArrowDirection } from "@saleor/utils/sort";
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -38,7 +41,10 @@ const useStyles = makeStyles(
{ name: "CustomerList" } { name: "CustomerList" }
); );
export interface CustomerListProps extends ListProps, ListActions { export interface CustomerListProps
extends ListProps,
ListActions,
SortPage<CustomerListUrlSortField> {
customers: ListCustomers_customers_edges_node[]; customers: ListCustomers_customers_edges_node[];
} }
@ -54,10 +60,12 @@ const CustomerList: React.FC<CustomerListProps> = props => {
onPreviousPage, onPreviousPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick, onRowClick,
onSort,
toolbar, toolbar,
toggle, toggle,
toggleAll, toggleAll,
selected, selected,
sort,
isChecked isChecked
} = props; } = props;
@ -73,15 +81,41 @@ const CustomerList: React.FC<CustomerListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCellHeader
direction={
sort.sort === CustomerListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(CustomerListUrlSortField.name)}
className={classes.colName}
>
<FormattedMessage defaultMessage="Customer Name" /> <FormattedMessage defaultMessage="Customer Name" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colEmail}> <TableCellHeader
direction={
sort.sort === CustomerListUrlSortField.email
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(CustomerListUrlSortField.email)}
className={classes.colEmail}
>
<FormattedMessage defaultMessage="Customer Email" /> <FormattedMessage defaultMessage="Customer Email" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colOrders}> <TableCellHeader
direction={
sort.sort === CustomerListUrlSortField.orders
? getArrowDirection(sort.asc)
: undefined
}
textAlign="center"
onClick={() => onSort(CustomerListUrlSortField.orders)}
className={classes.colOrders}
>
<FormattedMessage defaultMessage="No. of Orders" /> <FormattedMessage defaultMessage="No. of Orders" />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>

View file

@ -11,8 +11,10 @@ import {
ListActions, ListActions,
PageListProps, PageListProps,
SearchPageProps, SearchPageProps,
TabPageProps TabPageProps,
SortPage
} from "@saleor/types"; } from "@saleor/types";
import { CustomerListUrlSortField } from "@saleor/customers/urls";
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
import CustomerList from "../CustomerList/CustomerList"; import CustomerList from "../CustomerList/CustomerList";
@ -20,14 +22,13 @@ export interface CustomerListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
SearchPageProps, SearchPageProps,
SortPage<CustomerListUrlSortField>,
TabPageProps { TabPageProps {
customers: ListCustomers_customers_edges_node[]; customers: ListCustomers_customers_edges_node[];
} }
const CustomerListPage: React.FC<CustomerListPageProps> = ({ const CustomerListPage: React.FC<CustomerListPageProps> = ({
currentTab, currentTab,
customers,
disabled,
initialSearch, initialSearch,
onAdd, onAdd,
onAll, onAll,
@ -43,12 +44,7 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
return ( return (
<Container> <Container>
<PageHeader title={intl.formatMessage(sectionNames.customers)}> <PageHeader title={intl.formatMessage(sectionNames.customers)}>
<Button <Button color="primary" variant="contained" onClick={onAdd}>
color="primary"
variant="contained"
disabled={disabled}
onClick={onAdd}
>
<FormattedMessage <FormattedMessage
defaultMessage="Create customer" defaultMessage="Create customer"
description="button" description="button"
@ -73,11 +69,7 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
onTabDelete={onTabDelete} onTabDelete={onTabDelete}
onTabSave={onTabSave} onTabSave={onTabSave}
/> />
<CustomerList <CustomerList {...customerListProps} />
customers={customers}
disabled={disabled}
{...customerListProps}
/>
</Card> </Card>
</Container> </Container>
); );

View file

@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { import {
customerAddPath, customerAddPath,
@ -12,7 +13,8 @@ import {
customerListPath, customerListPath,
CustomerListUrlQueryParams, CustomerListUrlQueryParams,
customerPath, customerPath,
CustomerUrlQueryParams CustomerUrlQueryParams,
CustomerListUrlSortField
} from "./urls"; } from "./urls";
import CustomerAddressesViewComponent from "./views/CustomerAddresses"; import CustomerAddressesViewComponent from "./views/CustomerAddresses";
import CustomerCreateView from "./views/CustomerCreate"; import CustomerCreateView from "./views/CustomerCreate";
@ -21,16 +23,20 @@ import CustomerListViewComponent from "./views/CustomerList";
const CustomerListView: React.FC<RouteComponentProps<{}>> = ({ location }) => { const CustomerListView: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: CustomerListUrlQueryParams = qs; const params: CustomerListUrlQueryParams = asSortParams(
qs,
CustomerListUrlSortField
);
return <CustomerListViewComponent params={params} />; return <CustomerListViewComponent params={params} />;
}; };
interface CustomerDetailsRouteParams { interface CustomerDetailsRouteParams {
id: string; id: string;
} }
const CustomerDetailsView: React.FC< const CustomerDetailsView: React.FC<RouteComponentProps<
RouteComponentProps<CustomerDetailsRouteParams> CustomerDetailsRouteParams
> = ({ location, match }) => { >> = ({ location, match }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: CustomerUrlQueryParams = qs; const params: CustomerUrlQueryParams = qs;
@ -45,9 +51,9 @@ const CustomerDetailsView: React.FC<
interface CustomerAddressesRouteParams { interface CustomerAddressesRouteParams {
id: string; id: string;
} }
const CustomerAddressesView: React.FC< const CustomerAddressesView: React.FC<RouteComponentProps<
RouteComponentProps<CustomerAddressesRouteParams> CustomerAddressesRouteParams
> = ({ match }) => { >> = ({ match }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: CustomerAddressesUrlQueryParams = qs; const params: CustomerAddressesUrlQueryParams = qs;

View file

@ -1,5 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import makeQuery from "@saleor/hooks/makeQuery";
import { fragmentAddress } from "../orders/queries"; import { fragmentAddress } from "../orders/queries";
import { TypedQuery } from "../queries"; import { TypedQuery } from "../queries";
import { import {
@ -65,6 +66,7 @@ const customerList = gql`
$first: Int $first: Int
$last: Int $last: Int
$filter: CustomerFilterInput $filter: CustomerFilterInput
$sort: UserSortingInput
) { ) {
customers( customers(
after: $after after: $after
@ -72,6 +74,7 @@ const customerList = gql`
first: $first first: $first
last: $last last: $last
filter: $filter filter: $filter
sortBy: $sort
) { ) {
edges { edges {
node { node {
@ -90,7 +93,7 @@ const customerList = gql`
} }
} }
`; `;
export const TypedCustomerListQuery = TypedQuery< export const useCustomerListQuery = makeQuery<
ListCustomers, ListCustomers,
ListCustomersVariables ListCustomersVariables
>(customerList); >(customerList);

View file

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

View file

@ -8,7 +8,8 @@ import {
Filters, Filters,
Pagination, Pagination,
SingleAction, SingleAction,
TabActionDialog TabActionDialog,
Sort
} from "../types"; } from "../types";
export const customerSection = "/customers/"; export const customerSection = "/customers/";
@ -19,9 +20,16 @@ export enum CustomerListUrlFiltersEnum {
} }
export type CustomerListUrlFilters = Filters<CustomerListUrlFiltersEnum>; export type CustomerListUrlFilters = Filters<CustomerListUrlFiltersEnum>;
export type CustomerListUrlDialog = "remove" | TabActionDialog; export type CustomerListUrlDialog = "remove" | TabActionDialog;
export enum CustomerListUrlSortField {
name = "name",
email = "email",
orders = "orders"
}
export type CustomerListUrlSort = Sort<CustomerListUrlSortField>;
export type CustomerListUrlQueryParams = ActiveTab & export type CustomerListUrlQueryParams = ActiveTab &
BulkAction & BulkAction &
CustomerListUrlFilters & CustomerListUrlFilters &
CustomerListUrlSort &
Dialog<CustomerListUrlDialog> & Dialog<CustomerListUrlDialog> &
Pagination; Pagination;
export const customerListUrl = (params?: CustomerListUrlQueryParams) => export const customerListUrl = (params?: CustomerListUrlQueryParams) =>

View file

@ -19,9 +19,11 @@ import usePaginator, {
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import CustomerListPage from "../../components/CustomerListPage"; import CustomerListPage from "../../components/CustomerListPage";
import { TypedBulkRemoveCustomers } from "../../mutations"; import { TypedBulkRemoveCustomers } from "../../mutations";
import { TypedCustomerListQuery } from "../../queries"; import { useCustomerListQuery } from "../../queries";
import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers"; import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers";
import { import {
customerAddUrl, customerAddUrl,
@ -39,6 +41,7 @@ import {
getFilterVariables, getFilterVariables,
saveFilterTab saveFilterTab
} from "./filter"; } from "./filter";
import { getSortQueryVariables } from "./sort";
interface CustomerListProps { interface CustomerListProps {
params: CustomerListUrlQueryParams; params: CustomerListUrlQueryParams;
@ -56,6 +59,20 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
); );
const intl = useIntl(); 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 } = useCustomerListQuery({
displayLoader: true,
variables: queryVariables
});
const tabs = getFilterTabs(); const tabs = getFilterTabs();
const currentTab = const currentTab =
@ -116,130 +133,116 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
handleTabChange(tabs.length + 1); handleTabChange(tabs.length + 1);
}; };
const paginationState = createPaginationState(settings.rowNumber, params); const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
const queryVariables = React.useMemo( maybe(() => data.customers.pageInfo),
() => ({ paginationState,
...paginationState, params
filter: getFilterVariables(params)
}),
[params]
); );
const handleBulkCustomerDelete = (data: BulkRemoveCustomers) => {
if (data.customerBulkDelete.errors.length === 0) {
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
reset();
refetch();
closeModal();
}
};
const handleSort = createSortHandler(navigate, customerListUrl, params);
return ( return (
<TypedCustomerListQuery displayLoader variables={queryVariables}> <TypedBulkRemoveCustomers onCompleted={handleBulkCustomerDelete}>
{({ data, loading, refetch }) => { {(bulkRemoveCustomers, bulkRemoveCustomersOpts) => (
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( <>
maybe(() => data.customers.pageInfo), <CustomerListPage
paginationState, currentTab={currentTab}
params initialSearch={params.query || ""}
); onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(customerListUrl())}
const handleBulkCustomerDelete = (data: BulkRemoveCustomers) => { onTabChange={handleTabChange}
if (data.customerBulkDelete.errors.length === 0) { onTabDelete={() => openModal("delete-search")}
notify({ onTabSave={() => openModal("save-search")}
text: intl.formatMessage(commonMessages.savedChanges) tabs={tabs.map(tab => tab.name)}
}); customers={maybe(() => data.customers.edges.map(edge => edge.node))}
reset(); settings={settings}
refetch(); disabled={loading}
closeModal(); pageInfo={pageInfo}
} onAdd={() => navigate(customerAddUrl)}
}; onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
return ( onUpdateListSettings={updateListSettings}
<TypedBulkRemoveCustomers onCompleted={handleBulkCustomerDelete}> onRowClick={id => () => navigate(customerUrl(id))}
{(bulkRemoveCustomers, bulkRemoveCustomersOpts) => ( onSort={handleSort}
<> toolbar={
<CustomerListPage <IconButton
currentTab={currentTab} color="primary"
initialSearch={params.query || ""} onClick={() =>
onSearchChange={query => changeFilterField({ query })} navigate(
onAll={() => navigate(customerListUrl())} customerListUrl({
onTabChange={handleTabChange} action: "remove",
onTabDelete={() => openModal("delete-search")} ids: listElements
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
customers={maybe(() =>
data.customers.edges.map(edge => edge.node)
)}
settings={settings}
disabled={loading}
pageInfo={pageInfo}
onAdd={() => navigate(customerAddUrl)}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(customerUrl(id))}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
customerListUrl({
action: "remove",
ids: listElements
})
)
}
>
<DeleteIcon />
</IconButton>
}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
/>
<ActionDialog
open={
params.action === "remove" &&
maybe(() => params.ids.length > 0)
}
onClose={closeModal}
confirmButtonState={bulkRemoveCustomersOpts.status}
onConfirm={() =>
bulkRemoveCustomers({
variables: {
ids: params.ids
}
}) })
} )
variant="delete" }
title={intl.formatMessage({ >
defaultMessage: "Delete Customers", <DeleteIcon />
description: "dialog header" </IconButton>
})} }
> isChecked={isSelected}
<DialogContentText> selected={listElements.length}
<FormattedMessage sort={getSortParams(params)}
defaultMessage="Are you sure you want to delete {counter,plural,one{this customer} other{{displayQuantity} customers}}?" toggle={toggle}
values={{ toggleAll={toggleAll}
counter: maybe(() => params.ids.length), />
displayQuantity: ( <ActionDialog
<strong>{maybe(() => params.ids.length)}</strong> open={
) params.action === "remove" && maybe(() => params.ids.length > 0)
}} }
/> onClose={closeModal}
</DialogContentText> confirmButtonState={bulkRemoveCustomersOpts.status}
</ActionDialog> onConfirm={() =>
<SaveFilterTabDialog bulkRemoveCustomers({
open={params.action === "save-search"} variables: {
confirmButtonState="default" ids: params.ids
onClose={closeModal} }
onSubmit={handleTabSave} })
/> }
<DeleteFilterTabDialog variant="delete"
open={params.action === "delete-search"} title={intl.formatMessage({
confirmButtonState="default" defaultMessage: "Delete Customers",
onClose={closeModal} description: "dialog header"
onSubmit={handleTabDelete} })}
tabName={maybe(() => tabs[currentTab - 1].name, "...")} >
/> <DialogContentText>
</> <FormattedMessage
)} defaultMessage="Are you sure you want to delete {counter,plural,one{this customer} other{{displayQuantity} customers}}?"
</TypedBulkRemoveCustomers> values={{
); counter: maybe(() => params.ids.length),
}} displayQuantity: (
</TypedCustomerListQuery> <strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
)}
</TypedBulkRemoveCustomers>
); );
}; };
export default CustomerList; export default CustomerList;

View file

@ -0,0 +1,22 @@
import { CustomerListUrlSortField } from "@saleor/customers/urls";
import { UserSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(
sort: CustomerListUrlSortField
): UserSortField {
switch (sort) {
case CustomerListUrlSortField.email:
return UserSortField.EMAIL;
case CustomerListUrlSortField.name:
return UserSortField.LAST_NAME;
case CustomerListUrlSortField.orders:
return UserSortField.ORDER_COUNT;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -15,11 +15,17 @@ import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { maybe, renderCollection } from "@saleor/misc"; import { maybe, renderCollection } from "@saleor/misc";
import { ListActions, ListProps } from "@saleor/types"; import { ListActions, ListProps, SortPage } from "@saleor/types";
import { SaleType } from "@saleor/types/globalTypes"; import { SaleType } from "@saleor/types/globalTypes";
import { SaleListUrlSortField } from "@saleor/discounts/urls";
import TableCellHeader from "@saleor/components/TableCellHeader";
import { getArrowDirection } from "@saleor/utils/sort";
import { SaleList_sales_edges_node } from "../../types/SaleList"; import { SaleList_sales_edges_node } from "../../types/SaleList";
export interface SaleListProps extends ListProps, ListActions { export interface SaleListProps
extends ListProps,
ListActions,
SortPage<SaleListUrlSortField> {
defaultCurrency: string; defaultCurrency: string;
sales: SaleList_sales_edges_node[]; sales: SaleList_sales_edges_node[];
} }
@ -68,10 +74,12 @@ const SaleList: React.FC<SaleListProps> = props => {
onPreviousPage, onPreviousPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick, onRowClick,
onSort,
pageInfo, pageInfo,
sales, sales,
isChecked, isChecked,
selected, selected,
sort,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
@ -89,21 +97,57 @@ const SaleList: React.FC<SaleListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCellHeader
direction={
sort.sort === SaleListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(SaleListUrlSortField.name)}
className={classes.colName}
>
<FormattedMessage defaultMessage="Name" description="sale name" /> <FormattedMessage defaultMessage="Name" description="sale name" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colStart}> <TableCellHeader
direction={
sort.sort === SaleListUrlSortField.startDate
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(SaleListUrlSortField.startDate)}
className={classes.colStart}
>
<FormattedMessage <FormattedMessage
defaultMessage="Starts" defaultMessage="Starts"
description="sale start date" description="sale start date"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colEnd}> <TableCellHeader
direction={
sort.sort === SaleListUrlSortField.endDate
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(SaleListUrlSortField.endDate)}
className={classes.colEnd}
>
<FormattedMessage defaultMessage="Ends" description="sale end date" /> <FormattedMessage defaultMessage="Ends" description="sale end date" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colValue}> <TableCellHeader
direction={
sort.sort === SaleListUrlSortField.value
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(SaleListUrlSortField.value)}
className={classes.colValue}
>
<FormattedMessage defaultMessage="Value" description="sale value" /> <FormattedMessage defaultMessage="Value" description="sale value" />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>

View file

@ -11,8 +11,10 @@ import {
ListActions, ListActions,
PageListProps, PageListProps,
SearchPageProps, SearchPageProps,
TabPageProps TabPageProps,
SortPage
} from "@saleor/types"; } from "@saleor/types";
import { SaleListUrlSortField } from "@saleor/discounts/urls";
import { SaleList_sales_edges_node } from "../../types/SaleList"; import { SaleList_sales_edges_node } from "../../types/SaleList";
import SaleList from "../SaleList"; import SaleList from "../SaleList";
@ -20,6 +22,7 @@ export interface SaleListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
SearchPageProps, SearchPageProps,
SortPage<SaleListUrlSortField>,
TabPageProps { TabPageProps {
defaultCurrency: string; defaultCurrency: string;
sales: SaleList_sales_edges_node[]; sales: SaleList_sales_edges_node[];

View file

@ -15,11 +15,17 @@ import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { maybe, renderCollection } from "@saleor/misc"; import { maybe, renderCollection } from "@saleor/misc";
import { ListActions, ListProps } from "@saleor/types"; import { ListActions, ListProps, SortPage } from "@saleor/types";
import { DiscountValueTypeEnum } from "@saleor/types/globalTypes"; import { DiscountValueTypeEnum } from "@saleor/types/globalTypes";
import { VoucherListUrlSortField } from "@saleor/discounts/urls";
import TableCellHeader from "@saleor/components/TableCellHeader";
import { getArrowDirection } from "@saleor/utils/sort";
import { VoucherList_vouchers_edges_node } from "../../types/VoucherList"; import { VoucherList_vouchers_edges_node } from "../../types/VoucherList";
export interface VoucherListProps extends ListProps, ListActions { export interface VoucherListProps
extends ListProps,
ListActions,
SortPage<VoucherListUrlSortField> {
defaultCurrency: string; defaultCurrency: string;
vouchers: VoucherList_vouchers_edges_node[]; vouchers: VoucherList_vouchers_edges_node[];
} }
@ -83,10 +89,12 @@ const VoucherList: React.FC<VoucherListProps> = props => {
onPreviousPage, onPreviousPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick, onRowClick,
onSort,
pageInfo, pageInfo,
vouchers, vouchers,
isChecked, isChecked,
selected, selected,
sort,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
@ -104,36 +112,85 @@ const VoucherList: React.FC<VoucherListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCellHeader
direction={
sort.sort === VoucherListUrlSortField.code
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(VoucherListUrlSortField.code)}
className={classes.colName}
>
<FormattedMessage defaultMessage="Code" description="voucher code" /> <FormattedMessage defaultMessage="Code" description="voucher code" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colMinSpent}> <TableCellHeader
direction={
sort.sort === VoucherListUrlSortField.minSpent
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(VoucherListUrlSortField.minSpent)}
className={classes.colMinSpent}
>
<FormattedMessage <FormattedMessage
defaultMessage="Min. Spent" defaultMessage="Min. Spent"
description="minimum amount of spent money to activate voucher" description="minimum amount of spent money to activate voucher"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colStart}> <TableCellHeader
direction={
sort.sort === VoucherListUrlSortField.startDate
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(VoucherListUrlSortField.startDate)}
className={classes.colStart}
>
<FormattedMessage <FormattedMessage
defaultMessage="Starts" defaultMessage="Starts"
description="voucher is active from date" description="voucher is active from date"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colEnd}> <TableCellHeader
direction={
sort.sort === VoucherListUrlSortField.endDate
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(VoucherListUrlSortField.endDate)}
className={classes.colEnd}
>
<FormattedMessage <FormattedMessage
defaultMessage="Ends" defaultMessage="Ends"
description="voucher is active until date" description="voucher is active until date"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colValue}> <TableCellHeader
direction={
sort.sort === VoucherListUrlSortField.value
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(VoucherListUrlSortField.value)}
className={classes.colValue}
>
<FormattedMessage <FormattedMessage
defaultMessage="Value" defaultMessage="Value"
description="voucher value" description="voucher value"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colUses}> <TableCellHeader
direction={
sort.sort === VoucherListUrlSortField.limit
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(VoucherListUrlSortField.limit)}
className={classes.colUses}
>
<FormattedMessage defaultMessage="Uses" description="voucher uses" /> <FormattedMessage defaultMessage="Uses" description="voucher uses" />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>

View file

@ -11,8 +11,10 @@ import {
ListActions, ListActions,
PageListProps, PageListProps,
SearchPageProps, SearchPageProps,
TabPageProps TabPageProps,
SortPage
} from "@saleor/types"; } from "@saleor/types";
import { VoucherListUrlSortField } from "@saleor/discounts/urls";
import { VoucherList_vouchers_edges_node } from "../../types/VoucherList"; import { VoucherList_vouchers_edges_node } from "../../types/VoucherList";
import VoucherList from "../VoucherList"; import VoucherList from "../VoucherList";
@ -20,6 +22,7 @@ export interface VoucherListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
SearchPageProps, SearchPageProps,
SortPage<VoucherListUrlSortField>,
TabPageProps { TabPageProps {
defaultCurrency: string; defaultCurrency: string;
vouchers: VoucherList_vouchers_edges_node[]; vouchers: VoucherList_vouchers_edges_node[];

View file

@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { saleDetailsPageTab } from "./components/SaleDetailsPage"; import { saleDetailsPageTab } from "./components/SaleDetailsPage";
import { voucherDetailsPageTab } from "./components/VoucherDetailsPage"; import { voucherDetailsPageTab } from "./components/VoucherDetailsPage";
@ -17,7 +18,9 @@ import {
voucherListPath, voucherListPath,
VoucherListUrlQueryParams, VoucherListUrlQueryParams,
voucherPath, voucherPath,
VoucherUrlQueryParams VoucherUrlQueryParams,
SaleListUrlSortField,
VoucherListUrlSortField
} from "./urls"; } from "./urls";
import SaleCreateView from "./views/SaleCreate"; import SaleCreateView from "./views/SaleCreate";
import SaleDetailsViewComponent from "./views/SaleDetails"; import SaleDetailsViewComponent from "./views/SaleDetails";
@ -28,7 +31,7 @@ import VoucherListViewComponent from "./views/VoucherList";
const SaleListView: React.FC<RouteComponentProps<{}>> = ({ location }) => { const SaleListView: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: SaleListUrlQueryParams = qs; const params: SaleListUrlQueryParams = asSortParams(qs, SaleListUrlSortField);
return <SaleListViewComponent params={params} />; return <SaleListViewComponent params={params} />;
}; };
@ -41,6 +44,7 @@ const SaleDetailsView: React.FC<RouteComponentProps<{ id: string }>> = ({
...qs, ...qs,
activeTab: saleDetailsPageTab(activeTab) activeTab: saleDetailsPageTab(activeTab)
}; };
return ( return (
<SaleDetailsViewComponent <SaleDetailsViewComponent
id={decodeURIComponent(match.params.id)} id={decodeURIComponent(match.params.id)}
@ -51,7 +55,11 @@ const SaleDetailsView: React.FC<RouteComponentProps<{ id: string }>> = ({
const VoucherListView: React.FC<RouteComponentProps<{}>> = ({ location }) => { const VoucherListView: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: VoucherListUrlQueryParams = qs; const params: VoucherListUrlQueryParams = asSortParams(
qs,
VoucherListUrlSortField,
VoucherListUrlSortField.code
);
return <VoucherListViewComponent params={params} />; return <VoucherListViewComponent params={params} />;
}; };

View file

@ -1,5 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import makeQuery from "@saleor/hooks/makeQuery";
import { pageInfoFragment, TypedQuery } from "../queries"; import { pageInfoFragment, TypedQuery } from "../queries";
import { SaleDetails, SaleDetailsVariables } from "./types/SaleDetails"; import { SaleDetails, SaleDetailsVariables } from "./types/SaleDetails";
import { SaleList, SaleListVariables } from "./types/SaleList"; import { SaleList, SaleListVariables } from "./types/SaleList";
@ -172,6 +173,7 @@ export const saleList = gql`
$first: Int $first: Int
$last: Int $last: Int
$filter: SaleFilterInput $filter: SaleFilterInput
$sort: SaleSortingInput
) { ) {
sales( sales(
after: $after after: $after
@ -179,6 +181,7 @@ export const saleList = gql`
first: $first first: $first
last: $last last: $last
filter: $filter filter: $filter
sortBy: $sort
) { ) {
edges { edges {
node { node {
@ -191,7 +194,9 @@ export const saleList = gql`
} }
} }
`; `;
export const TypedSaleList = TypedQuery<SaleList, SaleListVariables>(saleList); export const useSaleListQuery = makeQuery<SaleList, SaleListVariables>(
saleList
);
export const voucherList = gql` export const voucherList = gql`
${pageInfoFragment} ${pageInfoFragment}
@ -202,6 +207,7 @@ export const voucherList = gql`
$first: Int $first: Int
$last: Int $last: Int
$filter: VoucherFilterInput $filter: VoucherFilterInput
$sort: VoucherSortingInput
) { ) {
vouchers( vouchers(
after: $after after: $after
@ -209,6 +215,7 @@ export const voucherList = gql`
first: $first first: $first
last: $last last: $last
filter: $filter filter: $filter
sortBy: $sort
) { ) {
edges { edges {
node { node {
@ -221,7 +228,7 @@ export const voucherList = gql`
} }
} }
`; `;
export const TypedVoucherList = TypedQuery<VoucherList, VoucherListVariables>( export const useVoucherListQuery = makeQuery<VoucherList, VoucherListVariables>(
voucherList voucherList
); );

View file

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

View file

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

View file

@ -7,7 +7,8 @@ import {
Dialog, Dialog,
Filters, Filters,
Pagination, Pagination,
TabActionDialog TabActionDialog,
Sort
} from "../types"; } from "../types";
import { SaleDetailsPageTab } from "./components/SaleDetailsPage"; import { SaleDetailsPageTab } from "./components/SaleDetailsPage";
import { VoucherDetailsPageTab } from "./components/VoucherDetailsPage"; import { VoucherDetailsPageTab } from "./components/VoucherDetailsPage";
@ -21,11 +22,20 @@ export enum SaleListUrlFiltersEnum {
} }
export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum>; export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum>;
export type SaleListUrlDialog = "remove" | TabActionDialog; export type SaleListUrlDialog = "remove" | TabActionDialog;
export enum SaleListUrlSortField {
name = "name",
endDate = "end-date",
startDate = "start-date",
type = "type",
value = "value"
}
export type SaleListUrlSort = Sort<SaleListUrlSortField>;
export type SaleListUrlQueryParams = ActiveTab & export type SaleListUrlQueryParams = ActiveTab &
BulkAction & BulkAction &
Dialog<SaleListUrlDialog> & Dialog<SaleListUrlDialog> &
Pagination & Pagination &
SaleListUrlFilters; SaleListUrlFilters &
SaleListUrlSort;
export const saleListUrl = (params?: SaleListUrlQueryParams) => export const saleListUrl = (params?: SaleListUrlQueryParams) =>
saleListPath + "?" + stringifyQs(params); saleListPath + "?" + stringifyQs(params);
export const salePath = (id: string) => urlJoin(saleSection, id); export const salePath = (id: string) => urlJoin(saleSection, id);
@ -53,11 +63,22 @@ export enum VoucherListUrlFiltersEnum {
} }
export type VoucherListUrlFilters = Filters<VoucherListUrlFiltersEnum>; export type VoucherListUrlFilters = Filters<VoucherListUrlFiltersEnum>;
export type VoucherListUrlDialog = "remove" | TabActionDialog; export type VoucherListUrlDialog = "remove" | TabActionDialog;
export enum VoucherListUrlSortField {
code = "code",
endDate = "end-date",
limit = "limit",
minSpent = "min-spent",
startDate = "start-date",
type = "type",
value = "value"
}
export type VoucherListUrlSort = Sort<VoucherListUrlSortField>;
export type VoucherListUrlQueryParams = ActiveTab & export type VoucherListUrlQueryParams = ActiveTab &
BulkAction & BulkAction &
Dialog<VoucherListUrlDialog> & Dialog<VoucherListUrlDialog> &
Pagination & Pagination &
VoucherListUrlFilters; VoucherListUrlFilters &
VoucherListUrlSort;
export const voucherListUrl = (params?: VoucherListUrlQueryParams) => export const voucherListUrl = (params?: VoucherListUrlQueryParams) =>
voucherListPath + "?" + stringifyQs(params); voucherListPath + "?" + stringifyQs(params);
export const voucherPath = (id: string) => urlJoin(voucherSection, id); export const voucherPath = (id: string) => urlJoin(voucherSection, id);

View file

@ -21,9 +21,11 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl"; import { commonMessages, sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import SaleListPage from "../../components/SaleListPage"; import SaleListPage from "../../components/SaleListPage";
import { TypedSaleBulkDelete } from "../../mutations"; import { TypedSaleBulkDelete } from "../../mutations";
import { TypedSaleList } from "../../queries"; import { useSaleListQuery } from "../../queries";
import { SaleBulkDelete } from "../../types/SaleBulkDelete"; import { SaleBulkDelete } from "../../types/SaleBulkDelete";
import { import {
saleAddUrl, saleAddUrl,
@ -41,6 +43,7 @@ import {
getFilterVariables, getFilterVariables,
saveFilterTab saveFilterTab
} from "./filter"; } from "./filter";
import { getSortQueryVariables } from "./sort";
interface SaleListProps { interface SaleListProps {
params: SaleListUrlQueryParams; params: SaleListUrlQueryParams;
@ -59,6 +62,20 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
); );
const intl = useIntl(); 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 } = useSaleListQuery({
displayLoader: true,
variables: queryVariables
});
const tabs = getFilterTabs(); const tabs = getFilterTabs();
const currentTab = const currentTab =
@ -119,135 +136,122 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
handleTabChange(tabs.length + 1); handleTabChange(tabs.length + 1);
}; };
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0); const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
return ( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
<TypedSaleList displayLoader variables={queryVariables}> maybe(() => data.sales.pageInfo),
{({ data, loading, refetch }) => { paginationState,
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( params
maybe(() => data.sales.pageInfo), );
paginationState,
params
);
const handleSaleBulkDelete = (data: SaleBulkDelete) => { const handleSaleBulkDelete = (data: SaleBulkDelete) => {
if (data.saleBulkDelete.errors.length === 0) { if (data.saleBulkDelete.errors.length === 0) {
notify({ notify({
text: intl.formatMessage(commonMessages.savedChanges) text: intl.formatMessage(commonMessages.savedChanges)
}); });
reset(); reset();
closeModal(); closeModal();
refetch(); refetch();
} }
}; };
const handleSort = createSortHandler(navigate, saleListUrl, params);
return (
<TypedSaleBulkDelete onCompleted={handleSaleBulkDelete}>
{(saleBulkDelete, saleBulkDeleteOpts) => {
const onSaleBulkDelete = () =>
saleBulkDelete({
variables: {
ids: params.ids
}
});
return ( return (
<TypedSaleBulkDelete onCompleted={handleSaleBulkDelete}> <>
{(saleBulkDelete, saleBulkDeleteOpts) => { <WindowTitle title={intl.formatMessage(sectionNames.sales)} />
const onSaleBulkDelete = () => <SaleListPage
saleBulkDelete({ currentTab={currentTab}
variables: { initialSearch={params.query || ""}
ids: params.ids onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(saleListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
defaultCurrency={maybe(() => shop.defaultCurrency)}
sales={maybe(() => data.sales.edges.map(edge => edge.node))}
settings={settings}
disabled={loading}
pageInfo={pageInfo}
onAdd={() => navigate(saleAddUrl)}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onSort={handleSort}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(saleUrl(id))}
isChecked={isSelected}
selected={listElements.length}
sort={getSortParams(params)}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
saleListUrl({
action: "remove",
ids: listElements
})
)
} }
}); >
<DeleteIcon />
return ( </IconButton>
<> }
<WindowTitle title={intl.formatMessage(sectionNames.sales)} /> />
<SaleListPage <ActionDialog
currentTab={currentTab} confirmButtonState={saleBulkDeleteOpts.status}
initialSearch={params.query || ""} onClose={closeModal}
onSearchChange={query => changeFilterField({ query })} onConfirm={onSaleBulkDelete}
onAll={() => navigate(saleListUrl())} open={params.action === "remove" && canOpenBulkActionDialog}
onTabChange={handleTabChange} title={intl.formatMessage({
onTabDelete={() => openModal("delete-search")} defaultMessage: "Delete Sales",
onTabSave={() => openModal("save-search")} description: "dialog header"
tabs={tabs.map(tab => tab.name)} })}
defaultCurrency={maybe(() => shop.defaultCurrency)} variant="delete"
sales={maybe(() => data.sales.edges.map(edge => edge.node))} >
settings={settings} {canOpenBulkActionDialog && (
disabled={loading} <DialogContentText>
pageInfo={pageInfo} <FormattedMessage
onAdd={() => navigate(saleAddUrl)} defaultMessage="Are you sure you want to delete {counter,plural,one{this sale} other{{displayQuantity} sales}}?"
onNextPage={loadNextPage} description="dialog content"
onPreviousPage={loadPreviousPage} values={{
onUpdateListSettings={updateListSettings} counter: params.ids.length,
onRowClick={id => () => navigate(saleUrl(id))} displayQuantity: <strong>{params.ids.length}</strong>
isChecked={isSelected} }}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
saleListUrl({
action: "remove",
ids: listElements
})
)
}
>
<DeleteIcon />
</IconButton>
}
/> />
<ActionDialog </DialogContentText>
confirmButtonState={saleBulkDeleteOpts.status} )}
onClose={closeModal} </ActionDialog>
onConfirm={onSaleBulkDelete} <SaveFilterTabDialog
open={params.action === "remove" && canOpenBulkActionDialog} open={params.action === "save-search"}
title={intl.formatMessage({ confirmButtonState="default"
defaultMessage: "Delete Sales", onClose={closeModal}
description: "dialog header" onSubmit={handleTabSave}
})} />
variant="delete" <DeleteFilterTabDialog
> open={params.action === "delete-search"}
{canOpenBulkActionDialog && ( confirmButtonState="default"
<DialogContentText> onClose={closeModal}
<FormattedMessage onSubmit={handleTabDelete}
defaultMessage="Are you sure you want to delete {counter,plural,one{this sale} other{{displayQuantity} sales}}?" tabName={maybe(() => tabs[currentTab - 1].name, "...")}
description="dialog content" />
values={{ </>
counter: params.ids.length,
displayQuantity: (
<strong>{params.ids.length}</strong>
)
}}
/>
</DialogContentText>
)}
</ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
);
}}
</TypedSaleBulkDelete>
); );
}} }}
</TypedSaleList> </TypedSaleBulkDelete>
); );
}; };
export default SaleList; export default SaleList;

View file

@ -0,0 +1,24 @@
import { SaleListUrlSortField } from "@saleor/discounts/urls";
import { SaleSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(sort: SaleListUrlSortField): SaleSortField {
switch (sort) {
case SaleListUrlSortField.name:
return SaleSortField.NAME;
case SaleListUrlSortField.startDate:
return SaleSortField.START_DATE;
case SaleListUrlSortField.endDate:
return SaleSortField.END_DATE;
case SaleListUrlSortField.type:
return SaleSortField.TYPE;
case SaleListUrlSortField.value:
return SaleSortField.VALUE;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -21,9 +21,11 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl"; import { commonMessages, sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import VoucherListPage from "../../components/VoucherListPage"; import VoucherListPage from "../../components/VoucherListPage";
import { TypedVoucherBulkDelete } from "../../mutations"; import { TypedVoucherBulkDelete } from "../../mutations";
import { TypedVoucherList } from "../../queries"; import { useVoucherListQuery } from "../../queries";
import { VoucherBulkDelete } from "../../types/VoucherBulkDelete"; import { VoucherBulkDelete } from "../../types/VoucherBulkDelete";
import { import {
voucherAddUrl, voucherAddUrl,
@ -41,6 +43,7 @@ import {
getFilterVariables, getFilterVariables,
saveFilterTab saveFilterTab
} from "./filter"; } from "./filter";
import { getSortQueryVariables } from "./sort";
interface VoucherListProps { interface VoucherListProps {
params: VoucherListUrlQueryParams; params: VoucherListUrlQueryParams;
@ -59,6 +62,20 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
); );
const intl = useIntl(); 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 } = useVoucherListQuery({
displayLoader: true,
variables: queryVariables
});
const tabs = getFilterTabs(); const tabs = getFilterTabs();
const currentTab = const currentTab =
@ -119,139 +136,122 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
handleTabChange(tabs.length + 1); handleTabChange(tabs.length + 1);
}; };
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0); const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
return ( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
<TypedVoucherList displayLoader variables={queryVariables}> maybe(() => data.vouchers.pageInfo),
{({ data, loading, refetch }) => { paginationState,
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( params
maybe(() => data.vouchers.pageInfo), );
paginationState,
params
);
const handleVoucherBulkDelete = (data: VoucherBulkDelete) => { const handleVoucherBulkDelete = (data: VoucherBulkDelete) => {
if (data.voucherBulkDelete.errors.length === 0) { if (data.voucherBulkDelete.errors.length === 0) {
notify({ notify({
text: intl.formatMessage(commonMessages.savedChanges) text: intl.formatMessage(commonMessages.savedChanges)
}); });
reset(); reset();
closeModal(); closeModal();
refetch(); refetch();
} }
}; };
const handleSort = createSortHandler(navigate, voucherListUrl, params);
return (
<TypedVoucherBulkDelete onCompleted={handleVoucherBulkDelete}>
{(voucherBulkDelete, voucherBulkDeleteOpts) => {
const onVoucherBulkDelete = () =>
voucherBulkDelete({
variables: {
ids: params.ids
}
});
return ( return (
<TypedVoucherBulkDelete onCompleted={handleVoucherBulkDelete}> <>
{(voucherBulkDelete, voucherBulkDeleteOpts) => { <WindowTitle title={intl.formatMessage(sectionNames.vouchers)} />
const onVoucherBulkDelete = () => <VoucherListPage
voucherBulkDelete({ currentTab={currentTab}
variables: { initialSearch={params.query || ""}
ids: params.ids onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(voucherListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
defaultCurrency={maybe(() => shop.defaultCurrency)}
settings={settings}
vouchers={maybe(() => data.vouchers.edges.map(edge => edge.node))}
disabled={loading}
pageInfo={pageInfo}
onAdd={() => navigate(voucherAddUrl)}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(voucherUrl(id))}
onSort={handleSort}
isChecked={isSelected}
selected={listElements.length}
sort={getSortParams(params)}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
voucherListUrl({
action: "remove",
ids: listElements
})
)
} }
}); >
<DeleteIcon />
return ( </IconButton>
<> }
<WindowTitle />
title={intl.formatMessage(sectionNames.vouchers)} <ActionDialog
confirmButtonState={voucherBulkDeleteOpts.status}
onClose={closeModal}
onConfirm={onVoucherBulkDelete}
open={params.action === "remove" && canOpenBulkActionDialog}
title={intl.formatMessage({
defaultMessage: "Delete Vouchers",
description: "dialog header"
})}
variant="delete"
>
{canOpenBulkActionDialog && (
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this voucher} other{{displayQuantity} vouchers}}?"
description="dialog content"
values={{
counter: params.ids.length,
displayQuantity: <strong>{params.ids.length}</strong>
}}
/> />
<VoucherListPage </DialogContentText>
currentTab={currentTab} )}
initialSearch={params.query || ""} </ActionDialog>
onSearchChange={query => changeFilterField({ query })} <SaveFilterTabDialog
onAll={() => navigate(voucherListUrl())} open={params.action === "save-search"}
onTabChange={handleTabChange} confirmButtonState="default"
onTabDelete={() => openModal("delete-search")} onClose={closeModal}
onTabSave={() => openModal("save-search")} onSubmit={handleTabSave}
tabs={tabs.map(tab => tab.name)} />
defaultCurrency={maybe(() => shop.defaultCurrency)} <DeleteFilterTabDialog
settings={settings} open={params.action === "delete-search"}
vouchers={maybe(() => confirmButtonState="default"
data.vouchers.edges.map(edge => edge.node) onClose={closeModal}
)} onSubmit={handleTabDelete}
disabled={loading} tabName={maybe(() => tabs[currentTab - 1].name, "...")}
pageInfo={pageInfo} />
onAdd={() => navigate(voucherAddUrl)} </>
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(voucherUrl(id))}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<IconButton
color="primary"
onClick={() =>
navigate(
voucherListUrl({
action: "remove",
ids: listElements
})
)
}
>
<DeleteIcon />
</IconButton>
}
/>
<ActionDialog
confirmButtonState={voucherBulkDeleteOpts.status}
onClose={closeModal}
onConfirm={onVoucherBulkDelete}
open={params.action === "remove" && canOpenBulkActionDialog}
title={intl.formatMessage({
defaultMessage: "Delete Vouchers",
description: "dialog header"
})}
variant="delete"
>
{canOpenBulkActionDialog && (
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this voucher} other{{displayQuantity} vouchers}}?"
description="dialog content"
values={{
counter: params.ids.length,
displayQuantity: (
<strong>{params.ids.length}</strong>
)
}}
/>
</DialogContentText>
)}
</ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
);
}}
</TypedVoucherBulkDelete>
); );
}} }}
</TypedVoucherList> </TypedVoucherBulkDelete>
); );
}; };
export default VoucherList; export default VoucherList;

View file

@ -0,0 +1,30 @@
import { VoucherListUrlSortField } from "@saleor/discounts/urls";
import { VoucherSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(
sort: VoucherListUrlSortField
): VoucherSortField {
switch (sort) {
case VoucherListUrlSortField.code:
return VoucherSortField.CODE;
case VoucherListUrlSortField.endDate:
return VoucherSortField.END_DATE;
case VoucherListUrlSortField.minSpent:
return VoucherSortField.MINIMUM_SPENT_AMOUNT;
case VoucherListUrlSortField.limit:
return VoucherSortField.USAGE_LIMIT;
case VoucherListUrlSortField.startDate:
return VoucherSortField.START_DATE;
case VoucherListUrlSortField.type:
return VoucherSortField.TYPE;
case VoucherListUrlSortField.value:
return VoucherSortField.VALUE;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -348,9 +348,22 @@ export function findInEnum<TEnum extends object>(
throw new Error(`Key ${needle} not found in enum`); throw new Error(`Key ${needle} not found in enum`);
} }
export function parseBoolean(a: string): boolean { export function findValueInEnum<TEnum extends object>(
needle: string,
haystack: TEnum
) {
const match = Object.entries(haystack).find(([_, value]) => value === needle);
if (!!match) {
return match[1] as TEnum;
}
throw new Error(`Value ${needle} not found in enum`);
}
export function parseBoolean(a: string, defaultValue: boolean): boolean {
if (a === undefined) { if (a === undefined) {
return true; return defaultValue;
} }
return a === "true"; return a === "true";
} }

View file

@ -15,10 +15,16 @@ import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { maybe, renderCollection } from "@saleor/misc"; import { maybe, renderCollection } from "@saleor/misc";
import { ListActions, ListProps } from "@saleor/types"; import { ListActions, ListProps, SortPage } from "@saleor/types";
import { MenuListUrlSortField } from "@saleor/navigation/urls";
import TableCellHeader from "@saleor/components/TableCellHeader";
import { getArrowDirection } from "@saleor/utils/sort";
import { MenuList_menus_edges_node } from "../../types/MenuList"; import { MenuList_menus_edges_node } from "../../types/MenuList";
export interface MenuListProps extends ListProps, ListActions { export interface MenuListProps
extends ListProps,
ListActions,
SortPage<MenuListUrlSortField> {
menus: MenuList_menus_edges_node[]; menus: MenuList_menus_edges_node[];
onDelete: (id: string) => void; onDelete: (id: string) => void;
} }
@ -57,8 +63,10 @@ const MenuList: React.FC<MenuListProps> = props => {
onPreviousPage, onPreviousPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick, onRowClick,
onSort,
pageInfo, pageInfo,
selected, selected,
sort,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
@ -77,19 +85,37 @@ const MenuList: React.FC<MenuListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colTitle}> <TableCellHeader
direction={
sort.sort === MenuListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(MenuListUrlSortField.name)}
className={classes.colTitle}
>
<FormattedMessage <FormattedMessage
defaultMessage="Menu Title" defaultMessage="Menu Title"
id="menuListMenutitle" id="menuListMenutitle"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colItems}> <TableCellHeader
direction={
sort.sort === MenuListUrlSortField.items
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(MenuListUrlSortField.items)}
className={classes.colItems}
>
<FormattedMessage <FormattedMessage
defaultMessage="Items" defaultMessage="Items"
description="number of menu items" description="number of menu items"
id="menuListItems" id="menuListItems"
/> />
</TableCell> </TableCellHeader>
<TableCell /> <TableCell />
</TableHead> </TableHead>
<TableFooter> <TableFooter>

View file

@ -7,18 +7,21 @@ import AppHeader from "@saleor/components/AppHeader";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import { ListActions, PageListProps, SortPage } from "@saleor/types";
import { MenuListUrlSortField } from "@saleor/navigation/urls";
import { MenuList_menus_edges_node } from "../../types/MenuList"; import { MenuList_menus_edges_node } from "../../types/MenuList";
import MenuList from "../MenuList"; import MenuList from "../MenuList";
export interface MenuListPageProps extends PageListProps, ListActions { export interface MenuListPageProps
extends PageListProps,
ListActions,
SortPage<MenuListUrlSortField> {
menus: MenuList_menus_edges_node[]; menus: MenuList_menus_edges_node[];
onBack: () => void; onBack: () => void;
onDelete: (id: string) => void; onDelete: (id: string) => void;
} }
const MenuListPage: React.FC<MenuListPageProps> = ({ const MenuListPage: React.FC<MenuListPageProps> = ({
disabled,
onAdd, onAdd,
onBack, onBack,
...listProps ...listProps
@ -30,12 +33,7 @@ const MenuListPage: React.FC<MenuListPageProps> = ({
{intl.formatMessage(sectionNames.configuration)} {intl.formatMessage(sectionNames.configuration)}
</AppHeader> </AppHeader>
<PageHeader title={intl.formatMessage(sectionNames.navigation)}> <PageHeader title={intl.formatMessage(sectionNames.navigation)}>
<Button <Button color="primary" variant="contained" onClick={onAdd}>
color="primary"
disabled={disabled}
variant="contained"
onClick={onAdd}
>
<FormattedMessage <FormattedMessage
defaultMessage="Create Menu" defaultMessage="Create Menu"
description="button" description="button"
@ -43,7 +41,7 @@ const MenuListPage: React.FC<MenuListPageProps> = ({
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<MenuList disabled={disabled} {...listProps} /> <MenuList {...listProps} />
</Container> </Container>
); );
}; };

View file

@ -2,13 +2,21 @@ import { parse as parseQs } from "qs";
import React from "react"; import React from "react";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; 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 MenuDetailsComponent from "./views/MenuDetails";
import MenuListComponent from "./views/MenuList"; import MenuListComponent from "./views/MenuList";
const MenuList: React.FC<RouteComponentProps<{}>> = ({ location }) => { const MenuList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
return <MenuListComponent params={qs} />; const params: MenuListUrlQueryParams = asSortParams(qs, MenuListUrlSortField);
return <MenuListComponent params={params} />;
}; };
const MenuDetails: React.FC<RouteComponentProps<{ id: string }>> = ({ const MenuDetails: React.FC<RouteComponentProps<{ id: string }>> = ({
@ -16,6 +24,7 @@ const MenuDetails: React.FC<RouteComponentProps<{ id: string }>> = ({
match match
}) => { }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
return ( return (
<MenuDetailsComponent <MenuDetailsComponent
id={decodeURIComponent(match.params.id)} id={decodeURIComponent(match.params.id)}

View file

@ -1,4 +1,5 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import makeQuery from "@saleor/hooks/makeQuery";
import { pageInfoFragment, TypedQuery } from "../queries"; import { pageInfoFragment, TypedQuery } from "../queries";
import { MenuDetails, MenuDetailsVariables } from "./types/MenuDetails"; import { MenuDetails, MenuDetailsVariables } from "./types/MenuDetails";
import { MenuList, MenuListVariables } from "./types/MenuList"; import { MenuList, MenuListVariables } from "./types/MenuList";
@ -75,8 +76,20 @@ export const menuDetailsFragment = gql`
const menuList = gql` const menuList = gql`
${menuFragment} ${menuFragment}
${pageInfoFragment} ${pageInfoFragment}
query MenuList($first: Int, $after: String, $last: Int, $before: String) { query MenuList(
menus(first: $first, after: $after, before: $before, last: $last) { $first: Int
$after: String
$last: Int
$before: String
$sort: MenuSortingInput
) {
menus(
first: $first
after: $after
before: $before
last: $last
sortBy: $sort
) {
edges { edges {
node { node {
...MenuFragment ...MenuFragment
@ -88,7 +101,9 @@ const menuList = gql`
} }
} }
`; `;
export const MenuListQuery = TypedQuery<MenuList, MenuListVariables>(menuList); export const useMenuListQuery = makeQuery<MenuList, MenuListVariables>(
menuList
);
const menuDetails = gql` const menuDetails = gql`
${menuDetailsFragment} ${menuDetailsFragment}

View file

@ -2,6 +2,8 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { MenuSortingInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: MenuList // GraphQL query operation: MenuList
// ==================================================== // ====================================================
@ -46,4 +48,5 @@ export interface MenuListVariables {
after?: string | null; after?: string | null;
last?: number | null; last?: number | null;
before?: string | null; before?: string | null;
sort?: MenuSortingInput | null;
} }

View file

@ -1,14 +1,20 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination, SingleAction } from "../types"; import { BulkAction, Dialog, Pagination, SingleAction, Sort } from "../types";
export const navigationSection = "/navigation"; export const navigationSection = "/navigation";
export const menuListPath = navigationSection; export const menuListPath = navigationSection;
export type MenuListUrlDialog = "add" | "remove" | "remove-many"; export type MenuListUrlDialog = "add" | "remove" | "remove-many";
export enum MenuListUrlSortField {
name = "name",
items = "items"
}
export type MenuListUrlSort = Sort<MenuListUrlSortField>;
export type MenuListUrlQueryParams = BulkAction & export type MenuListUrlQueryParams = BulkAction &
Dialog<MenuListUrlDialog> & Dialog<MenuListUrlDialog> &
MenuListUrlSort &
Pagination & Pagination &
SingleAction; SingleAction;
export const menuListUrl = (params?: MenuListUrlQueryParams) => export const menuListUrl = (params?: MenuListUrlQueryParams) =>

View file

@ -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<MenuListProps> = ({ 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 (
<MenuListQuery variables={paginationState}>
{({ 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 (
<MenuCreateMutation onCompleted={handleCreate}>
{(menuCreate, menuCreateOpts) => (
<MenuDeleteMutation onCompleted={handleDelete}>
{(menuDelete, menuDeleteOpts) => (
<MenuBulkDeleteMutation onCompleted={handleBulkDelete}>
{(menuBulkDelete, menuBulkDeleteOpts) => (
<>
<MenuListPage
disabled={loading}
menus={maybe(() =>
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={
<Button
color="primary"
onClick={() =>
navigate(
menuListUrl({
...params,
action: "remove-many",
ids: listElements
})
)
}
>
<FormattedMessage {...buttonMessages.remove} />
</Button>
}
/>
<MenuCreateDialog
open={params.action === "add"}
confirmButtonState={menuCreateOpts.status}
disabled={menuCreateOpts.loading}
onClose={closeModal}
onConfirm={formData =>
menuCreate({
variables: { input: formData }
})
}
/>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={menuDeleteOpts.status}
onConfirm={() =>
menuDelete({
variables: {
id: params.id
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Menu",
description: "dialog header",
id: "menuListDeleteMenuHeader"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {menuName}?"
id="menuListDeleteMenuContent"
values={{
menuName: maybe(
() =>
data.menus.edges.find(
edge => edge.node.id === params.id
).node.name,
"..."
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={
params.action === "remove-many" &&
maybe(() => 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"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this menu} other{{displayQuantity} menus}}?"
id="menuListDeleteMenusContent"
values={{
counter: maybe(
() => params.ids.length.toString(),
"..."
),
displayQuantity: (
<strong>
{maybe(
() => params.ids.length.toString(),
"..."
)}
</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
</>
)}
</MenuBulkDeleteMutation>
)}
</MenuDeleteMutation>
)}
</MenuCreateMutation>
);
}}
</MenuListQuery>
);
};
export default MenuList;

View file

@ -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<MenuListProps> = ({ 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 (
<MenuCreateMutation onCompleted={handleCreate}>
{(menuCreate, menuCreateOpts) => (
<MenuDeleteMutation onCompleted={handleDelete}>
{(menuDelete, menuDeleteOpts) => (
<MenuBulkDeleteMutation onCompleted={handleBulkDelete}>
{(menuBulkDelete, menuBulkDeleteOpts) => (
<>
<MenuListPage
disabled={loading}
menus={maybe(() => 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={
<Button
color="primary"
onClick={() =>
navigate(
menuListUrl({
...params,
action: "remove-many",
ids: listElements
})
)
}
>
<FormattedMessage {...buttonMessages.remove} />
</Button>
}
/>
<MenuCreateDialog
open={params.action === "add"}
confirmButtonState={menuCreateOpts.status}
disabled={menuCreateOpts.loading}
onClose={closeModal}
onConfirm={formData =>
menuCreate({
variables: { input: formData }
})
}
/>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={menuDeleteOpts.status}
onConfirm={() =>
menuDelete({
variables: {
id: params.id
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Menu",
description: "dialog header",
id: "menuListDeleteMenuHeader"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {menuName}?"
id="menuListDeleteMenuContent"
values={{
menuName: maybe(
() =>
data.menus.edges.find(
edge => edge.node.id === params.id
).node.name,
"..."
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={
params.action === "remove-many" &&
maybe(() => 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"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this menu} other{{displayQuantity} menus}}?"
id="menuListDeleteMenusContent"
values={{
counter: maybe(
() => params.ids.length.toString(),
"..."
),
displayQuantity: (
<strong>
{maybe(() => params.ids.length.toString(), "...")}
</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
</>
)}
</MenuBulkDeleteMutation>
)}
</MenuDeleteMutation>
)}
</MenuCreateMutation>
);
};
export default MenuList;

View file

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

View file

@ -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
);

View file

@ -19,7 +19,10 @@ import {
transformOrderStatus, transformOrderStatus,
transformPaymentStatus transformPaymentStatus
} from "@saleor/misc"; } 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"; import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -32,7 +35,7 @@ const useStyles = makeStyles(
width: 300 width: 300
}, },
colNumber: { colNumber: {
width: 120 width: 160
}, },
colTotal: {} colTotal: {}
}, },
@ -51,7 +54,10 @@ const useStyles = makeStyles(
{ name: "OrderDraftList" } { name: "OrderDraftList" }
); );
interface OrderDraftListProps extends ListProps, ListActions { interface OrderDraftListProps
extends ListProps,
ListActions,
SortPage<OrderDraftListUrlSortField> {
orders: OrderDraftList_draftOrders_edges_node[]; orders: OrderDraftList_draftOrders_edges_node[];
} }
@ -67,8 +73,10 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = props => {
onNextPage, onNextPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick, onRowClick,
onSort,
isChecked, isChecked,
selected, selected,
sort,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
@ -95,24 +103,58 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colNumber}> <TableCellHeader
direction={
sort.sort === OrderDraftListUrlSortField.number
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(OrderDraftListUrlSortField.number)}
className={classes.colNumber}
>
<FormattedMessage defaultMessage="No. of Order" /> <FormattedMessage defaultMessage="No. of Order" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colDate}> <TableCellHeader
direction={
sort.sort === OrderDraftListUrlSortField.date
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(OrderDraftListUrlSortField.date)}
className={classes.colDate}
>
<FormattedMessage <FormattedMessage
defaultMessage="Date" defaultMessage="Date"
description="order draft creation date" description="order draft creation date"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colCustomer}> <TableCellHeader
direction={
sort.sort === OrderDraftListUrlSortField.customer
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(OrderDraftListUrlSortField.customer)}
className={classes.colCustomer}
>
<FormattedMessage defaultMessage="Customer" /> <FormattedMessage defaultMessage="Customer" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colTotal}> <TableCellHeader
direction={
sort.sort === OrderDraftListUrlSortField.total
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(OrderDraftListUrlSortField.total)}
className={classes.colTotal}
>
<FormattedMessage <FormattedMessage
defaultMessage="Total" defaultMessage="Total"
description="order draft total price" description="order draft total price"
/> />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>

View file

@ -12,8 +12,10 @@ import {
ListActions, ListActions,
PageListProps, PageListProps,
SearchPageProps, SearchPageProps,
TabPageProps TabPageProps,
SortPage
} from "@saleor/types"; } from "@saleor/types";
import { OrderDraftListUrlSortField } from "@saleor/orders/urls";
import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList"; import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList";
import OrderDraftList from "../OrderDraftList"; import OrderDraftList from "../OrderDraftList";
@ -21,6 +23,7 @@ export interface OrderDraftListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
SearchPageProps, SearchPageProps,
SortPage<OrderDraftListUrlSortField>,
TabPageProps { TabPageProps {
orders: OrderDraftList_draftOrders_edges_node[]; orders: OrderDraftList_draftOrders_edges_node[];
} }

View file

@ -20,7 +20,10 @@ import {
transformOrderStatus, transformOrderStatus,
transformPaymentStatus transformPaymentStatus
} from "@saleor/misc"; } 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"; import { OrderList_orders_edges_node } from "../../types/OrderList";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -56,7 +59,10 @@ const useStyles = makeStyles(
{ name: "OrderList" } { name: "OrderList" }
); );
interface OrderListProps extends ListProps, ListActions { interface OrderListProps
extends ListProps,
ListActions,
SortPage<OrderListUrlSortField> {
orders: OrderList_orders_edges_node[]; orders: OrderList_orders_edges_node[];
} }
@ -72,8 +78,10 @@ export const OrderList: React.FC<OrderListProps> = props => {
onNextPage, onNextPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick, onRowClick,
onSort,
isChecked, isChecked,
selected, selected,
sort,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
@ -99,36 +107,86 @@ export const OrderList: React.FC<OrderListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colNumber}> <TableCellHeader
direction={
sort.sort === OrderListUrlSortField.number
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(OrderListUrlSortField.number)}
className={classes.colNumber}
>
<FormattedMessage defaultMessage="No. of Order" /> <FormattedMessage defaultMessage="No. of Order" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colDate}> <TableCellHeader
direction={
sort.sort === OrderListUrlSortField.date
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(OrderListUrlSortField.date)}
className={classes.colDate}
>
<FormattedMessage <FormattedMessage
defaultMessage="Date" defaultMessage="Date"
description="date when order was placed" description="date when order was placed"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colCustomer}> <TableCellHeader
direction={
sort.sort === OrderListUrlSortField.customer
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(OrderListUrlSortField.customer)}
className={classes.colCustomer}
>
<FormattedMessage <FormattedMessage
defaultMessage="Customer" defaultMessage="Customer"
description="e-mail or full name" description="e-mail or full name"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colPayment}> <TableCellHeader
direction={
sort.sort === OrderListUrlSortField.payment
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(OrderListUrlSortField.payment)}
className={classes.colPayment}
>
<FormattedMessage <FormattedMessage
defaultMessage="Payment" defaultMessage="Payment"
description="payment status" description="payment status"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colFulfillment}> <TableCellHeader
direction={
sort.sort === OrderListUrlSortField.fulfillment
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(OrderListUrlSortField.fulfillment)}
className={classes.colFulfillment}
>
<FormattedMessage defaultMessage="Fulfillment status" /> <FormattedMessage defaultMessage="Fulfillment status" />
</TableCell> </TableCellHeader>
<TableCell className={classes.colTotal}> <TableCellHeader
direction={
sort.sort === OrderListUrlSortField.total
? getArrowDirection(sort.asc)
: undefined
}
textAlign="right"
onClick={() => onSort(OrderListUrlSortField.total)}
className={classes.colTotal}
>
<FormattedMessage <FormattedMessage
defaultMessage="Total" defaultMessage="Total"
description="total order price" description="total order price"
/> />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>

View file

@ -7,7 +7,13 @@ import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { 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_orders_edges_node } from "../../types/OrderList";
import OrderList from "../OrderList"; import OrderList from "../OrderList";
import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter"; import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter";
@ -15,7 +21,8 @@ import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter";
export interface OrderListPageProps export interface OrderListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
FilterPageProps<OrderFilterKeys> { FilterPageProps<OrderFilterKeys>,
SortPage<OrderListUrlSortField> {
orders: OrderList_orders_edges_node[]; orders: OrderList_orders_edges_node[];
} }

View file

@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { import {
orderDraftListPath, orderDraftListPath,
@ -11,7 +12,9 @@ import {
orderListPath, orderListPath,
OrderListUrlQueryParams, OrderListUrlQueryParams,
orderPath, orderPath,
OrderUrlQueryParams OrderUrlQueryParams,
OrderDraftListUrlSortField,
OrderListUrlSortField
} from "./urls"; } from "./urls";
import OrderDetailsComponent from "./views/OrderDetails"; import OrderDetailsComponent from "./views/OrderDetails";
import OrderDraftListComponent from "./views/OrderDraftList"; import OrderDraftListComponent from "./views/OrderDraftList";
@ -19,12 +22,23 @@ import OrderListComponent from "./views/OrderList";
const OrderList: React.FC<RouteComponentProps<any>> = ({ location }) => { const OrderList: React.FC<RouteComponentProps<any>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: OrderListUrlQueryParams = qs; const params: OrderListUrlQueryParams = asSortParams(
qs,
OrderListUrlSortField,
OrderListUrlSortField.number,
false
);
return <OrderListComponent params={params} />; return <OrderListComponent params={params} />;
}; };
const OrderDraftList: React.FC<RouteComponentProps<any>> = ({ location }) => { const OrderDraftList: React.FC<RouteComponentProps<any>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: OrderDraftListUrlQueryParams = qs; const params: OrderDraftListUrlQueryParams = asSortParams(
qs,
OrderDraftListUrlSortField,
OrderDraftListUrlSortField.number,
false
);
return <OrderDraftListComponent params={params} />; return <OrderDraftListComponent params={params} />;
}; };

View file

@ -1,6 +1,7 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch"; import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch";
import makeQuery from "@saleor/hooks/makeQuery";
import { TypedQuery } from "../queries"; import { TypedQuery } from "../queries";
import { OrderDetails, OrderDetailsVariables } from "./types/OrderDetails"; import { OrderDetails, OrderDetailsVariables } from "./types/OrderDetails";
import { import {
@ -169,6 +170,7 @@ export const orderListQuery = gql`
$last: Int $last: Int
$before: String $before: String
$filter: OrderFilterInput $filter: OrderFilterInput
$sort: OrderSortingInput
) { ) {
orders( orders(
before: $before before: $before
@ -176,6 +178,7 @@ export const orderListQuery = gql`
first: $first first: $first
last: $last last: $last
filter: $filter filter: $filter
sortBy: $sort
) { ) {
edges { edges {
node { node {
@ -208,7 +211,7 @@ export const orderListQuery = gql`
} }
} }
`; `;
export const TypedOrderListQuery = TypedQuery<OrderList, OrderListVariables>( export const useOrderListQuery = makeQuery<OrderList, OrderListVariables>(
orderListQuery orderListQuery
); );
@ -220,6 +223,7 @@ export const orderDraftListQuery = gql`
$last: Int $last: Int
$before: String $before: String
$filter: OrderDraftFilterInput $filter: OrderDraftFilterInput
$sort: OrderSortingInput
) { ) {
draftOrders( draftOrders(
before: $before before: $before
@ -227,6 +231,7 @@ export const orderDraftListQuery = gql`
first: $first first: $first
last: $last last: $last
filter: $filter filter: $filter
sortBy: $sort
) { ) {
edges { edges {
node { node {
@ -259,7 +264,7 @@ export const orderDraftListQuery = gql`
} }
} }
`; `;
export const TypedOrderDraftListQuery = TypedQuery< export const useOrderDraftListQuery = makeQuery<
OrderDraftList, OrderDraftList,
OrderDraftListVariables OrderDraftListVariables
>(orderDraftListQuery); >(orderDraftListQuery);

View file

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

View file

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

View file

@ -9,7 +9,8 @@ import {
FiltersWithMultipleValues, FiltersWithMultipleValues,
Pagination, Pagination,
SingleAction, SingleAction,
TabActionDialog TabActionDialog,
Sort
} from "../types"; } from "../types";
const orderSectionUrl = "/orders"; const orderSectionUrl = "/orders";
@ -28,9 +29,19 @@ export enum OrderListUrlFiltersWithMultipleValuesEnum {
export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> & export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> &
FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValuesEnum>; FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValuesEnum>;
export type OrderListUrlDialog = "cancel" | TabActionDialog; export type OrderListUrlDialog = "cancel" | TabActionDialog;
export enum OrderListUrlSortField {
number = "number",
customer = "customer",
date = "date",
fulfillment = "status",
payment = "payment",
total = "total"
}
export type OrderListUrlSort = Sort<OrderListUrlSortField>;
export type OrderListUrlQueryParams = BulkAction & export type OrderListUrlQueryParams = BulkAction &
Dialog<OrderListUrlDialog> & Dialog<OrderListUrlDialog> &
OrderListUrlFilters & OrderListUrlFilters &
OrderListUrlSort &
Pagination & Pagination &
ActiveTab; ActiveTab;
export const orderListUrl = (params?: OrderListUrlQueryParams): string => { export const orderListUrl = (params?: OrderListUrlQueryParams): string => {
@ -48,10 +59,18 @@ export enum OrderDraftListUrlFiltersEnum {
} }
export type OrderDraftListUrlFilters = Filters<OrderDraftListUrlFiltersEnum>; export type OrderDraftListUrlFilters = Filters<OrderDraftListUrlFiltersEnum>;
export type OrderDraftListUrlDialog = "remove" | TabActionDialog; export type OrderDraftListUrlDialog = "remove" | TabActionDialog;
export enum OrderDraftListUrlSortField {
number = "number",
customer = "customer",
date = "date",
total = "total"
}
export type OrderDraftListUrlSort = Sort<OrderDraftListUrlSortField>;
export type OrderDraftListUrlQueryParams = ActiveTab & export type OrderDraftListUrlQueryParams = ActiveTab &
BulkAction & BulkAction &
Dialog<OrderDraftListUrlDialog> & Dialog<OrderDraftListUrlDialog> &
OrderDraftListUrlFilters & OrderDraftListUrlFilters &
OrderDraftListUrlSort &
Pagination; Pagination;
export const orderDraftListUrl = ( export const orderDraftListUrl = (
params?: OrderDraftListUrlQueryParams params?: OrderDraftListUrlQueryParams

View file

@ -18,12 +18,14 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import { getSortParams } from "@saleor/utils/sort";
import OrderDraftListPage from "../../components/OrderDraftListPage"; import OrderDraftListPage from "../../components/OrderDraftListPage";
import { import {
TypedOrderDraftBulkCancelMutation, TypedOrderDraftBulkCancelMutation,
useOrderDraftCreateMutation useOrderDraftCreateMutation
} from "../../mutations"; } from "../../mutations";
import { TypedOrderDraftListQuery } from "../../queries"; import { useOrderDraftListQuery } from "../../queries";
import { OrderDraftBulkCancel } from "../../types/OrderDraftBulkCancel"; import { OrderDraftBulkCancel } from "../../types/OrderDraftBulkCancel";
import { OrderDraftCreate } from "../../types/OrderDraftCreate"; import { OrderDraftCreate } from "../../types/OrderDraftCreate";
import { import {
@ -41,6 +43,7 @@ import {
getFilterVariables, getFilterVariables,
saveFilterTab saveFilterTab
} from "./filter"; } from "./filter";
import { getSortQueryVariables } from "./sort";
interface OrderDraftListProps { interface OrderDraftListProps {
params: OrderDraftListUrlQueryParams; params: OrderDraftListUrlQueryParams;
@ -135,131 +138,132 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
const queryVariables = React.useMemo( const queryVariables = React.useMemo(
() => ({ () => ({
...paginationState, ...paginationState,
filter: getFilterVariables(params) filter: getFilterVariables(params),
sort: getSortQueryVariables(params)
}), }),
[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 ( return (
<TypedOrderDraftListQuery displayLoader variables={queryVariables}> <TypedOrderDraftBulkCancelMutation onCompleted={handleOrderDraftBulkCancel}>
{({ data, loading, refetch }) => { {(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const onOrderDraftBulkDelete = () =>
maybe(() => data.draftOrders.pageInfo), orderDraftBulkDelete({
paginationState, variables: {
params ids: params.ids
); }
});
const handleOrderDraftBulkCancel = (data: OrderDraftBulkCancel) => {
if (data.draftOrderBulkDelete.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Deleted draft orders"
})
});
refetch();
reset();
closeModal();
}
};
return ( return (
<TypedOrderDraftBulkCancelMutation <>
onCompleted={handleOrderDraftBulkCancel} <OrderDraftListPage
> currentTab={currentTab}
{(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => { initialSearch={params.query || ""}
const onOrderDraftBulkDelete = () => onSearchChange={query => changeFilterField({ query })}
orderDraftBulkDelete({ onAll={() => navigate(orderDraftListUrl())}
variables: { onTabChange={handleTabChange}
ids: params.ids 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={
<IconButton
color="primary"
onClick={() =>
navigate(
orderDraftListUrl({
action: "remove",
ids: listElements
})
)
} }
}); >
<DeleteIcon />
return ( </IconButton>
<> }
<OrderDraftListPage />
currentTab={currentTab} <ActionDialog
initialSearch={params.query || ""} confirmButtonState={orderDraftBulkDeleteOpts.status}
onSearchChange={query => changeFilterField({ query })} onClose={closeModal}
onAll={() => navigate(orderDraftListUrl())} onConfirm={onOrderDraftBulkDelete}
onTabChange={handleTabChange} open={params.action === "remove"}
onTabDelete={() => openModal("delete-search")} title={intl.formatMessage({
onTabSave={() => openModal("save-search")} defaultMessage: "Delete Order Drafts",
tabs={tabs.map(tab => tab.name)} description: "dialog header"
disabled={loading} })}
settings={settings} variant="delete"
orders={maybe(() => >
data.draftOrders.edges.map(edge => edge.node) <DialogContentText>
)} <FormattedMessage
pageInfo={pageInfo} defaultMessage="Are you sure you want to delete {counter,plural,one{this order draft} other{{displayQuantity} orderDrafts}}?"
onAdd={createOrder} description="dialog content"
onNextPage={loadNextPage} values={{
onPreviousPage={loadPreviousPage} counter: maybe(() => params.ids.length),
onUpdateListSettings={updateListSettings} displayQuantity: (
onRowClick={id => () => navigate(orderUrl(id))} <strong>{maybe(() => params.ids.length)}</strong>
isChecked={isSelected} )
selected={listElements.length} }}
toggle={toggle} />
toggleAll={toggleAll} </DialogContentText>
toolbar={ </ActionDialog>
<IconButton <SaveFilterTabDialog
color="primary" open={params.action === "save-search"}
onClick={() => confirmButtonState="default"
navigate( onClose={closeModal}
orderDraftListUrl({ onSubmit={handleTabSave}
action: "remove", />
ids: listElements <DeleteFilterTabDialog
}) open={params.action === "delete-search"}
) confirmButtonState="default"
} onClose={closeModal}
> onSubmit={handleTabDelete}
<DeleteIcon /> tabName={maybe(() => tabs[currentTab - 1].name, "...")}
</IconButton> />
} </>
/>
<ActionDialog
confirmButtonState={orderDraftBulkDeleteOpts.status}
onClose={closeModal}
onConfirm={onOrderDraftBulkDelete}
open={params.action === "remove"}
title={intl.formatMessage({
defaultMessage: "Delete Order Drafts",
description: "dialog header"
})}
variant="delete"
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this order draft} other{{displayQuantity} orderDrafts}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
);
}}
</TypedOrderDraftBulkCancelMutation>
); );
}} }}
</TypedOrderDraftListQuery> </TypedOrderDraftBulkCancelMutation>
); );
}; };

View file

@ -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
);

View file

@ -17,13 +17,15 @@ import usePaginator, {
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import { getSortParams } from "@saleor/utils/sort";
import OrderBulkCancelDialog from "../../components/OrderBulkCancelDialog"; import OrderBulkCancelDialog from "../../components/OrderBulkCancelDialog";
import OrderListPage from "../../components/OrderListPage/OrderListPage"; import OrderListPage from "../../components/OrderListPage/OrderListPage";
import { import {
TypedOrderBulkCancelMutation, TypedOrderBulkCancelMutation,
useOrderDraftCreateMutation useOrderDraftCreateMutation
} from "../../mutations"; } from "../../mutations";
import { TypedOrderListQuery } from "../../queries"; import { useOrderListQuery } from "../../queries";
import { OrderBulkCancel } from "../../types/OrderBulkCancel"; import { OrderBulkCancel } from "../../types/OrderBulkCancel";
import { OrderDraftCreate } from "../../types/OrderDraftCreate"; import { OrderDraftCreate } from "../../types/OrderDraftCreate";
import { import {
@ -43,6 +45,7 @@ import {
getFilterVariables, getFilterVariables,
saveFilterTab saveFilterTab
} from "./filters"; } from "./filters";
import { getSortQueryVariables } from "./sort";
interface OrderListProps { interface OrderListProps {
params: OrderListUrlQueryParams; params: OrderListUrlQueryParams;
@ -146,128 +149,126 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
const queryVariables = React.useMemo( const queryVariables = React.useMemo(
() => ({ () => ({
...paginationState, ...paginationState,
filter: getFilterVariables(params) filter: getFilterVariables(params),
sort: getSortQueryVariables(params)
}), }),
[params, settings.rowNumber] [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 ( return (
<TypedOrderListQuery displayLoader variables={queryVariables}> <TypedOrderBulkCancelMutation onCompleted={handleOrderBulkCancel}>
{({ data, loading, refetch }) => { {(orderBulkCancel, orderBulkCancelOpts) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const onOrderBulkCancel = (restock: boolean) =>
maybe(() => data.orders.pageInfo), orderBulkCancel({
paginationState, variables: {
params ids: params.ids,
); restock
}
const handleOrderBulkCancel = (data: OrderBulkCancel) => { });
if (data.orderBulkCancel.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Orders cancelled"
})
});
reset();
refetch();
closeModal();
}
};
return ( return (
<TypedOrderBulkCancelMutation onCompleted={handleOrderBulkCancel}> <>
{(orderBulkCancel, orderBulkCancelOpts) => { <OrderListPage
const onOrderBulkCancel = (restock: boolean) => currencySymbol={currencySymbol}
orderBulkCancel({ settings={settings}
variables: { filtersList={createFilterChips(
ids: params.ids, params,
restock {
} formatDate
}); },
changeFilterField,
return ( intl
<> )}
<OrderListPage currentTab={currentTab}
currencySymbol={currencySymbol} disabled={loading}
settings={settings} orders={maybe(() => data.orders.edges.map(edge => edge.node))}
filtersList={createFilterChips( pageInfo={pageInfo}
params, sort={getSortParams(params)}
{ onAdd={createOrder}
formatDate onNextPage={loadNextPage}
}, onPreviousPage={loadPreviousPage}
changeFilterField, onUpdateListSettings={updateListSettings}
intl onRowClick={id => () => navigate(orderUrl(id))}
)} onSort={handleSort}
currentTab={currentTab} isChecked={isSelected}
disabled={loading} selected={listElements.length}
orders={maybe(() => toggle={toggle}
data.orders.edges.map(edge => edge.node) toggleAll={toggleAll}
)} toolbar={
pageInfo={pageInfo} <Button
onAdd={createOrder} color="primary"
onNextPage={loadNextPage} onClick={() => openModal("cancel", listElements)}
onPreviousPage={loadPreviousPage} >
onUpdateListSettings={updateListSettings} <FormattedMessage
onRowClick={id => () => navigate(orderUrl(id))} defaultMessage="Cancel"
isChecked={isSelected} description="cancel orders, button"
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<Button
color="primary"
onClick={() => openModal("cancel", listElements)}
>
<FormattedMessage
defaultMessage="Cancel"
description="cancel orders, button"
/>
</Button>
}
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
})
}
/> />
<OrderBulkCancelDialog </Button>
confirmButtonState={orderBulkCancelOpts.status} }
numberOfOrders={maybe( onSearchChange={query => changeFilterField({ query })}
() => params.ids.length.toString(), onFilterAdd={data =>
"..." changeFilterField(createFilter(params, data))
)} }
onClose={closeModal} onTabSave={() => openModal("save-search")}
onConfirm={onOrderBulkCancel} onTabDelete={() => openModal("delete-search")}
open={params.action === "cancel"} onTabChange={handleTabChange}
/> initialSearch={params.query || ""}
<SaveFilterTabDialog tabs={getFilterTabs().map(tab => tab.name)}
open={params.action === "save-search"} onAll={() =>
confirmButtonState="default" changeFilters({
onClose={closeModal} status: undefined
onSubmit={handleFilterTabSave} })
/> }
<DeleteFilterTabDialog />
open={params.action === "delete-search"} <OrderBulkCancelDialog
confirmButtonState="default" confirmButtonState={orderBulkCancelOpts.status}
onClose={closeModal} numberOfOrders={maybe(() => params.ids.length.toString(), "...")}
onSubmit={handleFilterTabDelete} onClose={closeModal}
tabName={maybe(() => tabs[currentTab - 1].name, "...")} onConfirm={onOrderBulkCancel}
/> open={params.action === "cancel"}
</> />
); <SaveFilterTabDialog
}} open={params.action === "save-search"}
</TypedOrderBulkCancelMutation> confirmButtonState="default"
onClose={closeModal}
onSubmit={handleFilterTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleFilterTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</>
); );
}} }}
</TypedOrderListQuery> </TypedOrderBulkCancelMutation>
); );
}; };

View file

@ -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
);

View file

@ -14,10 +14,16 @@ import StatusLabel from "@saleor/components/StatusLabel";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { maybe, renderCollection } from "@saleor/misc"; 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"; import { PageList_pages_edges_node } from "../../types/PageList";
export interface PageListProps extends ListProps, ListActions { export interface PageListProps
extends ListProps,
ListActions,
SortPage<PageListUrlSortField> {
pages: PageList_pages_edges_node[]; pages: PageList_pages_edges_node[];
} }
@ -54,10 +60,12 @@ const PageList: React.FC<PageListProps> = props => {
onNextPage, onNextPage,
pageInfo, pageInfo,
onRowClick, onRowClick,
onSort,
onUpdateListSettings, onUpdateListSettings,
onPreviousPage, onPreviousPage,
isChecked, isChecked,
selected, selected,
sort,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
@ -77,24 +85,51 @@ const PageList: React.FC<PageListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colTitle}> <TableCellHeader
direction={
sort.sort === PageListUrlSortField.title
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(PageListUrlSortField.title)}
className={classes.colTitle}
>
<FormattedMessage <FormattedMessage
defaultMessage="Title" defaultMessage="Title"
description="dialog header" description="dialog header"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colSlug}> <TableCellHeader
direction={
sort.sort === PageListUrlSortField.slug
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(PageListUrlSortField.slug)}
className={classes.colSlug}
>
<FormattedMessage <FormattedMessage
defaultMessage="Slug" defaultMessage="Slug"
description="page internal name" description="page internal name"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colVisibility}> <TableCellHeader
direction={
sort.sort === PageListUrlSortField.visible
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(PageListUrlSortField.visible)}
className={classes.colVisibility}
>
<FormattedMessage <FormattedMessage
defaultMessage="Visibility" defaultMessage="Visibility"
description="page status" description="page status"
/> />
</TableCell> </TableCellHeader>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
<TableRow> <TableRow>
@ -133,13 +168,13 @@ const PageList: React.FC<PageListProps> = props => {
onChange={() => toggle(page.id)} onChange={() => toggle(page.id)}
/> />
</TableCell> </TableCell>
<TableCell className={classes.colTitle}> <TableCellHeader className={classes.colTitle}>
{maybe<React.ReactNode>(() => page.title, <Skeleton />)} {maybe<React.ReactNode>(() => page.title, <Skeleton />)}
</TableCell> </TableCellHeader>
<TableCell className={classes.colSlug}> <TableCellHeader className={classes.colSlug}>
{maybe<React.ReactNode>(() => page.slug, <Skeleton />)} {maybe<React.ReactNode>(() => page.slug, <Skeleton />)}
</TableCell> </TableCellHeader>
<TableCell className={classes.colVisibility}> <TableCellHeader className={classes.colVisibility}>
{maybe<React.ReactNode>( {maybe<React.ReactNode>(
() => ( () => (
<StatusLabel <StatusLabel
@ -159,7 +194,7 @@ const PageList: React.FC<PageListProps> = props => {
), ),
<Skeleton /> <Skeleton />
)} )}
</TableCell> </TableCellHeader>
</TableRow> </TableRow>
); );
}, },

View file

@ -7,31 +7,23 @@ import AppHeader from "@saleor/components/AppHeader";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import { sectionNames } from "@saleor/intl"; 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_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<PageListUrlSortField> {
pages: PageList_pages_edges_node[]; pages: PageList_pages_edges_node[];
onBack: () => void; onBack: () => void;
} }
const PageListPage: React.FC<PageListPageProps> = ({ const PageListPage: React.FC<PageListPageProps> = ({
disabled,
settings,
onAdd, onAdd,
onBack, onBack,
onNextPage, ...listProps
onPreviousPage,
onRowClick,
onUpdateListSettings,
pageInfo,
pages,
isChecked,
selected,
toggle,
toggleAll,
toolbar
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -41,30 +33,11 @@ const PageListPage: React.FC<PageListPageProps> = ({
{intl.formatMessage(sectionNames.configuration)} {intl.formatMessage(sectionNames.configuration)}
</AppHeader> </AppHeader>
<PageHeader title={intl.formatMessage(sectionNames.pages)}> <PageHeader title={intl.formatMessage(sectionNames.pages)}>
<Button <Button onClick={onAdd} variant="contained" color="primary">
disabled={disabled}
onClick={onAdd}
variant="contained"
color="primary"
>
<FormattedMessage defaultMessage="Create page" description="button" /> <FormattedMessage defaultMessage="Create page" description="button" />
</Button> </Button>
</PageHeader> </PageHeader>
<PageList <PageList {...listProps} />
disabled={disabled}
settings={settings}
pages={pages}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
onUpdateListSettings={onUpdateListSettings}
onRowClick={onRowClick}
pageInfo={pageInfo}
isChecked={isChecked}
selected={selected}
toggle={toggle}
toggleAll={toggleAll}
toolbar={toolbar}
/>
</Container> </Container>
); );
}; };

View file

@ -4,13 +4,15 @@ import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { import {
pageCreatePath, pageCreatePath,
pageListPath, pageListPath,
PageListUrlQueryParams, PageListUrlQueryParams,
pagePath, pagePath,
PageUrlQueryParams PageUrlQueryParams,
PageListUrlSortField
} from "./urls"; } from "./urls";
import PageCreate from "./views/PageCreate"; import PageCreate from "./views/PageCreate";
import PageDetailsComponent from "./views/PageDetails"; import PageDetailsComponent from "./views/PageDetails";
@ -18,7 +20,11 @@ import PageListComponent from "./views/PageList";
const PageList: React.FC<RouteComponentProps<any>> = ({ location }) => { const PageList: React.FC<RouteComponentProps<any>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: PageListUrlQueryParams = qs; const params: PageListUrlQueryParams = asSortParams(
qs,
PageListUrlSortField,
PageListUrlSortField.title
);
return <PageListComponent params={params} />; return <PageListComponent params={params} />;
}; };

View file

@ -1,5 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import makeQuery from "@saleor/hooks/makeQuery";
import { TypedQuery } from "../queries"; import { TypedQuery } from "../queries";
import { PageDetails, PageDetailsVariables } from "./types/PageDetails"; import { PageDetails, PageDetailsVariables } from "./types/PageDetails";
import { PageList, PageListVariables } from "./types/PageList"; import { PageList, PageListVariables } from "./types/PageList";
@ -26,8 +27,20 @@ export const pageDetailsFragment = gql`
const pageList = gql` const pageList = gql`
${pageFragment} ${pageFragment}
query PageList($first: Int, $after: String, $last: Int, $before: String) { query PageList(
pages(before: $before, after: $after, first: $first, last: $last) { $first: Int
$after: String
$last: Int
$before: String
$sort: PageSortingInput
) {
pages(
before: $before
after: $after
first: $first
last: $last
sortBy: $sort
) {
edges { edges {
node { node {
...PageFragment ...PageFragment
@ -42,7 +55,7 @@ const pageList = gql`
} }
} }
`; `;
export const TypedPageListQuery = TypedQuery<PageList, PageListVariables>( export const usePageListQuery = makeQuery<PageList, PageListVariables>(
pageList pageList
); );

View file

@ -2,6 +2,8 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { PageSortingInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: PageList // GraphQL query operation: PageList
// ==================================================== // ====================================================
@ -42,4 +44,5 @@ export interface PageListVariables {
after?: string | null; after?: string | null;
last?: number | null; last?: number | null;
before?: string | null; before?: string | null;
sort?: PageSortingInput | null;
} }

View file

@ -1,14 +1,21 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination } from "../types"; import { BulkAction, Dialog, Pagination, Sort } from "../types";
export const pagesSection = "/pages/"; export const pagesSection = "/pages/";
export const pageListPath = pagesSection; export const pageListPath = pagesSection;
export type PageListUrlDialog = "publish" | "unpublish" | "remove"; export type PageListUrlDialog = "publish" | "unpublish" | "remove";
export enum PageListUrlSortField {
title = "title",
slug = "slug",
visible = "visible"
}
export type PageListUrlSort = Sort<PageListUrlSortField>;
export type PageListUrlQueryParams = BulkAction & export type PageListUrlQueryParams = BulkAction &
Dialog<PageListUrlDialog> & Dialog<PageListUrlDialog> &
PageListUrlSort &
Pagination; Pagination;
export const pageListUrl = (params?: PageListUrlQueryParams) => export const pageListUrl = (params?: PageListUrlQueryParams) =>
pageListPath + "?" + stringifyQs(params); pageListPath + "?" + stringifyQs(params);

View file

@ -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<PageListProps> = ({ 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 (
<TypedPageListQuery displayLoader variables={paginationState}>
{({ 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 (
<TypedPageBulkRemove onCompleted={handlePageBulkRemove}>
{(bulkPageRemove, bulkPageRemoveOpts) => (
<TypedPageBulkPublish onCompleted={handlePageBulkPublish}>
{(bulkPagePublish, bulkPagePublishOpts) => (
<>
<PageListPage
disabled={loading}
settings={settings}
pages={maybe(() =>
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={
<>
<Button
color="primary"
onClick={() => openModal("unpublish", listElements)}
>
<FormattedMessage
defaultMessage="Unpublish"
description="unpublish page, button"
/>
</Button>
<Button
color="primary"
onClick={() => openModal("publish", listElements)}
>
<FormattedMessage
defaultMessage="Publish"
description="publish page, button"
/>
</Button>
<IconButton
color="primary"
onClick={() => openModal("remove", listElements)}
>
<DeleteIcon />
</IconButton>
</>
}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
/>
<ActionDialog
open={params.action === "publish"}
onClose={closeModal}
confirmButtonState={bulkPagePublishOpts.status}
onConfirm={() =>
bulkPagePublish({
variables: {
ids: params.ids,
isPublished: true
}
})
}
title={intl.formatMessage({
defaultMessage: "Publish Pages",
description: "dialog header"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to publish {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={params.action === "unpublish"}
onClose={closeModal}
confirmButtonState={bulkPagePublishOpts.status}
onConfirm={() =>
bulkPagePublish({
variables: {
ids: params.ids,
isPublished: false
}
})
}
title={intl.formatMessage({
defaultMessage: "Unpublish Pages",
description: "dialog header"
})}
>
<FormattedMessage
defaultMessage="Are you sure you want to unpublish {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</ActionDialog>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={bulkPageRemoveOpts.status}
onConfirm={() =>
bulkPageRemove({
variables: {
ids: params.ids
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Pages",
description: "dialog header"
})}
>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</ActionDialog>
</>
)}
</TypedPageBulkPublish>
)}
</TypedPageBulkRemove>
);
}}
</TypedPageListQuery>
);
};
export default PageList;

View file

@ -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<PageListProps> = ({ 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 (
<TypedPageBulkRemove onCompleted={handlePageBulkRemove}>
{(bulkPageRemove, bulkPageRemoveOpts) => (
<TypedPageBulkPublish onCompleted={handlePageBulkPublish}>
{(bulkPagePublish, bulkPagePublishOpts) => (
<>
<PageListPage
disabled={loading}
settings={settings}
pages={maybe(() => 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={
<>
<Button
color="primary"
onClick={() => openModal("unpublish", listElements)}
>
<FormattedMessage
defaultMessage="Unpublish"
description="unpublish page, button"
/>
</Button>
<Button
color="primary"
onClick={() => openModal("publish", listElements)}
>
<FormattedMessage
defaultMessage="Publish"
description="publish page, button"
/>
</Button>
<IconButton
color="primary"
onClick={() => openModal("remove", listElements)}
>
<DeleteIcon />
</IconButton>
</>
}
isChecked={isSelected}
selected={listElements.length}
sort={getSortParams(params)}
toggle={toggle}
toggleAll={toggleAll}
/>
<ActionDialog
open={params.action === "publish"}
onClose={closeModal}
confirmButtonState={bulkPagePublishOpts.status}
onConfirm={() =>
bulkPagePublish({
variables: {
ids: params.ids,
isPublished: true
}
})
}
title={intl.formatMessage({
defaultMessage: "Publish Pages",
description: "dialog header"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to publish {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={params.action === "unpublish"}
onClose={closeModal}
confirmButtonState={bulkPagePublishOpts.status}
onConfirm={() =>
bulkPagePublish({
variables: {
ids: params.ids,
isPublished: false
}
})
}
title={intl.formatMessage({
defaultMessage: "Unpublish Pages",
description: "dialog header"
})}
>
<FormattedMessage
defaultMessage="Are you sure you want to unpublish {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</ActionDialog>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={bulkPageRemoveOpts.status}
onConfirm={() =>
bulkPageRemove({
variables: {
ids: params.ids
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Pages",
description: "dialog header"
})}
>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</ActionDialog>
</>
)}
</TypedPageBulkPublish>
)}
</TypedPageBulkRemove>
);
};
export default PageList;

View file

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

View file

@ -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
);

View file

@ -15,10 +15,15 @@ import StatusLabel from "@saleor/components/StatusLabel";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import { translateBoolean } from "@saleor/intl"; import { translateBoolean } from "@saleor/intl";
import { maybe, renderCollection } from "@saleor/misc"; 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"; import { Plugins_plugins_edges_node } from "../../types/Plugins";
export interface PluginListProps extends ListProps { export interface PluginListProps
extends ListProps,
SortPage<PluginListUrlSortField> {
plugins: Plugins_plugins_edges_node[]; plugins: Plugins_plugins_edges_node[];
} }
@ -53,7 +58,9 @@ const PluginList: React.FC<PluginListProps> = props => {
disabled, disabled,
onNextPage, onNextPage,
pageInfo, pageInfo,
sort,
onRowClick, onRowClick,
onSort,
onUpdateListSettings, onUpdateListSettings,
onPreviousPage onPreviousPage
} = props; } = props;
@ -64,18 +71,35 @@ const PluginList: React.FC<PluginListProps> = props => {
<Card> <Card>
<ResponsiveTable> <ResponsiveTable>
<TableHead> <TableHead>
<TableCell className={classes.colName}> <TableCellHeader
direction={
sort.sort === PluginListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(PluginListUrlSortField.name)}
className={classes.colName}
>
{intl.formatMessage({ {intl.formatMessage({
defaultMessage: "Name", defaultMessage: "Name",
description: "plugin name" description: "plugin name"
})} })}
</TableCell> </TableCellHeader>
<TableCell className={classes.colActive}> <TableCellHeader
direction={
sort.sort === PluginListUrlSortField.active
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(PluginListUrlSortField.active)}
className={classes.colActive}
>
{intl.formatMessage({ {intl.formatMessage({
defaultMessage: "Active", defaultMessage: "Active",
description: "plugin status" description: "plugin status"
})} })}
</TableCell> </TableCellHeader>
<TableCell className={classes.colAction}> <TableCell className={classes.colAction}>
{intl.formatMessage({ {intl.formatMessage({
defaultMessage: "Action", defaultMessage: "Action",

View file

@ -5,43 +5,31 @@ import AppHeader from "@saleor/components/AppHeader";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import { sectionNames } from "@saleor/intl"; 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 { Plugins_plugins_edges_node } from "../../types/Plugins";
import PluginsList from "../PluginsList/PluginsList"; import PluginsList from "../PluginsList/PluginsList";
export interface PluginsListPageProps extends PageListProps { export interface PluginsListPageProps
extends PageListProps,
SortPage<PluginListUrlSortField> {
plugins: Plugins_plugins_edges_node[]; plugins: Plugins_plugins_edges_node[];
onBack: () => void; onBack: () => void;
} }
const PluginsListPage: React.FC<PluginsListPageProps> = ({ const PluginsListPage: React.FC<PluginsListPageProps> = ({
disabled,
settings,
onBack, onBack,
onNextPage, ...listProps
onPreviousPage,
onRowClick,
onUpdateListSettings,
pageInfo,
plugins
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
return ( return (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.configuration)} {intl.formatMessage(sectionNames.configuration)}
</AppHeader> </AppHeader>
<PageHeader title={intl.formatMessage(sectionNames.plugins)} /> <PageHeader title={intl.formatMessage(sectionNames.plugins)} />
<PluginsList <PluginsList {...listProps} />
disabled={disabled}
settings={settings}
plugins={plugins}
onNextPage={onNextPage}
onPreviousPage={onPreviousPage}
onUpdateListSettings={onUpdateListSettings}
onRowClick={onRowClick}
pageInfo={pageInfo}
/>
</Container> </Container>
); );
}; };

View file

@ -4,25 +4,30 @@ import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { import {
pluginsListPath, pluginListPath,
PluginsListUrlQueryParams, PluginListUrlQueryParams,
pluginsPath, pluginPath,
PluginsUrlQueryParams PluginUrlQueryParams,
PluginListUrlSortField
} from "./urls"; } from "./urls";
import PluginsDetailsComponent from "./views/PluginsDetails"; import PluginsDetailsComponent from "./views/PluginsDetails";
import PluginsListComponent from "./views/PluginsList"; import PluginsListComponent from "./views/PluginList";
const PluginList: React.FC<RouteComponentProps<any>> = ({ location }) => { const PluginList: React.FC<RouteComponentProps<any>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: PluginsListUrlQueryParams = qs; const params: PluginListUrlQueryParams = asSortParams(
qs,
PluginListUrlSortField
);
return <PluginsListComponent params={params} />; return <PluginsListComponent params={params} />;
}; };
const PageDetails: React.FC<RouteComponentProps<any>> = ({ match }) => { const PageDetails: React.FC<RouteComponentProps<any>> = ({ match }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: PluginsUrlQueryParams = qs; const params: PluginUrlQueryParams = qs;
return ( return (
<PluginsDetailsComponent <PluginsDetailsComponent
@ -38,8 +43,8 @@ const Component = () => {
<> <>
<WindowTitle title={intl.formatMessage(sectionNames.plugins)} /> <WindowTitle title={intl.formatMessage(sectionNames.plugins)} />
<Switch> <Switch>
<Route exact path={pluginsListPath} component={PluginList} /> <Route exact path={pluginListPath} component={PluginList} />
<Route path={pluginsPath(":id")} component={PageDetails} /> <Route path={pluginPath(":id")} component={PageDetails} />
</Switch> </Switch>
</> </>
); );

View file

@ -1,5 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import makeQuery from "@saleor/hooks/makeQuery";
import { TypedQuery } from "../queries"; import { TypedQuery } from "../queries";
import { Plugin, PluginVariables } from "./types/Plugin"; import { Plugin, PluginVariables } from "./types/Plugin";
import { Plugins, PluginsVariables } from "./types/Plugins"; import { Plugins, PluginsVariables } from "./types/Plugins";
@ -29,8 +30,20 @@ export const pluginsDetailsFragment = gql`
const pluginsList = gql` const pluginsList = gql`
${pluginsFragment} ${pluginsFragment}
query Plugins($first: Int, $after: String, $last: Int, $before: String) { query Plugins(
plugins(before: $before, after: $after, first: $first, last: $last) { $first: Int
$after: String
$last: Int
$before: String
$sort: PluginSortingInput
) {
plugins(
before: $before
after: $after
first: $first
last: $last
sortBy: $sort
) {
edges { edges {
node { node {
...PluginFragment ...PluginFragment
@ -45,7 +58,7 @@ const pluginsList = gql`
} }
} }
`; `;
export const TypedPluginsListQuery = TypedQuery<Plugins, PluginsVariables>( export const usePluginsListQuery = makeQuery<Plugins, PluginsVariables>(
pluginsList pluginsList
); );

View file

@ -2,6 +2,8 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { PluginSortingInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: Plugins // GraphQL query operation: Plugins
// ==================================================== // ====================================================
@ -42,4 +44,5 @@ export interface PluginsVariables {
after?: string | null; after?: string | null;
last?: number | null; last?: number | null;
before?: string | null; before?: string | null;
sort?: PluginSortingInput | null;
} }

View file

@ -1,19 +1,26 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { 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 const pluginListPath = pluginSection;
export type PluginsListUrlQueryParams = Pagination & SingleAction; export enum PluginListUrlSortField {
export const pluginsListUrl = (params?: PluginsListUrlQueryParams) => name = "name",
pluginsListPath + "?" + stringifyQs(params); active = "active"
}
export type PluginListUrlSort = Sort<PluginListUrlSortField>;
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 PluginUrlDialog = "clear" | "edit";
export type PluginsUrlQueryParams = Dialog<PluginUrlDialog> & { export type PluginUrlQueryParams = Dialog<PluginUrlDialog> & {
field?: string; field?: string;
}; };
export const pluginsUrl = (id: string, params?: PluginsUrlQueryParams) => export const pluginsUrl = (id: string, params?: PluginUrlQueryParams) =>
pluginsPath(encodeURIComponent(id)) + "?" + stringifyQs(params); pluginPath(encodeURIComponent(id)) + "?" + stringifyQs(params);

View file

@ -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<PluginsListProps> = ({ 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 (
<>
<PluginsListPage
disabled={loading}
settings={settings}
plugins={maybe(() => 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;

View file

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

View file

@ -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
);

View file

@ -16,16 +16,16 @@ import { TypedPluginsDetailsQuery } from "../queries";
import { Plugin_plugin_configuration } from "../types/Plugin"; import { Plugin_plugin_configuration } from "../types/Plugin";
import { PluginUpdate } from "../types/PluginUpdate"; import { PluginUpdate } from "../types/PluginUpdate";
import { import {
pluginsListUrl, pluginListUrl,
pluginsUrl, pluginsUrl,
PluginsUrlQueryParams, PluginUrlQueryParams,
PluginUrlDialog PluginUrlDialog
} from "../urls"; } from "../urls";
import { isSecretField } from "../utils"; import { isSecretField } from "../utils";
export interface PluginsDetailsProps { export interface PluginsDetailsProps {
id: string; id: string;
params: PluginsUrlQueryParams; params: PluginUrlQueryParams;
} }
export function getConfigurationInput( export function getConfigurationInput(
@ -117,7 +117,7 @@ export const PluginsDetails: React.FC<PluginsDetailsProps> = ({
!params.action ? pluginUpdateOpts.status : "default" !params.action ? pluginUpdateOpts.status : "default"
} }
plugin={maybe(() => pluginDetails.data.plugin)} plugin={maybe(() => pluginDetails.data.plugin)}
onBack={() => navigate(pluginsListUrl())} onBack={() => navigate(pluginListUrl())}
onClear={field => openModal("clear", field)} onClear={field => openModal("clear", field)}
onEdit={field => openModal("edit", field)} onEdit={field => openModal("edit", field)}
onSubmit={formData => onSubmit={formData =>

View file

@ -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<PluginsListProps> = ({ params }) => {
const navigate = useNavigator();
const paginate = usePaginator();
const { updateListSettings, settings } = useListSettings(
ListViews.PLUGINS_LIST
);
const paginationState = createPaginationState(settings.rowNumber, params);
return (
<TypedPluginsListQuery displayLoader variables={paginationState}>
{({ data, loading }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.plugins.pageInfo),
paginationState,
params
);
return (
<>
<PluginsListPage
disabled={loading}
settings={settings}
plugins={maybe(() => 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))}
/>
</>
);
}}
</TypedPluginsListQuery>
);
};
export default PluginsList;

View file

@ -12,8 +12,11 @@ import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; 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 { maybe, renderCollection } from "../../../misc";
import { ListActions, ListProps } from "../../../types"; import { ListActions, ListProps, SortPage } from "../../../types";
import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList"; import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -39,7 +42,10 @@ const useStyles = makeStyles(
{ name: "ProductTypeList" } { name: "ProductTypeList" }
); );
interface ProductTypeListProps extends ListProps, ListActions { interface ProductTypeListProps
extends ListProps,
ListActions,
SortPage<ProductTypeListUrlSortField> {
productTypes: ProductTypeList_productTypes_edges_node[]; productTypes: ProductTypeList_productTypes_edges_node[];
} }
@ -53,8 +59,10 @@ const ProductTypeList: React.FC<ProductTypeListProps> = props => {
onNextPage, onNextPage,
onPreviousPage, onPreviousPage,
onRowClick, onRowClick,
onSort,
isChecked, isChecked,
selected, selected,
sort,
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
@ -73,18 +81,35 @@ const ProductTypeList: React.FC<ProductTypeListProps> = props => {
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCellHeader
direction={
sort.sort === ProductTypeListUrlSortField.name
? getArrowDirection(sort.asc)
: undefined
}
arrowPosition="right"
onClick={() => onSort(ProductTypeListUrlSortField.name)}
className={classes.colName}
>
<FormattedMessage <FormattedMessage
defaultMessage="Type Name" defaultMessage="Type Name"
description="product type name" description="product type name"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colType}> <TableCellHeader
direction={
sort.sort === ProductTypeListUrlSortField.digital
? getArrowDirection(sort.asc)
: undefined
}
onClick={() => onSort(ProductTypeListUrlSortField.digital)}
className={classes.colType}
>
<FormattedMessage <FormattedMessage
defaultMessage="Type" defaultMessage="Type"
description="product type is either simple or configurable" description="product type is either simple or configurable"
/> />
</TableCell> </TableCellHeader>
<TableCell className={classes.colTax}> <TableCell className={classes.colTax}>
<FormattedMessage <FormattedMessage
defaultMessage="Tax" defaultMessage="Tax"

View file

@ -8,11 +8,13 @@ import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar"; import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls";
import { import {
ListActions, ListActions,
PageListProps, PageListProps,
SearchPageProps, SearchPageProps,
TabPageProps TabPageProps,
SortPage
} from "../../../types"; } from "../../../types";
import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList"; import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList";
import ProductTypeList from "../ProductTypeList"; import ProductTypeList from "../ProductTypeList";
@ -21,6 +23,7 @@ export interface ProductTypeListPageProps
extends PageListProps, extends PageListProps,
ListActions, ListActions,
SearchPageProps, SearchPageProps,
SortPage<ProductTypeListUrlSortField>,
TabPageProps { TabPageProps {
productTypes: ProductTypeList_productTypes_edges_node[]; productTypes: ProductTypeList_productTypes_edges_node[];
onBack: () => void; onBack: () => void;

View file

@ -4,13 +4,15 @@ import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { asSortParams } from "@saleor/utils/sort";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import { import {
productTypeAddPath, productTypeAddPath,
productTypeListPath, productTypeListPath,
ProductTypeListUrlQueryParams, ProductTypeListUrlQueryParams,
productTypePath, productTypePath,
ProductTypeUrlQueryParams ProductTypeUrlQueryParams,
ProductTypeListUrlSortField
} from "./urls"; } from "./urls";
import ProductTypeCreate from "./views/ProductTypeCreate"; import ProductTypeCreate from "./views/ProductTypeCreate";
import ProductTypeListComponent from "./views/ProductTypeList"; import ProductTypeListComponent from "./views/ProductTypeList";
@ -18,16 +20,19 @@ import ProductTypeUpdateComponent from "./views/ProductTypeUpdate";
const ProductTypeList: React.FC<RouteComponentProps<{}>> = ({ location }) => { const ProductTypeList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: ProductTypeListUrlQueryParams = qs; const params: ProductTypeListUrlQueryParams = asSortParams(
qs,
ProductTypeListUrlSortField
);
return <ProductTypeListComponent params={params} />; return <ProductTypeListComponent params={params} />;
}; };
interface ProductTypeUpdateRouteParams { interface ProductTypeUpdateRouteParams {
id: string; id: string;
} }
const ProductTypeUpdate: React.FC< const ProductTypeUpdate: React.FC<RouteComponentProps<
RouteComponentProps<ProductTypeUpdateRouteParams> ProductTypeUpdateRouteParams
> = ({ match }) => { >> = ({ match }) => {
const qs = parseQs(location.search.substr(1)); const qs = parseQs(location.search.substr(1));
const params: ProductTypeUrlQueryParams = qs; const params: ProductTypeUrlQueryParams = qs;

View file

@ -1,6 +1,7 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import { attributeFragment } from "@saleor/attributes/queries"; import { attributeFragment } from "@saleor/attributes/queries";
import makeQuery from "@saleor/hooks/makeQuery";
import { pageInfoFragment, TypedQuery } from "../queries"; import { pageInfoFragment, TypedQuery } from "../queries";
import { ProductTypeCreateData } from "./types/ProductTypeCreateData"; import { ProductTypeCreateData } from "./types/ProductTypeCreateData";
import { import {
@ -52,6 +53,7 @@ export const productTypeListQuery = gql`
$first: Int $first: Int
$last: Int $last: Int
$filter: ProductTypeFilterInput $filter: ProductTypeFilterInput
$sort: ProductTypeSortingInput
) { ) {
productTypes( productTypes(
after: $after after: $after
@ -59,6 +61,7 @@ export const productTypeListQuery = gql`
first: $first first: $first
last: $last last: $last
filter: $filter filter: $filter
sortBy: $sort
) { ) {
edges { edges {
node { node {
@ -71,7 +74,7 @@ export const productTypeListQuery = gql`
} }
} }
`; `;
export const TypedProductTypeListQuery = TypedQuery< export const useProductTypeListQuery = makeQuery<
ProductTypeList, ProductTypeList,
ProductTypeListVariables ProductTypeListVariables
>(productTypeListQuery); >(productTypeListQuery);

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