Add sorting to lists

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

View file

@ -21,6 +21,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Enforce using "name" property in style hooks - #288 by @dominik-zeglen
- 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

View file

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

View file

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

View file

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

View file

@ -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} />;
};

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,26 @@
import { AttributeListUrlSortField } from "@saleor/attributes/urls";
import { AttributeSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(
sort: AttributeListUrlSortField
): AttributeSortField {
switch (sort) {
case AttributeListUrlSortField.name:
return AttributeSortField.NAME;
case AttributeListUrlSortField.slug:
return AttributeSortField.SLUG;
case AttributeListUrlSortField.searchable:
return AttributeSortField.FILTERABLE_IN_DASHBOARD;
case AttributeListUrlSortField.useInFacetedSearch:
return AttributeSortField.FILTERABLE_IN_STOREFRONT;
case AttributeListUrlSortField.visible:
return AttributeSortField.VISIBLE_IN_STOREFRONT;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -13,7 +13,10 @@ import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead";
import 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>

View file

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

View file

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

View file

@ -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} />;
};

View file

@ -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 {

View file

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

View file

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

View file

@ -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}

View file

@ -0,0 +1,22 @@
import { CategoryListUrlSortField } from "@saleor/categories/urls";
import { CategorySortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(
sort: CategoryListUrlSortField
): CategorySortField {
switch (sort) {
case CategoryListUrlSortField.name:
return CategorySortField.NAME;
case CategoryListUrlSortField.productCount:
return CategorySortField.PRODUCT_COUNT;
case CategoryListUrlSortField.subcategoryCount:
return CategorySortField.SUBCATEGORY_COUNT;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -13,7 +13,10 @@ import StatusLabel from "@saleor/components/StatusLabel";
import TableHead from "@saleor/components/TableHead";
import 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>

View file

@ -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[];
}

View file

@ -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 (

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,22 @@
import { CollectionListUrlSortField } from "@saleor/collections/urls";
import { CollectionSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(
sort: CollectionListUrlSortField
): CollectionSortField {
switch (sort) {
case CollectionListUrlSortField.name:
return CollectionSortField.NAME;
case CollectionListUrlSortField.available:
return CollectionSortField.AVAILABILITY;
case CollectionListUrlSortField.productCount:
return CollectionSortField.PRODUCT_COUNT;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -11,7 +11,7 @@ import { sectionNames } from "@saleor/intl";
import { menuListUrl } from "@saleor/navigation/urls";
import { 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),

View file

@ -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,

View file

@ -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({

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,11 +15,17 @@ import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead";
import 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>

View file

@ -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[];

View file

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

View file

@ -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[];

View file

@ -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} />;
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,9 +21,11 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl";
import { 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;

View file

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

View file

@ -348,9 +348,22 @@ export function findInEnum<TEnum extends object>(
throw new Error(`Key ${needle} not found in enum`);
}
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";
}

View file

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

View file

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

View file

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

View file

@ -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}

View file

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

View file

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

View file

@ -1,259 +0,0 @@
import Button from "@material-ui/core/Button";
import DialogContentText from "@material-ui/core/DialogContentText";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog";
import { configurationMenuUrl } from "@saleor/configuration";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import { buttonMessages, commonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import MenuCreateDialog from "../components/MenuCreateDialog";
import MenuListPage from "../components/MenuListPage";
import {
MenuBulkDeleteMutation,
MenuCreateMutation,
MenuDeleteMutation
} from "../mutations";
import { MenuListQuery } from "../queries";
import { MenuBulkDelete } from "../types/MenuBulkDelete";
import { MenuCreate } from "../types/MenuCreate";
import { MenuDelete } from "../types/MenuDelete";
import { menuListUrl, MenuListUrlQueryParams, menuUrl } from "../urls";
interface MenuListProps {
params: MenuListUrlQueryParams;
}
const MenuList: React.FC<MenuListProps> = ({ params }) => {
const navigate = useNavigator();
const notify = useNotifier();
const paginate = usePaginator();
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids
);
const { updateListSettings, settings } = useListSettings(
ListViews.NAVIGATION_LIST
);
const intl = useIntl();
const closeModal = () =>
navigate(
menuListUrl({
...params,
action: undefined,
id: undefined,
ids: undefined
}),
true
);
const paginationState = createPaginationState(settings.rowNumber, params);
return (
<MenuListQuery variables={paginationState}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.menus.pageInfo),
paginationState,
params
);
const handleCreate = (data: MenuCreate) => {
if (data.menuCreate.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Created menu",
id: "menuListCreatedMenu"
})
});
navigate(menuUrl(data.menuCreate.menu.id));
}
};
const handleBulkDelete = (data: MenuBulkDelete) => {
if (data.menuBulkDelete.errors.length === 0) {
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
closeModal();
reset();
refetch();
}
};
const handleDelete = (data: MenuDelete) => {
if (data.menuDelete.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Deleted menu",
id: "menuListDeletedMenu"
})
});
closeModal();
refetch();
}
};
return (
<MenuCreateMutation onCompleted={handleCreate}>
{(menuCreate, menuCreateOpts) => (
<MenuDeleteMutation onCompleted={handleDelete}>
{(menuDelete, menuDeleteOpts) => (
<MenuBulkDeleteMutation onCompleted={handleBulkDelete}>
{(menuBulkDelete, menuBulkDeleteOpts) => (
<>
<MenuListPage
disabled={loading}
menus={maybe(() =>
data.menus.edges.map(edge => edge.node)
)}
settings={settings}
onAdd={() =>
navigate(
menuListUrl({
action: "add"
})
)
}
onBack={() => navigate(configurationMenuUrl)}
onDelete={id =>
navigate(
menuListUrl({
action: "remove",
id
})
)
}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(menuUrl(id))}
pageInfo={pageInfo}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<Button
color="primary"
onClick={() =>
navigate(
menuListUrl({
...params,
action: "remove-many",
ids: listElements
})
)
}
>
<FormattedMessage {...buttonMessages.remove} />
</Button>
}
/>
<MenuCreateDialog
open={params.action === "add"}
confirmButtonState={menuCreateOpts.status}
disabled={menuCreateOpts.loading}
onClose={closeModal}
onConfirm={formData =>
menuCreate({
variables: { input: formData }
})
}
/>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={menuDeleteOpts.status}
onConfirm={() =>
menuDelete({
variables: {
id: params.id
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Menu",
description: "dialog header",
id: "menuListDeleteMenuHeader"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {menuName}?"
id="menuListDeleteMenuContent"
values={{
menuName: maybe(
() =>
data.menus.edges.find(
edge => edge.node.id === params.id
).node.name,
"..."
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={
params.action === "remove-many" &&
maybe(() => params.ids.length > 0)
}
onClose={closeModal}
confirmButtonState={menuBulkDeleteOpts.status}
onConfirm={() =>
menuBulkDelete({
variables: {
ids: params.ids
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Menus",
description: "dialog header",
id: "menuListDeleteMenusHeader"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this menu} other{{displayQuantity} menus}}?"
id="menuListDeleteMenusContent"
values={{
counter: maybe(
() => params.ids.length.toString(),
"..."
),
displayQuantity: (
<strong>
{maybe(
() => params.ids.length.toString(),
"..."
)}
</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
</>
)}
</MenuBulkDeleteMutation>
)}
</MenuDeleteMutation>
)}
</MenuCreateMutation>
);
}}
</MenuListQuery>
);
};
export default MenuList;

View file

@ -0,0 +1,266 @@
import Button from "@material-ui/core/Button";
import DialogContentText from "@material-ui/core/DialogContentText";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog";
import { configurationMenuUrl } from "@saleor/configuration";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import { buttonMessages, commonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import MenuCreateDialog from "../../components/MenuCreateDialog";
import MenuListPage from "../../components/MenuListPage";
import {
MenuBulkDeleteMutation,
MenuCreateMutation,
MenuDeleteMutation
} from "../../mutations";
import { useMenuListQuery } from "../../queries";
import { MenuBulkDelete } from "../../types/MenuBulkDelete";
import { MenuCreate } from "../../types/MenuCreate";
import { MenuDelete } from "../../types/MenuDelete";
import { menuListUrl, MenuListUrlQueryParams, menuUrl } from "../../urls";
import { getSortQueryVariables } from "./sort";
interface MenuListProps {
params: MenuListUrlQueryParams;
}
const MenuList: React.FC<MenuListProps> = ({ params }) => {
const navigate = useNavigator();
const notify = useNotifier();
const paginate = usePaginator();
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids
);
const { updateListSettings, settings } = useListSettings(
ListViews.NAVIGATION_LIST
);
const intl = useIntl();
const closeModal = () =>
navigate(
menuListUrl({
...params,
action: undefined,
id: undefined,
ids: undefined
}),
true
);
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
sort: getSortQueryVariables(params)
}),
[params]
);
const { data, loading, refetch } = useMenuListQuery({
displayLoader: true,
variables: queryVariables
});
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.menus.pageInfo),
paginationState,
params
);
const handleCreate = (data: MenuCreate) => {
if (data.menuCreate.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Created menu",
id: "menuListCreatedMenu"
})
});
navigate(menuUrl(data.menuCreate.menu.id));
}
};
const handleBulkDelete = (data: MenuBulkDelete) => {
if (data.menuBulkDelete.errors.length === 0) {
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
closeModal();
reset();
refetch();
}
};
const handleDelete = (data: MenuDelete) => {
if (data.menuDelete.errors.length === 0) {
notify({
text: intl.formatMessage({
defaultMessage: "Deleted menu",
id: "menuListDeletedMenu"
})
});
closeModal();
refetch();
}
};
const handleSort = createSortHandler(navigate, menuListUrl, params);
return (
<MenuCreateMutation onCompleted={handleCreate}>
{(menuCreate, menuCreateOpts) => (
<MenuDeleteMutation onCompleted={handleDelete}>
{(menuDelete, menuDeleteOpts) => (
<MenuBulkDeleteMutation onCompleted={handleBulkDelete}>
{(menuBulkDelete, menuBulkDeleteOpts) => (
<>
<MenuListPage
disabled={loading}
menus={maybe(() => data.menus.edges.map(edge => edge.node))}
settings={settings}
onAdd={() =>
navigate(
menuListUrl({
action: "add"
})
)
}
onBack={() => navigate(configurationMenuUrl)}
onDelete={id =>
navigate(
menuListUrl({
action: "remove",
id
})
)
}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(menuUrl(id))}
onSort={handleSort}
pageInfo={pageInfo}
isChecked={isSelected}
selected={listElements.length}
sort={getSortParams(params)}
toggle={toggle}
toggleAll={toggleAll}
toolbar={
<Button
color="primary"
onClick={() =>
navigate(
menuListUrl({
...params,
action: "remove-many",
ids: listElements
})
)
}
>
<FormattedMessage {...buttonMessages.remove} />
</Button>
}
/>
<MenuCreateDialog
open={params.action === "add"}
confirmButtonState={menuCreateOpts.status}
disabled={menuCreateOpts.loading}
onClose={closeModal}
onConfirm={formData =>
menuCreate({
variables: { input: formData }
})
}
/>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={menuDeleteOpts.status}
onConfirm={() =>
menuDelete({
variables: {
id: params.id
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Menu",
description: "dialog header",
id: "menuListDeleteMenuHeader"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {menuName}?"
id="menuListDeleteMenuContent"
values={{
menuName: maybe(
() =>
data.menus.edges.find(
edge => edge.node.id === params.id
).node.name,
"..."
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={
params.action === "remove-many" &&
maybe(() => params.ids.length > 0)
}
onClose={closeModal}
confirmButtonState={menuBulkDeleteOpts.status}
onConfirm={() =>
menuBulkDelete({
variables: {
ids: params.ids
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Menus",
description: "dialog header",
id: "menuListDeleteMenusHeader"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this menu} other{{displayQuantity} menus}}?"
id="menuListDeleteMenusContent"
values={{
counter: maybe(
() => params.ids.length.toString(),
"..."
),
displayQuantity: (
<strong>
{maybe(() => params.ids.length.toString(), "...")}
</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
</>
)}
</MenuBulkDeleteMutation>
)}
</MenuDeleteMutation>
)}
</MenuCreateMutation>
);
};
export default MenuList;

View file

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

View file

@ -0,0 +1,18 @@
import { MenuListUrlSortField } from "@saleor/navigation/urls";
import { MenuSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(sort: MenuListUrlSortField): MenuSortField {
switch (sort) {
case MenuListUrlSortField.name:
return MenuSortField.NAME;
case MenuListUrlSortField.items:
return MenuSortField.ITEMS_COUNT;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -19,7 +19,10 @@ import {
transformOrderStatus,
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>

View file

@ -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[];
}

View file

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

View file

@ -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[];
}

View file

@ -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} />;
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,24 @@
import { OrderDraftListUrlSortField } from "@saleor/orders/urls";
import { OrderSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(
sort: OrderDraftListUrlSortField
): OrderSortField {
switch (sort) {
case OrderDraftListUrlSortField.number:
return OrderSortField.NUMBER;
case OrderDraftListUrlSortField.date:
return OrderSortField.CREATION_DATE;
case OrderDraftListUrlSortField.customer:
return OrderSortField.CUSTOMER;
case OrderDraftListUrlSortField.total:
return OrderSortField.TOTAL;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -17,13 +17,15 @@ import usePaginator, {
import useShop from "@saleor/hooks/useShop";
import { 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>
);
};

View file

@ -0,0 +1,26 @@
import { OrderListUrlSortField } from "@saleor/orders/urls";
import { OrderSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(sort: OrderListUrlSortField): OrderSortField {
switch (sort) {
case OrderListUrlSortField.number:
return OrderSortField.NUMBER;
case OrderListUrlSortField.date:
return OrderSortField.CREATION_DATE;
case OrderListUrlSortField.customer:
return OrderSortField.CUSTOMER;
case OrderListUrlSortField.total:
return OrderSortField.TOTAL;
case OrderListUrlSortField.fulfillment:
return OrderSortField.FULFILLMENT_STATUS;
case OrderListUrlSortField.payment:
return OrderSortField.PAYMENT;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -14,10 +14,16 @@ import StatusLabel from "@saleor/components/StatusLabel";
import TableHead from "@saleor/components/TableHead";
import 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>
);
},

View file

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

View file

@ -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} />;
};

View file

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

View file

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

View file

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

View file

@ -1,255 +0,0 @@
import Button from "@material-ui/core/Button";
import DialogContentText from "@material-ui/core/DialogContentText";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog";
import { configurationMenuUrl } from "@saleor/configuration";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import PageListPage from "../components/PageListPage/PageListPage";
import { TypedPageBulkPublish, TypedPageBulkRemove } from "../mutations";
import { TypedPageListQuery } from "../queries";
import { PageBulkPublish } from "../types/PageBulkPublish";
import { PageBulkRemove } from "../types/PageBulkRemove";
import {
pageCreateUrl,
pageListUrl,
PageListUrlDialog,
PageListUrlQueryParams,
pageUrl
} from "../urls";
interface PageListProps {
params: PageListUrlQueryParams;
}
export const PageList: React.FC<PageListProps> = ({ params }) => {
const navigate = useNavigator();
const notify = useNotifier();
const paginate = usePaginator();
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids
);
const { updateListSettings, settings } = useListSettings(
ListViews.PAGES_LIST
);
const intl = useIntl();
const paginationState = createPaginationState(settings.rowNumber, params);
return (
<TypedPageListQuery displayLoader variables={paginationState}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.pages.pageInfo),
paginationState,
params
);
const closeModal = () =>
navigate(
pageListUrl({
...params,
action: undefined,
ids: undefined
}),
true
);
const openModal = (action: PageListUrlDialog, ids: string[]) =>
navigate(
pageListUrl({
...params,
action,
ids
})
);
const handlePageBulkPublish = (data: PageBulkPublish) => {
if (data.pageBulkPublish.errors.length === 0) {
closeModal();
notify({
text: intl.formatMessage({
defaultMessage: "Published pages",
description: "notification"
})
});
reset();
refetch();
}
};
const handlePageBulkRemove = (data: PageBulkRemove) => {
if (data.pageBulkDelete.errors.length === 0) {
closeModal();
notify({
text: intl.formatMessage({
defaultMessage: "Removed pages",
description: "notification"
})
});
reset();
refetch();
}
};
return (
<TypedPageBulkRemove onCompleted={handlePageBulkRemove}>
{(bulkPageRemove, bulkPageRemoveOpts) => (
<TypedPageBulkPublish onCompleted={handlePageBulkPublish}>
{(bulkPagePublish, bulkPagePublishOpts) => (
<>
<PageListPage
disabled={loading}
settings={settings}
pages={maybe(() =>
data.pages.edges.map(edge => edge.node)
)}
pageInfo={pageInfo}
onAdd={() => navigate(pageCreateUrl)}
onBack={() => navigate(configurationMenuUrl)}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(pageUrl(id))}
toolbar={
<>
<Button
color="primary"
onClick={() => openModal("unpublish", listElements)}
>
<FormattedMessage
defaultMessage="Unpublish"
description="unpublish page, button"
/>
</Button>
<Button
color="primary"
onClick={() => openModal("publish", listElements)}
>
<FormattedMessage
defaultMessage="Publish"
description="publish page, button"
/>
</Button>
<IconButton
color="primary"
onClick={() => openModal("remove", listElements)}
>
<DeleteIcon />
</IconButton>
</>
}
isChecked={isSelected}
selected={listElements.length}
toggle={toggle}
toggleAll={toggleAll}
/>
<ActionDialog
open={params.action === "publish"}
onClose={closeModal}
confirmButtonState={bulkPagePublishOpts.status}
onConfirm={() =>
bulkPagePublish({
variables: {
ids: params.ids,
isPublished: true
}
})
}
title={intl.formatMessage({
defaultMessage: "Publish Pages",
description: "dialog header"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to publish {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={params.action === "unpublish"}
onClose={closeModal}
confirmButtonState={bulkPagePublishOpts.status}
onConfirm={() =>
bulkPagePublish({
variables: {
ids: params.ids,
isPublished: false
}
})
}
title={intl.formatMessage({
defaultMessage: "Unpublish Pages",
description: "dialog header"
})}
>
<FormattedMessage
defaultMessage="Are you sure you want to unpublish {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</ActionDialog>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={bulkPageRemoveOpts.status}
onConfirm={() =>
bulkPageRemove({
variables: {
ids: params.ids
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Pages",
description: "dialog header"
})}
>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</ActionDialog>
</>
)}
</TypedPageBulkPublish>
)}
</TypedPageBulkRemove>
);
}}
</TypedPageListQuery>
);
};
export default PageList;

View file

@ -0,0 +1,265 @@
import Button from "@material-ui/core/Button";
import DialogContentText from "@material-ui/core/DialogContentText";
import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog";
import { configurationMenuUrl } from "@saleor/configuration";
import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import PageListPage from "../../components/PageListPage/PageListPage";
import { TypedPageBulkPublish, TypedPageBulkRemove } from "../../mutations";
import { usePageListQuery } from "../../queries";
import { PageBulkPublish } from "../../types/PageBulkPublish";
import { PageBulkRemove } from "../../types/PageBulkRemove";
import {
pageCreateUrl,
pageListUrl,
PageListUrlDialog,
PageListUrlQueryParams,
pageUrl
} from "../../urls";
import { getSortQueryVariables } from "./sort";
interface PageListProps {
params: PageListUrlQueryParams;
}
export const PageList: React.FC<PageListProps> = ({ params }) => {
const navigate = useNavigator();
const notify = useNotifier();
const paginate = usePaginator();
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids
);
const { updateListSettings, settings } = useListSettings(
ListViews.PAGES_LIST
);
const intl = useIntl();
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
sort: getSortQueryVariables(params)
}),
[params]
);
const { data, loading, refetch } = usePageListQuery({
displayLoader: true,
variables: queryVariables
});
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.pages.pageInfo),
paginationState,
params
);
const closeModal = () =>
navigate(
pageListUrl({
...params,
action: undefined,
ids: undefined
}),
true
);
const openModal = (action: PageListUrlDialog, ids: string[]) =>
navigate(
pageListUrl({
...params,
action,
ids
})
);
const handlePageBulkPublish = (data: PageBulkPublish) => {
if (data.pageBulkPublish.errors.length === 0) {
closeModal();
notify({
text: intl.formatMessage({
defaultMessage: "Published pages",
description: "notification"
})
});
reset();
refetch();
}
};
const handlePageBulkRemove = (data: PageBulkRemove) => {
if (data.pageBulkDelete.errors.length === 0) {
closeModal();
notify({
text: intl.formatMessage({
defaultMessage: "Removed pages",
description: "notification"
})
});
reset();
refetch();
}
};
const handleSort = createSortHandler(navigate, pageListUrl, params);
return (
<TypedPageBulkRemove onCompleted={handlePageBulkRemove}>
{(bulkPageRemove, bulkPageRemoveOpts) => (
<TypedPageBulkPublish onCompleted={handlePageBulkPublish}>
{(bulkPagePublish, bulkPagePublishOpts) => (
<>
<PageListPage
disabled={loading}
settings={settings}
pages={maybe(() => data.pages.edges.map(edge => edge.node))}
pageInfo={pageInfo}
onAdd={() => navigate(pageCreateUrl)}
onBack={() => navigate(configurationMenuUrl)}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(pageUrl(id))}
onSort={handleSort}
toolbar={
<>
<Button
color="primary"
onClick={() => openModal("unpublish", listElements)}
>
<FormattedMessage
defaultMessage="Unpublish"
description="unpublish page, button"
/>
</Button>
<Button
color="primary"
onClick={() => openModal("publish", listElements)}
>
<FormattedMessage
defaultMessage="Publish"
description="publish page, button"
/>
</Button>
<IconButton
color="primary"
onClick={() => openModal("remove", listElements)}
>
<DeleteIcon />
</IconButton>
</>
}
isChecked={isSelected}
selected={listElements.length}
sort={getSortParams(params)}
toggle={toggle}
toggleAll={toggleAll}
/>
<ActionDialog
open={params.action === "publish"}
onClose={closeModal}
confirmButtonState={bulkPagePublishOpts.status}
onConfirm={() =>
bulkPagePublish({
variables: {
ids: params.ids,
isPublished: true
}
})
}
title={intl.formatMessage({
defaultMessage: "Publish Pages",
description: "dialog header"
})}
>
<DialogContentText>
<FormattedMessage
defaultMessage="Are you sure you want to publish {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</DialogContentText>
</ActionDialog>
<ActionDialog
open={params.action === "unpublish"}
onClose={closeModal}
confirmButtonState={bulkPagePublishOpts.status}
onConfirm={() =>
bulkPagePublish({
variables: {
ids: params.ids,
isPublished: false
}
})
}
title={intl.formatMessage({
defaultMessage: "Unpublish Pages",
description: "dialog header"
})}
>
<FormattedMessage
defaultMessage="Are you sure you want to unpublish {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</ActionDialog>
<ActionDialog
open={params.action === "remove"}
onClose={closeModal}
confirmButtonState={bulkPageRemoveOpts.status}
onConfirm={() =>
bulkPageRemove({
variables: {
ids: params.ids
}
})
}
variant="delete"
title={intl.formatMessage({
defaultMessage: "Delete Pages",
description: "dialog header"
})}
>
<FormattedMessage
defaultMessage="Are you sure you want to delete {counter,plural,one{this page} other{{displayQuantity} pages}}?"
description="dialog content"
values={{
counter: maybe(() => params.ids.length),
displayQuantity: (
<strong>{maybe(() => params.ids.length)}</strong>
)
}}
/>
</ActionDialog>
</>
)}
</TypedPageBulkPublish>
)}
</TypedPageBulkRemove>
);
};
export default PageList;

View file

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

View file

@ -0,0 +1,20 @@
import { PageListUrlSortField } from "@saleor/pages/urls";
import { PageSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(sort: PageListUrlSortField): PageSortField {
switch (sort) {
case PageListUrlSortField.title:
return PageSortField.TITLE;
case PageListUrlSortField.visible:
return PageSortField.VISIBILITY;
case PageListUrlSortField.slug:
return PageSortField.SLUG;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -15,10 +15,15 @@ import StatusLabel from "@saleor/components/StatusLabel";
import TablePagination from "@saleor/components/TablePagination";
import { 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",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,74 @@
import { configurationMenuUrl } from "@saleor/configuration";
import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import React from "react";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import PluginsListPage from "../../components/PluginsListPage/PluginsListPage";
import { usePluginsListQuery } from "../../queries";
import {
PluginListUrlQueryParams,
pluginListUrl,
pluginsUrl
} from "../../urls";
import { getSortQueryVariables } from "./sort";
interface PluginsListProps {
params: PluginListUrlQueryParams;
}
export const PluginsList: React.FC<PluginsListProps> = ({ params }) => {
const navigate = useNavigator();
const paginate = usePaginator();
const { updateListSettings, settings } = useListSettings(
ListViews.PLUGINS_LIST
);
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
sort: getSortQueryVariables(params)
}),
[params]
);
const { data, loading } = usePluginsListQuery({
displayLoader: true,
variables: queryVariables
});
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.plugins.pageInfo),
paginationState,
params
);
const handleSort = createSortHandler(navigate, pluginListUrl, params);
return (
<>
<PluginsListPage
disabled={loading}
settings={settings}
plugins={maybe(() => data.plugins.edges.map(edge => edge.node))}
pageInfo={pageInfo}
sort={getSortParams(params)}
onAdd={() => navigate(configurationMenuUrl)}
onBack={() => navigate(configurationMenuUrl)}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onSort={handleSort}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(pluginsUrl(id))}
/>
</>
);
};
export default PluginsList;

View file

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

View file

@ -0,0 +1,20 @@
import { PluginListUrlSortField } from "@saleor/plugins/urls";
import { PluginSortField } from "@saleor/types/globalTypes";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(
sort: PluginListUrlSortField
): PluginSortField {
switch (sort) {
case PluginListUrlSortField.name:
return PluginSortField.NAME;
case PluginListUrlSortField.active:
return PluginSortField.IS_ACTIVE;
default:
return undefined;
}
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

View file

@ -16,16 +16,16 @@ import { TypedPluginsDetailsQuery } from "../queries";
import { Plugin_plugin_configuration } from "../types/Plugin";
import { 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 =>

View file

@ -1,56 +0,0 @@
import { configurationMenuUrl } from "@saleor/configuration";
import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator";
import usePaginator, {
createPaginationState
} from "@saleor/hooks/usePaginator";
import { maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import React from "react";
import PluginsListPage from "../components/PluginsListPage/PluginsListPage";
import { TypedPluginsListQuery } from "../queries";
import { PluginsListUrlQueryParams, pluginsUrl } from "../urls";
interface PluginsListProps {
params: PluginsListUrlQueryParams;
}
export const PluginsList: React.FC<PluginsListProps> = ({ params }) => {
const navigate = useNavigator();
const paginate = usePaginator();
const { updateListSettings, settings } = useListSettings(
ListViews.PLUGINS_LIST
);
const paginationState = createPaginationState(settings.rowNumber, params);
return (
<TypedPluginsListQuery displayLoader variables={paginationState}>
{({ data, loading }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.plugins.pageInfo),
paginationState,
params
);
return (
<>
<PluginsListPage
disabled={loading}
settings={settings}
plugins={maybe(() => data.plugins.edges.map(edge => edge.node))}
pageInfo={pageInfo}
onAdd={() => navigate(configurationMenuUrl)}
onBack={() => navigate(configurationMenuUrl)}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(pluginsUrl(id))}
/>
</>
);
}}
</TypedPluginsListQuery>
);
};
export default PluginsList;

View file

@ -12,8 +12,11 @@ import ResponsiveTable from "@saleor/components/ResponsiveTable";
import Skeleton from "@saleor/components/Skeleton";
import 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"

View file

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

View file

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

View file

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