diff --git a/src/productTypes/components/ProductTypeList/ProductTypeList.tsx b/src/productTypes/components/ProductTypeList/ProductTypeList.tsx index 5ba7d21d3..536ec3b90 100644 --- a/src/productTypes/components/ProductTypeList/ProductTypeList.tsx +++ b/src/productTypes/components/ProductTypeList/ProductTypeList.tsx @@ -1,4 +1,3 @@ -import Card from "@material-ui/core/Card"; import { createStyles, Theme, @@ -70,138 +69,132 @@ const ProductTypeList = withStyles(styles, { name: "ProductTypeList" })( const intl = useIntl(); return ( - - - - - - - - - - - - - - - - - - - - {renderCollection( - productTypes, - productType => { - const isSelected = productType - ? isChecked(productType.id) - : false; - return ( - - - toggle(productType.id)} - /> - - - {productType ? ( +
+ + + + + + + + + + + + + + + + + + {renderCollection( + productTypes, + productType => { + const isSelected = productType + ? isChecked(productType.id) + : false; + return ( + + + toggle(productType.id)} + /> + + + {productType ? ( + <> + {productType.name} + + {maybe(() => productType.hasVariants) + ? intl.formatMessage({ + defaultMessage: "Configurable", + description: "product type" + }) + : intl.formatMessage({ + defaultMessage: "Simple product", + description: "product type" + })} + + + ) : ( + + )} + + + {maybe(() => productType.isShippingRequired) !== + undefined ? ( + productType.isShippingRequired ? ( <> - {productType.name} - - {maybe(() => productType.hasVariants) - ? intl.formatMessage({ - defaultMessage: "Configurable", - description: "product type" - }) - : intl.formatMessage({ - defaultMessage: "Simple product", - description: "product type" - })} - + ) : ( - - )} - - - {maybe(() => productType.isShippingRequired) !== - undefined ? ( - productType.isShippingRequired ? ( - <> - - - ) : ( - <> - - - ) - ) : ( - - )} - - - {maybe(() => productType.taxType) ? ( - productType.taxType.description - ) : ( - - )} - - - ); - }, - () => ( - - - + <> + + + ) + ) : ( + + )} + + + {maybe(() => productType.taxType) ? ( + productType.taxType.description + ) : ( + + )} - ) - )} - -
-
+ ); + }, + () => ( + + + + + + ) + )} + + ); } ); diff --git a/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx b/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx index 747712b57..4dc25d51d 100644 --- a/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx +++ b/src/productTypes/components/ProductTypeListPage/ProductTypeListPage.tsx @@ -1,23 +1,46 @@ 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 AppHeader from "@saleor/components/AppHeader"; 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 "../../../types"; +import { + ListActions, + PageListProps, + SearchPageProps, + TabPageProps +} from "../../../types"; import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList"; import ProductTypeList from "../ProductTypeList"; -interface ProductTypeListPageProps extends PageListProps, ListActions { +interface ProductTypeListPageProps + extends PageListProps, + ListActions, + SearchPageProps, + TabPageProps { productTypes: ProductTypeList_productTypes_edges_node[]; onBack: () => void; } const ProductTypeListPage: React.StatelessComponent< ProductTypeListPageProps -> = ({ disabled, onAdd, onBack, ...listProps }) => { +> = ({ + currentTab, + initialSearch, + onAdd, + onAll, + onBack, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, + ...listProps +}) => { const intl = useIntl(); return ( @@ -26,19 +49,33 @@ const ProductTypeListPage: React.StatelessComponent< {intl.formatMessage(sectionNames.configuration)} - - + + + + ); }; diff --git a/src/productTypes/queries.ts b/src/productTypes/queries.ts index 63bbbbec0..d67b628d7 100644 --- a/src/productTypes/queries.ts +++ b/src/productTypes/queries.ts @@ -51,8 +51,15 @@ export const productTypeListQuery = gql` $before: String $first: Int $last: Int + $filter: ProductTypeFilterInput ) { - productTypes(after: $after, before: $before, first: $first, last: $last) { + productTypes( + after: $after + before: $before + first: $first + last: $last + filter: $filter + ) { edges { node { ...ProductTypeFragment diff --git a/src/productTypes/urls.ts b/src/productTypes/urls.ts index 86251789d..34733f1c4 100644 --- a/src/productTypes/urls.ts +++ b/src/productTypes/urls.ts @@ -1,15 +1,29 @@ import { stringify as stringifyQs } from "qs"; import urlJoin from "url-join"; -import { BulkAction, Dialog, Pagination, SingleAction } from "../types"; +import { + ActiveTab, + BulkAction, + Dialog, + Filters, + Pagination, + SingleAction, + TabActionDialog +} from "../types"; const productTypeSection = "/product-types/"; export const productTypeListPath = productTypeSection; -export type ProductTypeListUrlDialog = "remove"; -export type ProductTypeListUrlQueryParams = BulkAction & +export enum ProductTypeListUrlFiltersEnum { + query = "query" +} +export type ProductTypeListUrlFilters = Filters; +export type ProductTypeListUrlDialog = "remove" | TabActionDialog; +export type ProductTypeListUrlQueryParams = ActiveTab & + BulkAction & Dialog & - Pagination; + Pagination & + ProductTypeListUrlFilters; export const productTypeListUrl = (params?: ProductTypeListUrlQueryParams) => productTypeListPath + "?" + stringifyQs(params); diff --git a/src/productTypes/views/ProductTypeList.tsx b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx similarity index 59% rename from src/productTypes/views/ProductTypeList.tsx rename to src/productTypes/views/ProductTypeList/ProductTypeList.tsx index ca83ccc45..321a79199 100644 --- a/src/productTypes/views/ProductTypeList.tsx +++ b/src/productTypes/views/ProductTypeList/ProductTypeList.tsx @@ -5,26 +5,41 @@ 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 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 { commonMessages } from "@saleor/intl"; -import { PAGINATE_BY } from "../../config"; -import { configurationMenuUrl } from "../../configuration"; -import { getMutationState, maybe } from "../../misc"; -import ProductTypeListPage from "../components/ProductTypeListPage"; -import { TypedProductTypeBulkDeleteMutation } from "../mutations"; -import { TypedProductTypeListQuery } from "../queries"; -import { ProductTypeBulkDelete } from "../types/ProductTypeBulkDelete"; +import { ListViews } from "@saleor/types"; +import { configurationMenuUrl } from "../../../configuration"; +import { getMutationState, maybe } from "../../../misc"; +import ProductTypeListPage from "../../components/ProductTypeListPage"; +import { TypedProductTypeBulkDeleteMutation } from "../../mutations"; +import { TypedProductTypeListQuery } from "../../queries"; +import { ProductTypeBulkDelete } from "../../types/ProductTypeBulkDelete"; import { productTypeAddUrl, productTypeListUrl, + ProductTypeListUrlDialog, + ProductTypeListUrlFilters, ProductTypeListUrlQueryParams, productTypeUrl -} from "../urls"; +} from "../../urls"; +import { + areFiltersApplied, + deleteFilterTab, + getActiveFilters, + getFilterTabs, + getFilterVariables, + saveFilterTab +} from "./filter"; interface ProductTypeListProps { params: ProductTypeListUrlQueryParams; @@ -39,13 +54,79 @@ export const ProductTypeList: React.StatelessComponent< const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( params.ids ); + const { settings } = useListSettings(ListViews.PRODUCT_LIST); const intl = useIntl(); - const closeModal = () => navigate(productTypeListUrl(), true); + const tabs = getFilterTabs(); + + const currentTab = + params.activeTab === undefined + ? areFiltersApplied(params) + ? tabs.length + 1 + : 0 + : parseInt(params.activeTab, 0); + + const changeFilterField = (filter: ProductTypeListUrlFilters) => { + reset(); + navigate( + productTypeListUrl({ + ...getActiveFilters(params), + ...filter, + activeTab: undefined + }) + ); + }; + + const closeModal = () => + navigate( + productTypeListUrl({ + ...params, + action: undefined, + ids: undefined + }) + ); + + const openModal = (action: ProductTypeListUrlDialog, ids?: string[]) => + navigate( + productTypeListUrl({ + ...params, + action, + ids + }) + ); + + const handleTabChange = (tab: number) => { + reset(); + navigate( + productTypeListUrl({ + activeTab: tab.toString(), + ...getFilterTabs()[tab - 1].data + }) + ); + }; + + const handleTabDelete = () => { + deleteFilterTab(currentTab); + reset(); + navigate(productTypeListUrl()); + }; + + 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 paginationState = createPaginationState(PAGINATE_BY, params); return ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.productTypes.pageInfo), @@ -93,6 +174,14 @@ export const ProductTypeList: React.StatelessComponent< return ( <> changeFilterField({ query })} + onAll={() => navigate(productTypeListUrl())} + onTabChange={handleTabChange} + onTabDelete={() => openModal("delete-search")} + onTabSave={() => openModal("save-search")} + tabs={tabs.map(tab => tab.name)} disabled={loading} productTypes={maybe(() => data.productTypes.edges.map(edge => edge.node) @@ -150,6 +239,19 @@ export const ProductTypeList: React.StatelessComponent< /> + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/productTypes/views/ProductTypeList/filter.ts b/src/productTypes/views/ProductTypeList/filter.ts new file mode 100644 index 000000000..fffcd0cd8 --- /dev/null +++ b/src/productTypes/views/ProductTypeList/filter.ts @@ -0,0 +1,31 @@ +import { ProductTypeFilterInput } from "@saleor/types/globalTypes"; +import { + createFilterTabUtils, + createFilterUtils +} from "../../../utils/filters"; +import { + ProductTypeListUrlFilters, + ProductTypeListUrlFiltersEnum, + ProductTypeListUrlQueryParams +} from "../../urls"; + +export const PRODUCT_TYPE_FILTERS_KEY = "productTypeFilters"; + +export function getFilterVariables( + params: ProductTypeListUrlFilters +): ProductTypeFilterInput { + return { + search: params.query + }; +} + +export const { + deleteFilterTab, + getFilterTabs, + saveFilterTab +} = createFilterTabUtils(PRODUCT_TYPE_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + ProductTypeListUrlQueryParams, + ProductTypeListUrlFilters +>(ProductTypeListUrlFiltersEnum); diff --git a/src/productTypes/views/ProductTypeList/index.ts b/src/productTypes/views/ProductTypeList/index.ts new file mode 100644 index 000000000..9c1624523 --- /dev/null +++ b/src/productTypes/views/ProductTypeList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ProductTypeList"; +export * from "./ProductTypeList"; diff --git a/src/types.ts b/src/types.ts index a3be19eda..245eddcea 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,6 +24,7 @@ export enum ListViews { PAGES_LIST = "PAGES_LIST", PLUGINS_LIST = "PLUGIN_LIST", PRODUCT_LIST = "PRODUCT_LIST", + PRODUCT_TYPE_LIST = "PRODUCT_TYPE_LIST", SALES_LIST = "SALES_LIST", SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST", STAFF_MEMBERS_LIST = "STAFF_MEMBERS_LIST",