diff --git a/src/discounts/components/SaleList/SaleList.tsx b/src/discounts/components/SaleList/SaleList.tsx index 5f0a7bb80..0573355c8 100644 --- a/src/discounts/components/SaleList/SaleList.tsx +++ b/src/discounts/components/SaleList/SaleList.tsx @@ -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) => ( - - - - - - - - - - - - - - - - - - - - - - - {renderCollection( - sales, - sale => { - const isSelected = sale ? isChecked(sale.id) : false; +
+ + + + + + + + + + + + + + + + + + + + + {renderCollection( + sales, + sale => { + const isSelected = sale ? isChecked(sale.id) : false; - return ( - + + toggle(sale.id)} + /> + + + {maybe(() => sale.name, )} + + + {sale && sale.startDate ? ( + + ) : ( + + )} + + + {sale && sale.endDate ? ( + + ) : sale && sale.endDate === null ? ( + "-" + ) : ( + + )} + + - - toggle(sale.id)} - /> - - - {maybe(() => sale.name, )} - - - {sale && sale.startDate ? ( - + {sale && sale.type && sale.value ? ( + sale.type === SaleType.FIXED ? ( + ) : ( - - )} - - - {sale && sale.endDate ? ( - - ) : sale && sale.endDate === null ? ( - "-" - ) : ( - - )} - - - {sale && sale.type && sale.value ? ( - sale.type === SaleType.FIXED ? ( - - ) : ( - - ) - ) : ( - - )} - - - ); - }, - () => ( - - - + + ) + ) : ( + + )} - ) - )} - -
-
+ ); + }, + () => ( + + + + + + ) + )} + + ) ); SaleList.displayName = "SaleList"; diff --git a/src/discounts/components/SaleListPage/SaleListPage.tsx b/src/discounts/components/SaleListPage/SaleListPage.tsx index a7861b4c1..eece726ed 100644 --- a/src/discounts/components/SaleListPage/SaleListPage.tsx +++ b/src/discounts/components/SaleListPage/SaleListPage.tsx @@ -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 = ({ + currentTab, + initialSearch, onAdd, + onAll, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, ...listProps }) => { const intl = useIntl(); @@ -28,7 +46,22 @@ const SaleListPage: React.StatelessComponent = ({ - + + + + ); }; diff --git a/src/discounts/queries.ts b/src/discounts/queries.ts index 693c691c0..9057b5b89 100644 --- a/src/discounts/queries.ts +++ b/src/discounts/queries.ts @@ -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 diff --git a/src/discounts/types/SaleList.ts b/src/discounts/types/SaleList.ts index 0667b9b72..c1c9874b0 100644 --- a/src/discounts/types/SaleList.ts +++ b/src/discounts/types/SaleList.ts @@ -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; } diff --git a/src/discounts/urls.ts b/src/discounts/urls.ts index 0e3833d99..659449b7f 100644 --- a/src/discounts/urls.ts +++ b/src/discounts/urls.ts @@ -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; +export type SaleListUrlDialog = "remove" | TabActionDialog; +export type SaleListUrlQueryParams = ActiveTab & + BulkAction & Dialog & - Pagination; + Pagination & + SaleListUrlFilters; export const saleListUrl = (params?: SaleListUrlQueryParams) => saleListPath + "?" + stringifyQs(params); export const salePath = (id: string) => urlJoin(saleSection, id); diff --git a/src/discounts/views/SaleList.tsx b/src/discounts/views/SaleList/SaleList.tsx similarity index 64% rename from src/discounts/views/SaleList.tsx rename to src/discounts/views/SaleList/SaleList.tsx index 319b908dc..bca686bd2 100644 --- a/src/discounts/views/SaleList.tsx +++ b/src/discounts/views/SaleList/SaleList.tsx @@ -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 = ({ ); 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 ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.sales.pageInfo), @@ -91,6 +170,14 @@ export const SaleList: React.StatelessComponent = ({ <> 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 = ({ )} + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/discounts/views/SaleList/filter.ts b/src/discounts/views/SaleList/filter.ts new file mode 100644 index 000000000..69362c3d3 --- /dev/null +++ b/src/discounts/views/SaleList/filter.ts @@ -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(SALE_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + SaleListUrlQueryParams, + SaleListUrlFilters +>(SaleListUrlFiltersEnum); diff --git a/src/discounts/views/SaleList/index.ts b/src/discounts/views/SaleList/index.ts new file mode 100644 index 000000000..20d8fef01 --- /dev/null +++ b/src/discounts/views/SaleList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./SaleList"; +export * from "./SaleList"; diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index ab30edcc5..232a4a074 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -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;