Add search bar to vouchers

This commit is contained in:
dominik-zeglen 2019-09-12 12:06:28 +02:00
parent d5d795ddcc
commit 8312366e73
9 changed files with 364 additions and 198 deletions

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -98,163 +97,155 @@ const VoucherList = withStyles(styles, {
toggleAll, toggleAll,
toolbar toolbar
}: VoucherListProps & WithStyles<typeof styles>) => ( }: VoucherListProps & WithStyles<typeof styles>) => (
<Card> <Table>
<Table> <TableHead
<TableHead colSpan={numberOfColumns}
colSpan={numberOfColumns} selected={selected}
selected={selected} disabled={disabled}
disabled={disabled} items={vouchers}
items={vouchers} toggleAll={toggleAll}
toggleAll={toggleAll} toolbar={toolbar}
toolbar={toolbar} >
> <TableCell className={classes.colName}>
<TableCell className={classes.colName}> <FormattedMessage defaultMessage="Code" description="voucher code" />
<FormattedMessage </TableCell>
defaultMessage="Code" <TableCell className={classes.colMinSpent}>
description="voucher code" <FormattedMessage
/> defaultMessage="Min. Spent"
</TableCell> description="minimum amount of spent money to activate voucher"
<TableCell className={classes.colMinSpent}> />
<FormattedMessage </TableCell>
defaultMessage="Min. Spent" <TableCell className={classes.colStart}>
description="minimum amount of spent money to activate voucher" <FormattedMessage
/> defaultMessage="Starts"
</TableCell> description="voucher is active from date"
<TableCell className={classes.colStart}> />
<FormattedMessage </TableCell>
defaultMessage="Starts" <TableCell className={classes.colEnd}>
description="voucher is active from date" <FormattedMessage
/> defaultMessage="Ends"
</TableCell> description="voucher is active until date"
<TableCell className={classes.colEnd}> />
<FormattedMessage </TableCell>
defaultMessage="Ends" <TableCell className={classes.colValue}>
description="voucher is active until date" <FormattedMessage
/> defaultMessage="Value"
</TableCell> description="voucher value"
<TableCell className={classes.colValue}> />
<FormattedMessage </TableCell>
defaultMessage="Value" <TableCell className={classes.colUses}>
description="voucher value" <FormattedMessage defaultMessage="Uses" description="voucher uses" />
/> </TableCell>
</TableCell> </TableHead>
<TableCell className={classes.colUses}> <TableFooter>
<FormattedMessage <TableRow>
defaultMessage="Uses" <TablePagination
description="voucher uses" colSpan={numberOfColumns}
/> settings={settings}
</TableCell> hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
</TableHead> onNextPage={onNextPage}
<TableFooter> onUpdateListSettings={onUpdateListSettings}
<TableRow> hasPreviousPage={
<TablePagination pageInfo && !disabled ? pageInfo.hasPreviousPage : false
colSpan={numberOfColumns} }
settings={settings} onPreviousPage={onPreviousPage}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false} />
onNextPage={onNextPage} </TableRow>
onUpdateListSettings={onUpdateListSettings} </TableFooter>
hasPreviousPage={ <TableBody>
pageInfo && !disabled ? pageInfo.hasPreviousPage : false {renderCollection(
} vouchers,
onPreviousPage={onPreviousPage} voucher => {
/> const isSelected = voucher ? isChecked(voucher.id) : false;
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
vouchers,
voucher => {
const isSelected = voucher ? isChecked(voucher.id) : false;
return ( return (
<TableRow <TableRow
className={!!voucher ? classes.tableRow : undefined} className={!!voucher ? classes.tableRow : undefined}
hover={!!voucher} hover={!!voucher}
key={voucher ? voucher.id : "skeleton"} key={voucher ? voucher.id : "skeleton"}
selected={isSelected} selected={isSelected}
onClick={voucher ? onRowClick(voucher.id) : undefined}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(voucher.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{maybe<React.ReactNode>(() => voucher.code, <Skeleton />)}
</TableCell>
<TableCell className={classes.colMinSpent}>
{voucher && voucher.minAmountSpent ? (
<Money money={voucher.minAmountSpent} />
) : voucher && voucher.minAmountSpent === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colStart}>
{voucher && voucher.startDate ? (
<Date date={voucher.startDate} />
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colEnd}>
{voucher && voucher.endDate ? (
<Date date={voucher.endDate} />
) : voucher && voucher.endDate === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colValue}
onClick={voucher ? onRowClick(voucher.id) : undefined} onClick={voucher ? onRowClick(voucher.id) : undefined}
> >
<TableCell padding="checkbox"> {voucher &&
<Checkbox voucher.discountValueType &&
checked={isSelected} voucher.discountValue ? (
disabled={disabled} voucher.discountValueType ===
disableClickPropagation DiscountValueTypeEnum.FIXED ? (
onChange={() => toggle(voucher.id)} <Money
/> money={{
</TableCell> amount: voucher.discountValue,
<TableCell className={classes.colName}> currency: defaultCurrency
{maybe<React.ReactNode>(() => voucher.code, <Skeleton />)} }}
</TableCell> />
<TableCell className={classes.colMinSpent}>
{voucher && voucher.minAmountSpent ? (
<Money money={voucher.minAmountSpent} />
) : voucher && voucher.minAmountSpent === null ? (
"-"
) : ( ) : (
<Skeleton /> <Percent amount={voucher.discountValue} />
)} )
</TableCell> ) : (
<TableCell className={classes.colStart}> <Skeleton />
{voucher && voucher.startDate ? ( )}
<Date date={voucher.startDate} /> </TableCell>
) : ( <TableCell className={classes.colUses}>
<Skeleton /> {voucher && voucher.usageLimit ? (
)} voucher.usageLimit
</TableCell> ) : voucher && voucher.usageLimit === null ? (
<TableCell className={classes.colEnd}> "-"
{voucher && voucher.endDate ? ( ) : (
<Date date={voucher.endDate} /> <Skeleton />
) : voucher && voucher.endDate === null ? ( )}
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colValue}
onClick={voucher ? onRowClick(voucher.id) : undefined}
>
{voucher &&
voucher.discountValueType &&
voucher.discountValue ? (
voucher.discountValueType ===
DiscountValueTypeEnum.FIXED ? (
<Money
money={{
amount: voucher.discountValue,
currency: defaultCurrency
}}
/>
) : (
<Percent amount={voucher.discountValue} />
)
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colUses}>
{voucher && voucher.usageLimit ? (
voucher.usageLimit
) : voucher && voucher.usageLimit === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No vouchers found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) );
)} },
</TableBody> () => (
</Table> <TableRow>
</Card> <TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No vouchers found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
) )
); );
VoucherList.displayName = "VoucherList"; VoucherList.displayName = "VoucherList";

View file

@ -1,36 +1,41 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import { VoucherList_vouchers_edges_node } from "../../types/VoucherList"; import { VoucherList_vouchers_edges_node } from "../../types/VoucherList";
import VoucherList from "../VoucherList"; import VoucherList from "../VoucherList";
export interface VoucherListPageProps extends PageListProps, ListActions { export interface VoucherListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
defaultCurrency: string; defaultCurrency: string;
vouchers: VoucherList_vouchers_edges_node[]; vouchers: VoucherList_vouchers_edges_node[];
} }
const VoucherListPage: React.StatelessComponent<VoucherListPageProps> = ({ const VoucherListPage: React.StatelessComponent<VoucherListPageProps> = ({
defaultCurrency, currentTab,
disabled, initialSearch,
settings,
onAdd, onAdd,
onNextPage, onAll,
onPreviousPage, onSearchChange,
onUpdateListSettings, onTabChange,
onRowClick, onTabDelete,
pageInfo, onTabSave,
vouchers, tabs,
isChecked, ...listProps
selected,
toggle,
toggleAll,
toolbar
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -44,22 +49,26 @@ const VoucherListPage: React.StatelessComponent<VoucherListPageProps> = ({
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<VoucherList <Card>
defaultCurrency={defaultCurrency} <SearchBar
settings={settings} allTabLabel={intl.formatMessage({
disabled={disabled} defaultMessage: "All Vouchers",
onNextPage={onNextPage} description: "tab name"
onPreviousPage={onPreviousPage} })}
onUpdateListSettings={onUpdateListSettings} currentTab={currentTab}
onRowClick={onRowClick} initialSearch={initialSearch}
pageInfo={pageInfo} searchPlaceholder={intl.formatMessage({
vouchers={vouchers} defaultMessage: "Search Voucher"
isChecked={isChecked} })}
selected={selected} tabs={tabs}
toggle={toggle} onAll={onAll}
toggleAll={toggleAll} onSearchChange={onSearchChange}
toolbar={toolbar} onTabChange={onTabChange}
/> onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<VoucherList {...listProps} />
</Card>
</Container> </Container>
); );
}; };

View file

@ -196,8 +196,20 @@ export const TypedSaleList = TypedQuery<SaleList, SaleListVariables>(saleList);
export const voucherList = gql` export const voucherList = gql`
${pageInfoFragment} ${pageInfoFragment}
${voucherFragment} ${voucherFragment}
query VoucherList($after: String, $before: String, $first: Int, $last: Int) { query VoucherList(
vouchers(after: $after, before: $before, first: $first, last: $last) { $after: String
$before: String
$first: Int
$last: Int
$filter: VoucherFilterInput
) {
vouchers(
after: $after
before: $before
first: $first
last: $last
filter: $filter
) {
edges { edges {
node { node {
...VoucherFragment ...VoucherFragment

View file

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

View file

@ -48,10 +48,16 @@ export const saleAddUrl = saleAddPath;
export const voucherSection = urlJoin(discountSection, "vouchers"); export const voucherSection = urlJoin(discountSection, "vouchers");
export const voucherListPath = voucherSection; export const voucherListPath = voucherSection;
export type VoucherListUrlDialog = "remove"; export enum VoucherListUrlFiltersEnum {
export type VoucherListUrlQueryParams = BulkAction & query = "query"
}
export type VoucherListUrlFilters = Filters<VoucherListUrlFiltersEnum>;
export type VoucherListUrlDialog = "remove" | TabActionDialog;
export type VoucherListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<VoucherListUrlDialog> & Dialog<VoucherListUrlDialog> &
Pagination; Pagination &
VoucherListUrlFilters;
export const voucherListUrl = (params?: VoucherListUrlQueryParams) => export const voucherListUrl = (params?: VoucherListUrlQueryParams) =>
voucherListPath + "?" + stringifyQs(params); voucherListPath + "?" + stringifyQs(params);
export const voucherPath = (id: string) => urlJoin(voucherSection, id); export const voucherPath = (id: string) => urlJoin(voucherSection, id);

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
@ -17,16 +21,26 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl"; import { commonMessages, sectionNames } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import VoucherListPage from "../components/VoucherListPage"; import VoucherListPage from "../../components/VoucherListPage";
import { TypedVoucherBulkDelete } from "../mutations"; import { TypedVoucherBulkDelete } from "../../mutations";
import { TypedVoucherList } from "../queries"; import { TypedVoucherList } from "../../queries";
import { VoucherBulkDelete } from "../types/VoucherBulkDelete"; import { VoucherBulkDelete } from "../../types/VoucherBulkDelete";
import { import {
voucherAddUrl, voucherAddUrl,
voucherListUrl, voucherListUrl,
VoucherListUrlDialog,
VoucherListUrlFilters,
VoucherListUrlQueryParams, VoucherListUrlQueryParams,
voucherUrl voucherUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface VoucherListProps { interface VoucherListProps {
params: VoucherListUrlQueryParams; params: VoucherListUrlQueryParams;
@ -47,13 +61,78 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const closeModal = () => navigate(voucherListUrl(), true); const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: VoucherListUrlFilters) => {
reset();
navigate(
voucherListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () =>
navigate(
voucherListUrl({
...params,
action: undefined,
ids: undefined
})
);
const openModal = (action: VoucherListUrlDialog, ids?: string[]) =>
navigate(
voucherListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
voucherListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(voucherListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0); const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
return ( return (
<TypedVoucherList displayLoader variables={paginationState}> <TypedVoucherList displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.vouchers.pageInfo), maybe(() => data.vouchers.pageInfo),
@ -92,6 +171,14 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
title={intl.formatMessage(sectionNames.vouchers)} title={intl.formatMessage(sectionNames.vouchers)}
/> />
<VoucherListPage <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)} defaultCurrency={maybe(() => shop.defaultCurrency)}
settings={settings} settings={settings}
vouchers={maybe(() => vouchers={maybe(() =>
@ -153,6 +240,19 @@ export const VoucherList: React.StatelessComponent<VoucherListProps> = ({
</DialogContentText> </DialogContentText>
)} )}
</ActionDialog> </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, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { VoucherFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
VoucherListUrlFilters,
VoucherListUrlFiltersEnum,
VoucherListUrlQueryParams
} from "../../urls";
export const VOUCHER_FILTERS_KEY = "VoucherFilters";
export function getFilterVariables(
params: VoucherListUrlFilters
): VoucherFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<VoucherListUrlFilters>(VOUCHER_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
VoucherListUrlQueryParams,
VoucherListUrlFilters
>(VoucherListUrlFiltersEnum);

View file

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

View file

@ -230,6 +230,12 @@ export enum TaxRateType {
WINE = "WINE", WINE = "WINE",
} }
export enum VoucherDiscountType {
FIXED = "FIXED",
PERCENTAGE = "PERCENTAGE",
SHIPPING = "SHIPPING",
}
export enum VoucherTypeEnum { export enum VoucherTypeEnum {
ENTIRE_ORDER = "ENTIRE_ORDER", ENTIRE_ORDER = "ENTIRE_ORDER",
SHIPPING = "SHIPPING", SHIPPING = "SHIPPING",
@ -673,6 +679,14 @@ export interface UserCreateInput {
redirectUrl?: string | null; redirectUrl?: string | null;
} }
export interface VoucherFilterInput {
status?: (DiscountStatusEnum | null)[] | null;
timesUsed?: IntRangeInput | null;
discountType?: (VoucherDiscountType | null)[] | null;
started?: DateTimeRangeInput | null;
search?: string | null;
}
export interface VoucherInput { export interface VoucherInput {
type?: VoucherTypeEnum | null; type?: VoucherTypeEnum | null;
name?: string | null; name?: string | null;