Add search to sales

This commit is contained in:
dominik-zeglen 2019-09-11 17:52:02 +02:00
parent 41e64fe5e8
commit a18ff0fedc
9 changed files with 335 additions and 131 deletions

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import {
createStyles,
Theme,
@ -83,124 +82,119 @@ const SaleList = withStyles(styles, {
toggleAll,
toolbar
}: SaleListProps & WithStyles<typeof styles>) => (
<Card>
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={sales}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Name" description="sale name" />
</TableCell>
<TableCell className={classes.colStart}>
<FormattedMessage
defaultMessage="Starts"
description="sale start date"
/>
</TableCell>
<TableCell className={classes.colEnd}>
<FormattedMessage
defaultMessage="Ends"
description="sale end date"
/>
</TableCell>
<TableCell className={classes.colValue}>
<FormattedMessage defaultMessage="Value" description="sale value" />
</TableCell>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns}
settings={settings}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
}
onPreviousPage={onPreviousPage}
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
sales,
sale => {
const isSelected = sale ? isChecked(sale.id) : false;
<Table>
<TableHead
colSpan={numberOfColumns}
selected={selected}
disabled={disabled}
items={sales}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Name" description="sale name" />
</TableCell>
<TableCell className={classes.colStart}>
<FormattedMessage
defaultMessage="Starts"
description="sale start date"
/>
</TableCell>
<TableCell className={classes.colEnd}>
<FormattedMessage defaultMessage="Ends" description="sale end date" />
</TableCell>
<TableCell className={classes.colValue}>
<FormattedMessage defaultMessage="Value" description="sale value" />
</TableCell>
</TableHead>
<TableFooter>
<TableRow>
<TablePagination
colSpan={numberOfColumns}
settings={settings}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
}
onPreviousPage={onPreviousPage}
/>
</TableRow>
</TableFooter>
<TableBody>
{renderCollection(
sales,
sale => {
const isSelected = sale ? isChecked(sale.id) : false;
return (
<TableRow
className={!!sale ? classes.tableRow : undefined}
hover={!!sale}
key={sale ? sale.id : "skeleton"}
return (
<TableRow
className={!!sale ? classes.tableRow : undefined}
hover={!!sale}
key={sale ? sale.id : "skeleton"}
onClick={sale ? onRowClick(sale.id) : undefined}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(sale.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{maybe<React.ReactNode>(() => sale.name, <Skeleton />)}
</TableCell>
<TableCell className={classes.colStart}>
{sale && sale.startDate ? (
<Date date={sale.startDate} />
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colEnd}>
{sale && sale.endDate ? (
<Date date={sale.endDate} />
) : sale && sale.endDate === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colValue}
onClick={sale ? onRowClick(sale.id) : undefined}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(sale.id)}
/>
</TableCell>
<TableCell className={classes.colName}>
{maybe<React.ReactNode>(() => sale.name, <Skeleton />)}
</TableCell>
<TableCell className={classes.colStart}>
{sale && sale.startDate ? (
<Date date={sale.startDate} />
{sale && sale.type && sale.value ? (
sale.type === SaleType.FIXED ? (
<Money
money={{
amount: sale.value,
currency: defaultCurrency
}}
/>
) : (
<Skeleton />
)}
</TableCell>
<TableCell className={classes.colEnd}>
{sale && sale.endDate ? (
<Date date={sale.endDate} />
) : sale && sale.endDate === null ? (
"-"
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colValue}
onClick={sale ? onRowClick(sale.id) : undefined}
>
{sale && sale.type && sale.value ? (
sale.type === SaleType.FIXED ? (
<Money
money={{
amount: sale.value,
currency: defaultCurrency
}}
/>
) : (
<Percent amount={sale.value} />
)
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No sales found" />
<Percent amount={sale.value} />
)
) : (
<Skeleton />
)}
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
</Card>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No sales found" />
</TableCell>
</TableRow>
)
)}
</TableBody>
</Table>
)
);
SaleList.displayName = "SaleList";

View file

@ -1,22 +1,40 @@
import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types";
import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import { SaleList_sales_edges_node } from "../../types/SaleList";
import SaleList from "../SaleList";
export interface SaleListPageProps extends PageListProps, ListActions {
export interface SaleListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
defaultCurrency: string;
sales: SaleList_sales_edges_node[];
}
const SaleListPage: React.StatelessComponent<SaleListPageProps> = ({
currentTab,
initialSearch,
onAdd,
onAll,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...listProps
}) => {
const intl = useIntl();
@ -28,7 +46,22 @@ const SaleListPage: React.StatelessComponent<SaleListPageProps> = ({
<FormattedMessage defaultMessage="Create Sale" description="button" />
</Button>
</PageHeader>
<SaleList {...listProps} />
<Card>
<SearchBar
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Sale"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<SaleList {...listProps} />
</Card>
</Container>
);
};

View file

@ -166,8 +166,20 @@ export const voucherDetailsFragment = gql`
export const saleList = gql`
${pageInfoFragment}
${saleFragment}
query SaleList($after: String, $before: String, $first: Int, $last: Int) {
sales(after: $after, before: $before, first: $first, last: $last) {
query SaleList(
$after: String
$before: String
$first: Int
$last: Int
$filter: SaleFilterInput
) {
sales(
after: $after
before: $before
first: $first
last: $last
filter: $filter
) {
edges {
node {
...SaleFragment

View file

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

View file

@ -1,7 +1,14 @@
import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join";
import { ActiveTab, BulkAction, Dialog, Pagination } from "../types";
import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
TabActionDialog
} from "../types";
import { SaleDetailsPageTab } from "./components/SaleDetailsPage";
import { VoucherDetailsPageTab } from "./components/VoucherDetailsPage";
@ -9,10 +16,16 @@ export const discountSection = "/discounts/";
export const saleSection = urlJoin(discountSection, "sales");
export const saleListPath = saleSection;
export type SaleListUrlDialog = "remove";
export type SaleListUrlQueryParams = BulkAction &
export enum SaleListUrlFiltersEnum {
query = "query"
}
export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum>;
export type SaleListUrlDialog = "remove" | TabActionDialog;
export type SaleListUrlQueryParams = ActiveTab &
BulkAction &
Dialog<SaleListUrlDialog> &
Pagination;
Pagination &
SaleListUrlFilters;
export const saleListUrl = (params?: SaleListUrlQueryParams) =>
saleListPath + "?" + stringifyQs(params);
export const salePath = (id: string) => urlJoin(saleSection, id);

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
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 useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings";
@ -17,16 +21,26 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages, sectionNames } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types";
import SaleListPage from "../components/SaleListPage";
import { TypedSaleBulkDelete } from "../mutations";
import { TypedSaleList } from "../queries";
import { SaleBulkDelete } from "../types/SaleBulkDelete";
import SaleListPage from "../../components/SaleListPage";
import { TypedSaleBulkDelete } from "../../mutations";
import { TypedSaleList } from "../../queries";
import { SaleBulkDelete } from "../../types/SaleBulkDelete";
import {
saleAddUrl,
saleListUrl,
SaleListUrlDialog,
SaleListUrlFilters,
SaleListUrlQueryParams,
saleUrl
} from "../urls";
} from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface SaleListProps {
params: SaleListUrlQueryParams;
@ -47,13 +61,78 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
);
const intl = useIntl();
const closeModal = () => navigate(saleListUrl(), true);
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: SaleListUrlFilters) => {
reset();
navigate(
saleListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () =>
navigate(
saleListUrl({
...params,
action: undefined,
ids: undefined
})
);
const openModal = (action: SaleListUrlDialog, ids?: string[]) =>
navigate(
saleListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
saleListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(saleListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
const canOpenBulkActionDialog = maybe(() => params.ids.length > 0);
return (
<TypedSaleList displayLoader variables={paginationState}>
<TypedSaleList displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.sales.pageInfo),
@ -91,6 +170,14 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
<>
<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}
@ -150,6 +237,19 @@ export const SaleList: React.StatelessComponent<SaleListProps> = ({
</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, "...")}
/>
</>
);
}}

View file

@ -0,0 +1,31 @@
import { SaleFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
SaleListUrlFilters,
SaleListUrlFiltersEnum,
SaleListUrlQueryParams
} from "../../urls";
export const SALE_FILTERS_KEY = "saleFilters";
export function getFilterVariables(
params: SaleListUrlFilters
): SaleFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<SaleListUrlFilters>(SALE_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
SaleListUrlQueryParams,
SaleListUrlFilters
>(SaleListUrlFiltersEnum);

View file

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

View file

@ -43,6 +43,12 @@ export enum ConfigurationTypeFieldEnum {
STRING = "STRING",
}
export enum DiscountStatusEnum {
ACTIVE = "ACTIVE",
EXPIRED = "EXPIRED",
SCHEDULED = "SCHEDULED",
}
export enum DiscountValueTypeEnum {
FIXED = "FIXED",
PERCENTAGE = "PERCENTAGE",
@ -393,6 +399,11 @@ export interface DateRangeInput {
lte?: any | null;
}
export interface DateTimeRangeInput {
gte?: any | null;
lte?: any | null;
}
export interface DraftOrderInput {
billingAddress?: AddressInput | null;
user?: string | null;
@ -564,6 +575,13 @@ export interface ReorderInput {
sortOrder?: number | null;
}
export interface SaleFilterInput {
status?: (DiscountStatusEnum | null)[] | null;
saleType?: DiscountValueTypeEnum | null;
started?: DateTimeRangeInput | null;
search?: string | null;
}
export interface SaleInput {
name?: string | null;
type?: DiscountValueTypeEnum | null;