Add sorting to lists
This commit is contained in:
parent
97abf33fd2
commit
9c688d3aef
153 changed files with 6897 additions and 2853 deletions
|
@ -21,6 +21,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Enforce using "name" property in style hooks - #288 by @dominik-zeglen
|
||||
- Add ability to reset own password - #289 by @dominik-zeglen
|
||||
- Move mutation state to mutation - #297 by @dominik-zeglen
|
||||
- Add table sorting - #292 by @dominik-zeglen
|
||||
|
||||
## 2.0.0
|
||||
|
||||
|
|
193
schema.graphql
193
schema.graphql
|
@ -318,8 +318,8 @@ enum AttributeSortField {
|
|||
}
|
||||
|
||||
input AttributeSortingInput {
|
||||
field: AttributeSortField!
|
||||
direction: OrderDirection!
|
||||
field: AttributeSortField
|
||||
}
|
||||
|
||||
type AttributeTranslatableContent implements Node {
|
||||
|
@ -570,6 +570,17 @@ input CategoryInput {
|
|||
backgroundImageAlt: String
|
||||
}
|
||||
|
||||
enum CategorySortField {
|
||||
NAME
|
||||
PRODUCT_COUNT
|
||||
SUBCATEGORY_COUNT
|
||||
}
|
||||
|
||||
input CategorySortingInput {
|
||||
direction: OrderDirection!
|
||||
field: CategorySortField
|
||||
}
|
||||
|
||||
type CategoryTranslatableContent implements Node {
|
||||
seoTitle: String
|
||||
seoDescription: String
|
||||
|
@ -950,6 +961,17 @@ type CollectionReorderProducts {
|
|||
productErrors: [ProductError!]
|
||||
}
|
||||
|
||||
enum CollectionSortField {
|
||||
NAME
|
||||
AVAILABILITY
|
||||
PRODUCT_COUNT
|
||||
}
|
||||
|
||||
input CollectionSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: CollectionSortField
|
||||
}
|
||||
|
||||
type CollectionTranslatableContent implements Node {
|
||||
seoTitle: String
|
||||
seoDescription: String
|
||||
|
@ -1398,6 +1420,8 @@ input DateTimeRangeInput {
|
|||
|
||||
scalar Decimal
|
||||
|
||||
union DefaultTranslationItem = ProductTranslatableContent | CollectionTranslatableContent | CategoryTranslatableContent | AttributeTranslatableContent | AttributeValueTranslatableContent | ProductVariantTranslatableContent | PageTranslatableContent | ShippingMethodTranslatableContent | SaleTranslatableContent | VoucherTranslatableContent | MenuItemTranslatableContent
|
||||
|
||||
type DigitalContent implements Node {
|
||||
useDefaultSettings: Boolean!
|
||||
automaticFulfillment: Boolean!
|
||||
|
@ -1990,6 +2014,11 @@ input MenuItemMoveInput {
|
|||
sortOrder: Int
|
||||
}
|
||||
|
||||
input MenuItemSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: MenuItemsSortField
|
||||
}
|
||||
|
||||
type MenuItemTranslatableContent implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
|
@ -2014,6 +2043,20 @@ type MenuItemUpdate {
|
|||
menuItem: MenuItem
|
||||
}
|
||||
|
||||
enum MenuItemsSortField {
|
||||
NAME
|
||||
}
|
||||
|
||||
enum MenuSortField {
|
||||
NAME
|
||||
ITEMS_COUNT
|
||||
}
|
||||
|
||||
input MenuSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: MenuSortField
|
||||
}
|
||||
|
||||
type MenuUpdate {
|
||||
errors: [Error!]
|
||||
menuErrors: [MenuError!]
|
||||
|
@ -2575,6 +2618,20 @@ type OrderRefund {
|
|||
orderErrors: [OrderError!]
|
||||
}
|
||||
|
||||
enum OrderSortField {
|
||||
NUMBER
|
||||
CREATION_DATE
|
||||
CUSTOMER
|
||||
PAYMENT
|
||||
FULFILLMENT_STATUS
|
||||
TOTAL
|
||||
}
|
||||
|
||||
input OrderSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: OrderSortField
|
||||
}
|
||||
|
||||
enum OrderStatus {
|
||||
DRAFT
|
||||
UNFULFILLED
|
||||
|
@ -2696,6 +2753,19 @@ input PageInput {
|
|||
seo: SeoInput
|
||||
}
|
||||
|
||||
enum PageSortField {
|
||||
TITLE
|
||||
SLUG
|
||||
VISIBILITY
|
||||
CREATION_DATE
|
||||
PUBLICATION_DATE
|
||||
}
|
||||
|
||||
input PageSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: PageSortField
|
||||
}
|
||||
|
||||
type PageTranslatableContent implements Node {
|
||||
seoTitle: String
|
||||
seoDescription: String
|
||||
|
@ -2892,6 +2962,16 @@ input PluginFilterInput {
|
|||
search: String
|
||||
}
|
||||
|
||||
enum PluginSortField {
|
||||
NAME
|
||||
IS_ACTIVE
|
||||
}
|
||||
|
||||
input PluginSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: PluginSortField
|
||||
}
|
||||
|
||||
type PluginUpdate {
|
||||
errors: [Error!]
|
||||
plugin: Plugin
|
||||
|
@ -3115,9 +3195,9 @@ input ProductInput {
|
|||
}
|
||||
|
||||
input ProductOrder {
|
||||
field: ProductOrderField
|
||||
attributeId: ID
|
||||
direction: OrderDirection!
|
||||
attributeId: ID
|
||||
field: ProductOrderField
|
||||
}
|
||||
|
||||
enum ProductOrderField {
|
||||
|
@ -3256,6 +3336,17 @@ type ProductTypeReorderAttributes {
|
|||
productErrors: [ProductError!]
|
||||
}
|
||||
|
||||
enum ProductTypeSortField {
|
||||
NAME
|
||||
DIGITAL
|
||||
SHIPPING_REQUIRED
|
||||
}
|
||||
|
||||
input ProductTypeSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: ProductTypeSortField
|
||||
}
|
||||
|
||||
type ProductTypeUpdate {
|
||||
errors: [Error!]
|
||||
productErrors: [ProductError!]
|
||||
|
@ -3436,11 +3527,11 @@ type ProductVariantUpdatePrivateMeta {
|
|||
|
||||
type Query {
|
||||
webhook(id: ID!): Webhook
|
||||
webhooks(filter: WebhookFilterInput, before: String, after: String, first: Int, last: Int): WebhookCountableConnection
|
||||
webhooks(sortBy: WebhookSortingInput, filter: WebhookFilterInput, before: String, after: String, first: Int, last: Int): WebhookCountableConnection
|
||||
webhookEvents: [WebhookEvent]
|
||||
webhookSamplePayload(eventType: WebhookEventTypeEnum!): JSONString
|
||||
translations(kind: TranslatableKinds!, before: String, after: String, first: Int, last: Int): TranslatableItemConnection
|
||||
translation(id: ID!, kind: TranslatableKinds!): TranslatableItem
|
||||
translation(id: ID!, kind: TranslatableKinds!): DefaultTranslationItem
|
||||
shop: Shop
|
||||
shippingZone(id: ID!): ShippingZone
|
||||
shippingZones(before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection
|
||||
|
@ -3448,14 +3539,14 @@ type Query {
|
|||
digitalContents(before: String, after: String, first: Int, last: Int): DigitalContentCountableConnection
|
||||
attributes(query: String, inCategory: ID, inCollection: ID, filter: AttributeFilterInput, sortBy: AttributeSortingInput, before: String, after: String, first: Int, last: Int): AttributeCountableConnection
|
||||
attribute(id: ID!): Attribute
|
||||
categories(query: String, filter: CategoryFilterInput, level: Int, before: String, after: String, first: Int, last: Int): CategoryCountableConnection
|
||||
categories(query: String, filter: CategoryFilterInput, sortBy: CategorySortingInput, level: Int, before: String, after: String, first: Int, last: Int): CategoryCountableConnection
|
||||
category(id: ID!): Category
|
||||
collection(id: ID!): Collection
|
||||
collections(filter: CollectionFilterInput, query: String, before: String, after: String, first: Int, last: Int): CollectionCountableConnection
|
||||
collections(filter: CollectionFilterInput, sortBy: CollectionSortingInput, query: String, before: String, after: String, first: Int, last: Int): CollectionCountableConnection
|
||||
product(id: ID!): Product
|
||||
products(filter: ProductFilterInput, attributes: [AttributeScalar], categories: [ID], collections: [ID], sortBy: ProductOrder, stockAvailability: StockAvailability, query: String, before: String, after: String, first: Int, last: Int): ProductCountableConnection
|
||||
productType(id: ID!): ProductType
|
||||
productTypes(filter: ProductTypeFilterInput, query: String, before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection
|
||||
productTypes(filter: ProductTypeFilterInput, query: String, sortBy: ProductTypeSortingInput, before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection
|
||||
productVariant(id: ID!): ProductVariant
|
||||
productVariants(ids: [ID], before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection
|
||||
reportProductSales(period: ReportingPeriod!, before: String, after: String, first: Int, last: Int): ProductVariantCountableConnection
|
||||
|
@ -3463,37 +3554,38 @@ type Query {
|
|||
payments(before: String, after: String, first: Int, last: Int): PaymentCountableConnection
|
||||
paymentClientToken(gateway: String!): String @deprecated(reason: "DEPRECATED: Will be removed in Saleor 2.10, use payment gateway config instead in availablePaymentGateways.")
|
||||
page(id: ID, slug: String): Page
|
||||
pages(query: String, filter: PageFilterInput, before: String, after: String, first: Int, last: Int): PageCountableConnection
|
||||
pages(query: String, sortBy: PageSortingInput, filter: PageFilterInput, before: String, after: String, first: Int, last: Int): PageCountableConnection
|
||||
homepageEvents(before: String, after: String, first: Int, last: Int): OrderEventCountableConnection
|
||||
order(id: ID!): Order
|
||||
orders(filter: OrderFilterInput, query: String, created: ReportingPeriod, status: OrderStatusFilter, before: String, after: String, first: Int, last: Int): OrderCountableConnection
|
||||
draftOrders(filter: OrderDraftFilterInput, query: String, created: ReportingPeriod, before: String, after: String, first: Int, last: Int): OrderCountableConnection
|
||||
orders(sortBy: OrderSortingInput, filter: OrderFilterInput, query: String, created: ReportingPeriod, status: OrderStatusFilter, before: String, after: String, first: Int, last: Int): OrderCountableConnection
|
||||
draftOrders(sortBy: OrderSortingInput, filter: OrderDraftFilterInput, query: String, created: ReportingPeriod, before: String, after: String, first: Int, last: Int): OrderCountableConnection
|
||||
ordersTotal(period: ReportingPeriod): TaxedMoney
|
||||
orderByToken(token: UUID!): Order
|
||||
menu(id: ID, name: String): Menu
|
||||
menus(query: String, filter: MenuFilterInput, before: String, after: String, first: Int, last: Int): MenuCountableConnection
|
||||
menus(query: String, sortBy: MenuSortingInput, filter: MenuFilterInput, before: String, after: String, first: Int, last: Int): MenuCountableConnection
|
||||
menuItem(id: ID!): MenuItem
|
||||
menuItems(query: String, filter: MenuItemFilterInput, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection
|
||||
menuItems(query: String, sortBy: MenuItemSortingInput, filter: MenuItemFilterInput, before: String, after: String, first: Int, last: Int): MenuItemCountableConnection
|
||||
giftCard(id: ID!): GiftCard
|
||||
giftCards(before: String, after: String, first: Int, last: Int): GiftCardCountableConnection
|
||||
plugin(id: ID!): Plugin
|
||||
plugins(filter: PluginFilterInput, before: String, after: String, first: Int, last: Int): PluginCountableConnection
|
||||
plugins(filter: PluginFilterInput, sortBy: PluginSortingInput, before: String, after: String, first: Int, last: Int): PluginCountableConnection
|
||||
sale(id: ID!): Sale
|
||||
sales(filter: SaleFilterInput, query: String, before: String, after: String, first: Int, last: Int): SaleCountableConnection
|
||||
sales(filter: SaleFilterInput, sortBy: SaleSortingInput, query: String, before: String, after: String, first: Int, last: Int): SaleCountableConnection
|
||||
voucher(id: ID!): Voucher
|
||||
vouchers(filter: VoucherFilterInput, query: String, before: String, after: String, first: Int, last: Int): VoucherCountableConnection
|
||||
vouchers(filter: VoucherFilterInput, sortBy: VoucherSortingInput, query: String, before: String, after: String, first: Int, last: Int): VoucherCountableConnection
|
||||
taxTypes: [TaxType]
|
||||
checkout(token: UUID): Checkout
|
||||
checkouts(before: String, after: String, first: Int, last: Int): CheckoutCountableConnection
|
||||
checkoutLine(id: ID): CheckoutLine
|
||||
checkoutLines(before: String, after: String, first: Int, last: Int): CheckoutLineCountableConnection
|
||||
addressValidationRules(countryCode: CountryCode!, countryArea: String, city: String, cityArea: String): AddressValidationData
|
||||
customers(filter: CustomerFilterInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection
|
||||
customers(filter: CustomerFilterInput, sortBy: UserSortingInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection
|
||||
me: User
|
||||
staffUsers(filter: StaffUserInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection
|
||||
serviceAccounts(filter: ServiceAccountFilterInput, before: String, after: String, first: Int, last: Int): ServiceAccountCountableConnection
|
||||
staffUsers(filter: StaffUserInput, sortBy: UserSortingInput, query: String, before: String, after: String, first: Int, last: Int): UserCountableConnection
|
||||
serviceAccounts(filter: ServiceAccountFilterInput, sortBy: ServiceAccountSortingInput, before: String, after: String, first: Int, last: Int): ServiceAccountCountableConnection
|
||||
serviceAccount(id: ID!): ServiceAccount
|
||||
user(id: ID!): User
|
||||
node(id: ID!): Node
|
||||
_entities(representations: [_Any]): [_Entity]
|
||||
_service: _Service
|
||||
}
|
||||
|
@ -3590,6 +3682,19 @@ type SaleRemoveCatalogues {
|
|||
sale: Sale
|
||||
}
|
||||
|
||||
enum SaleSortField {
|
||||
NAME
|
||||
START_DATE
|
||||
END_DATE
|
||||
VALUE
|
||||
TYPE
|
||||
}
|
||||
|
||||
input SaleSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: SaleSortField
|
||||
}
|
||||
|
||||
type SaleTranslatableContent implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
|
@ -3681,6 +3786,16 @@ input ServiceAccountInput {
|
|||
permissions: [PermissionEnum]
|
||||
}
|
||||
|
||||
enum ServiceAccountSortField {
|
||||
NAME
|
||||
CREATION_DATE
|
||||
}
|
||||
|
||||
input ServiceAccountSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: ServiceAccountSortField
|
||||
}
|
||||
|
||||
type ServiceAccountToken implements Node {
|
||||
name: String
|
||||
authToken: String
|
||||
|
@ -4109,7 +4224,7 @@ enum TransactionKind {
|
|||
CONFIRM
|
||||
}
|
||||
|
||||
union TranslatableItem = ProductTranslatableContent | CollectionTranslatableContent | CategoryTranslatableContent | AttributeTranslatableContent | AttributeValueTranslatableContent | ProductVariantTranslatableContent | PageTranslatableContent | ShippingMethodTranslatableContent | SaleTranslatableContent | VoucherTranslatableContent | MenuItemTranslatableContent
|
||||
union TranslatableItem = Product | Category | Collection | Attribute | AttributeValue | ProductVariant | Page | ShippingMethod | Sale | Voucher | MenuItem
|
||||
|
||||
type TranslatableItemConnection {
|
||||
pageInfo: PageInfo!
|
||||
|
@ -4230,6 +4345,18 @@ input UserCreateInput {
|
|||
redirectUrl: String
|
||||
}
|
||||
|
||||
enum UserSortField {
|
||||
FIRST_NAME
|
||||
LAST_NAME
|
||||
EMAIL
|
||||
ORDER_COUNT
|
||||
}
|
||||
|
||||
input UserSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: UserSortField
|
||||
}
|
||||
|
||||
type UserUpdateMeta {
|
||||
errors: [Error!]
|
||||
accountErrors: [AccountError!]
|
||||
|
@ -4369,6 +4496,21 @@ type VoucherRemoveCatalogues {
|
|||
voucher: Voucher
|
||||
}
|
||||
|
||||
enum VoucherSortField {
|
||||
CODE
|
||||
START_DATE
|
||||
END_DATE
|
||||
VALUE
|
||||
TYPE
|
||||
USAGE_LIMIT
|
||||
MINIMUM_SPENT_AMOUNT
|
||||
}
|
||||
|
||||
input VoucherSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: VoucherSortField
|
||||
}
|
||||
|
||||
type VoucherTranslatableContent implements Node {
|
||||
id: ID!
|
||||
name: String
|
||||
|
@ -4475,6 +4617,17 @@ input WebhookFilterInput {
|
|||
isActive: Boolean
|
||||
}
|
||||
|
||||
enum WebhookSortField {
|
||||
NAME
|
||||
SERVICE_ACCOUNT
|
||||
TARGET_URL
|
||||
}
|
||||
|
||||
input WebhookSortingInput {
|
||||
direction: OrderDirection!
|
||||
field: WebhookSortField
|
||||
}
|
||||
|
||||
type WebhookUpdate {
|
||||
errors: [Error!]
|
||||
webhook: Webhook
|
||||
|
|
|
@ -13,10 +13,16 @@ import TableHead from "@saleor/components/TableHead";
|
|||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { translateBoolean } from "@saleor/intl";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { AttributeListUrlSortField } from "@saleor/attributes/urls";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
|
||||
|
||||
export interface AttributeListProps extends ListProps, ListActions {
|
||||
export interface AttributeListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<AttributeListUrlSortField> {
|
||||
attributes: AttributeList_attributes_edges_node[];
|
||||
}
|
||||
|
||||
|
@ -24,19 +30,19 @@ const useStyles = makeStyles(
|
|||
theme => ({
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
colFaceted: {
|
||||
width: 150
|
||||
width: 180
|
||||
},
|
||||
colName: {
|
||||
width: "auto"
|
||||
},
|
||||
colSearchable: {
|
||||
width: 150
|
||||
width: 180
|
||||
},
|
||||
colSlug: {
|
||||
width: 200
|
||||
},
|
||||
colVisible: {
|
||||
width: 150
|
||||
width: 180
|
||||
}
|
||||
},
|
||||
colFaceted: {
|
||||
|
@ -70,9 +76,11 @@ const AttributeList: React.FC<AttributeListProps> = ({
|
|||
onRowClick,
|
||||
pageInfo,
|
||||
selected,
|
||||
sort,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
toolbar,
|
||||
onSort
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
@ -87,33 +95,77 @@ const AttributeList: React.FC<AttributeListProps> = ({
|
|||
toggleAll={toggleAll}
|
||||
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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
className={classes.colName}
|
||||
direction={
|
||||
sort.sort === AttributeListUrlSortField.name
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(AttributeListUrlSortField.name)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Default Label"
|
||||
description="attribute's label'"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colVisible}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
className={classes.colVisible}
|
||||
direction={
|
||||
sort.sort === AttributeListUrlSortField.visible
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
textAlign="center"
|
||||
onClick={() => onSort(AttributeListUrlSortField.visible)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Visible"
|
||||
description="attribute is visible"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSearchable}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
className={classes.colSearchable}
|
||||
direction={
|
||||
sort.sort === AttributeListUrlSortField.searchable
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
textAlign="center"
|
||||
onClick={() => onSort(AttributeListUrlSortField.searchable)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Searchable"
|
||||
description="attribute can be searched in dashboard"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colFaceted}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
className={classes.colFaceted}
|
||||
direction={
|
||||
sort.sort === AttributeListUrlSortField.useInFacetedSearch
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
textAlign="center"
|
||||
onClick={() => onSort(AttributeListUrlSortField.useInFacetedSearch)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Use in faceted search"
|
||||
description="attribute can be searched in storefront"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
|
|
@ -6,13 +6,15 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import SearchBar from "@saleor/components/SearchBar";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { AttributeListUrlSortField } from "@saleor/attributes/urls";
|
||||
import Container from "../../../components/Container";
|
||||
import PageHeader from "../../../components/PageHeader";
|
||||
import {
|
||||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps
|
||||
TabPageProps,
|
||||
SortPage
|
||||
} from "../../../types";
|
||||
import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
|
||||
import AttributeList from "../AttributeList/AttributeList";
|
||||
|
@ -21,6 +23,7 @@ export interface AttributeListPageProps
|
|||
extends PageListProps,
|
||||
ListActions,
|
||||
SearchPageProps,
|
||||
SortPage<AttributeListUrlSortField>,
|
||||
TabPageProps {
|
||||
attributes: AttributeList_attributes_edges_node[];
|
||||
onBack: () => void;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
|||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { useIntl } from "react-intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
attributeAddPath,
|
||||
|
@ -11,7 +12,8 @@ import {
|
|||
attributeListPath,
|
||||
AttributeListUrlQueryParams,
|
||||
attributePath,
|
||||
AttributeUrlQueryParams
|
||||
AttributeUrlQueryParams,
|
||||
AttributeListUrlSortField
|
||||
} from "./urls";
|
||||
import AttributeCreateComponent from "./views/AttributeCreate";
|
||||
import AttributeDetailsComponent from "./views/AttributeDetails";
|
||||
|
@ -19,7 +21,11 @@ import AttributeListComponent from "./views/AttributeList";
|
|||
|
||||
const AttributeList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: AttributeListUrlQueryParams = qs;
|
||||
const params: AttributeListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
AttributeListUrlSortField
|
||||
);
|
||||
|
||||
return <AttributeListComponent params={params} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { pageInfoFragment, TypedQuery } from "../queries";
|
||||
import {
|
||||
AttributeDetails,
|
||||
|
@ -59,6 +60,7 @@ const attributeList = gql`
|
|||
$after: String
|
||||
$first: Int
|
||||
$last: Int
|
||||
$sort: AttributeSortingInput
|
||||
) {
|
||||
attributes(
|
||||
filter: $filter
|
||||
|
@ -68,6 +70,7 @@ const attributeList = gql`
|
|||
after: $after
|
||||
first: $first
|
||||
last: $last
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
@ -85,7 +88,7 @@ const attributeList = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeListQuery = TypedQuery<
|
||||
export const useAttributeListQuery = makeQuery<
|
||||
AttributeList,
|
||||
AttributeListVariables
|
||||
>(attributeList);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { AttributeFilterInput } from "./../../types/globalTypes";
|
||||
import { AttributeFilterInput, AttributeSortingInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: AttributeList
|
||||
|
@ -57,4 +57,5 @@ export interface AttributeListVariables {
|
|||
after?: string | null;
|
||||
first?: number | null;
|
||||
last?: number | null;
|
||||
sort?: AttributeSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
Filters,
|
||||
Pagination,
|
||||
SingleAction,
|
||||
Sort,
|
||||
TabActionDialog
|
||||
} from "../types";
|
||||
|
||||
|
@ -18,8 +19,17 @@ export enum AttributeListUrlFiltersEnum {
|
|||
}
|
||||
export type AttributeListUrlFilters = Filters<AttributeListUrlFiltersEnum>;
|
||||
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 &
|
||||
AttributeListUrlFilters &
|
||||
AttributeListUrlSort &
|
||||
BulkAction &
|
||||
Dialog<AttributeListUrlDialog> &
|
||||
Pagination;
|
||||
|
|
|
@ -21,13 +21,15 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
|||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import { PAGINATE_BY } from "../../../config";
|
||||
import useBulkActions from "../../../hooks/useBulkActions";
|
||||
import { maybe } from "../../../misc";
|
||||
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
|
||||
import AttributeListPage from "../../components/AttributeListPage";
|
||||
import { AttributeBulkDeleteMutation } from "../../mutations";
|
||||
import { AttributeListQuery } from "../../queries";
|
||||
import { useAttributeListQuery } from "../../queries";
|
||||
import { AttributeBulkDelete } from "../../types/AttributeBulkDelete";
|
||||
import {
|
||||
attributeAddUrl,
|
||||
|
@ -37,6 +39,7 @@ import {
|
|||
AttributeListUrlQueryParams,
|
||||
attributeUrl
|
||||
} from "../../urls";
|
||||
import { getSortQueryVariables } from "./sort";
|
||||
|
||||
interface AttributeListProps {
|
||||
params: AttributeListUrlQueryParams;
|
||||
|
@ -51,6 +54,19 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
const { data, loading, refetch } = useAttributeListQuery({
|
||||
variables: queryVariables
|
||||
});
|
||||
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
|
@ -111,105 +127,93 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
handleTabChange(tabs.length + 1);
|
||||
};
|
||||
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
}),
|
||||
[params]
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.attributes.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleBulkDelete = (data: AttributeBulkDelete) => {
|
||||
if (data.attributeBulkDelete.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Attributes successfully delete",
|
||||
description: "deleted multiple attributes"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSort = createSortHandler(navigate, attributeListUrl, params);
|
||||
|
||||
return (
|
||||
<AttributeListQuery variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.attributes.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleBulkDelete = (data: AttributeBulkDelete) => {
|
||||
if (data.attributeBulkDelete.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Attributes successfully delete",
|
||||
description: "deleted multiple attributes"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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 onCompleted={handleBulkDelete}>
|
||||
{(attributeBulkDelete, attributeBulkDeleteOpts) => (
|
||||
<>
|
||||
<AttributeListPage
|
||||
attributes={maybe(() =>
|
||||
data.attributes.edges.map(edge => edge.node)
|
||||
)}
|
||||
</AttributeBulkDeleteMutation>
|
||||
);
|
||||
}}
|
||||
</AttributeListQuery>
|
||||
currentTab={currentTab}
|
||||
disabled={loading || attributeBulkDeleteOpts.loading}
|
||||
initialSearch={params.query || ""}
|
||||
isChecked={isSelected}
|
||||
onAdd={() => navigate(attributeAddUrl())}
|
||||
onAll={() => navigate(attributeListUrl())}
|
||||
onBack={() => navigate(configurationMenuUrl)}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onRowClick={id => () => navigate(attributeUrl(id))}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onSort={handleSort}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
pageInfo={pageInfo}
|
||||
selected={listElements.length}
|
||||
sort={getSortParams(params)}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={
|
||||
<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";
|
||||
|
|
26
src/attributes/views/AttributeList/sort.ts
Normal file
26
src/attributes/views/AttributeList/sort.ts
Normal 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
|
||||
);
|
|
@ -13,7 +13,10 @@ import Skeleton from "@saleor/components/Skeleton";
|
|||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { CategoryListUrlSortField } from "@saleor/categories/urls";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
|
@ -44,7 +47,10 @@ const useStyles = makeStyles(
|
|||
{ name: "CategoryList" }
|
||||
);
|
||||
|
||||
interface CategoryListProps extends ListProps, ListActions {
|
||||
interface CategoryListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<CategoryListUrlSortField> {
|
||||
categories?: CategoryFragment[];
|
||||
isRoot: boolean;
|
||||
onAdd?();
|
||||
|
@ -57,6 +63,7 @@ const CategoryList: React.FC<CategoryListProps> = props => {
|
|||
categories,
|
||||
disabled,
|
||||
settings,
|
||||
sort,
|
||||
pageInfo,
|
||||
isChecked,
|
||||
isRoot,
|
||||
|
@ -67,7 +74,8 @@ const CategoryList: React.FC<CategoryListProps> = props => {
|
|||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick
|
||||
onRowClick,
|
||||
onSort
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
@ -82,21 +90,53 @@ const CategoryList: React.FC<CategoryListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSubcategories}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
isRoot && sort.sort === CategoryListUrlSortField.subcategoryCount
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
className={classes.colSubcategories}
|
||||
disableClick={!isRoot}
|
||||
onClick={() =>
|
||||
isRoot && onSort(CategoryListUrlSortField.subcategoryCount)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Subcategories"
|
||||
description="number of subcategories"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
isRoot && sort.sort === CategoryListUrlSortField.productCount
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
className={classes.colProducts}
|
||||
disableClick={!isRoot}
|
||||
onClick={() =>
|
||||
isRoot && onSort(CategoryListUrlSortField.productCount)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="No. of Products"
|
||||
description="number of products"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
|
|
@ -12,14 +12,17 @@ import {
|
|||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps
|
||||
TabPageProps,
|
||||
SortPage
|
||||
} from "@saleor/types";
|
||||
import { CategoryListUrlSortField } from "@saleor/categories/urls";
|
||||
import CategoryList from "../CategoryList";
|
||||
|
||||
export interface CategoryTableProps
|
||||
extends PageListProps,
|
||||
ListActions,
|
||||
SearchPageProps,
|
||||
SortPage<CategoryListUrlSortField>,
|
||||
TabPageProps {
|
||||
categories: CategoryFragment[];
|
||||
}
|
||||
|
@ -46,7 +49,8 @@ export const CategoryListPage: React.FC<CategoryTableProps> = ({
|
|||
onTabChange,
|
||||
onTabDelete,
|
||||
onTabSave,
|
||||
onUpdateListSettings
|
||||
onUpdateListSettings,
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
|
@ -94,6 +98,7 @@ export const CategoryListPage: React.FC<CategoryTableProps> = ({
|
|||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onRowClick}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
{...listProps}
|
||||
/>
|
||||
</Card>
|
||||
</Container>
|
||||
|
|
|
@ -205,12 +205,14 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
|||
isRoot={false}
|
||||
pageInfo={pageInfo}
|
||||
selected={selected}
|
||||
sort={undefined}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={subcategoryListToolbar}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onCategoryClick}
|
||||
onSort={() => undefined}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
|
|
|
@ -4,13 +4,15 @@ import { useIntl } from "react-intl";
|
|||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
categoryAddPath,
|
||||
categoryListPath,
|
||||
CategoryListUrlQueryParams,
|
||||
categoryPath,
|
||||
CategoryUrlQueryParams
|
||||
CategoryUrlQueryParams,
|
||||
CategoryListUrlSortField
|
||||
} from "./urls";
|
||||
import { CategoryCreateView } from "./views/CategoryCreate";
|
||||
import CategoryDetailsView, { getActiveTab } from "./views/CategoryDetails";
|
||||
|
@ -19,14 +21,15 @@ import CategoryListComponent from "./views/CategoryList";
|
|||
interface CategoryDetailsRouteParams {
|
||||
id: string;
|
||||
}
|
||||
const CategoryDetails: React.FC<
|
||||
RouteComponentProps<CategoryDetailsRouteParams>
|
||||
> = ({ location, match }) => {
|
||||
const CategoryDetails: React.FC<RouteComponentProps<
|
||||
CategoryDetailsRouteParams
|
||||
>> = ({ location, match }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: CategoryUrlQueryParams = {
|
||||
...qs,
|
||||
activeTab: getActiveTab(qs.activeTab)
|
||||
};
|
||||
|
||||
return (
|
||||
<CategoryDetailsView
|
||||
id={decodeURIComponent(match.params.id)}
|
||||
|
@ -38,9 +41,9 @@ const CategoryDetails: React.FC<
|
|||
interface CategoryCreateRouteParams {
|
||||
id: string;
|
||||
}
|
||||
const CategoryCreate: React.FC<
|
||||
RouteComponentProps<CategoryCreateRouteParams>
|
||||
> = ({ match }) => (
|
||||
const CategoryCreate: React.FC<RouteComponentProps<
|
||||
CategoryCreateRouteParams
|
||||
>> = ({ match }) => (
|
||||
<CategoryCreateView
|
||||
parentId={match.params.id ? decodeURIComponent(match.params.id) : undefined}
|
||||
/>
|
||||
|
@ -48,7 +51,10 @@ const CategoryCreate: React.FC<
|
|||
|
||||
const CategoryList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: CategoryListUrlQueryParams = qs;
|
||||
const params: CategoryListUrlQueryParams = {
|
||||
...asSortParams(qs, CategoryListUrlSortField)
|
||||
};
|
||||
|
||||
return <CategoryListComponent params={params} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ export const rootCategories = gql`
|
|||
$last: Int
|
||||
$before: String
|
||||
$filter: CategoryFilterInput
|
||||
$sort: CategorySortingInput
|
||||
) {
|
||||
categories(
|
||||
level: 0
|
||||
|
@ -54,6 +55,7 @@ export const rootCategories = gql`
|
|||
last: $last
|
||||
before: $before
|
||||
filter: $filter
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { CategoryFilterInput } from "./../../types/globalTypes";
|
||||
import { CategoryFilterInput, CategorySortingInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: RootCategories
|
||||
|
@ -55,4 +55,5 @@ export interface RootCategoriesVariables {
|
|||
last?: number | null;
|
||||
before?: string | null;
|
||||
filter?: CategoryFilterInput | null;
|
||||
sort?: CategorySortingInput | null;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import {
|
|||
Dialog,
|
||||
Filters,
|
||||
Pagination,
|
||||
TabActionDialog
|
||||
TabActionDialog,
|
||||
Sort
|
||||
} from "../types";
|
||||
import { CategoryPageTab } from "./components/CategoryUpdatePage";
|
||||
|
||||
|
@ -19,9 +20,16 @@ export enum CategoryListUrlFiltersEnum {
|
|||
}
|
||||
export type CategoryListUrlFilters = Filters<CategoryListUrlFiltersEnum>;
|
||||
export type CategoryListUrlDialog = "delete" | TabActionDialog;
|
||||
export enum CategoryListUrlSortField {
|
||||
name = "name",
|
||||
productCount = "products",
|
||||
subcategoryCount = "subcategories"
|
||||
}
|
||||
export type CategoryListUrlSort = Sort<CategoryListUrlSortField>;
|
||||
export type CategoryListUrlQueryParams = ActiveTab &
|
||||
BulkAction &
|
||||
CategoryListUrlFilters &
|
||||
CategoryListUrlSort &
|
||||
Dialog<CategoryListUrlDialog> &
|
||||
Pagination;
|
||||
export const categoryListUrl = (params?: CategoryListUrlQueryParams) =>
|
||||
|
|
|
@ -17,6 +17,8 @@ import usePaginator, {
|
|||
} from "@saleor/hooks/usePaginator";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage";
|
||||
import { useCategoryBulkDeleteMutation } from "../../mutations";
|
||||
import { useRootCategoriesQuery } from "../../queries";
|
||||
|
@ -37,6 +39,7 @@ import {
|
|||
getFilterVariables,
|
||||
saveFilterTab
|
||||
} from "./filter";
|
||||
import { getSortQueryVariables } from "./sort";
|
||||
|
||||
interface CategoryListProps {
|
||||
params: CategoryListUrlQueryParams;
|
||||
|
@ -57,7 +60,8 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
|
|||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
|
@ -148,6 +152,8 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
|
|||
onCompleted: handleCategoryBulkDelete
|
||||
});
|
||||
|
||||
const handleSort = createSortHandler(navigate, categoryListUrl, params);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CategoryListPage
|
||||
|
@ -164,8 +170,10 @@ export const CategoryList: React.FC<CategoryListProps> = ({ params }) => {
|
|||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
settings={settings}
|
||||
sort={getSortParams(params)}
|
||||
onAdd={() => navigate(categoryAddUrl())}
|
||||
onRowClick={id => () => navigate(categoryUrl(id))}
|
||||
onSort={handleSort}
|
||||
disabled={loading}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
|
|
22
src/categories/views/CategoryList/sort.ts
Normal file
22
src/categories/views/CategoryList/sort.ts
Normal 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
|
||||
);
|
|
@ -13,7 +13,10 @@ import StatusLabel from "@saleor/components/StatusLabel";
|
|||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { CollectionListUrlSortField } from "@saleor/collections/urls";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -41,7 +44,10 @@ const useStyles = makeStyles(
|
|||
{ name: "CollectionList" }
|
||||
);
|
||||
|
||||
interface CollectionListProps extends ListProps, ListActions {
|
||||
interface CollectionListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<CollectionListUrlSortField> {
|
||||
collections: CollectionList_collections_edges_node[];
|
||||
}
|
||||
|
||||
|
@ -52,10 +58,12 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
|||
collections,
|
||||
disabled,
|
||||
settings,
|
||||
sort,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
onSort,
|
||||
pageInfo,
|
||||
isChecked,
|
||||
selected,
|
||||
|
@ -77,18 +85,43 @@ const CollectionList: React.FC<CollectionListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage defaultMessage="Category Name" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colProducts}>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === CollectionListUrlSortField.name
|
||||
? 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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAvailability}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === CollectionListUrlSortField.available
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(CollectionListUrlSortField.available)}
|
||||
className={classes.colAvailability}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Availability"
|
||||
description="collection availability"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
|
|
@ -11,8 +11,10 @@ import {
|
|||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps
|
||||
TabPageProps,
|
||||
SortPage
|
||||
} from "@saleor/types";
|
||||
import { CollectionListUrlSortField } from "@saleor/collections/urls";
|
||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||
import CollectionList from "../CollectionList/CollectionList";
|
||||
|
||||
|
@ -20,6 +22,7 @@ export interface CollectionListPageProps
|
|||
extends PageListProps,
|
||||
ListActions,
|
||||
SearchPageProps,
|
||||
SortPage<CollectionListUrlSortField>,
|
||||
TabPageProps {
|
||||
collections: CollectionList_collections_edges_node[];
|
||||
}
|
||||
|
|
|
@ -4,13 +4,15 @@ import { useIntl } from "react-intl";
|
|||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
collectionAddPath,
|
||||
collectionListPath,
|
||||
CollectionListUrlQueryParams,
|
||||
collectionPath,
|
||||
CollectionUrlQueryParams
|
||||
CollectionUrlQueryParams,
|
||||
CollectionListUrlSortField
|
||||
} from "./urls";
|
||||
import CollectionCreate from "./views/CollectionCreate";
|
||||
import CollectionDetailsView from "./views/CollectionDetails";
|
||||
|
@ -18,16 +20,19 @@ import CollectionListView from "./views/CollectionList";
|
|||
|
||||
const CollectionList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: CollectionListUrlQueryParams = qs;
|
||||
const params: CollectionListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
CollectionListUrlSortField
|
||||
);
|
||||
return <CollectionListView params={params} />;
|
||||
};
|
||||
|
||||
interface CollectionDetailsRouteProps {
|
||||
id: string;
|
||||
}
|
||||
const CollectionDetails: React.FC<
|
||||
RouteComponentProps<CollectionDetailsRouteProps>
|
||||
> = ({ location, match }) => {
|
||||
const CollectionDetails: React.FC<RouteComponentProps<
|
||||
CollectionDetailsRouteProps
|
||||
>> = ({ location, match }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: CollectionUrlQueryParams = qs;
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { TypedQuery } from "../queries";
|
||||
import {
|
||||
CollectionDetails,
|
||||
|
@ -61,6 +62,7 @@ export const collectionList = gql`
|
|||
$last: Int
|
||||
$before: String
|
||||
$filter: CollectionFilterInput
|
||||
$sort: CollectionSortingInput
|
||||
) {
|
||||
collections(
|
||||
first: $first
|
||||
|
@ -68,6 +70,7 @@ export const collectionList = gql`
|
|||
before: $before
|
||||
last: $last
|
||||
filter: $filter
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
@ -86,7 +89,7 @@ export const collectionList = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionListQuery = TypedQuery<
|
||||
export const useCollectionListQuery = makeQuery<
|
||||
CollectionList,
|
||||
CollectionListVariables
|
||||
>(collectionList);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { CollectionFilterInput } from "./../../types/globalTypes";
|
||||
import { CollectionFilterInput, CollectionSortingInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: CollectionList
|
||||
|
@ -50,4 +50,5 @@ export interface CollectionListVariables {
|
|||
last?: number | null;
|
||||
before?: string | null;
|
||||
filter?: CollectionFilterInput | null;
|
||||
sort?: CollectionSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import {
|
|||
Dialog,
|
||||
Filters,
|
||||
Pagination,
|
||||
TabActionDialog
|
||||
TabActionDialog,
|
||||
Sort
|
||||
} from "../types";
|
||||
|
||||
const collectionSectionUrl = "/collections/";
|
||||
|
@ -22,9 +23,16 @@ export type CollectionListUrlDialog =
|
|||
| "unpublish"
|
||||
| "remove"
|
||||
| TabActionDialog;
|
||||
export enum CollectionListUrlSortField {
|
||||
name = "name",
|
||||
available = "available",
|
||||
productCount = "products"
|
||||
}
|
||||
export type CollectionListUrlSort = Sort<CollectionListUrlSortField>;
|
||||
export type CollectionListUrlQueryParams = ActiveTab &
|
||||
BulkAction &
|
||||
CollectionListUrlFilters &
|
||||
CollectionListUrlSort &
|
||||
Dialog<CollectionListUrlDialog> &
|
||||
Pagination;
|
||||
export const collectionListUrl = (params?: CollectionListUrlQueryParams) =>
|
||||
|
|
|
@ -20,12 +20,14 @@ import usePaginator, {
|
|||
import { commonMessages } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
|
||||
import {
|
||||
TypedCollectionBulkDelete,
|
||||
TypedCollectionBulkPublish
|
||||
} from "../../mutations";
|
||||
import { TypedCollectionListQuery } from "../../queries";
|
||||
import { useCollectionListQuery } from "../../queries";
|
||||
import { CollectionBulkDelete } from "../../types/CollectionBulkDelete";
|
||||
import { CollectionBulkPublish } from "../../types/CollectionBulkPublish";
|
||||
import {
|
||||
|
@ -44,6 +46,7 @@ import {
|
|||
getFilterVariables,
|
||||
saveFilterTab
|
||||
} from "./filter";
|
||||
import { getSortQueryVariables } from "./sort";
|
||||
|
||||
interface CollectionListProps {
|
||||
params: CollectionListUrlQueryParams;
|
||||
|
@ -61,6 +64,20 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
const { data, loading, refetch } = useCollectionListQuery({
|
||||
displayLoader: true,
|
||||
variables: queryVariables
|
||||
});
|
||||
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
|
@ -121,226 +138,213 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
handleTabChange(tabs.length + 1);
|
||||
};
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
}),
|
||||
[params]
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.collections.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleCollectionBulkDelete = (data: CollectionBulkDelete) => {
|
||||
if (data.collectionBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCollectionBulkPublish = (data: CollectionBulkPublish) => {
|
||||
if (data.collectionBulkPublish.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSort = createSortHandler(navigate, collectionListUrl, params);
|
||||
|
||||
return (
|
||||
<TypedCollectionListQuery displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.collections.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleCollectionBulkDelete = (data: CollectionBulkDelete) => {
|
||||
if (data.collectionBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCollectionBulkPublish = (data: CollectionBulkPublish) => {
|
||||
if (data.collectionBulkPublish.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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, "...")}
|
||||
/>
|
||||
</>
|
||||
<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)
|
||||
)}
|
||||
</TypedCollectionBulkPublish>
|
||||
)}
|
||||
</TypedCollectionBulkDelete>
|
||||
);
|
||||
}}
|
||||
</TypedCollectionListQuery>
|
||||
settings={settings}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onSort={handleSort}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
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;
|
||||
|
|
22
src/collections/views/CollectionList/sort.ts
Normal file
22
src/collections/views/CollectionList/sort.ts
Normal 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
|
||||
);
|
|
@ -11,7 +11,7 @@ import { sectionNames } from "@saleor/intl";
|
|||
import { menuListUrl } from "@saleor/navigation/urls";
|
||||
import { orderDraftListUrl, orderListUrl } from "@saleor/orders/urls";
|
||||
import { pageListUrl } from "@saleor/pages/urls";
|
||||
import { pluginsListUrl } from "@saleor/plugins/urls";
|
||||
import { pluginListUrl } from "@saleor/plugins/urls";
|
||||
import { productListUrl } from "@saleor/products/urls";
|
||||
import { productTypeListUrl } from "@saleor/productTypes/urls";
|
||||
import { serviceListUrl } from "@saleor/services/urls";
|
||||
|
@ -71,7 +71,7 @@ function searchInViews(
|
|||
},
|
||||
{
|
||||
label: intl.formatMessage(sectionNames.plugins),
|
||||
url: pluginsListUrl()
|
||||
url: pluginListUrl()
|
||||
},
|
||||
{
|
||||
label: intl.formatMessage(sectionNames.productTypes),
|
||||
|
|
|
@ -16,6 +16,11 @@ const useStyles = makeStyles(
|
|||
arrowUp: {
|
||||
transform: "rotate(180deg)"
|
||||
},
|
||||
disableClick: {
|
||||
"&&": {
|
||||
cursor: "unset"
|
||||
}
|
||||
},
|
||||
label: {
|
||||
alignSelf: "center",
|
||||
display: "inline-block"
|
||||
|
@ -48,6 +53,7 @@ export type TableCellHeaderArrowPosition = "left" | "right";
|
|||
export interface TableCellHeaderProps extends TableCellProps {
|
||||
arrowPosition?: TableCellHeaderArrowPosition;
|
||||
direction?: TableCellHeaderArrowDirection;
|
||||
disableClick?: boolean;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
}
|
||||
|
||||
|
@ -58,12 +64,18 @@ const TableCellHeader: React.FC<TableCellHeaderProps> = props => {
|
|||
children,
|
||||
className,
|
||||
direction,
|
||||
disableClick,
|
||||
textAlign,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<TableCell {...rest} className={classNames(classes.root, className)}>
|
||||
<TableCell
|
||||
{...rest}
|
||||
className={classNames(classes.root, className, {
|
||||
[classes.disableClick]: disableClick
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.labelContainer, {
|
||||
[classes.labelContainerActive]: !!direction && !!arrowPosition,
|
||||
|
|
|
@ -20,7 +20,7 @@ import { sectionNames } from "@saleor/intl";
|
|||
import { maybe } from "@saleor/misc";
|
||||
import { menuListUrl } from "@saleor/navigation/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 { serviceListUrl } from "@saleor/services/urls";
|
||||
import { shippingZonesListUrl } from "@saleor/shipping/urls";
|
||||
|
@ -153,7 +153,7 @@ export function createConfigurationMenu(intl: IntlShape): MenuSection[] {
|
|||
),
|
||||
permission: PermissionEnum.MANAGE_PLUGINS,
|
||||
title: intl.formatMessage(sectionNames.plugins),
|
||||
url: pluginsListUrl()
|
||||
url: pluginListUrl()
|
||||
},
|
||||
{
|
||||
description: intl.formatMessage({
|
||||
|
|
|
@ -12,7 +12,10 @@ import Skeleton from "@saleor/components/Skeleton";
|
|||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
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";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -38,7 +41,10 @@ const useStyles = makeStyles(
|
|||
{ name: "CustomerList" }
|
||||
);
|
||||
|
||||
export interface CustomerListProps extends ListProps, ListActions {
|
||||
export interface CustomerListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<CustomerListUrlSortField> {
|
||||
customers: ListCustomers_customers_edges_node[];
|
||||
}
|
||||
|
||||
|
@ -54,10 +60,12 @@ const CustomerList: React.FC<CustomerListProps> = props => {
|
|||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
onSort,
|
||||
toolbar,
|
||||
toggle,
|
||||
toggleAll,
|
||||
selected,
|
||||
sort,
|
||||
isChecked
|
||||
} = props;
|
||||
|
||||
|
@ -73,15 +81,41 @@ const CustomerList: React.FC<CustomerListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colEmail}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === CustomerListUrlSortField.email
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(CustomerListUrlSortField.email)}
|
||||
className={classes.colEmail}
|
||||
>
|
||||
<FormattedMessage defaultMessage="Customer Email" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colOrders}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === CustomerListUrlSortField.orders
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
textAlign="center"
|
||||
onClick={() => onSort(CustomerListUrlSortField.orders)}
|
||||
className={classes.colOrders}
|
||||
>
|
||||
<FormattedMessage defaultMessage="No. of Orders" />
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
|
|
@ -11,8 +11,10 @@ import {
|
|||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps
|
||||
TabPageProps,
|
||||
SortPage
|
||||
} from "@saleor/types";
|
||||
import { CustomerListUrlSortField } from "@saleor/customers/urls";
|
||||
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
|
||||
import CustomerList from "../CustomerList/CustomerList";
|
||||
|
||||
|
@ -20,14 +22,13 @@ export interface CustomerListPageProps
|
|||
extends PageListProps,
|
||||
ListActions,
|
||||
SearchPageProps,
|
||||
SortPage<CustomerListUrlSortField>,
|
||||
TabPageProps {
|
||||
customers: ListCustomers_customers_edges_node[];
|
||||
}
|
||||
|
||||
const CustomerListPage: React.FC<CustomerListPageProps> = ({
|
||||
currentTab,
|
||||
customers,
|
||||
disabled,
|
||||
initialSearch,
|
||||
onAdd,
|
||||
onAll,
|
||||
|
@ -43,12 +44,7 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
|
|||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.customers)}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
disabled={disabled}
|
||||
onClick={onAdd}
|
||||
>
|
||||
<Button color="primary" variant="contained" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create customer"
|
||||
description="button"
|
||||
|
@ -73,11 +69,7 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
|
|||
onTabDelete={onTabDelete}
|
||||
onTabSave={onTabSave}
|
||||
/>
|
||||
<CustomerList
|
||||
customers={customers}
|
||||
disabled={disabled}
|
||||
{...customerListProps}
|
||||
/>
|
||||
<CustomerList {...customerListProps} />
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
|
|||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
customerAddPath,
|
||||
|
@ -12,7 +13,8 @@ import {
|
|||
customerListPath,
|
||||
CustomerListUrlQueryParams,
|
||||
customerPath,
|
||||
CustomerUrlQueryParams
|
||||
CustomerUrlQueryParams,
|
||||
CustomerListUrlSortField
|
||||
} from "./urls";
|
||||
import CustomerAddressesViewComponent from "./views/CustomerAddresses";
|
||||
import CustomerCreateView from "./views/CustomerCreate";
|
||||
|
@ -21,16 +23,20 @@ import CustomerListViewComponent from "./views/CustomerList";
|
|||
|
||||
const CustomerListView: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: CustomerListUrlQueryParams = qs;
|
||||
const params: CustomerListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
CustomerListUrlSortField
|
||||
);
|
||||
|
||||
return <CustomerListViewComponent params={params} />;
|
||||
};
|
||||
|
||||
interface CustomerDetailsRouteParams {
|
||||
id: string;
|
||||
}
|
||||
const CustomerDetailsView: React.FC<
|
||||
RouteComponentProps<CustomerDetailsRouteParams>
|
||||
> = ({ location, match }) => {
|
||||
const CustomerDetailsView: React.FC<RouteComponentProps<
|
||||
CustomerDetailsRouteParams
|
||||
>> = ({ location, match }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: CustomerUrlQueryParams = qs;
|
||||
|
||||
|
@ -45,9 +51,9 @@ const CustomerDetailsView: React.FC<
|
|||
interface CustomerAddressesRouteParams {
|
||||
id: string;
|
||||
}
|
||||
const CustomerAddressesView: React.FC<
|
||||
RouteComponentProps<CustomerAddressesRouteParams>
|
||||
> = ({ match }) => {
|
||||
const CustomerAddressesView: React.FC<RouteComponentProps<
|
||||
CustomerAddressesRouteParams
|
||||
>> = ({ match }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: CustomerAddressesUrlQueryParams = qs;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { fragmentAddress } from "../orders/queries";
|
||||
import { TypedQuery } from "../queries";
|
||||
import {
|
||||
|
@ -65,6 +66,7 @@ const customerList = gql`
|
|||
$first: Int
|
||||
$last: Int
|
||||
$filter: CustomerFilterInput
|
||||
$sort: UserSortingInput
|
||||
) {
|
||||
customers(
|
||||
after: $after
|
||||
|
@ -72,6 +74,7 @@ const customerList = gql`
|
|||
first: $first
|
||||
last: $last
|
||||
filter: $filter
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
@ -90,7 +93,7 @@ const customerList = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCustomerListQuery = TypedQuery<
|
||||
export const useCustomerListQuery = makeQuery<
|
||||
ListCustomers,
|
||||
ListCustomersVariables
|
||||
>(customerList);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// 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
|
||||
|
@ -51,4 +51,5 @@ export interface ListCustomersVariables {
|
|||
first?: number | null;
|
||||
last?: number | null;
|
||||
filter?: CustomerFilterInput | null;
|
||||
sort?: UserSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
Filters,
|
||||
Pagination,
|
||||
SingleAction,
|
||||
TabActionDialog
|
||||
TabActionDialog,
|
||||
Sort
|
||||
} from "../types";
|
||||
|
||||
export const customerSection = "/customers/";
|
||||
|
@ -19,9 +20,16 @@ export enum CustomerListUrlFiltersEnum {
|
|||
}
|
||||
export type CustomerListUrlFilters = Filters<CustomerListUrlFiltersEnum>;
|
||||
export type CustomerListUrlDialog = "remove" | TabActionDialog;
|
||||
export enum CustomerListUrlSortField {
|
||||
name = "name",
|
||||
email = "email",
|
||||
orders = "orders"
|
||||
}
|
||||
export type CustomerListUrlSort = Sort<CustomerListUrlSortField>;
|
||||
export type CustomerListUrlQueryParams = ActiveTab &
|
||||
BulkAction &
|
||||
CustomerListUrlFilters &
|
||||
CustomerListUrlSort &
|
||||
Dialog<CustomerListUrlDialog> &
|
||||
Pagination;
|
||||
export const customerListUrl = (params?: CustomerListUrlQueryParams) =>
|
||||
|
|
|
@ -19,9 +19,11 @@ import usePaginator, {
|
|||
import { commonMessages } from "@saleor/intl";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import CustomerListPage from "../../components/CustomerListPage";
|
||||
import { TypedBulkRemoveCustomers } from "../../mutations";
|
||||
import { TypedCustomerListQuery } from "../../queries";
|
||||
import { useCustomerListQuery } from "../../queries";
|
||||
import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers";
|
||||
import {
|
||||
customerAddUrl,
|
||||
|
@ -39,6 +41,7 @@ import {
|
|||
getFilterVariables,
|
||||
saveFilterTab
|
||||
} from "./filter";
|
||||
import { getSortQueryVariables } from "./sort";
|
||||
|
||||
interface CustomerListProps {
|
||||
params: CustomerListUrlQueryParams;
|
||||
|
@ -56,6 +59,20 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
|||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
const { data, loading, refetch } = useCustomerListQuery({
|
||||
displayLoader: true,
|
||||
variables: queryVariables
|
||||
});
|
||||
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
|
@ -116,130 +133,116 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
|
|||
handleTabChange(tabs.length + 1);
|
||||
};
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
}),
|
||||
[params]
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.customers.pageInfo),
|
||||
paginationState,
|
||||
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 (
|
||||
<TypedCustomerListQuery displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.customers.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleBulkCustomerDelete = (data: BulkRemoveCustomers) => {
|
||||
if (data.customerBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TypedBulkRemoveCustomers onCompleted={handleBulkCustomerDelete}>
|
||||
{(bulkRemoveCustomers, bulkRemoveCustomersOpts) => (
|
||||
<>
|
||||
<CustomerListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(customerListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
customers={maybe(() =>
|
||||
data.customers.edges.map(edge => edge.node)
|
||||
)}
|
||||
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
|
||||
}
|
||||
<TypedBulkRemoveCustomers onCompleted={handleBulkCustomerDelete}>
|
||||
{(bulkRemoveCustomers, bulkRemoveCustomersOpts) => (
|
||||
<>
|
||||
<CustomerListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(customerListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
customers={maybe(() => data.customers.edges.map(edge => edge.node))}
|
||||
settings={settings}
|
||||
disabled={loading}
|
||||
pageInfo={pageInfo}
|
||||
onAdd={() => navigate(customerAddUrl)}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onRowClick={id => () => navigate(customerUrl(id))}
|
||||
onSort={handleSort}
|
||||
toolbar={
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
customerListUrl({
|
||||
action: "remove",
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
variant="delete"
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Customers",
|
||||
description: "dialog header"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter,plural,one{this customer} other{{displayQuantity} customers}}?"
|
||||
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, "...")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TypedBulkRemoveCustomers>
|
||||
);
|
||||
}}
|
||||
</TypedCustomerListQuery>
|
||||
)
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
sort={getSortParams(params)}
|
||||
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",
|
||||
description: "dialog header"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter,plural,one{this customer} other{{displayQuantity} customers}}?"
|
||||
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, "...")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TypedBulkRemoveCustomers>
|
||||
);
|
||||
};
|
||||
export default CustomerList;
|
||||
|
|
22
src/customers/views/CustomerList/sort.ts
Normal file
22
src/customers/views/CustomerList/sort.ts
Normal 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
|
||||
);
|
|
@ -15,11 +15,17 @@ import Skeleton from "@saleor/components/Skeleton";
|
|||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { 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";
|
||||
|
||||
export interface SaleListProps extends ListProps, ListActions {
|
||||
export interface SaleListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<SaleListUrlSortField> {
|
||||
defaultCurrency: string;
|
||||
sales: SaleList_sales_edges_node[];
|
||||
}
|
||||
|
@ -68,10 +74,12 @@ const SaleList: React.FC<SaleListProps> = props => {
|
|||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
onSort,
|
||||
pageInfo,
|
||||
sales,
|
||||
isChecked,
|
||||
selected,
|
||||
sort,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
|
@ -89,21 +97,57 @@ const SaleList: React.FC<SaleListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colStart}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === SaleListUrlSortField.startDate
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
textAlign="right"
|
||||
onClick={() => onSort(SaleListUrlSortField.startDate)}
|
||||
className={classes.colStart}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Starts"
|
||||
description="sale start date"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colEnd}>
|
||||
</TableCellHeader>
|
||||
<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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colValue}>
|
||||
</TableCellHeader>
|
||||
<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" />
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
|
|
@ -11,8 +11,10 @@ import {
|
|||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps
|
||||
TabPageProps,
|
||||
SortPage
|
||||
} from "@saleor/types";
|
||||
import { SaleListUrlSortField } from "@saleor/discounts/urls";
|
||||
import { SaleList_sales_edges_node } from "../../types/SaleList";
|
||||
import SaleList from "../SaleList";
|
||||
|
||||
|
@ -20,6 +22,7 @@ export interface SaleListPageProps
|
|||
extends PageListProps,
|
||||
ListActions,
|
||||
SearchPageProps,
|
||||
SortPage<SaleListUrlSortField>,
|
||||
TabPageProps {
|
||||
defaultCurrency: string;
|
||||
sales: SaleList_sales_edges_node[];
|
||||
|
|
|
@ -15,11 +15,17 @@ import Skeleton from "@saleor/components/Skeleton";
|
|||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { 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";
|
||||
|
||||
export interface VoucherListProps extends ListProps, ListActions {
|
||||
export interface VoucherListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<VoucherListUrlSortField> {
|
||||
defaultCurrency: string;
|
||||
vouchers: VoucherList_vouchers_edges_node[];
|
||||
}
|
||||
|
@ -83,10 +89,12 @@ const VoucherList: React.FC<VoucherListProps> = props => {
|
|||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
onSort,
|
||||
pageInfo,
|
||||
vouchers,
|
||||
isChecked,
|
||||
selected,
|
||||
sort,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
|
@ -104,36 +112,85 @@ const VoucherList: React.FC<VoucherListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colMinSpent}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === VoucherListUrlSortField.minSpent
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(VoucherListUrlSortField.minSpent)}
|
||||
className={classes.colMinSpent}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Min. Spent"
|
||||
description="minimum amount of spent money to activate voucher"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colStart}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === VoucherListUrlSortField.startDate
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(VoucherListUrlSortField.startDate)}
|
||||
className={classes.colStart}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Starts"
|
||||
description="voucher is active from date"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colEnd}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === VoucherListUrlSortField.endDate
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(VoucherListUrlSortField.endDate)}
|
||||
className={classes.colEnd}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Ends"
|
||||
description="voucher is active until date"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colValue}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === VoucherListUrlSortField.value
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(VoucherListUrlSortField.value)}
|
||||
className={classes.colValue}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Value"
|
||||
description="voucher value"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colUses}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === VoucherListUrlSortField.limit
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(VoucherListUrlSortField.limit)}
|
||||
className={classes.colUses}
|
||||
>
|
||||
<FormattedMessage defaultMessage="Uses" description="voucher uses" />
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
|
|
@ -11,8 +11,10 @@ import {
|
|||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps
|
||||
TabPageProps,
|
||||
SortPage
|
||||
} from "@saleor/types";
|
||||
import { VoucherListUrlSortField } from "@saleor/discounts/urls";
|
||||
import { VoucherList_vouchers_edges_node } from "../../types/VoucherList";
|
||||
import VoucherList from "../VoucherList";
|
||||
|
||||
|
@ -20,6 +22,7 @@ export interface VoucherListPageProps
|
|||
extends PageListProps,
|
||||
ListActions,
|
||||
SearchPageProps,
|
||||
SortPage<VoucherListUrlSortField>,
|
||||
TabPageProps {
|
||||
defaultCurrency: string;
|
||||
vouchers: VoucherList_vouchers_edges_node[];
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
|
|||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import { saleDetailsPageTab } from "./components/SaleDetailsPage";
|
||||
import { voucherDetailsPageTab } from "./components/VoucherDetailsPage";
|
||||
|
@ -17,7 +18,9 @@ import {
|
|||
voucherListPath,
|
||||
VoucherListUrlQueryParams,
|
||||
voucherPath,
|
||||
VoucherUrlQueryParams
|
||||
VoucherUrlQueryParams,
|
||||
SaleListUrlSortField,
|
||||
VoucherListUrlSortField
|
||||
} from "./urls";
|
||||
import SaleCreateView from "./views/SaleCreate";
|
||||
import SaleDetailsViewComponent from "./views/SaleDetails";
|
||||
|
@ -28,7 +31,7 @@ import VoucherListViewComponent from "./views/VoucherList";
|
|||
|
||||
const SaleListView: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: SaleListUrlQueryParams = qs;
|
||||
const params: SaleListUrlQueryParams = asSortParams(qs, SaleListUrlSortField);
|
||||
return <SaleListViewComponent params={params} />;
|
||||
};
|
||||
|
||||
|
@ -41,6 +44,7 @@ const SaleDetailsView: React.FC<RouteComponentProps<{ id: string }>> = ({
|
|||
...qs,
|
||||
activeTab: saleDetailsPageTab(activeTab)
|
||||
};
|
||||
|
||||
return (
|
||||
<SaleDetailsViewComponent
|
||||
id={decodeURIComponent(match.params.id)}
|
||||
|
@ -51,7 +55,11 @@ const SaleDetailsView: React.FC<RouteComponentProps<{ id: string }>> = ({
|
|||
|
||||
const VoucherListView: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: VoucherListUrlQueryParams = qs;
|
||||
const params: VoucherListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
VoucherListUrlSortField,
|
||||
VoucherListUrlSortField.code
|
||||
);
|
||||
return <VoucherListViewComponent params={params} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { pageInfoFragment, TypedQuery } from "../queries";
|
||||
import { SaleDetails, SaleDetailsVariables } from "./types/SaleDetails";
|
||||
import { SaleList, SaleListVariables } from "./types/SaleList";
|
||||
|
@ -172,6 +173,7 @@ export const saleList = gql`
|
|||
$first: Int
|
||||
$last: Int
|
||||
$filter: SaleFilterInput
|
||||
$sort: SaleSortingInput
|
||||
) {
|
||||
sales(
|
||||
after: $after
|
||||
|
@ -179,6 +181,7 @@ export const saleList = gql`
|
|||
first: $first
|
||||
last: $last
|
||||
filter: $filter
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
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`
|
||||
${pageInfoFragment}
|
||||
|
@ -202,6 +207,7 @@ export const voucherList = gql`
|
|||
$first: Int
|
||||
$last: Int
|
||||
$filter: VoucherFilterInput
|
||||
$sort: VoucherSortingInput
|
||||
) {
|
||||
vouchers(
|
||||
after: $after
|
||||
|
@ -209,6 +215,7 @@ export const voucherList = gql`
|
|||
first: $first
|
||||
last: $last
|
||||
filter: $filter
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
@ -221,7 +228,7 @@ export const voucherList = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedVoucherList = TypedQuery<VoucherList, VoucherListVariables>(
|
||||
export const useVoucherListQuery = makeQuery<VoucherList, VoucherListVariables>(
|
||||
voucherList
|
||||
);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// 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
|
||||
|
@ -47,4 +47,5 @@ export interface SaleListVariables {
|
|||
first?: number | null;
|
||||
last?: number | null;
|
||||
filter?: SaleFilterInput | null;
|
||||
sort?: SaleSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// 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
|
||||
|
@ -63,4 +63,5 @@ export interface VoucherListVariables {
|
|||
first?: number | null;
|
||||
last?: number | null;
|
||||
filter?: VoucherFilterInput | null;
|
||||
sort?: VoucherSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import {
|
|||
Dialog,
|
||||
Filters,
|
||||
Pagination,
|
||||
TabActionDialog
|
||||
TabActionDialog,
|
||||
Sort
|
||||
} from "../types";
|
||||
import { SaleDetailsPageTab } from "./components/SaleDetailsPage";
|
||||
import { VoucherDetailsPageTab } from "./components/VoucherDetailsPage";
|
||||
|
@ -21,11 +22,20 @@ export enum SaleListUrlFiltersEnum {
|
|||
}
|
||||
export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum>;
|
||||
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 &
|
||||
BulkAction &
|
||||
Dialog<SaleListUrlDialog> &
|
||||
Pagination &
|
||||
SaleListUrlFilters;
|
||||
SaleListUrlFilters &
|
||||
SaleListUrlSort;
|
||||
export const saleListUrl = (params?: SaleListUrlQueryParams) =>
|
||||
saleListPath + "?" + stringifyQs(params);
|
||||
export const salePath = (id: string) => urlJoin(saleSection, id);
|
||||
|
@ -53,11 +63,22 @@ export enum VoucherListUrlFiltersEnum {
|
|||
}
|
||||
export type VoucherListUrlFilters = Filters<VoucherListUrlFiltersEnum>;
|
||||
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 &
|
||||
BulkAction &
|
||||
Dialog<VoucherListUrlDialog> &
|
||||
Pagination &
|
||||
VoucherListUrlFilters;
|
||||
VoucherListUrlFilters &
|
||||
VoucherListUrlSort;
|
||||
export const voucherListUrl = (params?: VoucherListUrlQueryParams) =>
|
||||
voucherListPath + "?" + stringifyQs(params);
|
||||
export const voucherPath = (id: string) => urlJoin(voucherSection, id);
|
||||
|
|
|
@ -21,9 +21,11 @@ import useShop from "@saleor/hooks/useShop";
|
|||
import { commonMessages, sectionNames } 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 SaleListPage from "../../components/SaleListPage";
|
||||
import { TypedSaleBulkDelete } from "../../mutations";
|
||||
import { TypedSaleList } from "../../queries";
|
||||
import { useSaleListQuery } from "../../queries";
|
||||
import { SaleBulkDelete } from "../../types/SaleBulkDelete";
|
||||
import {
|
||||
saleAddUrl,
|
||||
|
@ -41,6 +43,7 @@ import {
|
|||
getFilterVariables,
|
||||
saveFilterTab
|
||||
} from "./filter";
|
||||
import { getSortQueryVariables } from "./sort";
|
||||
|
||||
interface SaleListProps {
|
||||
params: SaleListUrlQueryParams;
|
||||
|
@ -59,6 +62,20 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
|||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
const { data, loading, refetch } = useSaleListQuery({
|
||||
displayLoader: true,
|
||||
variables: queryVariables
|
||||
});
|
||||
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
|
@ -119,135 +136,122 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
|
|||
handleTabChange(tabs.length + 1);
|
||||
};
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
|
||||
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
|
||||
|
||||
return (
|
||||
<TypedSaleList displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.sales.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.sales.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleSaleBulkDelete = (data: SaleBulkDelete) => {
|
||||
if (data.saleBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
closeModal();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
const handleSaleBulkDelete = (data: SaleBulkDelete) => {
|
||||
if (data.saleBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
closeModal();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSort = createSortHandler(navigate, saleListUrl, params);
|
||||
|
||||
return (
|
||||
<TypedSaleBulkDelete onCompleted={handleSaleBulkDelete}>
|
||||
{(saleBulkDelete, saleBulkDeleteOpts) => {
|
||||
const onSaleBulkDelete = () =>
|
||||
saleBulkDelete({
|
||||
variables: {
|
||||
ids: params.ids
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TypedSaleBulkDelete onCompleted={handleSaleBulkDelete}>
|
||||
{(saleBulkDelete, saleBulkDeleteOpts) => {
|
||||
const onSaleBulkDelete = () =>
|
||||
saleBulkDelete({
|
||||
variables: {
|
||||
ids: params.ids
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.sales)} />
|
||||
<SaleListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(saleListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
defaultCurrency={maybe(() => shop.defaultCurrency)}
|
||||
sales={maybe(() => data.sales.edges.map(edge => edge.node))}
|
||||
settings={settings}
|
||||
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
|
||||
})
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.sales)} />
|
||||
<SaleListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(saleListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
defaultCurrency={maybe(() => shop.defaultCurrency)}
|
||||
sales={maybe(() => data.sales.edges.map(edge => edge.node))}
|
||||
settings={settings}
|
||||
disabled={loading}
|
||||
pageInfo={pageInfo}
|
||||
onAdd={() => navigate(saleAddUrl)}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onRowClick={id => () => navigate(saleUrl(id))}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
saleListUrl({
|
||||
action: "remove",
|
||||
ids: listElements
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<ActionDialog
|
||||
confirmButtonState={saleBulkDeleteOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={onSaleBulkDelete}
|
||||
open={params.action === "remove" && canOpenBulkActionDialog}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Sales",
|
||||
description: "dialog header"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
{canOpenBulkActionDialog && (
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter,plural,one{this sale} other{{displayQuantity} sales}}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: params.ids.length,
|
||||
displayQuantity: <strong>{params.ids.length}</strong>
|
||||
}}
|
||||
/>
|
||||
<ActionDialog
|
||||
confirmButtonState={saleBulkDeleteOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={onSaleBulkDelete}
|
||||
open={params.action === "remove" && canOpenBulkActionDialog}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Sales",
|
||||
description: "dialog header"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
{canOpenBulkActionDialog && (
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter,plural,one{this sale} other{{displayQuantity} sales}}?"
|
||||
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>
|
||||
</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, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedSaleList>
|
||||
</TypedSaleBulkDelete>
|
||||
);
|
||||
};
|
||||
export default SaleList;
|
||||
|
|
24
src/discounts/views/SaleList/sort.ts
Normal file
24
src/discounts/views/SaleList/sort.ts
Normal 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
|
||||
);
|
|
@ -21,9 +21,11 @@ import useShop from "@saleor/hooks/useShop";
|
|||
import { commonMessages, sectionNames } 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 VoucherListPage from "../../components/VoucherListPage";
|
||||
import { TypedVoucherBulkDelete } from "../../mutations";
|
||||
import { TypedVoucherList } from "../../queries";
|
||||
import { useVoucherListQuery } from "../../queries";
|
||||
import { VoucherBulkDelete } from "../../types/VoucherBulkDelete";
|
||||
import {
|
||||
voucherAddUrl,
|
||||
|
@ -41,6 +43,7 @@ import {
|
|||
getFilterVariables,
|
||||
saveFilterTab
|
||||
} from "./filter";
|
||||
import { getSortQueryVariables } from "./sort";
|
||||
|
||||
interface VoucherListProps {
|
||||
params: VoucherListUrlQueryParams;
|
||||
|
@ -59,6 +62,20 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
|
|||
);
|
||||
const intl = useIntl();
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
const { data, loading, refetch } = useVoucherListQuery({
|
||||
displayLoader: true,
|
||||
variables: queryVariables
|
||||
});
|
||||
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
|
@ -119,139 +136,122 @@ export const VoucherList: React.FC<VoucherListProps> = ({ params }) => {
|
|||
handleTabChange(tabs.length + 1);
|
||||
};
|
||||
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
|
||||
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
|
||||
|
||||
return (
|
||||
<TypedVoucherList displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.vouchers.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.vouchers.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleVoucherBulkDelete = (data: VoucherBulkDelete) => {
|
||||
if (data.voucherBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
closeModal();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
const handleVoucherBulkDelete = (data: VoucherBulkDelete) => {
|
||||
if (data.voucherBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
closeModal();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSort = createSortHandler(navigate, voucherListUrl, params);
|
||||
|
||||
return (
|
||||
<TypedVoucherBulkDelete onCompleted={handleVoucherBulkDelete}>
|
||||
{(voucherBulkDelete, voucherBulkDeleteOpts) => {
|
||||
const onVoucherBulkDelete = () =>
|
||||
voucherBulkDelete({
|
||||
variables: {
|
||||
ids: params.ids
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TypedVoucherBulkDelete onCompleted={handleVoucherBulkDelete}>
|
||||
{(voucherBulkDelete, voucherBulkDeleteOpts) => {
|
||||
const onVoucherBulkDelete = () =>
|
||||
voucherBulkDelete({
|
||||
variables: {
|
||||
ids: params.ids
|
||||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.vouchers)} />
|
||||
<VoucherListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(voucherListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
defaultCurrency={maybe(() => shop.defaultCurrency)}
|
||||
settings={settings}
|
||||
vouchers={maybe(() => 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
|
||||
})
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle
|
||||
title={intl.formatMessage(sectionNames.vouchers)}
|
||||
>
|
||||
<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>
|
||||
}}
|
||||
/>
|
||||
<VoucherListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(voucherListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
defaultCurrency={maybe(() => shop.defaultCurrency)}
|
||||
settings={settings}
|
||||
vouchers={maybe(() =>
|
||||
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))}
|
||||
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>
|
||||
</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, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedVoucherList>
|
||||
</TypedVoucherBulkDelete>
|
||||
);
|
||||
};
|
||||
export default VoucherList;
|
||||
|
|
30
src/discounts/views/VoucherList/sort.ts
Normal file
30
src/discounts/views/VoucherList/sort.ts
Normal 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
|
||||
);
|
17
src/misc.ts
17
src/misc.ts
|
@ -348,9 +348,22 @@ export function findInEnum<TEnum extends object>(
|
|||
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) {
|
||||
return true;
|
||||
return defaultValue;
|
||||
}
|
||||
return a === "true";
|
||||
}
|
||||
|
|
|
@ -15,10 +15,16 @@ import Skeleton from "@saleor/components/Skeleton";
|
|||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { 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";
|
||||
|
||||
export interface MenuListProps extends ListProps, ListActions {
|
||||
export interface MenuListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<MenuListUrlSortField> {
|
||||
menus: MenuList_menus_edges_node[];
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
@ -57,8 +63,10 @@ const MenuList: React.FC<MenuListProps> = props => {
|
|||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
onSort,
|
||||
pageInfo,
|
||||
selected,
|
||||
sort,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
|
@ -77,19 +85,37 @@ const MenuList: React.FC<MenuListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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
|
||||
defaultMessage="Menu Title"
|
||||
id="menuListMenutitle"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colItems}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === MenuListUrlSortField.items
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
textAlign="right"
|
||||
onClick={() => onSort(MenuListUrlSortField.items)}
|
||||
className={classes.colItems}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Items"
|
||||
description="number of menu items"
|
||||
id="menuListItems"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
<TableCell />
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
|
|
|
@ -7,18 +7,21 @@ import AppHeader from "@saleor/components/AppHeader";
|
|||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import { ListActions, PageListProps, SortPage } from "@saleor/types";
|
||||
import { MenuListUrlSortField } from "@saleor/navigation/urls";
|
||||
import { MenuList_menus_edges_node } from "../../types/MenuList";
|
||||
import MenuList from "../MenuList";
|
||||
|
||||
export interface MenuListPageProps extends PageListProps, ListActions {
|
||||
export interface MenuListPageProps
|
||||
extends PageListProps,
|
||||
ListActions,
|
||||
SortPage<MenuListUrlSortField> {
|
||||
menus: MenuList_menus_edges_node[];
|
||||
onBack: () => void;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
const MenuListPage: React.FC<MenuListPageProps> = ({
|
||||
disabled,
|
||||
onAdd,
|
||||
onBack,
|
||||
...listProps
|
||||
|
@ -30,12 +33,7 @@ const MenuListPage: React.FC<MenuListPageProps> = ({
|
|||
{intl.formatMessage(sectionNames.configuration)}
|
||||
</AppHeader>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.navigation)}>
|
||||
<Button
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
variant="contained"
|
||||
onClick={onAdd}
|
||||
>
|
||||
<Button color="primary" variant="contained" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create Menu"
|
||||
description="button"
|
||||
|
@ -43,7 +41,7 @@ const MenuListPage: React.FC<MenuListPageProps> = ({
|
|||
/>
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<MenuList disabled={disabled} {...listProps} />
|
||||
<MenuList {...listProps} />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,13 +2,21 @@ import { parse as parseQs } from "qs";
|
|||
import React from "react";
|
||||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { menuListPath, menuPath } from "./urls";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import {
|
||||
menuListPath,
|
||||
menuPath,
|
||||
MenuListUrlQueryParams,
|
||||
MenuListUrlSortField
|
||||
} from "./urls";
|
||||
import MenuDetailsComponent from "./views/MenuDetails";
|
||||
import MenuListComponent from "./views/MenuList";
|
||||
|
||||
const MenuList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
||||
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 }>> = ({
|
||||
|
@ -16,6 +24,7 @@ const MenuDetails: React.FC<RouteComponentProps<{ id: string }>> = ({
|
|||
match
|
||||
}) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
|
||||
return (
|
||||
<MenuDetailsComponent
|
||||
id={decodeURIComponent(match.params.id)}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import gql from "graphql-tag";
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { pageInfoFragment, TypedQuery } from "../queries";
|
||||
import { MenuDetails, MenuDetailsVariables } from "./types/MenuDetails";
|
||||
import { MenuList, MenuListVariables } from "./types/MenuList";
|
||||
|
@ -75,8 +76,20 @@ export const menuDetailsFragment = gql`
|
|||
const menuList = gql`
|
||||
${menuFragment}
|
||||
${pageInfoFragment}
|
||||
query MenuList($first: Int, $after: String, $last: Int, $before: String) {
|
||||
menus(first: $first, after: $after, before: $before, last: $last) {
|
||||
query MenuList(
|
||||
$first: Int
|
||||
$after: String
|
||||
$last: Int
|
||||
$before: String
|
||||
$sort: MenuSortingInput
|
||||
) {
|
||||
menus(
|
||||
first: $first
|
||||
after: $after
|
||||
before: $before
|
||||
last: $last
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...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`
|
||||
${menuDetailsFragment}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { MenuSortingInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MenuList
|
||||
// ====================================================
|
||||
|
@ -46,4 +48,5 @@ export interface MenuListVariables {
|
|||
after?: string | null;
|
||||
last?: number | null;
|
||||
before?: string | null;
|
||||
sort?: MenuSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
import { stringify as stringifyQs } from "qs";
|
||||
import urlJoin from "url-join";
|
||||
|
||||
import { BulkAction, Dialog, Pagination, SingleAction } from "../types";
|
||||
import { BulkAction, Dialog, Pagination, SingleAction, Sort } from "../types";
|
||||
|
||||
export const navigationSection = "/navigation";
|
||||
|
||||
export const menuListPath = navigationSection;
|
||||
export type MenuListUrlDialog = "add" | "remove" | "remove-many";
|
||||
export enum MenuListUrlSortField {
|
||||
name = "name",
|
||||
items = "items"
|
||||
}
|
||||
export type MenuListUrlSort = Sort<MenuListUrlSortField>;
|
||||
export type MenuListUrlQueryParams = BulkAction &
|
||||
Dialog<MenuListUrlDialog> &
|
||||
MenuListUrlSort &
|
||||
Pagination &
|
||||
SingleAction;
|
||||
export const menuListUrl = (params?: MenuListUrlQueryParams) =>
|
||||
|
|
|
@ -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;
|
266
src/navigation/views/MenuList/MenuList.tsx
Normal file
266
src/navigation/views/MenuList/MenuList.tsx
Normal 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;
|
2
src/navigation/views/MenuList/index.ts
Normal file
2
src/navigation/views/MenuList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./MenuList";
|
||||
export * from "./MenuList";
|
18
src/navigation/views/MenuList/sort.ts
Normal file
18
src/navigation/views/MenuList/sort.ts
Normal 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
|
||||
);
|
|
@ -19,7 +19,10 @@ import {
|
|||
transformOrderStatus,
|
||||
transformPaymentStatus
|
||||
} from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { OrderDraftListUrlSortField } from "@saleor/orders/urls";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -32,7 +35,7 @@ const useStyles = makeStyles(
|
|||
width: 300
|
||||
},
|
||||
colNumber: {
|
||||
width: 120
|
||||
width: 160
|
||||
},
|
||||
colTotal: {}
|
||||
},
|
||||
|
@ -51,7 +54,10 @@ const useStyles = makeStyles(
|
|||
{ name: "OrderDraftList" }
|
||||
);
|
||||
|
||||
interface OrderDraftListProps extends ListProps, ListActions {
|
||||
interface OrderDraftListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<OrderDraftListUrlSortField> {
|
||||
orders: OrderDraftList_draftOrders_edges_node[];
|
||||
}
|
||||
|
||||
|
@ -67,8 +73,10 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = props => {
|
|||
onNextPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
onSort,
|
||||
isChecked,
|
||||
selected,
|
||||
sort,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
|
@ -95,24 +103,58 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colDate}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === OrderDraftListUrlSortField.date
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(OrderDraftListUrlSortField.date)}
|
||||
className={classes.colDate}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Date"
|
||||
description="order draft creation date"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colCustomer}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === OrderDraftListUrlSortField.customer
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(OrderDraftListUrlSortField.customer)}
|
||||
className={classes.colCustomer}
|
||||
>
|
||||
<FormattedMessage defaultMessage="Customer" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTotal}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === OrderDraftListUrlSortField.total
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
textAlign="right"
|
||||
onClick={() => onSort(OrderDraftListUrlSortField.total)}
|
||||
className={classes.colTotal}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Total"
|
||||
description="order draft total price"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
|
|
@ -12,8 +12,10 @@ import {
|
|||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps
|
||||
TabPageProps,
|
||||
SortPage
|
||||
} from "@saleor/types";
|
||||
import { OrderDraftListUrlSortField } from "@saleor/orders/urls";
|
||||
import { OrderDraftList_draftOrders_edges_node } from "../../types/OrderDraftList";
|
||||
import OrderDraftList from "../OrderDraftList";
|
||||
|
||||
|
@ -21,6 +23,7 @@ export interface OrderDraftListPageProps
|
|||
extends PageListProps,
|
||||
ListActions,
|
||||
SearchPageProps,
|
||||
SortPage<OrderDraftListUrlSortField>,
|
||||
TabPageProps {
|
||||
orders: OrderDraftList_draftOrders_edges_node[];
|
||||
}
|
||||
|
|
|
@ -20,7 +20,10 @@ import {
|
|||
transformOrderStatus,
|
||||
transformPaymentStatus
|
||||
} from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { OrderListUrlSortField } from "@saleor/orders/urls";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
import { OrderList_orders_edges_node } from "../../types/OrderList";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -56,7 +59,10 @@ const useStyles = makeStyles(
|
|||
{ name: "OrderList" }
|
||||
);
|
||||
|
||||
interface OrderListProps extends ListProps, ListActions {
|
||||
interface OrderListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<OrderListUrlSortField> {
|
||||
orders: OrderList_orders_edges_node[];
|
||||
}
|
||||
|
||||
|
@ -72,8 +78,10 @@ export const OrderList: React.FC<OrderListProps> = props => {
|
|||
onNextPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
onSort,
|
||||
isChecked,
|
||||
selected,
|
||||
sort,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
|
@ -99,36 +107,86 @@ export const OrderList: React.FC<OrderListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colDate}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === OrderListUrlSortField.date
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(OrderListUrlSortField.date)}
|
||||
className={classes.colDate}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Date"
|
||||
description="date when order was placed"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colCustomer}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === OrderListUrlSortField.customer
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(OrderListUrlSortField.customer)}
|
||||
className={classes.colCustomer}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Customer"
|
||||
description="e-mail or full name"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPayment}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === OrderListUrlSortField.payment
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(OrderListUrlSortField.payment)}
|
||||
className={classes.colPayment}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Payment"
|
||||
description="payment status"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colFulfillment}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === OrderListUrlSortField.fulfillment
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(OrderListUrlSortField.fulfillment)}
|
||||
className={classes.colFulfillment}
|
||||
>
|
||||
<FormattedMessage defaultMessage="Fulfillment status" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTotal}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === OrderListUrlSortField.total
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
textAlign="right"
|
||||
onClick={() => onSort(OrderListUrlSortField.total)}
|
||||
className={classes.colTotal}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Total"
|
||||
description="total order price"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
|
|
@ -7,7 +7,13 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
|
||||
import {
|
||||
FilterPageProps,
|
||||
ListActions,
|
||||
PageListProps,
|
||||
SortPage
|
||||
} from "@saleor/types";
|
||||
import { OrderListUrlSortField } from "@saleor/orders/urls";
|
||||
import { OrderList_orders_edges_node } from "../../types/OrderList";
|
||||
import OrderList from "../OrderList";
|
||||
import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter";
|
||||
|
@ -15,7 +21,8 @@ import OrderListFilter, { OrderFilterKeys } from "../OrderListFilter";
|
|||
export interface OrderListPageProps
|
||||
extends PageListProps,
|
||||
ListActions,
|
||||
FilterPageProps<OrderFilterKeys> {
|
||||
FilterPageProps<OrderFilterKeys>,
|
||||
SortPage<OrderListUrlSortField> {
|
||||
orders: OrderList_orders_edges_node[];
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
|
|||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
orderDraftListPath,
|
||||
|
@ -11,7 +12,9 @@ import {
|
|||
orderListPath,
|
||||
OrderListUrlQueryParams,
|
||||
orderPath,
|
||||
OrderUrlQueryParams
|
||||
OrderUrlQueryParams,
|
||||
OrderDraftListUrlSortField,
|
||||
OrderListUrlSortField
|
||||
} from "./urls";
|
||||
import OrderDetailsComponent from "./views/OrderDetails";
|
||||
import OrderDraftListComponent from "./views/OrderDraftList";
|
||||
|
@ -19,12 +22,23 @@ import OrderListComponent from "./views/OrderList";
|
|||
|
||||
const OrderList: React.FC<RouteComponentProps<any>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: OrderListUrlQueryParams = qs;
|
||||
const params: OrderListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
OrderListUrlSortField,
|
||||
OrderListUrlSortField.number,
|
||||
false
|
||||
);
|
||||
return <OrderListComponent params={params} />;
|
||||
};
|
||||
const OrderDraftList: React.FC<RouteComponentProps<any>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: OrderDraftListUrlQueryParams = qs;
|
||||
const params: OrderDraftListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
OrderDraftListUrlSortField,
|
||||
OrderDraftListUrlSortField.number,
|
||||
false
|
||||
);
|
||||
|
||||
return <OrderDraftListComponent params={params} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import makeTopLevelSearch from "@saleor/hooks/makeTopLevelSearch";
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { TypedQuery } from "../queries";
|
||||
import { OrderDetails, OrderDetailsVariables } from "./types/OrderDetails";
|
||||
import {
|
||||
|
@ -169,6 +170,7 @@ export const orderListQuery = gql`
|
|||
$last: Int
|
||||
$before: String
|
||||
$filter: OrderFilterInput
|
||||
$sort: OrderSortingInput
|
||||
) {
|
||||
orders(
|
||||
before: $before
|
||||
|
@ -176,6 +178,7 @@ export const orderListQuery = gql`
|
|||
first: $first
|
||||
last: $last
|
||||
filter: $filter
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
@ -208,7 +211,7 @@ export const orderListQuery = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedOrderListQuery = TypedQuery<OrderList, OrderListVariables>(
|
||||
export const useOrderListQuery = makeQuery<OrderList, OrderListVariables>(
|
||||
orderListQuery
|
||||
);
|
||||
|
||||
|
@ -220,6 +223,7 @@ export const orderDraftListQuery = gql`
|
|||
$last: Int
|
||||
$before: String
|
||||
$filter: OrderDraftFilterInput
|
||||
$sort: OrderSortingInput
|
||||
) {
|
||||
draftOrders(
|
||||
before: $before
|
||||
|
@ -227,6 +231,7 @@ export const orderDraftListQuery = gql`
|
|||
first: $first
|
||||
last: $last
|
||||
filter: $filter
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
@ -259,7 +264,7 @@ export const orderDraftListQuery = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedOrderDraftListQuery = TypedQuery<
|
||||
export const useOrderDraftListQuery = makeQuery<
|
||||
OrderDraftList,
|
||||
OrderDraftListVariables
|
||||
>(orderDraftListQuery);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { OrderDraftFilterInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes";
|
||||
import { OrderDraftFilterInput, OrderSortingInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: OrderDraftList
|
||||
|
@ -82,4 +82,5 @@ export interface OrderDraftListVariables {
|
|||
last?: number | null;
|
||||
before?: string | null;
|
||||
filter?: OrderDraftFilterInput | null;
|
||||
sort?: OrderSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { OrderFilterInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes";
|
||||
import { OrderFilterInput, OrderSortingInput, PaymentChargeStatusEnum, OrderStatus } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: OrderList
|
||||
|
@ -82,4 +82,5 @@ export interface OrderListVariables {
|
|||
last?: number | null;
|
||||
before?: string | null;
|
||||
filter?: OrderFilterInput | null;
|
||||
sort?: OrderSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ import {
|
|||
FiltersWithMultipleValues,
|
||||
Pagination,
|
||||
SingleAction,
|
||||
TabActionDialog
|
||||
TabActionDialog,
|
||||
Sort
|
||||
} from "../types";
|
||||
|
||||
const orderSectionUrl = "/orders";
|
||||
|
@ -28,9 +29,19 @@ export enum OrderListUrlFiltersWithMultipleValuesEnum {
|
|||
export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> &
|
||||
FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValuesEnum>;
|
||||
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 &
|
||||
Dialog<OrderListUrlDialog> &
|
||||
OrderListUrlFilters &
|
||||
OrderListUrlSort &
|
||||
Pagination &
|
||||
ActiveTab;
|
||||
export const orderListUrl = (params?: OrderListUrlQueryParams): string => {
|
||||
|
@ -48,10 +59,18 @@ export enum OrderDraftListUrlFiltersEnum {
|
|||
}
|
||||
export type OrderDraftListUrlFilters = Filters<OrderDraftListUrlFiltersEnum>;
|
||||
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 &
|
||||
BulkAction &
|
||||
Dialog<OrderDraftListUrlDialog> &
|
||||
OrderDraftListUrlFilters &
|
||||
OrderDraftListUrlSort &
|
||||
Pagination;
|
||||
export const orderDraftListUrl = (
|
||||
params?: OrderDraftListUrlQueryParams
|
||||
|
|
|
@ -18,12 +18,14 @@ import usePaginator, {
|
|||
} from "@saleor/hooks/usePaginator";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import OrderDraftListPage from "../../components/OrderDraftListPage";
|
||||
import {
|
||||
TypedOrderDraftBulkCancelMutation,
|
||||
useOrderDraftCreateMutation
|
||||
} from "../../mutations";
|
||||
import { TypedOrderDraftListQuery } from "../../queries";
|
||||
import { useOrderDraftListQuery } from "../../queries";
|
||||
import { OrderDraftBulkCancel } from "../../types/OrderDraftBulkCancel";
|
||||
import { OrderDraftCreate } from "../../types/OrderDraftCreate";
|
||||
import {
|
||||
|
@ -41,6 +43,7 @@ import {
|
|||
getFilterVariables,
|
||||
saveFilterTab
|
||||
} from "./filter";
|
||||
import { getSortQueryVariables } from "./sort";
|
||||
|
||||
interface OrderDraftListProps {
|
||||
params: OrderDraftListUrlQueryParams;
|
||||
|
@ -135,131 +138,132 @@ export const OrderDraftList: React.FC<OrderDraftListProps> = ({ params }) => {
|
|||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params]
|
||||
);
|
||||
const { data, loading, refetch } = useOrderDraftListQuery({
|
||||
displayLoader: true,
|
||||
variables: queryVariables
|
||||
});
|
||||
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.draftOrders.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleOrderDraftBulkCancel = (data: OrderDraftBulkCancel) => {
|
||||
if (data.draftOrderBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Deleted draft orders"
|
||||
})
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSort = createSortHandler(navigate, orderDraftListUrl, params);
|
||||
|
||||
return (
|
||||
<TypedOrderDraftListQuery displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.draftOrders.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleOrderDraftBulkCancel = (data: OrderDraftBulkCancel) => {
|
||||
if (data.draftOrderBulkDelete.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Deleted draft orders"
|
||||
})
|
||||
});
|
||||
refetch();
|
||||
reset();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
<TypedOrderDraftBulkCancelMutation onCompleted={handleOrderDraftBulkCancel}>
|
||||
{(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => {
|
||||
const onOrderDraftBulkDelete = () =>
|
||||
orderDraftBulkDelete({
|
||||
variables: {
|
||||
ids: params.ids
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TypedOrderDraftBulkCancelMutation
|
||||
onCompleted={handleOrderDraftBulkCancel}
|
||||
>
|
||||
{(orderDraftBulkDelete, orderDraftBulkDeleteOpts) => {
|
||||
const onOrderDraftBulkDelete = () =>
|
||||
orderDraftBulkDelete({
|
||||
variables: {
|
||||
ids: params.ids
|
||||
<>
|
||||
<OrderDraftListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(orderDraftListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
disabled={loading}
|
||||
settings={settings}
|
||||
orders={maybe(() =>
|
||||
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
|
||||
})
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<OrderDraftListPage
|
||||
currentTab={currentTab}
|
||||
initialSearch={params.query || ""}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onAll={() => navigate(orderDraftListUrl())}
|
||||
onTabChange={handleTabChange}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
disabled={loading}
|
||||
settings={settings}
|
||||
orders={maybe(() =>
|
||||
data.draftOrders.edges.map(edge => edge.node)
|
||||
)}
|
||||
pageInfo={pageInfo}
|
||||
onAdd={createOrder}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onRowClick={id => () => navigate(orderUrl(id))}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
orderDraftListUrl({
|
||||
action: "remove",
|
||||
ids: listElements
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</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>
|
||||
>
|
||||
<DeleteIcon />
|
||||
</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, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedOrderDraftListQuery>
|
||||
</TypedOrderDraftBulkCancelMutation>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
24
src/orders/views/OrderDraftList/sort.ts
Normal file
24
src/orders/views/OrderDraftList/sort.ts
Normal 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
|
||||
);
|
|
@ -17,13 +17,15 @@ import usePaginator, {
|
|||
import useShop from "@saleor/hooks/useShop";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import OrderBulkCancelDialog from "../../components/OrderBulkCancelDialog";
|
||||
import OrderListPage from "../../components/OrderListPage/OrderListPage";
|
||||
import {
|
||||
TypedOrderBulkCancelMutation,
|
||||
useOrderDraftCreateMutation
|
||||
} from "../../mutations";
|
||||
import { TypedOrderListQuery } from "../../queries";
|
||||
import { useOrderListQuery } from "../../queries";
|
||||
import { OrderBulkCancel } from "../../types/OrderBulkCancel";
|
||||
import { OrderDraftCreate } from "../../types/OrderDraftCreate";
|
||||
import {
|
||||
|
@ -43,6 +45,7 @@ import {
|
|||
getFilterVariables,
|
||||
saveFilterTab
|
||||
} from "./filters";
|
||||
import { getSortQueryVariables } from "./sort";
|
||||
|
||||
interface OrderListProps {
|
||||
params: OrderListUrlQueryParams;
|
||||
|
@ -146,128 +149,126 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
|
|||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params)
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params, settings.rowNumber]
|
||||
);
|
||||
const { data, loading, refetch } = useOrderListQuery({
|
||||
displayLoader: true,
|
||||
variables: queryVariables
|
||||
});
|
||||
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.orders.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleOrderBulkCancel = (data: OrderBulkCancel) => {
|
||||
if (data.orderBulkCancel.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Orders cancelled"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSort = createSortHandler(navigate, orderListUrl, params);
|
||||
|
||||
return (
|
||||
<TypedOrderListQuery displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.orders.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleOrderBulkCancel = (data: OrderBulkCancel) => {
|
||||
if (data.orderBulkCancel.errors.length === 0) {
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Orders cancelled"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
<TypedOrderBulkCancelMutation onCompleted={handleOrderBulkCancel}>
|
||||
{(orderBulkCancel, orderBulkCancelOpts) => {
|
||||
const onOrderBulkCancel = (restock: boolean) =>
|
||||
orderBulkCancel({
|
||||
variables: {
|
||||
ids: params.ids,
|
||||
restock
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TypedOrderBulkCancelMutation onCompleted={handleOrderBulkCancel}>
|
||||
{(orderBulkCancel, orderBulkCancelOpts) => {
|
||||
const onOrderBulkCancel = (restock: boolean) =>
|
||||
orderBulkCancel({
|
||||
variables: {
|
||||
ids: params.ids,
|
||||
restock
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<OrderListPage
|
||||
currencySymbol={currencySymbol}
|
||||
settings={settings}
|
||||
filtersList={createFilterChips(
|
||||
params,
|
||||
{
|
||||
formatDate
|
||||
},
|
||||
changeFilterField,
|
||||
intl
|
||||
)}
|
||||
currentTab={currentTab}
|
||||
disabled={loading}
|
||||
orders={maybe(() =>
|
||||
data.orders.edges.map(edge => edge.node)
|
||||
)}
|
||||
pageInfo={pageInfo}
|
||||
onAdd={createOrder}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onRowClick={id => () => navigate(orderUrl(id))}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={
|
||||
<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
|
||||
})
|
||||
}
|
||||
<>
|
||||
<OrderListPage
|
||||
currencySymbol={currencySymbol}
|
||||
settings={settings}
|
||||
filtersList={createFilterChips(
|
||||
params,
|
||||
{
|
||||
formatDate
|
||||
},
|
||||
changeFilterField,
|
||||
intl
|
||||
)}
|
||||
currentTab={currentTab}
|
||||
disabled={loading}
|
||||
orders={maybe(() => data.orders.edges.map(edge => edge.node))}
|
||||
pageInfo={pageInfo}
|
||||
sort={getSortParams(params)}
|
||||
onAdd={createOrder}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onRowClick={id => () => navigate(orderUrl(id))}
|
||||
onSort={handleSort}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => openModal("cancel", listElements)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
description="cancel orders, button"
|
||||
/>
|
||||
<OrderBulkCancelDialog
|
||||
confirmButtonState={orderBulkCancelOpts.status}
|
||||
numberOfOrders={maybe(
|
||||
() => params.ids.length.toString(),
|
||||
"..."
|
||||
)}
|
||||
onClose={closeModal}
|
||||
onConfirm={onOrderBulkCancel}
|
||||
open={params.action === "cancel"}
|
||||
/>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleFilterTabSave}
|
||||
/>
|
||||
<DeleteFilterTabDialog
|
||||
open={params.action === "delete-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleFilterTabDelete}
|
||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedOrderBulkCancelMutation>
|
||||
</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
|
||||
confirmButtonState={orderBulkCancelOpts.status}
|
||||
numberOfOrders={maybe(() => params.ids.length.toString(), "...")}
|
||||
onClose={closeModal}
|
||||
onConfirm={onOrderBulkCancel}
|
||||
open={params.action === "cancel"}
|
||||
/>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
26
src/orders/views/OrderList/sort.ts
Normal file
26
src/orders/views/OrderList/sort.ts
Normal 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
|
||||
);
|
|
@ -14,10 +14,16 @@ import StatusLabel from "@saleor/components/StatusLabel";
|
|||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||
import { PageListUrlSortField } from "@saleor/pages/urls";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
import { PageList_pages_edges_node } from "../../types/PageList";
|
||||
|
||||
export interface PageListProps extends ListProps, ListActions {
|
||||
export interface PageListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<PageListUrlSortField> {
|
||||
pages: PageList_pages_edges_node[];
|
||||
}
|
||||
|
||||
|
@ -54,10 +60,12 @@ const PageList: React.FC<PageListProps> = props => {
|
|||
onNextPage,
|
||||
pageInfo,
|
||||
onRowClick,
|
||||
onSort,
|
||||
onUpdateListSettings,
|
||||
onPreviousPage,
|
||||
isChecked,
|
||||
selected,
|
||||
sort,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
|
@ -77,24 +85,51 @@ const PageList: React.FC<PageListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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
|
||||
defaultMessage="Title"
|
||||
description="dialog header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSlug}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === PageListUrlSortField.slug
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
arrowPosition="right"
|
||||
onClick={() => onSort(PageListUrlSortField.slug)}
|
||||
className={classes.colSlug}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Slug"
|
||||
description="page internal name"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colVisibility}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === PageListUrlSortField.visible
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
arrowPosition="right"
|
||||
onClick={() => onSort(PageListUrlSortField.visible)}
|
||||
className={classes.colVisibility}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Visibility"
|
||||
description="page status"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
@ -133,13 +168,13 @@ const PageList: React.FC<PageListProps> = props => {
|
|||
onChange={() => toggle(page.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colTitle}>
|
||||
<TableCellHeader className={classes.colTitle}>
|
||||
{maybe<React.ReactNode>(() => page.title, <Skeleton />)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colSlug}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader className={classes.colSlug}>
|
||||
{maybe<React.ReactNode>(() => page.slug, <Skeleton />)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colVisibility}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader className={classes.colVisibility}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => (
|
||||
<StatusLabel
|
||||
|
@ -159,7 +194,7 @@ const PageList: React.FC<PageListProps> = props => {
|
|||
),
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -7,31 +7,23 @@ import AppHeader from "@saleor/components/AppHeader";
|
|||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import { ListActions, PageListProps, SortPage } from "@saleor/types";
|
||||
import { PageListUrlSortField } from "@saleor/pages/urls";
|
||||
import { PageList_pages_edges_node } from "../../types/PageList";
|
||||
import PageList from "../PageList/PageList";
|
||||
import PageList from "../PageList";
|
||||
|
||||
export interface PageListPageProps extends PageListProps, ListActions {
|
||||
export interface PageListPageProps
|
||||
extends PageListProps,
|
||||
ListActions,
|
||||
SortPage<PageListUrlSortField> {
|
||||
pages: PageList_pages_edges_node[];
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
const PageListPage: React.FC<PageListPageProps> = ({
|
||||
disabled,
|
||||
settings,
|
||||
onAdd,
|
||||
onBack,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick,
|
||||
onUpdateListSettings,
|
||||
pageInfo,
|
||||
pages,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
|
@ -41,30 +33,11 @@ const PageListPage: React.FC<PageListPageProps> = ({
|
|||
{intl.formatMessage(sectionNames.configuration)}
|
||||
</AppHeader>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.pages)}>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
onClick={onAdd}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
<Button onClick={onAdd} variant="contained" color="primary">
|
||||
<FormattedMessage defaultMessage="Create page" description="button" />
|
||||
</Button>
|
||||
</PageHeader>
|
||||
<PageList
|
||||
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}
|
||||
/>
|
||||
<PageList {...listProps} />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,13 +4,15 @@ import { useIntl } from "react-intl";
|
|||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
pageCreatePath,
|
||||
pageListPath,
|
||||
PageListUrlQueryParams,
|
||||
pagePath,
|
||||
PageUrlQueryParams
|
||||
PageUrlQueryParams,
|
||||
PageListUrlSortField
|
||||
} from "./urls";
|
||||
import PageCreate from "./views/PageCreate";
|
||||
import PageDetailsComponent from "./views/PageDetails";
|
||||
|
@ -18,7 +20,11 @@ import PageListComponent from "./views/PageList";
|
|||
|
||||
const PageList: React.FC<RouteComponentProps<any>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: PageListUrlQueryParams = qs;
|
||||
const params: PageListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
PageListUrlSortField,
|
||||
PageListUrlSortField.title
|
||||
);
|
||||
return <PageListComponent params={params} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { TypedQuery } from "../queries";
|
||||
import { PageDetails, PageDetailsVariables } from "./types/PageDetails";
|
||||
import { PageList, PageListVariables } from "./types/PageList";
|
||||
|
@ -26,8 +27,20 @@ export const pageDetailsFragment = gql`
|
|||
|
||||
const pageList = gql`
|
||||
${pageFragment}
|
||||
query PageList($first: Int, $after: String, $last: Int, $before: String) {
|
||||
pages(before: $before, after: $after, first: $first, last: $last) {
|
||||
query PageList(
|
||||
$first: Int
|
||||
$after: String
|
||||
$last: Int
|
||||
$before: String
|
||||
$sort: PageSortingInput
|
||||
) {
|
||||
pages(
|
||||
before: $before
|
||||
after: $after
|
||||
first: $first
|
||||
last: $last
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...PageFragment
|
||||
|
@ -42,7 +55,7 @@ const pageList = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedPageListQuery = TypedQuery<PageList, PageListVariables>(
|
||||
export const usePageListQuery = makeQuery<PageList, PageListVariables>(
|
||||
pageList
|
||||
);
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { PageSortingInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: PageList
|
||||
// ====================================================
|
||||
|
@ -42,4 +44,5 @@ export interface PageListVariables {
|
|||
after?: string | null;
|
||||
last?: number | null;
|
||||
before?: string | null;
|
||||
sort?: PageSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
import { stringify as stringifyQs } from "qs";
|
||||
import urlJoin from "url-join";
|
||||
|
||||
import { BulkAction, Dialog, Pagination } from "../types";
|
||||
import { BulkAction, Dialog, Pagination, Sort } from "../types";
|
||||
|
||||
export const pagesSection = "/pages/";
|
||||
|
||||
export const pageListPath = pagesSection;
|
||||
export type PageListUrlDialog = "publish" | "unpublish" | "remove";
|
||||
export enum PageListUrlSortField {
|
||||
title = "title",
|
||||
slug = "slug",
|
||||
visible = "visible"
|
||||
}
|
||||
export type PageListUrlSort = Sort<PageListUrlSortField>;
|
||||
export type PageListUrlQueryParams = BulkAction &
|
||||
Dialog<PageListUrlDialog> &
|
||||
PageListUrlSort &
|
||||
Pagination;
|
||||
export const pageListUrl = (params?: PageListUrlQueryParams) =>
|
||||
pageListPath + "?" + stringifyQs(params);
|
||||
|
|
|
@ -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;
|
265
src/pages/views/PageList/PageList.tsx
Normal file
265
src/pages/views/PageList/PageList.tsx
Normal 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;
|
2
src/pages/views/PageList/index.ts
Normal file
2
src/pages/views/PageList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PageList";
|
||||
export * from "./PageList";
|
20
src/pages/views/PageList/sort.ts
Normal file
20
src/pages/views/PageList/sort.ts
Normal 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
|
||||
);
|
|
@ -15,10 +15,15 @@ import StatusLabel from "@saleor/components/StatusLabel";
|
|||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { translateBoolean } from "@saleor/intl";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListProps } from "@saleor/types";
|
||||
import { ListProps, SortPage } from "@saleor/types";
|
||||
import { PluginListUrlSortField } from "@saleor/plugins/urls";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
import { Plugins_plugins_edges_node } from "../../types/Plugins";
|
||||
|
||||
export interface PluginListProps extends ListProps {
|
||||
export interface PluginListProps
|
||||
extends ListProps,
|
||||
SortPage<PluginListUrlSortField> {
|
||||
plugins: Plugins_plugins_edges_node[];
|
||||
}
|
||||
|
||||
|
@ -53,7 +58,9 @@ const PluginList: React.FC<PluginListProps> = props => {
|
|||
disabled,
|
||||
onNextPage,
|
||||
pageInfo,
|
||||
sort,
|
||||
onRowClick,
|
||||
onSort,
|
||||
onUpdateListSettings,
|
||||
onPreviousPage
|
||||
} = props;
|
||||
|
@ -64,18 +71,35 @@ const PluginList: React.FC<PluginListProps> = props => {
|
|||
<Card>
|
||||
<ResponsiveTable>
|
||||
<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({
|
||||
defaultMessage: "Name",
|
||||
description: "plugin name"
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActive}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === PluginListUrlSortField.active
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(PluginListUrlSortField.active)}
|
||||
className={classes.colActive}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Active",
|
||||
description: "plugin status"
|
||||
})}
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
<TableCell className={classes.colAction}>
|
||||
{intl.formatMessage({
|
||||
defaultMessage: "Action",
|
||||
|
|
|
@ -5,43 +5,31 @@ import AppHeader from "@saleor/components/AppHeader";
|
|||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { PageListProps } from "@saleor/types";
|
||||
import { PageListProps, SortPage } from "@saleor/types";
|
||||
import { PluginListUrlSortField } from "@saleor/plugins/urls";
|
||||
import { Plugins_plugins_edges_node } from "../../types/Plugins";
|
||||
import PluginsList from "../PluginsList/PluginsList";
|
||||
|
||||
export interface PluginsListPageProps extends PageListProps {
|
||||
export interface PluginsListPageProps
|
||||
extends PageListProps,
|
||||
SortPage<PluginListUrlSortField> {
|
||||
plugins: Plugins_plugins_edges_node[];
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
const PluginsListPage: React.FC<PluginsListPageProps> = ({
|
||||
disabled,
|
||||
settings,
|
||||
onBack,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick,
|
||||
onUpdateListSettings,
|
||||
pageInfo,
|
||||
plugins
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.configuration)}
|
||||
</AppHeader>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.plugins)} />
|
||||
<PluginsList
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
plugins={plugins}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
onRowClick={onRowClick}
|
||||
pageInfo={pageInfo}
|
||||
/>
|
||||
<PluginsList {...listProps} />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,25 +4,30 @@ import { useIntl } from "react-intl";
|
|||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
pluginsListPath,
|
||||
PluginsListUrlQueryParams,
|
||||
pluginsPath,
|
||||
PluginsUrlQueryParams
|
||||
pluginListPath,
|
||||
PluginListUrlQueryParams,
|
||||
pluginPath,
|
||||
PluginUrlQueryParams,
|
||||
PluginListUrlSortField
|
||||
} from "./urls";
|
||||
import PluginsDetailsComponent from "./views/PluginsDetails";
|
||||
import PluginsListComponent from "./views/PluginsList";
|
||||
import PluginsListComponent from "./views/PluginList";
|
||||
|
||||
const PluginList: React.FC<RouteComponentProps<any>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: PluginsListUrlQueryParams = qs;
|
||||
const params: PluginListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
PluginListUrlSortField
|
||||
);
|
||||
return <PluginsListComponent params={params} />;
|
||||
};
|
||||
|
||||
const PageDetails: React.FC<RouteComponentProps<any>> = ({ match }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: PluginsUrlQueryParams = qs;
|
||||
const params: PluginUrlQueryParams = qs;
|
||||
|
||||
return (
|
||||
<PluginsDetailsComponent
|
||||
|
@ -38,8 +43,8 @@ const Component = () => {
|
|||
<>
|
||||
<WindowTitle title={intl.formatMessage(sectionNames.plugins)} />
|
||||
<Switch>
|
||||
<Route exact path={pluginsListPath} component={PluginList} />
|
||||
<Route path={pluginsPath(":id")} component={PageDetails} />
|
||||
<Route exact path={pluginListPath} component={PluginList} />
|
||||
<Route path={pluginPath(":id")} component={PageDetails} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { TypedQuery } from "../queries";
|
||||
import { Plugin, PluginVariables } from "./types/Plugin";
|
||||
import { Plugins, PluginsVariables } from "./types/Plugins";
|
||||
|
@ -29,8 +30,20 @@ export const pluginsDetailsFragment = gql`
|
|||
|
||||
const pluginsList = gql`
|
||||
${pluginsFragment}
|
||||
query Plugins($first: Int, $after: String, $last: Int, $before: String) {
|
||||
plugins(before: $before, after: $after, first: $first, last: $last) {
|
||||
query Plugins(
|
||||
$first: Int
|
||||
$after: String
|
||||
$last: Int
|
||||
$before: String
|
||||
$sort: PluginSortingInput
|
||||
) {
|
||||
plugins(
|
||||
before: $before
|
||||
after: $after
|
||||
first: $first
|
||||
last: $last
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...PluginFragment
|
||||
|
@ -45,7 +58,7 @@ const pluginsList = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedPluginsListQuery = TypedQuery<Plugins, PluginsVariables>(
|
||||
export const usePluginsListQuery = makeQuery<Plugins, PluginsVariables>(
|
||||
pluginsList
|
||||
);
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { PluginSortingInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Plugins
|
||||
// ====================================================
|
||||
|
@ -42,4 +44,5 @@ export interface PluginsVariables {
|
|||
after?: string | null;
|
||||
last?: number | null;
|
||||
before?: string | null;
|
||||
sort?: PluginSortingInput | null;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
import { stringify as stringifyQs } from "qs";
|
||||
import urlJoin from "url-join";
|
||||
|
||||
import { Dialog, Pagination, SingleAction } from "../types";
|
||||
import { Dialog, Pagination, SingleAction, Sort } from "../types";
|
||||
|
||||
export const pluginsSection = "/plugins/";
|
||||
export const pluginSection = "/plugins/";
|
||||
|
||||
export const pluginsListPath = pluginsSection;
|
||||
export type PluginsListUrlQueryParams = Pagination & SingleAction;
|
||||
export const pluginsListUrl = (params?: PluginsListUrlQueryParams) =>
|
||||
pluginsListPath + "?" + stringifyQs(params);
|
||||
export const pluginListPath = pluginSection;
|
||||
export enum PluginListUrlSortField {
|
||||
name = "name",
|
||||
active = "active"
|
||||
}
|
||||
export type PluginListUrlSort = Sort<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 PluginsUrlQueryParams = Dialog<PluginUrlDialog> & {
|
||||
export type PluginUrlQueryParams = Dialog<PluginUrlDialog> & {
|
||||
field?: string;
|
||||
};
|
||||
export const pluginsUrl = (id: string, params?: PluginsUrlQueryParams) =>
|
||||
pluginsPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
export const pluginsUrl = (id: string, params?: PluginUrlQueryParams) =>
|
||||
pluginPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
|
|
74
src/plugins/views/PluginList/PluginList.tsx
Normal file
74
src/plugins/views/PluginList/PluginList.tsx
Normal 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;
|
2
src/plugins/views/PluginList/index.ts
Normal file
2
src/plugins/views/PluginList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./PluginList";
|
||||
export * from "./PluginList";
|
20
src/plugins/views/PluginList/sort.ts
Normal file
20
src/plugins/views/PluginList/sort.ts
Normal 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
|
||||
);
|
|
@ -16,16 +16,16 @@ import { TypedPluginsDetailsQuery } from "../queries";
|
|||
import { Plugin_plugin_configuration } from "../types/Plugin";
|
||||
import { PluginUpdate } from "../types/PluginUpdate";
|
||||
import {
|
||||
pluginsListUrl,
|
||||
pluginListUrl,
|
||||
pluginsUrl,
|
||||
PluginsUrlQueryParams,
|
||||
PluginUrlQueryParams,
|
||||
PluginUrlDialog
|
||||
} from "../urls";
|
||||
import { isSecretField } from "../utils";
|
||||
|
||||
export interface PluginsDetailsProps {
|
||||
id: string;
|
||||
params: PluginsUrlQueryParams;
|
||||
params: PluginUrlQueryParams;
|
||||
}
|
||||
|
||||
export function getConfigurationInput(
|
||||
|
@ -117,7 +117,7 @@ export const PluginsDetails: React.FC<PluginsDetailsProps> = ({
|
|||
!params.action ? pluginUpdateOpts.status : "default"
|
||||
}
|
||||
plugin={maybe(() => pluginDetails.data.plugin)}
|
||||
onBack={() => navigate(pluginsListUrl())}
|
||||
onBack={() => navigate(pluginListUrl())}
|
||||
onClear={field => openModal("clear", field)}
|
||||
onEdit={field => openModal("edit", field)}
|
||||
onSubmit={formData =>
|
||||
|
|
|
@ -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;
|
|
@ -12,8 +12,11 @@ import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
|||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls";
|
||||
import { getArrowDirection } from "@saleor/utils/sort";
|
||||
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { ListActions, ListProps } from "../../../types";
|
||||
import { ListActions, ListProps, SortPage } from "../../../types";
|
||||
import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -39,7 +42,10 @@ const useStyles = makeStyles(
|
|||
{ name: "ProductTypeList" }
|
||||
);
|
||||
|
||||
interface ProductTypeListProps extends ListProps, ListActions {
|
||||
interface ProductTypeListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
SortPage<ProductTypeListUrlSortField> {
|
||||
productTypes: ProductTypeList_productTypes_edges_node[];
|
||||
}
|
||||
|
||||
|
@ -53,8 +59,10 @@ const ProductTypeList: React.FC<ProductTypeListProps> = props => {
|
|||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick,
|
||||
onSort,
|
||||
isChecked,
|
||||
selected,
|
||||
sort,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
|
@ -73,18 +81,35 @@ const ProductTypeList: React.FC<ProductTypeListProps> = props => {
|
|||
toggleAll={toggleAll}
|
||||
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
|
||||
defaultMessage="Type Name"
|
||||
description="product type name"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colType}>
|
||||
</TableCellHeader>
|
||||
<TableCellHeader
|
||||
direction={
|
||||
sort.sort === ProductTypeListUrlSortField.digital
|
||||
? getArrowDirection(sort.asc)
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onSort(ProductTypeListUrlSortField.digital)}
|
||||
className={classes.colType}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Type"
|
||||
description="product type is either simple or configurable"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableCellHeader>
|
||||
<TableCell className={classes.colTax}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Tax"
|
||||
|
|
|
@ -8,11 +8,13 @@ import Container from "@saleor/components/Container";
|
|||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SearchBar from "@saleor/components/SearchBar";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { ProductTypeListUrlSortField } from "@saleor/productTypes/urls";
|
||||
import {
|
||||
ListActions,
|
||||
PageListProps,
|
||||
SearchPageProps,
|
||||
TabPageProps
|
||||
TabPageProps,
|
||||
SortPage
|
||||
} from "../../../types";
|
||||
import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList";
|
||||
import ProductTypeList from "../ProductTypeList";
|
||||
|
@ -21,6 +23,7 @@ export interface ProductTypeListPageProps
|
|||
extends PageListProps,
|
||||
ListActions,
|
||||
SearchPageProps,
|
||||
SortPage<ProductTypeListUrlSortField>,
|
||||
TabPageProps {
|
||||
productTypes: ProductTypeList_productTypes_edges_node[];
|
||||
onBack: () => void;
|
||||
|
|
|
@ -4,13 +4,15 @@ import { useIntl } from "react-intl";
|
|||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { asSortParams } from "@saleor/utils/sort";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
productTypeAddPath,
|
||||
productTypeListPath,
|
||||
ProductTypeListUrlQueryParams,
|
||||
productTypePath,
|
||||
ProductTypeUrlQueryParams
|
||||
ProductTypeUrlQueryParams,
|
||||
ProductTypeListUrlSortField
|
||||
} from "./urls";
|
||||
import ProductTypeCreate from "./views/ProductTypeCreate";
|
||||
import ProductTypeListComponent from "./views/ProductTypeList";
|
||||
|
@ -18,16 +20,19 @@ import ProductTypeUpdateComponent from "./views/ProductTypeUpdate";
|
|||
|
||||
const ProductTypeList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: ProductTypeListUrlQueryParams = qs;
|
||||
const params: ProductTypeListUrlQueryParams = asSortParams(
|
||||
qs,
|
||||
ProductTypeListUrlSortField
|
||||
);
|
||||
return <ProductTypeListComponent params={params} />;
|
||||
};
|
||||
|
||||
interface ProductTypeUpdateRouteParams {
|
||||
id: string;
|
||||
}
|
||||
const ProductTypeUpdate: React.FC<
|
||||
RouteComponentProps<ProductTypeUpdateRouteParams>
|
||||
> = ({ match }) => {
|
||||
const ProductTypeUpdate: React.FC<RouteComponentProps<
|
||||
ProductTypeUpdateRouteParams
|
||||
>> = ({ match }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: ProductTypeUrlQueryParams = qs;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { attributeFragment } from "@saleor/attributes/queries";
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import { pageInfoFragment, TypedQuery } from "../queries";
|
||||
import { ProductTypeCreateData } from "./types/ProductTypeCreateData";
|
||||
import {
|
||||
|
@ -52,6 +53,7 @@ export const productTypeListQuery = gql`
|
|||
$first: Int
|
||||
$last: Int
|
||||
$filter: ProductTypeFilterInput
|
||||
$sort: ProductTypeSortingInput
|
||||
) {
|
||||
productTypes(
|
||||
after: $after
|
||||
|
@ -59,6 +61,7 @@ export const productTypeListQuery = gql`
|
|||
first: $first
|
||||
last: $last
|
||||
filter: $filter
|
||||
sortBy: $sort
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
|
@ -71,7 +74,7 @@ export const productTypeListQuery = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductTypeListQuery = TypedQuery<
|
||||
export const useProductTypeListQuery = makeQuery<
|
||||
ProductTypeList,
|
||||
ProductTypeListVariables
|
||||
>(productTypeListQuery);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue